From a19828c9d110cd10cc84d73195aa817eae4529a9 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Mon, 25 Jul 2022 13:18:14 +0100 Subject: [PATCH] [wue] move WUE, Windows To Go and WinPE calls to their own source --- .vs/rufus.vcxproj | 2 + .vs/rufus.vcxproj.filters | 6 + src/Makefile.am | 2 +- src/Makefile.in | 10 +- src/format.c | 560 +-------------------------- src/format.h | 4 + src/missing.h | 32 +- src/rufus.c | 186 +-------- src/rufus.h | 2 - src/rufus.rc | 10 +- src/wue.c | 778 ++++++++++++++++++++++++++++++++++++++ src/wue.h | 32 ++ 12 files changed, 882 insertions(+), 742 deletions(-) create mode 100644 src/wue.c create mode 100644 src/wue.h diff --git a/.vs/rufus.vcxproj b/.vs/rufus.vcxproj index bb244d38..11b65194 100644 --- a/.vs/rufus.vcxproj +++ b/.vs/rufus.vcxproj @@ -373,6 +373,7 @@ + @@ -405,6 +406,7 @@ + diff --git a/.vs/rufus.vcxproj.filters b/.vs/rufus.vcxproj.filters index 37f31fcb..d42d2867 100644 --- a/.vs/rufus.vcxproj.filters +++ b/.vs/rufus.vcxproj.filters @@ -90,6 +90,9 @@ Source Files + + Source Files + @@ -182,6 +185,9 @@ Header Files + + Header Files + diff --git a/src/Makefile.am b/src/Makefile.am index b7e2bb07..7a461b6c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,7 +15,7 @@ AM_V_WINDRES = $(AM_V_WINDRES_$(V)) $(AM_V_WINDRES) $(AM_RCFLAGS) -i $< -o $@ rufus_SOURCES = badblocks.c checksum.c dev.c dos.c dos_locale.c drive.c format.c format_ext.c format_fat32.c icon.c iso.c localization.c \ - net.c parser.c pki.c process.c re.c rufus.c smart.c stdfn.c stdio.c stdlg.c syslinux.c ui.c vhd.c + net.c parser.c pki.c process.c re.c rufus.c smart.c stdfn.c stdio.c stdlg.c syslinux.c ui.c vhd.c wue.c rufus_CFLAGS = -I$(srcdir)/ms-sys/inc -I$(srcdir)/syslinux/libfat -I$(srcdir)/syslinux/libinstaller -I$(srcdir)/syslinux/win -I$(srcdir)/libcdio $(AM_CFLAGS) \ -DEXT2_FLAT_INCLUDES=0 -DSOLUTION=rufus rufus_LDFLAGS = $(AM_LDFLAGS) -mwindows -L ../.mingw diff --git a/src/Makefile.in b/src/Makefile.in index 6ee3ec59..2a1be230 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -98,7 +98,7 @@ am_rufus_OBJECTS = rufus-badblocks.$(OBJEXT) rufus-checksum.$(OBJEXT) \ rufus-rufus.$(OBJEXT) rufus-smart.$(OBJEXT) \ rufus-stdfn.$(OBJEXT) rufus-stdio.$(OBJEXT) \ rufus-stdlg.$(OBJEXT) rufus-syslinux.$(OBJEXT) \ - rufus-ui.$(OBJEXT) rufus-vhd.$(OBJEXT) + rufus-ui.$(OBJEXT) rufus-vhd.$(OBJEXT) rufus-wue.$(OBJEXT) rufus_OBJECTS = $(am_rufus_OBJECTS) am__DEPENDENCIES_1 = rufus_DEPENDENCIES = rufus_rc.o bled/libbled.a ext2fs/libext2fs.a \ @@ -282,7 +282,7 @@ AM_V_WINDRES_1 = $(WINDRES) AM_V_WINDRES_ = $(AM_V_WINDRES_$(AM_DEFAULT_VERBOSITY)) AM_V_WINDRES = $(AM_V_WINDRES_$(V)) rufus_SOURCES = badblocks.c checksum.c dev.c dos.c dos_locale.c drive.c format.c format_ext.c format_fat32.c icon.c iso.c localization.c \ - net.c parser.c pki.c process.c re.c rufus.c smart.c stdfn.c stdio.c stdlg.c syslinux.c ui.c vhd.c + net.c parser.c pki.c process.c re.c rufus.c smart.c stdfn.c stdio.c stdlg.c syslinux.c ui.c vhd.c wue.c rufus_CFLAGS = -I$(srcdir)/ms-sys/inc -I$(srcdir)/syslinux/libfat -I$(srcdir)/syslinux/libinstaller -I$(srcdir)/syslinux/win -I$(srcdir)/libcdio $(AM_CFLAGS) \ -DEXT2_FLAT_INCLUDES=0 -DSOLUTION=rufus @@ -495,6 +495,12 @@ rufus-vhd.o: vhd.c rufus-vhd.obj: vhd.c $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(rufus_CFLAGS) $(CFLAGS) -c -o rufus-vhd.obj `if test -f 'vhd.c'; then $(CYGPATH_W) 'vhd.c'; else $(CYGPATH_W) '$(srcdir)/vhd.c'; fi` +rufus-wue.o: wue.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(rufus_CFLAGS) $(CFLAGS) -c -o rufus-wue.o `test -f 'wue.c' || echo '$(srcdir)/'`wue.c + +rufus-wue.obj: wue.c + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(rufus_CFLAGS) $(CFLAGS) -c -o rufus-wue.obj `if test -f 'wue.c'; then $(CYGPATH_W) 'wue.c'; else $(CYGPATH_W) '$(srcdir)/wue.c'; fi` + # This directory's subdirectories are mostly independent; you can cd # into them and run 'make' without going through this Makefile. # To change the values of 'make' variables: instead of editing Makefiles, diff --git a/src/format.c b/src/format.c index c99a2ab6..c2383a37 100644 --- a/src/format.c +++ b/src/format.c @@ -44,6 +44,7 @@ #include "localization.h" #include "br.h" +#include "wue.h" #include "fat16.h" #include "fat32.h" #include "ntfs.h" @@ -65,18 +66,14 @@ const char* FileSystemLabel[FS_MAX] = { "FAT", "FAT32", "NTFS", "UDF", "exFAT", DWORD FormatStatus = 0, LastWriteError = 0; badblocks_report report = { 0 }; static float format_percent = 0.0f; -static int task_number = 0; +static int task_number = 0, actual_fs_type; static unsigned int sec_buf_pos = 0; 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; -static int actual_fs_type, wintogo_index = -1, wininst_index = 0; -extern int unattend_xml_flags; 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 const grub_patch_t grub_patch[2]; -extern char* unattend_xml_path; -extern const char* bypass_name[4]; uint8_t *grub2_buf = NULL, *sec_buf = NULL; long grub2_len; @@ -664,7 +661,7 @@ out: return r; } -static BOOL FormatPartition(DWORD DriveIndex, uint64_t PartitionOffset, DWORD UnitAllocationSize, USHORT FSType, LPCSTR Label, DWORD Flags) +BOOL FormatPartition(DWORD DriveIndex, uint64_t PartitionOffset, DWORD UnitAllocationSize, USHORT FSType, LPCSTR Label, DWORD Flags) { if ((DriveIndex < 0x80) || (DriveIndex > 0x100) || (FSType >= FS_MAX) || ((UnitAllocationSize != 0) && (!IS_POWER_OF_2(UnitAllocationSize)))) { @@ -1110,557 +1107,6 @@ BOOL WritePBR(HANDLE hLogicalVolume) return FALSE; } -/* - * Setup WinPE for bootable USB - */ -static BOOL SetupWinPE(char drive_letter) -{ - char src[64], dst[32]; - const char* basedir[3] = { "i386", "amd64", "minint" }; - const char* patch_str_org[2] = { "\\minint\\txtsetup.sif", "\\minint\\system32\\" }; - const char* patch_str_rep[2][2] = { { "\\i386\\txtsetup.sif", "\\i386\\system32\\" } , - { "\\amd64\\txtsetup.sif", "\\amd64\\system32\\" } }; - const char *win_nt_bt_org = "$win_nt$.~bt"; - const char *rdisk_zero = "rdisk(0)"; - const LARGE_INTEGER liZero = { {0, 0} }; - char setupsrcdev[64]; - HANDLE handle = INVALID_HANDLE_VALUE; - DWORD i, j, size, rw_size, index = 0; - BOOL r = FALSE; - char* buffer = NULL; - - if ((img_report.winpe & WINPE_AMD64) == WINPE_AMD64) - index = 1; - else if ((img_report.winpe & WINPE_MININT) == WINPE_MININT) - index = 2; - // Allow other values than harddisk 1, as per user choice for disk ID - static_sprintf(setupsrcdev, "SetupSourceDevice = \"\\device\\harddisk%d\\partition1\"", - ComboBox_GetCurSel(hDiskID)); - // Copy of ntdetect.com in root - static_sprintf(src, "%c:\\%s\\ntdetect.com", toupper(drive_letter), basedir[2*(index/2)]); - static_sprintf(dst, "%c:\\ntdetect.com", toupper(drive_letter)); - CopyFileA(src, dst, TRUE); - if (!img_report.uses_minint) { - // Create a copy of txtsetup.sif, as we want to keep the i386/amd64 files unmodified - static_sprintf(src, "%c:\\%s\\txtsetup.sif", toupper(drive_letter), basedir[index]); - static_sprintf(dst, "%c:\\txtsetup.sif", toupper(drive_letter)); - if (!CopyFileA(src, dst, TRUE)) { - uprintf("Did not copy %s as %s: %s\n", src, dst, WindowsErrorString()); - } - if (insert_section_data(dst, "[SetupData]", setupsrcdev, FALSE) == NULL) { - uprintf("Failed to add SetupSourceDevice in %s\n", dst); - goto out; - } - uprintf("Successfully added '%s' to %s\n", setupsrcdev, dst); - } - - static_sprintf(src, "%c:\\%s\\setupldr.bin", toupper(drive_letter), basedir[2*(index/2)]); - static_sprintf(dst, "%c:\\BOOTMGR", toupper(drive_letter)); - if (!CopyFileA(src, dst, TRUE)) { - uprintf("Did not copy %s as %s: %s\n", src, dst, WindowsErrorString()); - } - - // \minint with /minint option doesn't require further processing => return true - // \minint and no \i386 without /minint is unclear => return error - if (img_report.winpe&WINPE_MININT) { - if (img_report.uses_minint) { - uprintf("Detected \\minint directory with /minint option: nothing to patch\n"); - r = TRUE; - } else if (!(img_report.winpe&(WINPE_I386|WINPE_AMD64))) { - uprintf("Detected \\minint directory only but no /minint option: not sure what to do\n"); - } - goto out; - } - - // At this stage we only handle \i386 - handle = CreateFileA(dst, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (handle == INVALID_HANDLE_VALUE) { - uprintf("Could not open %s for patching: %s\n", dst, WindowsErrorString()); - goto out; - } - size = GetFileSize(handle, NULL); - if (size == INVALID_FILE_SIZE) { - uprintf("Could not get size for file %s: %s\n", dst, WindowsErrorString()); - goto out; - } - buffer = (char*)malloc(size); - if (buffer == NULL) - goto out; - if ((!ReadFile(handle, buffer, size, &rw_size, NULL)) || (size != rw_size)) { - uprintf("Could not read file %s: %s\n", dst, WindowsErrorString()); - goto out; - } - if (!SetFilePointerEx(handle, liZero, NULL, FILE_BEGIN)) { - uprintf("Could not rewind file %s: %s\n", dst, WindowsErrorString()); - goto out; - } - - // Patch setupldr.bin - uprintf("Patching file %s\n", dst); - // Remove CRC check for 32 bit part of setupldr.bin from Win2k3 - if ((size > 0x2061) && (buffer[0x2060] == 0x74) && (buffer[0x2061] == 0x03)) { - buffer[0x2060] = 0xeb; - buffer[0x2061] = 0x1a; - uprintf(" 0x00002060: 0x74 0x03 -> 0xEB 0x1A (disable Win2k3 CRC check)\n"); - } - for (i=1; i '%s'\n", i, &buffer[i], patch_str_rep[index][j]); - strcpy(&buffer[i], patch_str_rep[index][j]); - i += (DWORD)max(strlen(patch_str_org[j]), strlen(patch_str_rep[index][j])); // in case org is a substring of rep - } - } - } - - if (!img_report.uses_minint) { - // Additional setupldr.bin/bootmgr patching - for (i=0; i rdisk(#) disk masquerading - // NB: only the first one seems to be needed - if (safe_strnicmp(&buffer[i], rdisk_zero, strlen(rdisk_zero)-1) == 0) { - buffer[i+6] = 0x30 + ComboBox_GetCurSel(hDiskID); - uprintf(" 0x%08X: '%s' -> 'rdisk(%c)'\n", i, rdisk_zero, buffer[i+6]); - } - // $WIN_NT$_~BT -> i386/amd64 - if (safe_strnicmp(&buffer[i], win_nt_bt_org, strlen(win_nt_bt_org)-1) == 0) { - uprintf(" 0x%08X: '%s' -> '%s%s'\n", i, &buffer[i], basedir[index], &buffer[i+strlen(win_nt_bt_org)]); - strcpy(&buffer[i], basedir[index]); - // This ensures that we keep the terminator backslash - buffer[i+strlen(basedir[index])] = buffer[i+strlen(win_nt_bt_org)]; - buffer[i+strlen(basedir[index])+1] = 0; - } - } - } - - if (!WriteFileWithRetry(handle, buffer, size, &rw_size, WRITE_RETRIES)) { - uprintf("Could not write patched file: %s\n", WindowsErrorString()); - goto out; - } - r = TRUE; - -out: - safe_closehandle(handle); - safe_free(buffer); - return r; -} - -// Checks which versions of Windows are available in an install image -// to set our extraction index. Asks the user to select one if needed. -// Returns -2 on user cancel, -1 on other error, >=0 on success. -int SetWinToGoIndex(void) -{ - char *mounted_iso, *val, mounted_image_path[128]; - char xml_file[MAX_PATH] = ""; - char *install_names[MAX_WININST]; - StrArray version_name, version_index; - int i; - BOOL bNonStandard = FALSE; - - // Sanity checks - wintogo_index = -1; - wininst_index = 0; - if ((nWindowsVersion < WINDOWS_8) || ((WimExtractCheck(FALSE) & 4) == 0) || - (ComboBox_GetCurItemData(hFileSystem) != FS_NTFS)) { - return -1; - } - - // If we have multiple windows install images, ask the user the one to use - if (img_report.wininst_index > 1) { - for (i = 0; i < img_report.wininst_index; i++) - install_names[i] = &img_report.wininst_path[i][2]; - wininst_index = _log2(SelectionDialog(BS_AUTORADIOBUTTON, lmprintf(MSG_130), - lmprintf(MSG_131), install_names, img_report.wininst_index, 1)); - if (wininst_index < 0) - return -2; - if (wininst_index >= MAX_WININST) - wininst_index = 0; - } - - // 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); - if (mounted_iso == NULL) { - uprintf("Could not mount ISO for Windows To Go selection"); - return FALSE; - } - static_sprintf(mounted_image_path, "%s%s", mounted_iso, &img_report.wininst_path[wininst_index][2]); - } - - // Now take a look at the XML file in install.wim to list our versions - if ((GetTempFileNameU(temp_dir, APPLICATION_NAME, 0, xml_file) == 0) || (xml_file[0] == 0)) { - // Last ditch effort to get a tmp file - just extract it to the current directory - static_strcpy(xml_file, ".\\RufVXml.tmp"); - } - // GetTempFileName() may leave a file behind - DeleteFileU(xml_file); - - // Must use the Windows WIM API as 7z messes up the XML - if (!WimExtractFile_API(img_report.is_windows_img ? image_path : mounted_image_path, - 0, "[1].xml", xml_file, FALSE)) { - uprintf("Could not acquire WIM index"); - goto out; - } - - StrArrayCreate(&version_name, 16); - StrArrayCreate(&version_index, 16); - for (i = 0; StrArrayAdd(&version_index, get_token_data_file_indexed("IMAGE INDEX", xml_file, i + 1), FALSE) >= 0; i++) { - // Some people are apparently creating *unofficial* Windows ISOs that don't have DISPLAYNAME elements. - // If we are parsing such an ISO, try to fall back to using DESCRIPTION. Of course, since we don't use - // a formal XML parser, if an ISO mixes entries with both DISPLAYNAME and DESCRIPTION and others with - // only DESCRIPTION, the version names we report will be wrong. - // But hey, there's only so far I'm willing to go to help people who, not content to have demonstrated - // their utter ignorance on development matters, are also trying to lecture experienced developers - // about specific "noob mistakes"... that don't exist in the code they are trying to criticize. - if (StrArrayAdd(&version_name, get_token_data_file_indexed("DISPLAYNAME", xml_file, i + 1), FALSE) < 0) { - bNonStandard = TRUE; - if (StrArrayAdd(&version_name, get_token_data_file_indexed("DESCRIPTION", xml_file, i + 1), FALSE) < 0) { - uprintf("Warning: Could not find a description for image index %d", i + 1); - StrArrayAdd(&version_name, "Unknown Windows Version", TRUE); - } - } - } - if (bNonStandard) - uprintf("Warning: Nonstandard Windows image (missing entries)"); - - if (i > 1) - // NB: _log2 returns -2 if SelectionDialog() returns negative (user cancelled) - i = _log2(SelectionDialog(BS_AUTORADIOBUTTON, - lmprintf(MSG_291), lmprintf(MSG_292), version_name.String, i, 1)) + 1; - if (i < 0) - wintogo_index = -2; // Cancelled by the user - else if (i == 0) - wintogo_index = 1; - else - wintogo_index = atoi(version_index.String[i - 1]); - if (i > 0) { - // Get the version data from the XML index - val = get_token_data_file_indexed("MAJOR", xml_file, i); - img_report.win_version.major = (uint16_t)safe_atoi(val); - free(val); - val = get_token_data_file_indexed("MINOR", xml_file, i); - img_report.win_version.minor = (uint16_t)safe_atoi(val); - free(val); - val = get_token_data_file_indexed("BUILD", xml_file, i); - img_report.win_version.build = (uint16_t)safe_atoi(val); - free(val); - val = get_token_data_file_indexed("SPBUILD", xml_file, i); - img_report.win_version.revision = (uint16_t)safe_atoi(val); - free(val); - if ((img_report.win_version.major == 10) && (img_report.win_version.build > 20000)) - img_report.win_version.major = 11; - // If we couldn't obtain the major and build, we have a problem - if (img_report.win_version.major == 0 || img_report.win_version.build == 0) - uprintf("Warning: Could not obtain version information from XML index (Nonstandard Windows image?)"); - uprintf("Will use '%s' (Build: %d, Index %s) for Windows To Go", - version_name.String[i - 1], img_report.win_version.build, version_index.String[i - 1]); - // Need Windows 10 Creator Update or later for boot on REMOVABLE to work - if ((img_report.win_version.build < 15000) && (SelectedDrive.MediaType != FixedMedia)) { - if (MessageBoxExU(hMainDialog, lmprintf(MSG_098), lmprintf(MSG_190), - MB_YESNO | MB_ICONWARNING | MB_IS_RTL, selected_langid) != IDYES) - wintogo_index = -2; - } - // Display a notice about WppRecorder.sys for 1809 ISOs - if (img_report.win_version.build == 17763) { - notification_info more_info; - more_info.id = MORE_INFO_URL; - more_info.url = WPPRECORDER_MORE_INFO_URL; - Notification(MSG_INFO, NULL, &more_info, lmprintf(MSG_128, "Windows To Go"), lmprintf(MSG_133)); - } - } - StrArrayDestroy(&version_name); - StrArrayDestroy(&version_index); - -out: - DeleteFileU(xml_file); - if (!img_report.is_windows_img) - UnMountISO(); - return wintogo_index; -} - -// https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-8.1-and-8/jj721578(v=ws.11) -// As opposed to the technet guide above, we don't set internal drives offline, -// due to people wondering why they can't see them by default and we also use -// bcdedit rather than 'unattend.xml' to disable the recovery environment. -static BOOL SetupWinToGo(DWORD DriveIndex, const char* drive_name, BOOL use_esp) -{ - char *mounted_iso, *ms_efi = NULL, mounted_image_path[128], cmd[MAX_PATH]; - ULONG cluster_size; - - uprintf("Windows To Go mode selected"); - // Additional sanity checks - if ((use_esp) && (SelectedDrive.MediaType != FixedMedia) && (nWindowsBuildNumber < 15000)) { - FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_NOT_SUPPORTED; - return FALSE; - } - - if (!img_report.is_windows_img) { - mounted_iso = MountISO(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); - return FALSE; - } - static_sprintf(mounted_image_path, "%s%s", mounted_iso, &img_report.wininst_path[wininst_index][2]); - uprintf("Mounted ISO as '%s'", mounted_iso); - } - - // Now we use the WIM API to apply that image - if (!WimApplyImage(img_report.is_windows_img ? image_path : mounted_image_path, wintogo_index, drive_name)) { - uprintf("Failed to apply Windows To Go image"); - if (!IS_ERROR(FormatStatus)) - FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|APPERR(ERROR_ISO_EXTRACT); - if (!img_report.is_windows_img) - UnMountISO(); - return FALSE; - } - if (!img_report.is_windows_img) - UnMountISO(); - - if (use_esp) { - uprintf("Setting up EFI System Partition"); - // According to Ubuntu (https://bugs.launchpad.net/ubuntu/+source/partman-efi/+bug/811485) you want to use FAT32. - // However, you have to be careful that the cluster size needs to be greater or equal to the sector size, which - // in turn has an impact on the minimum EFI partition size we can create (see ms_efi_size_MB in drive.c) - if (SelectedDrive.SectorSize <= 1024) - cluster_size = 1024; - else if (SelectedDrive.SectorSize <= 4096) - cluster_size = 4096; - else // Go for broke - cluster_size = (ULONG)SelectedDrive.SectorSize; - // Boy do you *NOT* want to specify a label here, and spend HOURS figuring out why your EFI partition cannot boot... - // Also, we use the Large FAT32 facility Microsoft APIs are *UTTERLY USELESS* for achieving what we want: - // 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, "", - 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); - if (ms_efi == NULL) { - FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_CANT_ASSIGN_LETTER); - return FALSE; - } - } - - // We invoke the 'bcdboot' command from the host, as the one from the drive produces problems (#558) - // and of course, we couldn't invoke an ARM64 'bcdboot' binary on an x86 host anyway... - // Also, since Rufus should (usually) be running as a 32 bit app, on 64 bit systems, we need to use - // 'C:\Windows\Sysnative' and not 'C:\Windows\System32' to invoke bcdboot, as 'C:\Windows\System32' - // will get converted to 'C:\Windows\SysWOW64' behind the scenes, and there is no bcdboot.exe there. - uprintf("Enabling boot using command:"); - static_sprintf(cmd, "%s\\bcdboot.exe %s\\Windows /v /f %s /s %s", sysnative_dir, drive_name, - HAS_BOOTMGR_BIOS(img_report) ? (HAS_BOOTMGR_EFI(img_report) ? "ALL" : "BIOS") : "UEFI", - (use_esp)?ms_efi:drive_name); - uprintf(cmd); - if (RunCommand(cmd, sysnative_dir, usb_debug) != 0) { - // Try to continue... but report a failure - uprintf("Failed to enable boot"); - FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_ISO_EXTRACT); - } - - UpdateProgressWithInfo(OP_FILE_COPY, MSG_267, wim_proc_files + 2 * wim_extra_files, wim_nb_files); - - // Setting internal drives offline for Windows To Go is crucial if, for instance, you are using ReFS - // on Windows 10 (therefore ReFS v3.4) and don't want a Windows 11 To Go boot to automatically - // "upgrade" the ReFS version on all drives to v3.7, thereby preventing you from being able to mount - // those volumes back on Windows 10 ever again. Yes, I have been stung by this Microsoft bullshit! - // See: https://gist.github.com/0xbadfca11/da0598e47dd643d933dc#Mountability - if (unattend_xml_flags & UNATTEND_OFFLINE_INTERNAL_DRIVES) { - uprintf("Setting the target's internal drives offline using command:"); - // This applies the "offlineServicing" section of the unattend.xml (while ignoring the other sections) - static_sprintf(cmd, "dism /Image:%s\\ /Apply-Unattend:%s", drive_name, unattend_xml_path); - uprintf(cmd); - RunCommand(cmd, NULL, usb_debug); - } - - uprintf("Disabling use of the Windows Recovery Environment using command:"); - static_sprintf(cmd, "%s\\bcdedit.exe /store %s\\EFI\\Microsoft\\Boot\\BCD /set {default} recoveryenabled no", - sysnative_dir, (use_esp) ? ms_efi : drive_name); - uprintf(cmd); - RunCommand(cmd, sysnative_dir, usb_debug); - - UpdateProgressWithInfo(OP_FILE_COPY, MSG_267, wim_nb_files, wim_nb_files); - - if (use_esp) { - Sleep(200); - AltUnmountVolume(ms_efi, FALSE); - } - - return TRUE; -} - -/* - * Add unattend.xml to 'sources\boot.wim' (install) or 'Windows\Panther\' (Windows To Go) - * NB: Work with a copy of unattend_xml_flags as a paremeter since we will modify it. - */ -BOOL ApplyWindowsCustomization(char drive_letter, int flags) -{ - BOOL r = FALSE, is_hive_mounted = FALSE; - int i; - const int wim_index = 2; - const char* offline_hive_name = "RUFUS_OFFLINE_HIVE"; - char boot_wim_path[] = "?:\\sources\\boot.wim", key_path[64]; - char appraiserres_dll_src[] = "?:\\sources\\appraiserres.dll"; - char appraiserres_dll_dst[] = "?:\\sources\\appraiserres.bak"; - char *mount_path = NULL, path[MAX_PATH]; - HKEY hKey = NULL, hSubKey = NULL; - LSTATUS status; - DWORD dwDisp, dwVal = 1; - - assert(unattend_xml_path != NULL); - uprintf("Applying Windows customization:"); - if (flags & UNATTEND_WINDOWS_TO_GO) { - static_sprintf(path, "%c:\\Windows\\Panther", drive_letter); - if (!CreateDirectoryA(path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) { - uprintf("Could not create '%s' : %s", path, WindowsErrorString()); - goto out; - } - static_sprintf(path, "%c:\\Windows\\Panther\\unattend.xml", drive_letter); - if (!CopyFileA(unattend_xml_path, path, TRUE)) { - uprintf("Could not create '%s' : %s", path, WindowsErrorString()); - goto out; - } - uprintf("Added '%s'", path); - } else { - boot_wim_path[0] = drive_letter; - if (flags & UNATTEND_WINPE_SETUP_MASK) { - // Create a backup of sources\appraiserres.dll and then create an empty file to - // allow in-place upgrades without TPM/SB. Note that we need to create an empty, - // appraiserres.dll otherwise setup.exe extracts its own. - appraiserres_dll_src[0] = drive_letter; - appraiserres_dll_dst[0] = drive_letter; - if (!MoveFileExU(appraiserres_dll_src, appraiserres_dll_dst, MOVEFILE_REPLACE_EXISTING)) - uprintf("Could not rename '%s': %s", appraiserres_dll_src, WindowsErrorString()); - else - CloseHandle(CreateFileU(appraiserres_dll_src, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, - NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)); - uprintf("Renamed '%s' → '%s'", appraiserres_dll_src, appraiserres_dll_dst); - } - - UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 0, PATCH_PROGRESS_TOTAL); - // We only need to mount boot.wim if we have windowsPE data to deal with. If - // not, we can just copy our unattend.xml in \sources\$OEM$\$$\Panther\. - if (flags & UNATTEND_WINPE_SETUP_MASK) { - uprintf("Mounting '%s'...", boot_wim_path); - mount_path = WimMountImage(boot_wim_path, wim_index); - if (mount_path == NULL) - goto out; - } - - if (flags & UNATTEND_SECUREBOOT_TPM_MINRAM) { - // Try to create the registry keys directly, and fallback to using unattend - // if that fails (which the Windows Store version is expected to do). - static_sprintf(path, "%s\\Windows\\System32\\config\\SYSTEM", mount_path); - if (!MountRegistryHive(HKEY_LOCAL_MACHINE, offline_hive_name, path)) { - uprintf("Falling back to creating the registry keys through unattend.xml"); - goto copy_unattend; - } - UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 101, PATCH_PROGRESS_TOTAL); - is_hive_mounted = TRUE; - - static_sprintf(key_path, "%s\\Setup", offline_hive_name); - status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, key_path, 0, KEY_READ | KEY_CREATE_SUB_KEY, &hKey); - if (status != ERROR_SUCCESS) { - SetLastError(status); - uprintf("Could not open 'HKLM\\SYSTEM\\Setup' registry key: %s", WindowsErrorString()); - goto copy_unattend; - } - - status = RegCreateKeyExA(hKey, "LabConfig", 0, NULL, 0, - KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_CREATE_SUB_KEY, NULL, &hSubKey, &dwDisp); - if (status != ERROR_SUCCESS) { - SetLastError(status); - uprintf("Could not create 'HKLM\\SYSTEM\\Setup\\LabConfig' registry key: %s", WindowsErrorString()); - goto copy_unattend; - } - - for (i = 0; i < ARRAYSIZE(bypass_name); i++) { - status = RegSetValueExA(hSubKey, bypass_name[i], 0, REG_DWORD, (LPBYTE)&dwVal, sizeof(DWORD)); - if (status != ERROR_SUCCESS) { - SetLastError(status); - uprintf("Could not set 'HKLM\\SYSTEM\\Setup\\LabConfig\\%s' registry key: %s", - bypass_name[i], WindowsErrorString()); - goto copy_unattend; - } - uprintf("Created 'HKLM\\SYSTEM\\Setup\\LabConfig\\%s' registry key", bypass_name[i]); - } - // We were successfull in creating the keys so disable the windowsPE section from unattend.xml - // We do this by replacing '' with '' - // (provided that the registry key creation was the only item for this pass) - if ((flags & UNATTEND_WINPE_SETUP_MASK) == UNATTEND_SECUREBOOT_TPM_MINRAM) { - if (replace_in_token_data(unattend_xml_path, " entries from windowsPE (and only windowsPE). - assert(FALSE); - } - UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 102, PATCH_PROGRESS_TOTAL); - } - -copy_unattend: - if (flags & UNATTEND_WINPE_SETUP_MASK) { - // If we have a windowsPE section, copy the answer files to the root of boot.wim as - // Autounattend.xml. This also results in that file being automatically copied over - // to %WINDIR%\Panther\unattend.xml for later passes processing. - assert(mount_path != NULL); - static_sprintf(path, "%s\\Autounattend.xml", mount_path); - if (!CopyFileU(unattend_xml_path, path, TRUE)) { - uprintf("Could not create boot.wim 'Autounattend.xml': %s", WindowsErrorString()); - goto out; - } - uprintf("Added 'Autounattend.xml' to '%s'", boot_wim_path); - } else { - // If there is no windowsPE section in our unattend, then copying it as Autounattend.xml on - // the root of boot.wim will not work as Windows Setup does *NOT* carry Autounattend.xml into - // %WINDIR%\Panther\unattend.xml then (See: https://github.com/pbatard/rufus/issues/1981). - // So instead, copy it to \sources\$OEM$\$$\Panther\unattend.xml on the media, as the content - // of \sources\$OEM$\$$\* will get copied into %WINDIR%\ during the file copy phase. - static_sprintf(path, "%c:\\sources\\$OEM$\\$$\\Panther", drive_letter); - i = SHCreateDirectoryExA(NULL, path, NULL); - if (i != ERROR_SUCCESS) { - SetLastError(i); - uprintf("Error: Could not create directory '%s': %s", path, WindowsErrorString()); - goto out; - } - static_sprintf(path, "%c:\\sources\\$OEM$\\$$\\Panther\\unattend.xml", drive_letter); - if (!CopyFileU(unattend_xml_path, path, TRUE)) { - uprintf("Could not create '%s': %s", path, WindowsErrorString()); - goto out; - } - uprintf("Created '%s'", path); - } - UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 103, PATCH_PROGRESS_TOTAL); - } - r = TRUE; - -out: - if (hSubKey != NULL) - RegCloseKey(hSubKey); - if (hKey != NULL) - RegCloseKey(hKey); - if (is_hive_mounted) { - UnmountRegistryHive(HKEY_LOCAL_MACHINE, offline_hive_name); - UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 104, PATCH_PROGRESS_TOTAL); - } - if (mount_path) { - uprintf("Unmounting '%s'...", boot_wim_path, wim_index); - WimUnmountImage(boot_wim_path, wim_index); - UpdateProgressWithInfo(OP_PATCH, MSG_325, PATCH_PROGRESS_TOTAL, PATCH_PROGRESS_TOTAL); - } - free(mount_path); - return r; -} - static void update_progress(const uint64_t processed_bytes) { // NB: We don't really care about resetting this value to UINT64_MAX for a new pass. diff --git a/src/format.h b/src/format.h index 398b60a6..8bc514e8 100644 --- a/src/format.h +++ b/src/format.h @@ -16,6 +16,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + +#include #include #include // for MEDIA_TYPE @@ -112,3 +114,5 @@ typedef BOOLEAN (WINAPI* EnableVolumeCompression_t)( 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); +BOOL FormatPartition(DWORD DriveIndex, uint64_t PartitionOffset, DWORD UnitAllocationSize, USHORT FSType, LPCSTR Label, DWORD Flags); +DWORD WINAPI FormatThread(void* param); diff --git a/src/missing.h b/src/missing.h index a204d1b0..17bdc605 100644 --- a/src/missing.h +++ b/src/missing.h @@ -115,11 +115,39 @@ static __inline int _log2(register int val) return ret; } -// Remap bits from a byte according to an 8x8 bit matrix +/// +/// Remaps bits from a byte according to an 8x8 bit matrix. +/// +/// The byte to remap. +/// An 8-byte array where each byte has a single bit set to the position to remap to. +/// Indicates whether the reverse mapping operation should be applied. +/// The remapped byte data. static __inline uint8_t remap8(uint8_t src, uint8_t* map, const BOOL reverse) { uint8_t i, m = 1, r = 0; - for (i = 0, m = 1; i < 8; i++, m <<= 1) { + for (i = 0, m = 1; i < (sizeof(src) * 8); i++, m <<= 1) { + if (reverse) { + if (src & map[i]) + r |= m; + } else { + if (src & m) + r |= map[i]; + } + } + return r; +} + +/// +/// Remaps bits from a 16-bit word according to a 16x16 bit matrix. +/// +/// The word to remap. +/// A 16-word array where each word has a single bit set to the position to remap to. +/// Indicates whether the reverse mapping operation should be applied. +/// The remapped byte data. +static __inline uint16_t remap16(uint16_t src, uint16_t* map, const BOOL reverse) +{ + uint16_t i, m = 1, r = 0; + for (i = 0, m = 1; i < (sizeof(src) * 8); i++, m <<= 1) { if (reverse) { if (src & map[i]) r |= m; diff --git a/src/rufus.c b/src/rufus.c index 35bd9372..823b0479 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -39,6 +39,7 @@ #include #include "rufus.h" +#include "format.h" #include "missing.h" #include "resource.h" #include "msapi_utf8.h" @@ -46,6 +47,7 @@ #include "ui.h" #include "re.h" +#include "wue.h" #include "drive.h" #include "settings.h" #include "bled/bled.h" @@ -78,7 +80,7 @@ static BOOL app_changed_label = FALSE; static BOOL allowed_filesystem[FS_MAX] = { 0 }; static int64_t last_iso_blocking_status; static int selected_pt = -1, selected_fs = FS_UNKNOWN, preselected_fs = FS_UNKNOWN; -static int image_index = 0, select_index = 0, unattend_xml_mask = UNATTEND_DEFAULT_SELECTION_MASK; +static int image_index = 0, select_index = 0; static RECT relaunch_rc = { -65536, -65536, 0, 0}; static UINT uMBRChecked = BST_UNCHECKED; static HANDLE format_thread = NULL; @@ -125,7 +127,7 @@ BOOL write_as_image = FALSE, write_as_esp = FALSE, use_vds = FALSE, ignore_boot_ BOOL appstore_version = FALSE, is_vds_available = TRUE; 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, unattend_xml_flags = 0; +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]; @@ -133,12 +135,11 @@ 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, *unattend_xml_path = NULL; +char *archive_path = NULL, image_option_txt[128], *fido_url = 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 }; const char* flash_type[BADLOCKS_PATTERN_TYPES] = { "SLC", "MLC", "TLC" }; -const char* bypass_name[] = { "BypassTPMCheck", "BypassSecureBootCheck", "BypassRAMCheck" }; RUFUS_DRIVE rufus_drive[MAX_DRIVES] = { 0 }; // TODO: Remember to update copyright year in stdlg's AboutCallback() WM_INITDIALOG, @@ -1175,7 +1176,12 @@ static void UpdateImage(BOOL update_image_option_only) IGNORE_RETVAL(ComboBox_SetCurSel(hImageOption, imop_win_sel)); } -static uint8_t FindArch(const char* filename) +/// +/// Parse a PE executable file and return its CPU architecture. +/// +/// The path of the PE executable to parse. +/// An enum ArchType value (as defined in rufus.h) +static uint8_t FindArch(const char* path) { uint8_t ret = ARCH_UNKNOWN; HANDLE hFile = NULL, hFileMapping = NULL; @@ -1184,9 +1190,9 @@ static uint8_t FindArch(const char* filename) // PE headers, so we don't need to care about using PIMAGE_NT_HEADERS[32|64] PIMAGE_NT_HEADERS pImageNTHeader = NULL; - hFile = CreateFileU(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + hFile = CreateFileU(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == NULL) { - uprintf("FindArch: Could not open file '%s': %s", filename, WindowsErrorString()); + uprintf("FindArch: Could not open file '%s': %s", path, WindowsErrorString()); return 0; } @@ -1250,172 +1256,6 @@ out: return ret; } -/// -/// Create an installation answer file containing the sections specified by the flags. -/// -/// The processor architecture of the Windows image being used. -/// A bitmask representing the sections to enable. -/// See "Windows User Experience flags and masks" from in rufus.h -/// The path of a newly created answer file on success or NULL on error. -static char* CreateUnattendXml(int arch, int flags) -{ - static char path[MAX_PATH]; - FILE* fd; - int i, order; - const char* xml_arch_names[5] = { "x86", "amd64", "arm", "arm64" }; - unattend_xml_flags = flags; - if (arch < ARCH_X86_32 || arch >= ARCH_ARM_64 || flags == 0) - return NULL; - arch--; - // coverity[swapped_arguments] - if (GetTempFileNameU(temp_dir, APPLICATION_NAME, 0, path) == 0) - return NULL; - fd = fopen(path, "w"); - if (fd == NULL) - return NULL; - - fprintf(fd, "\n"); - fprintf(fd, "\n"); - - // This part produces the unbecoming display of a command prompt window during initial setup as well - // as alters the layout and options of the initial Windows installer screens, which may scare users. - // So, in format.c, we'll try to insert the registry keys directly and drop this section. However, - // because Microsoft prevents Store apps from editing an offline registry, we do need this fallback. - if (flags & UNATTEND_WINPE_SETUP_MASK) { - order = 1; - fprintf(fd, " \n"); - fprintf(fd, " \n", xml_arch_names[arch]); - // WinPE will complain if we don't provide a product key. *Any* product key. This is soooo idiotic... - fprintf(fd, " \n"); - fprintf(fd, " \n"); - fprintf(fd, " \n"); - fprintf(fd, " \n"); - fprintf(fd, " \n"); - if (flags & UNATTEND_SECUREBOOT_TPM_MINRAM) { - fprintf(fd, " \n"); - for (i = 0; i < ARRAYSIZE(bypass_name); i++) { - fprintf(fd, " \n"); - fprintf(fd, " %d\n", order++); - fprintf(fd, " reg add HKLM\\SYSTEM\\Setup\\LabConfig /v %s /t REG_DWORD /d 1 /f\n", bypass_name[i]); - fprintf(fd, " \n"); - } - fprintf(fd, " \n"); - } - fprintf(fd, " \n"); - fprintf(fd, " \n"); - } - - if (flags & UNATTEND_SPECIALIZE_DEPLOYMENT_MASK) { - order = 1; - fprintf(fd, " \n"); - fprintf(fd, " \n", xml_arch_names[arch]); - fprintf(fd, " \n"); - // This part was picked from https://github.com/AveYo/MediaCreationTool.bat/blob/main/bypass11/AutoUnattend.xml - if (flags & UNATTEND_NO_ONLINE_ACCOUNT) { - fprintf(fd, " \n"); - fprintf(fd, " %d\n", order++); - fprintf(fd, " reg add HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OOBE /v BypassNRO /t REG_DWORD /d 1 /f\n"); - fprintf(fd, " \n"); - } - fprintf(fd, " \n"); - fprintf(fd, " \n"); - fprintf(fd, " \n"); - } - - if (flags & UNATTEND_OOBE_MASK) { - order = 1; - fprintf(fd, " \n"); - if (flags & UNATTEND_OOBE_SHELL_SETUP_MASK) { - fprintf(fd, " \n", xml_arch_names[arch]); - // https://docs.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-shell-setup-oobe-protectyourpc - // It is really super insidous of Microsoft to call this option "ProtectYourPC", when it's really only about - // data collection. But of course, if it was called "AllowDataCollection", everyone would turn it off... - if (flags & UNATTEND_NO_DATA_COLLECTION) { - fprintf(fd, " \n"); - fprintf(fd, " 3\n"); - fprintf(fd, " \n"); - } - if (flags & UNATTEND_DUPLICATE_USER) { - order = 1; - char username[128] = { 0 }; - DWORD size = sizeof(username); - if (GetUserNameU(username, &size) && username[0] != 0) { - // If we create a local account in unattend.xml, then we can get Windows 11 - // 22H2 to skip MSA even if the network is connected during installation. - fprintf(fd, " \n"); - fprintf(fd, " \n"); - fprintf(fd, " \n"); - fprintf(fd, " %s\n", username); - fprintf(fd, " %s\n", username); - fprintf(fd, " Administrators;Power Users\n"); - // Sets an empty password for the account (which, in Microsoft's convoluted ways, - // needs to be initialized to the Base64 encoded UTF-16 string "Password"). - // The use of an empty password has both the advantage of not having to ask users - // to type in a password in Rufus (which they might be weary of) as well as allowing - // automated logon during setup. - fprintf(fd, " \n"); - fprintf(fd, " UABhAHMAcwB3AG8AcgBkAA==\n"); - fprintf(fd, " false</PlainText>\n"); - fprintf(fd, " </Password>\n"); - fprintf(fd, " </LocalAccount>\n"); - fprintf(fd, " </LocalAccounts>\n"); - fprintf(fd, " </UserAccounts>\n"); - // Since we set a blank password, we'll ask the user to change it at next logon. - fprintf(fd, " <FirstLogonCommands>\n"); - fprintf(fd, " <SynchronousCommand wcm:action=\"add\">\n"); - fprintf(fd, " <Order>%d</Order>\n", order++); - fprintf(fd, " <CommandLine>net user &quot;%s&quot; /logonpasswordchg:yes</CommandLine>\n", username); - fprintf(fd, " </SynchronousCommand>\n"); - fprintf(fd, " </FirstLogonCommands>\n"); - } else { - uprintf("Warning: Could not retreive current user name. Local Account was not created"); - } - } - fprintf(fd, " </component>\n"); - } - if (flags & UNATTEND_OOBE_INTERNATIONAL_MASK) { - fprintf(fd, " <component name=\"Microsoft-Windows-International-Core\" processorArchitecture=\"%s\" language=\"neutral\" " - "xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " - "publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]); - // What a frigging mess retreiving and trying to match the various locales - // Microsoft has made. And, *NO*, the new User Language Settings have not - // improved things in the slightest. They made it much worse for developers! - fprintf(fd, " <InputLocale>%s</InputLocale>\n", - ReadRegistryKeyStr(REGKEY_HKCU, "Keyboard Layout\\Preload\\1")); - fprintf(fd, " <SystemLocale>%s</SystemLocale>\n", ToLocaleName(GetSystemDefaultLCID())); - fprintf(fd, " <UserLocale>%s</UserLocale>\n", ToLocaleName(GetUserDefaultLCID())); - fprintf(fd, " <UILanguage>%s</UILanguage>\n", ToLocaleName(GetUserDefaultUILanguage())); - fprintf(fd, " <UILanguageFallback>%s</UILanguageFallback>\n", - // NB: Officially, this is a REG_MULTI_SZ string - ReadRegistryKeyStr(REGKEY_HKLM, "SYSTEM\\CurrentControlSet\\Control\\Nls\\Language\\InstallLanguageFallback")); - fprintf(fd, " </component>\n"); - } - fprintf(fd, " </settings>\n"); - } - - if (flags & UNATTEND_OFFLINE_SERVICING_MASK) { - fprintf(fd, " <settings pass=\"offlineServicing\">\n"); - if (flags & UNATTEND_OFFLINE_INTERNAL_DRIVES) { - fprintf(fd, " <component name=\"Microsoft-Windows-PartitionManager\" processorArchitecture=\"%s\" language=\"neutral\" " - "xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " - "publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]); - fprintf(fd, " <SanPolicy>4</SanPolicy>\n"); - fprintf(fd, " </component>\n"); - } - fprintf(fd, " </settings>\n"); - } - - fprintf(fd, "</unattend>\n"); - fclose(fd); - return path; -} - // The scanning process can be blocking for message processing => use a thread DWORD WINAPI ImageScanThread(LPVOID param) { diff --git a/src/rufus.h b/src/rufus.h index c9afd527..2bfc6554 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -637,7 +637,6 @@ extern char* WimMountImage(const char* image, int index); extern BOOL WimUnmountImage(const char* image, int index); extern int8_t IsBootableImage(const char* path); extern BOOL AppendVHDFooter(const char* vhd_path); -extern int SetWinToGoIndex(void); extern int IsHDD(DWORD DriveIndex, uint16_t vid, uint16_t pid, const char* strid); extern char* GetSignatureName(const char* path, const char* country_code, BOOL bSilent); extern uint64_t GetSignatureTimeStamp(const char* path); @@ -670,7 +669,6 @@ extern HANDLE CreatePreallocatedFile(const char* lpFileName, DWORD dwDesiredAcce DWORD dwFlagsAndAttributes, LONGLONG fileSize); #define GetTextWidth(hDlg, id) GetTextSize(GetDlgItem(hDlg, id), NULL).cx -DWORD WINAPI FormatThread(void* param); DWORD WINAPI SaveImageThread(void* param); DWORD WINAPI SumThread(void* param); diff --git a/src/rufus.rc b/src/rufus.rc index 0f5c0577..ab8e015a 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 3.20.1921" +CAPTION "Rufus 3.20.1922" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -395,8 +395,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 3,20,1921,0 - PRODUCTVERSION 3,20,1921,0 + FILEVERSION 3,20,1922,0 + PRODUCTVERSION 3,20,1922,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -414,13 +414,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "3.20.1921" + VALUE "FileVersion", "3.20.1922" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2022 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-3.20.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "3.20.1921" + VALUE "ProductVersion", "3.20.1922" END END BLOCK "VarFileInfo" diff --git a/src/wue.c b/src/wue.c new file mode 100644 index 00000000..b98b3083 --- /dev/null +++ b/src/wue.c @@ -0,0 +1,778 @@ +/* + * Rufus: The Reliable USB Formatting Utility + * Windows User Experience + * Copyright © 2022 Pete Batard <pete@akeo.ie> + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <windows.h> +#include <windowsx.h> +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +#include "rufus.h" +#include "drive.h" +#include "format.h" +#include "missing.h" +#include "resource.h" +#include "registry.h" +#include "msapi_utf8.h" +#include "localization.h" + + /* Memory leaks detection - define _CRTDBG_MAP_ALLOC as preprocessor macro */ +#ifdef _CRTDBG_MAP_ALLOC +#include <stdlib.h> +#include <crtdbg.h> +#endif + +const char* bypass_name[] = { "BypassTPMCheck", "BypassSecureBootCheck", "BypassRAMCheck" }; + +int unattend_xml_flags = 0, wintogo_index = -1, wininst_index = 0; +int unattend_xml_mask = UNATTEND_DEFAULT_SELECTION_MASK; +char* unattend_xml_path = NULL; + +extern uint32_t wim_nb_files, wim_proc_files, wim_extra_files; + + /// <summary> + /// Create an installation answer file containing the sections specified by the flags. + /// </summary> + /// <param name="arch">The processor architecture of the Windows image being used.</param> + /// <param name="flags">A bitmask representing the sections to enable. + /// See "Windows User Experience flags and masks" from rufus.h</param> + /// <returns>The path of a newly created answer file on success or NULL on error.</returns> +char* CreateUnattendXml(int arch, int flags) +{ + static char path[MAX_PATH]; + FILE* fd; + int i, order; + const char* xml_arch_names[5] = { "x86", "amd64", "arm", "arm64" }; + unattend_xml_flags = flags; + if (arch < ARCH_X86_32 || arch >= ARCH_ARM_64 || flags == 0) + return NULL; + arch--; + // coverity[swapped_arguments] + if (GetTempFileNameU(temp_dir, APPLICATION_NAME, 0, path) == 0) + return NULL; + fd = fopen(path, "w"); + if (fd == NULL) + return NULL; + + fprintf(fd, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); + fprintf(fd, "<unattend xmlns=\"urn:schemas-microsoft-com:unattend\">\n"); + + // This part produces the unbecoming display of a command prompt window during initial setup as well + // as alters the layout and options of the initial Windows installer screens, which may scare users. + // So, in format.c, we'll try to insert the registry keys directly and drop this section. However, + // because Microsoft prevents Store apps from editing an offline registry, we do need this fallback. + if (flags & UNATTEND_WINPE_SETUP_MASK) { + order = 1; + fprintf(fd, " <settings pass=\"windowsPE\">\n"); + fprintf(fd, " <component name=\"Microsoft-Windows-Setup\" processorArchitecture=\"%s\" language=\"neutral\" " + "xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + "publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]); + // WinPE will complain if we don't provide a product key. *Any* product key. This is soooo idiotic... + fprintf(fd, " <UserData>\n"); + fprintf(fd, " <ProductKey>\n"); + fprintf(fd, " <Key />\n"); + fprintf(fd, " </ProductKey>\n"); + fprintf(fd, " </UserData>\n"); + if (flags & UNATTEND_SECUREBOOT_TPM_MINRAM) { + fprintf(fd, " <RunSynchronous>\n"); + for (i = 0; i < ARRAYSIZE(bypass_name); i++) { + fprintf(fd, " <RunSynchronousCommand wcm:action=\"add\">\n"); + fprintf(fd, " <Order>%d</Order>\n", order++); + fprintf(fd, " <Path>reg add HKLM\\SYSTEM\\Setup\\LabConfig /v %s /t REG_DWORD /d 1 /f</Path>\n", bypass_name[i]); + fprintf(fd, " </RunSynchronousCommand>\n"); + } + fprintf(fd, " </RunSynchronous>\n"); + } + fprintf(fd, " </component>\n"); + fprintf(fd, " </settings>\n"); + } + + if (flags & UNATTEND_SPECIALIZE_DEPLOYMENT_MASK) { + order = 1; + fprintf(fd, " <settings pass=\"specialize\">\n"); + fprintf(fd, " <component name=\"Microsoft-Windows-Deployment\" processorArchitecture=\"%s\" language=\"neutral\" " + "xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + "publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]); + fprintf(fd, " <RunSynchronous>\n"); + // This part was picked from https://github.com/AveYo/MediaCreationTool.bat/blob/main/bypass11/AutoUnattend.xml + if (flags & UNATTEND_NO_ONLINE_ACCOUNT) { + fprintf(fd, " <RunSynchronousCommand wcm:action=\"add\">\n"); + fprintf(fd, " <Order>%d</Order>\n", order++); + fprintf(fd, " <Path>reg add HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OOBE /v BypassNRO /t REG_DWORD /d 1 /f</Path>\n"); + fprintf(fd, " </RunSynchronousCommand>\n"); + } + fprintf(fd, " </RunSynchronous>\n"); + fprintf(fd, " </component>\n"); + fprintf(fd, " </settings>\n"); + } + + if (flags & UNATTEND_OOBE_MASK) { + order = 1; + fprintf(fd, " <settings pass=\"oobeSystem\">\n"); + if (flags & UNATTEND_OOBE_SHELL_SETUP_MASK) { + fprintf(fd, " <component name=\"Microsoft-Windows-Shell-Setup\" processorArchitecture=\"%s\" language=\"neutral\" " + "xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + "publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]); + // https://docs.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-shell-setup-oobe-protectyourpc + // It is really super insidous of Microsoft to call this option "ProtectYourPC", when it's really only about + // data collection. But of course, if it was called "AllowDataCollection", everyone would turn it off... + if (flags & UNATTEND_NO_DATA_COLLECTION) { + fprintf(fd, " <OOBE>\n"); + fprintf(fd, " <ProtectYourPC>3</ProtectYourPC>\n"); + fprintf(fd, " </OOBE>\n"); + } + if (flags & UNATTEND_DUPLICATE_USER) { + order = 1; + char username[128] = { 0 }; + DWORD size = sizeof(username); + if (GetUserNameU(username, &size) && username[0] != 0) { + // If we create a local account in unattend.xml, then we can get Windows 11 + // 22H2 to skip MSA even if the network is connected during installation. + fprintf(fd, " <UserAccounts>\n"); + fprintf(fd, " <LocalAccounts>\n"); + fprintf(fd, " <LocalAccount wcm:action=\"add\">\n"); + fprintf(fd, " <Name>%s</Name>\n", username); + fprintf(fd, " <DisplayName>%s</DisplayName>\n", username); + fprintf(fd, " <Group>Administrators;Power Users</Group>\n"); + // Sets an empty password for the account (which, in Microsoft's convoluted ways, + // needs to be initialized to the Base64 encoded UTF-16 string "Password"). + // The use of an empty password has both the advantage of not having to ask users + // to type in a password in Rufus (which they might be weary of) as well as allowing + // automated logon during setup. + fprintf(fd, " <Password>\n"); + fprintf(fd, " <Value>UABhAHMAcwB3AG8AcgBkAA==</Value>\n"); + fprintf(fd, " <PlainText>false</PlainText>\n"); + fprintf(fd, " </Password>\n"); + fprintf(fd, " </LocalAccount>\n"); + fprintf(fd, " </LocalAccounts>\n"); + fprintf(fd, " </UserAccounts>\n"); + // Since we set a blank password, we'll ask the user to change it at next logon. + fprintf(fd, " <FirstLogonCommands>\n"); + fprintf(fd, " <SynchronousCommand wcm:action=\"add\">\n"); + fprintf(fd, " <Order>%d</Order>\n", order++); + fprintf(fd, " <CommandLine>net user &quot;%s&quot; /logonpasswordchg:yes</CommandLine>\n", username); + fprintf(fd, " </SynchronousCommand>\n"); + fprintf(fd, " </FirstLogonCommands>\n"); + } else { + uprintf("Warning: Could not retreive current user name. Local Account was not created"); + } + } + fprintf(fd, " </component>\n"); + } + if (flags & UNATTEND_OOBE_INTERNATIONAL_MASK) { + fprintf(fd, " <component name=\"Microsoft-Windows-International-Core\" processorArchitecture=\"%s\" language=\"neutral\" " + "xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + "publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]); + // What a frigging mess retreiving and trying to match the various locales + // Microsoft has made. And, *NO*, the new User Language Settings have not + // improved things in the slightest. They made it much worse for developers! + fprintf(fd, " <InputLocale>%s</InputLocale>\n", + ReadRegistryKeyStr(REGKEY_HKCU, "Keyboard Layout\\Preload\\1")); + fprintf(fd, " <SystemLocale>%s</SystemLocale>\n", ToLocaleName(GetSystemDefaultLCID())); + fprintf(fd, " <UserLocale>%s</UserLocale>\n", ToLocaleName(GetUserDefaultLCID())); + fprintf(fd, " <UILanguage>%s</UILanguage>\n", ToLocaleName(GetUserDefaultUILanguage())); + fprintf(fd, " <UILanguageFallback>%s</UILanguageFallback>\n", + // NB: Officially, this is a REG_MULTI_SZ string + ReadRegistryKeyStr(REGKEY_HKLM, "SYSTEM\\CurrentControlSet\\Control\\Nls\\Language\\InstallLanguageFallback")); + fprintf(fd, " </component>\n"); + } + fprintf(fd, " </settings>\n"); + } + + if (flags & UNATTEND_OFFLINE_SERVICING_MASK) { + fprintf(fd, " <settings pass=\"offlineServicing\">\n"); + if (flags & UNATTEND_OFFLINE_INTERNAL_DRIVES) { + fprintf(fd, " <component name=\"Microsoft-Windows-PartitionManager\" processorArchitecture=\"%s\" language=\"neutral\" " + "xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + "publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\">\n", xml_arch_names[arch]); + fprintf(fd, " <SanPolicy>4</SanPolicy>\n"); + fprintf(fd, " </component>\n"); + } + fprintf(fd, " </settings>\n"); + } + + fprintf(fd, "</unattend>\n"); + fclose(fd); + return path; +} + +/// <summary> +/// Setup and patch WinPE for Windows XP bootable USBs. +/// </summary> +/// <param name="drive_letter">The letter identifying the target drive.</param> +/// <returns>TRUE on success, FALSE on error.</returns> +BOOL SetupWinPE(char drive_letter) +{ + char src[64], dst[32]; + const char* basedir[3] = { "i386", "amd64", "minint" }; + const char* patch_str_org[2] = { "\\minint\\txtsetup.sif", "\\minint\\system32\\" }; + const char* patch_str_rep[2][2] = { { "\\i386\\txtsetup.sif", "\\i386\\system32\\" } , + { "\\amd64\\txtsetup.sif", "\\amd64\\system32\\" } }; + const char* win_nt_bt_org = "$win_nt$.~bt"; + const char* rdisk_zero = "rdisk(0)"; + const LARGE_INTEGER liZero = { {0, 0} }; + char setupsrcdev[64]; + HANDLE handle = INVALID_HANDLE_VALUE; + DWORD i, j, size, rw_size, index = 0; + BOOL r = FALSE; + char* buffer = NULL; + + if ((img_report.winpe & WINPE_AMD64) == WINPE_AMD64) + index = 1; + else if ((img_report.winpe & WINPE_MININT) == WINPE_MININT) + index = 2; + // Allow other values than harddisk 1, as per user choice for disk ID + static_sprintf(setupsrcdev, "SetupSourceDevice = \"\\device\\harddisk%d\\partition1\"", + ComboBox_GetCurSel(hDiskID)); + // Copy of ntdetect.com in root + static_sprintf(src, "%c:\\%s\\ntdetect.com", toupper(drive_letter), basedir[2 * (index / 2)]); + static_sprintf(dst, "%c:\\ntdetect.com", toupper(drive_letter)); + CopyFileA(src, dst, TRUE); + if (!img_report.uses_minint) { + // Create a copy of txtsetup.sif, as we want to keep the i386/amd64 files unmodified + static_sprintf(src, "%c:\\%s\\txtsetup.sif", toupper(drive_letter), basedir[index]); + static_sprintf(dst, "%c:\\txtsetup.sif", toupper(drive_letter)); + if (!CopyFileA(src, dst, TRUE)) { + uprintf("Did not copy %s as %s: %s\n", src, dst, WindowsErrorString()); + } + if (insert_section_data(dst, "[SetupData]", setupsrcdev, FALSE) == NULL) { + uprintf("Failed to add SetupSourceDevice in %s\n", dst); + goto out; + } + uprintf("Successfully added '%s' to %s\n", setupsrcdev, dst); + } + + static_sprintf(src, "%c:\\%s\\setupldr.bin", toupper(drive_letter), basedir[2 * (index / 2)]); + static_sprintf(dst, "%c:\\BOOTMGR", toupper(drive_letter)); + if (!CopyFileA(src, dst, TRUE)) { + uprintf("Did not copy %s as %s: %s\n", src, dst, WindowsErrorString()); + } + + // \minint with /minint option doesn't require further processing => return true + // \minint and no \i386 without /minint is unclear => return error + if (img_report.winpe & WINPE_MININT) { + if (img_report.uses_minint) { + uprintf("Detected \\minint directory with /minint option: nothing to patch\n"); + r = TRUE; + } else if (!(img_report.winpe & (WINPE_I386 | WINPE_AMD64))) { + uprintf("Detected \\minint directory only but no /minint option: not sure what to do\n"); + } + goto out; + } + + // At this stage we only handle \i386 + handle = CreateFileA(dst, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (handle == INVALID_HANDLE_VALUE) { + uprintf("Could not open %s for patching: %s\n", dst, WindowsErrorString()); + goto out; + } + size = GetFileSize(handle, NULL); + if (size == INVALID_FILE_SIZE) { + uprintf("Could not get size for file %s: %s\n", dst, WindowsErrorString()); + goto out; + } + buffer = (char*)malloc(size); + if (buffer == NULL) + goto out; + if ((!ReadFile(handle, buffer, size, &rw_size, NULL)) || (size != rw_size)) { + uprintf("Could not read file %s: %s\n", dst, WindowsErrorString()); + goto out; + } + if (!SetFilePointerEx(handle, liZero, NULL, FILE_BEGIN)) { + uprintf("Could not rewind file %s: %s\n", dst, WindowsErrorString()); + goto out; + } + + // Patch setupldr.bin + uprintf("Patching file %s\n", dst); + // Remove CRC check for 32 bit part of setupldr.bin from Win2k3 + if ((size > 0x2061) && (buffer[0x2060] == 0x74) && (buffer[0x2061] == 0x03)) { + buffer[0x2060] = 0xeb; + buffer[0x2061] = 0x1a; + uprintf(" 0x00002060: 0x74 0x03 -> 0xEB 0x1A (disable Win2k3 CRC check)\n"); + } + for (i = 1; i < size - 32; i++) { + for (j = 0; j < ARRAYSIZE(patch_str_org); j++) { + if (safe_strnicmp(&buffer[i], patch_str_org[j], strlen(patch_str_org[j]) - 1) == 0) { + assert(index < 2); + uprintf(" 0x%08X: '%s' -> '%s'\n", i, &buffer[i], patch_str_rep[index][j]); + strcpy(&buffer[i], patch_str_rep[index][j]); + i += (DWORD)max(strlen(patch_str_org[j]), strlen(patch_str_rep[index][j])); // in case org is a substring of rep + } + } + } + + if (!img_report.uses_minint) { + // Additional setupldr.bin/bootmgr patching + for (i = 0; i < size - 32; i++) { + // rdisk(0) -> rdisk(#) disk masquerading + // NB: only the first one seems to be needed + if (safe_strnicmp(&buffer[i], rdisk_zero, strlen(rdisk_zero) - 1) == 0) { + buffer[i + 6] = 0x30 + ComboBox_GetCurSel(hDiskID); + uprintf(" 0x%08X: '%s' -> 'rdisk(%c)'\n", i, rdisk_zero, buffer[i + 6]); + } + // $WIN_NT$_~BT -> i386/amd64 + if (safe_strnicmp(&buffer[i], win_nt_bt_org, strlen(win_nt_bt_org) - 1) == 0) { + uprintf(" 0x%08X: '%s' -> '%s%s'\n", i, &buffer[i], basedir[index], &buffer[i + strlen(win_nt_bt_org)]); + strcpy(&buffer[i], basedir[index]); + // This ensures that we keep the terminator backslash + buffer[i + strlen(basedir[index])] = buffer[i + strlen(win_nt_bt_org)]; + buffer[i + strlen(basedir[index]) + 1] = 0; + } + } + } + + if (!WriteFileWithRetry(handle, buffer, size, &rw_size, WRITE_RETRIES)) { + uprintf("Could not write patched file: %s\n", WindowsErrorString()); + goto out; + } + r = TRUE; + +out: + safe_closehandle(handle); + safe_free(buffer); + return r; +} + +/// <summary> +/// Checks which versions of Windows are available in an install image +/// to set our extraction index. Asks the user to select one if needed. +/// </summary> +/// <param name="">(none)</param> +/// <returns>-2 on user cancel, -1 on other error, >=0 on success.</returns> +int SetWinToGoIndex(void) +{ + char* mounted_iso, * val, mounted_image_path[128]; + char xml_file[MAX_PATH] = ""; + char* install_names[MAX_WININST]; + StrArray version_name, version_index; + int i; + BOOL bNonStandard = FALSE; + + // Sanity checks + wintogo_index = -1; + wininst_index = 0; + if ((nWindowsVersion < WINDOWS_8) || ((WimExtractCheck(FALSE) & 4) == 0) || + (ComboBox_GetCurItemData(hFileSystem) != FS_NTFS)) { + return -1; + } + + // If we have multiple windows install images, ask the user the one to use + if (img_report.wininst_index > 1) { + for (i = 0; i < img_report.wininst_index; i++) + install_names[i] = &img_report.wininst_path[i][2]; + wininst_index = _log2(SelectionDialog(BS_AUTORADIOBUTTON, lmprintf(MSG_130), + lmprintf(MSG_131), install_names, img_report.wininst_index, 1)); + if (wininst_index < 0) + return -2; + if (wininst_index >= MAX_WININST) + wininst_index = 0; + } + + // 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); + if (mounted_iso == NULL) { + uprintf("Could not mount ISO for Windows To Go selection"); + return FALSE; + } + static_sprintf(mounted_image_path, "%s%s", mounted_iso, &img_report.wininst_path[wininst_index][2]); + } + + // Now take a look at the XML file in install.wim to list our versions + if ((GetTempFileNameU(temp_dir, APPLICATION_NAME, 0, xml_file) == 0) || (xml_file[0] == 0)) { + // Last ditch effort to get a tmp file - just extract it to the current directory + static_strcpy(xml_file, ".\\RufVXml.tmp"); + } + // GetTempFileName() may leave a file behind + DeleteFileU(xml_file); + + // Must use the Windows WIM API as 7z messes up the XML + if (!WimExtractFile_API(img_report.is_windows_img ? image_path : mounted_image_path, + 0, "[1].xml", xml_file, FALSE)) { + uprintf("Could not acquire WIM index"); + goto out; + } + + StrArrayCreate(&version_name, 16); + StrArrayCreate(&version_index, 16); + for (i = 0; StrArrayAdd(&version_index, get_token_data_file_indexed("IMAGE INDEX", xml_file, i + 1), FALSE) >= 0; i++) { + // Some people are apparently creating *unofficial* Windows ISOs that don't have DISPLAYNAME elements. + // If we are parsing such an ISO, try to fall back to using DESCRIPTION. Of course, since we don't use + // a formal XML parser, if an ISO mixes entries with both DISPLAYNAME and DESCRIPTION and others with + // only DESCRIPTION, the version names we report will be wrong. + // But hey, there's only so far I'm willing to go to help people who, not content to have demonstrated + // their utter ignorance on development matters, are also trying to lecture experienced developers + // about specific "noob mistakes"... that don't exist in the code they are trying to criticize. + if (StrArrayAdd(&version_name, get_token_data_file_indexed("DISPLAYNAME", xml_file, i + 1), FALSE) < 0) { + bNonStandard = TRUE; + if (StrArrayAdd(&version_name, get_token_data_file_indexed("DESCRIPTION", xml_file, i + 1), FALSE) < 0) { + uprintf("Warning: Could not find a description for image index %d", i + 1); + StrArrayAdd(&version_name, "Unknown Windows Version", TRUE); + } + } + } + if (bNonStandard) + uprintf("Warning: Nonstandard Windows image (missing <DISPLAYNAME> entries)"); + + if (i > 1) + // NB: _log2 returns -2 if SelectionDialog() returns negative (user cancelled) + i = _log2(SelectionDialog(BS_AUTORADIOBUTTON, + lmprintf(MSG_291), lmprintf(MSG_292), version_name.String, i, 1)) + 1; + if (i < 0) + wintogo_index = -2; // Cancelled by the user + else if (i == 0) + wintogo_index = 1; + else + wintogo_index = atoi(version_index.String[i - 1]); + if (i > 0) { + // Get the version data from the XML index + val = get_token_data_file_indexed("MAJOR", xml_file, i); + img_report.win_version.major = (uint16_t)safe_atoi(val); + free(val); + val = get_token_data_file_indexed("MINOR", xml_file, i); + img_report.win_version.minor = (uint16_t)safe_atoi(val); + free(val); + val = get_token_data_file_indexed("BUILD", xml_file, i); + img_report.win_version.build = (uint16_t)safe_atoi(val); + free(val); + val = get_token_data_file_indexed("SPBUILD", xml_file, i); + img_report.win_version.revision = (uint16_t)safe_atoi(val); + free(val); + if ((img_report.win_version.major == 10) && (img_report.win_version.build > 20000)) + img_report.win_version.major = 11; + // If we couldn't obtain the major and build, we have a problem + if (img_report.win_version.major == 0 || img_report.win_version.build == 0) + uprintf("Warning: Could not obtain version information from XML index (Nonstandard Windows image?)"); + uprintf("Will use '%s' (Build: %d, Index %s) for Windows To Go", + version_name.String[i - 1], img_report.win_version.build, version_index.String[i - 1]); + // Need Windows 10 Creator Update or later for boot on REMOVABLE to work + if ((img_report.win_version.build < 15000) && (SelectedDrive.MediaType != FixedMedia)) { + if (MessageBoxExU(hMainDialog, lmprintf(MSG_098), lmprintf(MSG_190), + MB_YESNO | MB_ICONWARNING | MB_IS_RTL, selected_langid) != IDYES) + wintogo_index = -2; + } + // Display a notice about WppRecorder.sys for 1809 ISOs + if (img_report.win_version.build == 17763) { + notification_info more_info; + more_info.id = MORE_INFO_URL; + more_info.url = WPPRECORDER_MORE_INFO_URL; + Notification(MSG_INFO, NULL, &more_info, lmprintf(MSG_128, "Windows To Go"), lmprintf(MSG_133)); + } + } + StrArrayDestroy(&version_name); + StrArrayDestroy(&version_index); + +out: + DeleteFileU(xml_file); + if (!img_report.is_windows_img) + UnMountISO(); + return wintogo_index; +} + +/// <summary> +/// Setup a Windows To Go drive according to the official Microsoft instructions detailed at: +/// https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-8.1-and-8/jj721578(v=ws.11). +/// Note that as opposed to the technet guide above we use bcdedit rather than 'unattend.xml' +/// to disable the recovery environment. +/// </summary> +/// <param name="DriveIndex">The Rufus drive index for the target media.</param> +/// <param name="drive_name">The path of the target media.</param> +/// <param name="use_esp">Whether to create an ESP on the target media.</param> +/// <returns>TRUE on success, FALSE on error.</returns> +BOOL SetupWinToGo(DWORD DriveIndex, const char* drive_name, BOOL use_esp) +{ + char* mounted_iso, * ms_efi = NULL, mounted_image_path[128], cmd[MAX_PATH]; + ULONG cluster_size; + + uprintf("Windows To Go mode selected"); + // Additional sanity checks + if ((use_esp) && (SelectedDrive.MediaType != FixedMedia) && (nWindowsBuildNumber < 15000)) { + FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_NOT_SUPPORTED; + return FALSE; + } + + if (!img_report.is_windows_img) { + mounted_iso = MountISO(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); + return FALSE; + } + static_sprintf(mounted_image_path, "%s%s", mounted_iso, &img_report.wininst_path[wininst_index][2]); + uprintf("Mounted ISO as '%s'", mounted_iso); + } + + // Now we use the WIM API to apply that image + if (!WimApplyImage(img_report.is_windows_img ? image_path : mounted_image_path, wintogo_index, drive_name)) { + uprintf("Failed to apply Windows To Go image"); + if (!IS_ERROR(FormatStatus)) + FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_ISO_EXTRACT); + if (!img_report.is_windows_img) + UnMountISO(); + return FALSE; + } + if (!img_report.is_windows_img) + UnMountISO(); + + if (use_esp) { + uprintf("Setting up EFI System Partition"); + // According to Ubuntu (https://bugs.launchpad.net/ubuntu/+source/partman-efi/+bug/811485) you want to use FAT32. + // However, you have to be careful that the cluster size needs to be greater or equal to the sector size, which + // in turn has an impact on the minimum EFI partition size we can create (see ms_efi_size_MB in drive.c) + if (SelectedDrive.SectorSize <= 1024) + cluster_size = 1024; + else if (SelectedDrive.SectorSize <= 4096) + cluster_size = 4096; + else // Go for broke + cluster_size = (ULONG)SelectedDrive.SectorSize; + // Boy do you *NOT* want to specify a label here, and spend HOURS figuring out why your EFI partition cannot boot... + // Also, we use the Large FAT32 facility Microsoft APIs are *UTTERLY USELESS* for achieving what we want: + // 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, "", + 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); + if (ms_efi == NULL) { + FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_CANT_ASSIGN_LETTER); + return FALSE; + } + } + + // We invoke the 'bcdboot' command from the host, as the one from the drive produces problems (#558) + // and of course, we couldn't invoke an ARM64 'bcdboot' binary on an x86 host anyway... + // Also, since Rufus should (usually) be running as a 32 bit app, on 64 bit systems, we need to use + // 'C:\Windows\Sysnative' and not 'C:\Windows\System32' to invoke bcdboot, as 'C:\Windows\System32' + // will get converted to 'C:\Windows\SysWOW64' behind the scenes, and there is no bcdboot.exe there. + uprintf("Enabling boot using command:"); + static_sprintf(cmd, "%s\\bcdboot.exe %s\\Windows /v /f %s /s %s", sysnative_dir, drive_name, + HAS_BOOTMGR_BIOS(img_report) ? (HAS_BOOTMGR_EFI(img_report) ? "ALL" : "BIOS") : "UEFI", + (use_esp) ? ms_efi : drive_name); + uprintf(cmd); + if (RunCommand(cmd, sysnative_dir, usb_debug) != 0) { + // Try to continue... but report a failure + uprintf("Failed to enable boot"); + FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_ISO_EXTRACT); + } + + UpdateProgressWithInfo(OP_FILE_COPY, MSG_267, wim_proc_files + 2 * wim_extra_files, wim_nb_files); + + // Setting internal drives offline for Windows To Go is crucial if, for instance, you are using ReFS + // on Windows 10 (therefore ReFS v3.4) and don't want a Windows 11 To Go boot to automatically + // "upgrade" the ReFS version on all drives to v3.7, thereby preventing you from being able to mount + // those volumes back on Windows 10 ever again. Yes, I have been stung by this Microsoft bullshit! + // See: https://gist.github.com/0xbadfca11/da0598e47dd643d933dc#Mountability + if (unattend_xml_flags & UNATTEND_OFFLINE_INTERNAL_DRIVES) { + uprintf("Setting the target's internal drives offline using command:"); + // This applies the "offlineServicing" section of the unattend.xml (while ignoring the other sections) + static_sprintf(cmd, "dism /Image:%s\\ /Apply-Unattend:%s", drive_name, unattend_xml_path); + uprintf(cmd); + RunCommand(cmd, NULL, usb_debug); + } + + uprintf("Disabling use of the Windows Recovery Environment using command:"); + static_sprintf(cmd, "%s\\bcdedit.exe /store %s\\EFI\\Microsoft\\Boot\\BCD /set {default} recoveryenabled no", + sysnative_dir, (use_esp) ? ms_efi : drive_name); + uprintf(cmd); + RunCommand(cmd, sysnative_dir, usb_debug); + + UpdateProgressWithInfo(OP_FILE_COPY, MSG_267, wim_nb_files, wim_nb_files); + + if (use_esp) { + Sleep(200); + AltUnmountVolume(ms_efi, FALSE); + } + + return TRUE; +} + +/// <summary> +/// Add unattend.xml to 'sources\boot.wim' (install) or 'Windows\Panther\' (Windows To Go). +/// </summary> +/// <param name="drive_letter">The letter of the drive where the \sources\boot.wim image resides.</param> +/// <param name="flags">A bitmap of unattend flags to apply.</param> +/// <returns>TRUE on success, FALSE on error.</returns> +BOOL ApplyWindowsCustomization(char drive_letter, int flags) +// NB: Work with a copy of unattend_xml_flags as a paremeter since we will modify it. +{ + BOOL r = FALSE, is_hive_mounted = FALSE; + int i; + const int wim_index = 2; + const char* offline_hive_name = "RUFUS_OFFLINE_HIVE"; + char boot_wim_path[] = "?:\\sources\\boot.wim", key_path[64]; + char appraiserres_dll_src[] = "?:\\sources\\appraiserres.dll"; + char appraiserres_dll_dst[] = "?:\\sources\\appraiserres.bak"; + char* mount_path = NULL, path[MAX_PATH]; + HKEY hKey = NULL, hSubKey = NULL; + LSTATUS status; + DWORD dwDisp, dwVal = 1; + + assert(unattend_xml_path != NULL); + uprintf("Applying Windows customization:"); + if (flags & UNATTEND_WINDOWS_TO_GO) { + static_sprintf(path, "%c:\\Windows\\Panther", drive_letter); + if (!CreateDirectoryA(path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) { + uprintf("Could not create '%s' : %s", path, WindowsErrorString()); + goto out; + } + static_sprintf(path, "%c:\\Windows\\Panther\\unattend.xml", drive_letter); + if (!CopyFileA(unattend_xml_path, path, TRUE)) { + uprintf("Could not create '%s' : %s", path, WindowsErrorString()); + goto out; + } + uprintf("Added '%s'", path); + } else { + boot_wim_path[0] = drive_letter; + if (flags & UNATTEND_WINPE_SETUP_MASK) { + // Create a backup of sources\appraiserres.dll and then create an empty file to + // allow in-place upgrades without TPM/SB. Note that we need to create an empty, + // appraiserres.dll otherwise setup.exe extracts its own. + appraiserres_dll_src[0] = drive_letter; + appraiserres_dll_dst[0] = drive_letter; + if (!MoveFileExU(appraiserres_dll_src, appraiserres_dll_dst, MOVEFILE_REPLACE_EXISTING)) + uprintf("Could not rename '%s': %s", appraiserres_dll_src, WindowsErrorString()); + else + CloseHandle(CreateFileU(appraiserres_dll_src, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)); + uprintf("Renamed '%s' → '%s'", appraiserres_dll_src, appraiserres_dll_dst); + } + + UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 0, PATCH_PROGRESS_TOTAL); + // We only need to mount boot.wim if we have windowsPE data to deal with. If + // not, we can just copy our unattend.xml in \sources\$OEM$\$$\Panther\. + if (flags & UNATTEND_WINPE_SETUP_MASK) { + uprintf("Mounting '%s'...", boot_wim_path); + mount_path = WimMountImage(boot_wim_path, wim_index); + if (mount_path == NULL) + goto out; + } + + if (flags & UNATTEND_SECUREBOOT_TPM_MINRAM) { + // Try to create the registry keys directly, and fallback to using unattend + // if that fails (which the Windows Store version is expected to do). + static_sprintf(path, "%s\\Windows\\System32\\config\\SYSTEM", mount_path); + if (!MountRegistryHive(HKEY_LOCAL_MACHINE, offline_hive_name, path)) { + uprintf("Falling back to creating the registry keys through unattend.xml"); + goto copy_unattend; + } + UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 101, PATCH_PROGRESS_TOTAL); + is_hive_mounted = TRUE; + + static_sprintf(key_path, "%s\\Setup", offline_hive_name); + status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, key_path, 0, KEY_READ | KEY_CREATE_SUB_KEY, &hKey); + if (status != ERROR_SUCCESS) { + SetLastError(status); + uprintf("Could not open 'HKLM\\SYSTEM\\Setup' registry key: %s", WindowsErrorString()); + goto copy_unattend; + } + + status = RegCreateKeyExA(hKey, "LabConfig", 0, NULL, 0, + KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_CREATE_SUB_KEY, NULL, &hSubKey, &dwDisp); + if (status != ERROR_SUCCESS) { + SetLastError(status); + uprintf("Could not create 'HKLM\\SYSTEM\\Setup\\LabConfig' registry key: %s", WindowsErrorString()); + goto copy_unattend; + } + + for (i = 0; i < ARRAYSIZE(bypass_name); i++) { + status = RegSetValueExA(hSubKey, bypass_name[i], 0, REG_DWORD, (LPBYTE)&dwVal, sizeof(DWORD)); + if (status != ERROR_SUCCESS) { + SetLastError(status); + uprintf("Could not set 'HKLM\\SYSTEM\\Setup\\LabConfig\\%s' registry key: %s", + bypass_name[i], WindowsErrorString()); + goto copy_unattend; + } + uprintf("Created 'HKLM\\SYSTEM\\Setup\\LabConfig\\%s' registry key", bypass_name[i]); + } + // We were successfull in creating the keys so disable the windowsPE section from unattend.xml + // We do this by replacing '<settings pass="windowsPE">' with '<settings pass="disabled">' + // (provided that the registry key creation was the only item for this pass) + if ((flags & UNATTEND_WINPE_SETUP_MASK) == UNATTEND_SECUREBOOT_TPM_MINRAM) { + if (replace_in_token_data(unattend_xml_path, "<settings", "windowsPE", "disabled", FALSE) == NULL) + uprintf("Warning: Could not disable 'windowsPE' pass from unattend.xml"); + // Remove the flags, since we accomplished the registry creation outside of unattend. + flags &= ~UNATTEND_SECUREBOOT_TPM_MINRAM; + } else { + // TODO: If we add other tasks besides LabConfig reg keys, we'll need to figure out how + // to comment out the <RunSynchronous> entries from windowsPE (and only windowsPE). + assert(FALSE); + } + UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 102, PATCH_PROGRESS_TOTAL); + } + + copy_unattend: + if (flags & UNATTEND_WINPE_SETUP_MASK) { + // If we have a windowsPE section, copy the answer files to the root of boot.wim as + // Autounattend.xml. This also results in that file being automatically copied over + // to %WINDIR%\Panther\unattend.xml for later passes processing. + assert(mount_path != NULL); + static_sprintf(path, "%s\\Autounattend.xml", mount_path); + if (!CopyFileU(unattend_xml_path, path, TRUE)) { + uprintf("Could not create boot.wim 'Autounattend.xml': %s", WindowsErrorString()); + goto out; + } + uprintf("Added 'Autounattend.xml' to '%s'", boot_wim_path); + } else { + // If there is no windowsPE section in our unattend, then copying it as Autounattend.xml on + // the root of boot.wim will not work as Windows Setup does *NOT* carry Autounattend.xml into + // %WINDIR%\Panther\unattend.xml then (See: https://github.com/pbatard/rufus/issues/1981). + // So instead, copy it to \sources\$OEM$\$$\Panther\unattend.xml on the media, as the content + // of \sources\$OEM$\$$\* will get copied into %WINDIR%\ during the file copy phase. + static_sprintf(path, "%c:\\sources\\$OEM$\\$$\\Panther", drive_letter); + i = SHCreateDirectoryExA(NULL, path, NULL); + if (i != ERROR_SUCCESS) { + SetLastError(i); + uprintf("Error: Could not create directory '%s': %s", path, WindowsErrorString()); + goto out; + } + static_sprintf(path, "%c:\\sources\\$OEM$\\$$\\Panther\\unattend.xml", drive_letter); + if (!CopyFileU(unattend_xml_path, path, TRUE)) { + uprintf("Could not create '%s': %s", path, WindowsErrorString()); + goto out; + } + uprintf("Created '%s'", path); + } + UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 103, PATCH_PROGRESS_TOTAL); + } + r = TRUE; + +out: + if (hSubKey != NULL) + RegCloseKey(hSubKey); + if (hKey != NULL) + RegCloseKey(hKey); + if (is_hive_mounted) { + UnmountRegistryHive(HKEY_LOCAL_MACHINE, offline_hive_name); + UpdateProgressWithInfoForce(OP_PATCH, MSG_325, 104, PATCH_PROGRESS_TOTAL); + } + if (mount_path) { + uprintf("Unmounting '%s'...", boot_wim_path, wim_index); + WimUnmountImage(boot_wim_path, wim_index); + UpdateProgressWithInfo(OP_PATCH, MSG_325, PATCH_PROGRESS_TOTAL, PATCH_PROGRESS_TOTAL); + } + free(mount_path); + return r; +} diff --git a/src/wue.h b/src/wue.h new file mode 100644 index 00000000..5b56ac32 --- /dev/null +++ b/src/wue.h @@ -0,0 +1,32 @@ +/* + * Rufus: The Reliable USB Formatting Utility + * Windows User Experience + * Copyright © 2022 Pete Batard <pete@akeo.ie> + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <windows.h> + +#pragma once + +extern int unattend_xml_flags, unattend_xml_mask; +extern int wintogo_index, wininst_index; +extern char* unattend_xml_path; + +char* CreateUnattendXml(int arch, int flags); +BOOL ApplyWindowsCustomization(char drive_letter, int flags); +int SetWinToGoIndex(void); +BOOL SetupWinPE(char drive_letter); +BOOL SetupWinToGo(DWORD DriveIndex, const char* drive_name, BOOL use_esp);