mirror of https://github.com/pbatard/rufus.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
693 lines
24 KiB
693 lines
24 KiB
/*
|
|
* Rufus: The Reliable USB Formatting Utility
|
|
* Virtual Disk Handling functions
|
|
* Copyright © 2013-2025 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/>.
|
|
*/
|
|
|
|
// MinGW includes virdisk.h in windows.h, but we we don't want that
|
|
// because we must apply a delay-loading workaround, and that workaround
|
|
// has to apply between the winnt.h include and the virdisk.h include.
|
|
// So we define _INC_VIRTDISK, to prevent the virdisk.h include in
|
|
// windows.h, and then take care of the workaround (and virtdisk.h
|
|
// include) in vhd.h.
|
|
#define _INC_VIRTDISK
|
|
#include <windows.h>
|
|
#undef _INC_VIRTDISK
|
|
#include <windowsx.h>
|
|
#include <stdlib.h>
|
|
#include <io.h>
|
|
#include <rpc.h>
|
|
#include <time.h>
|
|
|
|
#include "rufus.h"
|
|
#include "ui.h"
|
|
#include "vhd.h"
|
|
#include "missing.h"
|
|
#include "resource.h"
|
|
#include "settings.h"
|
|
#include "msapi_utf8.h"
|
|
|
|
#include "drive.h"
|
|
#include "wimlib.h"
|
|
#include "registry.h"
|
|
#include "bled/bled.h"
|
|
|
|
extern char* save_image_type;
|
|
extern BOOL ignore_boot_marker, has_ffu_support;
|
|
extern RUFUS_DRIVE rufus_drive[MAX_DRIVES];
|
|
extern HANDLE format_thread;
|
|
extern FILE* fd_md5sum;
|
|
extern uint64_t total_blocks, extra_blocks, nb_blocks, last_nb_blocks;
|
|
|
|
static char physical_path[128] = "";
|
|
static int progress_op = OP_FILE_COPY, progress_msg = MSG_267;
|
|
static HANDLE mounted_handle = INVALID_HANDLE_VALUE;
|
|
static struct wimlib_progress_info_split last_split_progress;
|
|
|
|
typedef struct {
|
|
const char* ext;
|
|
uint8_t type;
|
|
} comp_assoc;
|
|
|
|
static comp_assoc file_assoc[] = {
|
|
{ ".zip", BLED_COMPRESSION_ZIP },
|
|
{ ".Z", BLED_COMPRESSION_LZW },
|
|
{ ".gz", BLED_COMPRESSION_GZIP },
|
|
{ ".lzma", BLED_COMPRESSION_LZMA },
|
|
{ ".bz2", BLED_COMPRESSION_BZIP2 },
|
|
{ ".xz", BLED_COMPRESSION_XZ },
|
|
{ ".vtsi", BLED_COMPRESSION_VTSI },
|
|
{ ".zst", BLED_COMPRESSION_ZSTD },
|
|
{ ".ffu", BLED_COMPRESSION_MAX },
|
|
{ ".vhd", BLED_COMPRESSION_MAX + 1 },
|
|
{ ".vhdx", BLED_COMPRESSION_MAX + 2 },
|
|
};
|
|
|
|
// Look for a boot marker in the MBR area of the image
|
|
static int8_t IsCompressedBootableImage(const char* path)
|
|
{
|
|
char *ext = NULL, *physical_disk = NULL;
|
|
unsigned char *buf = NULL;
|
|
int i;
|
|
FILE* fd = NULL;
|
|
BOOL r = 0;
|
|
int64_t dc = 0;
|
|
|
|
img_report.compression_type = BLED_COMPRESSION_NONE;
|
|
if (safe_strlen(path) > 4)
|
|
for (ext = (char*)&path[safe_strlen(path) - 1]; (*ext != '.') && (ext != path); ext--);
|
|
|
|
for (i = 0; i < ARRAYSIZE(file_assoc); i++) {
|
|
if (safe_stricmp(ext, file_assoc[i].ext) == 0) {
|
|
img_report.compression_type = file_assoc[i].type;
|
|
buf = malloc(MBR_SIZE);
|
|
if (buf == NULL)
|
|
return 0;
|
|
ErrorStatus = 0;
|
|
if (img_report.compression_type < BLED_COMPRESSION_MAX) {
|
|
bled_init(0, uprintf, NULL, NULL, NULL, NULL, &ErrorStatus);
|
|
dc = bled_uncompress_to_buffer(path, (char*)buf, MBR_SIZE, file_assoc[i].type);
|
|
bled_exit();
|
|
} else if (img_report.compression_type == BLED_COMPRESSION_MAX) {
|
|
// Dism, through FfuProvider.dll, can mount a .ffu as a physicaldrive, which we
|
|
// could then use to poke the MBR as we do for VHD... Except Microsoft did design
|
|
// dism to FAIL AND EXIT, after mounting the ffu as a virtual drive, if it doesn't
|
|
// find something that looks like Windows at the specified image index... which it
|
|
// usually won't in our case. So, curse Microsoft and their incredible short-
|
|
// sightedness (or, most likely in this case, intentional malice, by BREACHING the
|
|
// OS contract to keep useful disk APIs for their usage, and their usage only).
|
|
// Then again, considering that .ffu's are GPT based, the marker should always be
|
|
// present, so just check for the FFU signature and pretend there's a marker then.
|
|
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
|
|
// create FFU is dism, and dism appears to use "SignedImage " always,.we
|
|
// might as well use this to our advantage.
|
|
if (strncmp(&buf[4], "SignedImage ", 12) == 0) {
|
|
// At this stage, the buffer is only used for marker validation.
|
|
buf[0x1FE] = 0x55;
|
|
buf[0x1FF] = 0xAA;
|
|
}
|
|
} else
|
|
uprintf("Could not open %s: %d", path, errno);
|
|
} else {
|
|
uprintf(" An FFU image was selected, but this system does not have FFU support!");
|
|
}
|
|
} else {
|
|
physical_disk = VhdMountImageAndGetSize(path, &img_report.projected_size);
|
|
if (physical_disk != NULL) {
|
|
img_report.is_vhd = TRUE;
|
|
fd = fopenU(physical_disk, "rb");
|
|
if (fd != NULL) {
|
|
dc = fread(buf, 1, MBR_SIZE, fd);
|
|
fclose(fd);
|
|
}
|
|
}
|
|
VhdUnmountImage();
|
|
}
|
|
if (dc != MBR_SIZE) {
|
|
free(buf);
|
|
return FALSE;
|
|
}
|
|
if ((buf[0x1FE] == 0x55) && (buf[0x1FF] == 0xAA))
|
|
r = 1;
|
|
else if (ignore_boot_marker)
|
|
r = 2;
|
|
free(buf);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
// 0: non-bootable, 1: bootable, 2: forced bootable
|
|
int8_t IsBootableImage(const char* path)
|
|
{
|
|
HANDLE handle = INVALID_HANDLE_VALUE;
|
|
LARGE_INTEGER liImageSize;
|
|
DWORD size;
|
|
uint64_t wim_magic = 0;
|
|
LARGE_INTEGER ptr = { 0 };
|
|
int8_t is_bootable_img;
|
|
|
|
uprintf("Disk image analysis:");
|
|
handle = CreateFileU(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
|
uprintf(" Could not open image '%s'", path);
|
|
is_bootable_img = -1;
|
|
goto out;
|
|
}
|
|
|
|
is_bootable_img = IsCompressedBootableImage(path) ? 1 : 0;
|
|
if (img_report.compression_type == BLED_COMPRESSION_NONE)
|
|
is_bootable_img = AnalyzeMBR(handle, " Image", FALSE) ? 1 : (ignore_boot_marker ? 2 : 0);
|
|
|
|
if (!GetFileSizeEx(handle, &liImageSize)) {
|
|
uprintf(" Could not get image size: %s", WindowsErrorString());
|
|
is_bootable_img = -2;
|
|
goto out;
|
|
}
|
|
img_report.image_size = (uint64_t)liImageSize.QuadPart;
|
|
if (img_report.projected_size == 0)
|
|
img_report.projected_size = img_report.image_size;
|
|
size = sizeof(wim_magic);
|
|
IGNORE_RETVAL(SetFilePointerEx(handle, ptr, NULL, FILE_BEGIN));
|
|
img_report.is_windows_img = ReadFile(handle, &wim_magic, size, &size, NULL) && (wim_magic == WIM_MAGIC);
|
|
if (img_report.is_windows_img)
|
|
goto out;
|
|
|
|
out:
|
|
safe_closehandle(handle);
|
|
return is_bootable_img;
|
|
}
|
|
|
|
// WIM operations progress callback
|
|
static enum wimlib_progress_status WimProgressFunc(enum wimlib_progress_msg msg_type,
|
|
union wimlib_progress_info* info, void* progctx)
|
|
{
|
|
static BOOL init[3] = { 0 };
|
|
|
|
if IS_ERROR(ErrorStatus)
|
|
return WIMLIB_PROGRESS_STATUS_ABORT;
|
|
|
|
switch (msg_type) {
|
|
case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
|
|
memset(init, 0, sizeof(init));
|
|
uprintf("Applying image %d (\"%S\") from '%S' to '%S'",
|
|
info->extract.image, info->extract.image_name,
|
|
info->extract.wimfile_name, info->extract.target);
|
|
break;
|
|
case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
|
|
if (!init[0]) {
|
|
uprintf("Creating file structure...");
|
|
init[0] = TRUE;
|
|
uprint_progress(0, 0);
|
|
}
|
|
UpdateProgressWithInfoUpTo(98, progress_op, progress_msg, info->extract.current_file_count, info->extract.end_file_count * 6);
|
|
uprint_progress(info->extract.current_file_count, info->extract.end_file_count);
|
|
break;
|
|
case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
|
|
if (!init[1]) {
|
|
uprintf("\nExtracting file data...");
|
|
init[1] = TRUE;
|
|
uprint_progress(0, 0);
|
|
}
|
|
UpdateProgressWithInfoUpTo(98, progress_op, progress_msg, info->extract.total_bytes + (4 * info->extract.completed_bytes), info->extract.total_bytes * 6);
|
|
uprint_progress(info->extract.completed_bytes, info->extract.total_bytes);
|
|
break;
|
|
case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
|
|
if (!init[2]) {
|
|
uprintf("\nApplying metadata to files...");
|
|
init[2] = TRUE;
|
|
uprint_progress(0, 0);
|
|
}
|
|
UpdateProgressWithInfoUpTo(98, progress_op, progress_msg, info->extract.current_file_count + (5 * info->extract.end_file_count), info->extract.end_file_count * 6);
|
|
uprint_progress(info->extract.current_file_count, info->extract.end_file_count);
|
|
if (info->extract.current_file_count >= info->extract.end_file_count)
|
|
uprintf("\n");
|
|
break;
|
|
case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
|
|
last_split_progress = info->split;
|
|
uprintf("● %S", info->split.part_name);
|
|
break;
|
|
case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
|
|
if (fd_md5sum != NULL) {
|
|
// Don't bother computing the hash at write time - just do it post creation
|
|
uint8_t sum[MD5_HASHSIZE];
|
|
char* filename = wchar_to_utf8(info->split.part_name);
|
|
if (filename != NULL) {
|
|
HashFile(HASH_MD5, filename, sum);
|
|
for (int j = 0; j < MD5_HASHSIZE; j++)
|
|
fprintf(fd_md5sum, "%02x", sum[j]);
|
|
fprintf(fd_md5sum, " ./%s\n", &filename[3]);
|
|
free(filename);
|
|
}
|
|
}
|
|
break;
|
|
case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
|
|
uint64_t completed_bytes = last_split_progress.completed_bytes + info->write_streams.completed_compressed_bytes;
|
|
nb_blocks = last_nb_blocks + completed_bytes / 2048;
|
|
UpdateProgressWithInfo(OP_FILE_COPY, MSG_231, nb_blocks, total_blocks + extra_blocks);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return WIMLIB_PROGRESS_STATUS_CONTINUE;
|
|
}
|
|
|
|
// Return the WIM version of an image
|
|
uint32_t GetWimVersion(const char* image)
|
|
{
|
|
int r;
|
|
WIMStruct* wim;
|
|
struct wimlib_wim_info info;
|
|
|
|
if (image == NULL)
|
|
return 0;
|
|
|
|
r = wimlib_open_wimU(image, 0, &wim);
|
|
if (r == 0) {
|
|
r = wimlib_get_wim_info(wim, &info);
|
|
wimlib_free(wim);
|
|
if (r == 0)
|
|
return info.wim_version;
|
|
}
|
|
uprintf("WARNING: Could not get WIM version: Error %d", r);
|
|
return 0;
|
|
}
|
|
|
|
// Extract a file from a WIM image. Returns the allocated path of the extracted file or NULL on error.
|
|
BOOL WimExtractFile(const char* image, int index, const char* src, const char* dst)
|
|
{
|
|
int r = 1;
|
|
WIMStruct* wim;
|
|
char tmp[MAX_PATH] = "", *p;
|
|
|
|
if ((image == NULL) || (src == NULL) || (dst == NULL))
|
|
goto out;
|
|
|
|
assert(strrchr(src, '\\') != NULL);
|
|
assert(strrchr(dst, '\\') != NULL);
|
|
if (strrchr(src, '\\') == NULL || strrchr(dst, '\\') == NULL)
|
|
goto out;
|
|
p = strrchr(dst, '\\');
|
|
*p = '\0';
|
|
|
|
wimlib_global_init(0);
|
|
wimlib_set_print_errors(true);
|
|
r = wimlib_open_wimU(image, 0, &wim);
|
|
if (r == 0) {
|
|
r = wimlib_extract_pathsU(wim, index, dst, &src, 1, WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE);
|
|
wimlib_free(wim);
|
|
static_strcpy(tmp, dst);
|
|
static_strcat(tmp, strrchr(src, '\\'));
|
|
*p = '\\';
|
|
if (!MoveFileExU(tmp, dst, MOVEFILE_REPLACE_EXISTING)) {
|
|
uprintf(" Could not rename %s to %s: %s", tmp, dst, WindowsErrorString());
|
|
r = 1;
|
|
}
|
|
}
|
|
wimlib_global_cleanup();
|
|
|
|
out:
|
|
return (r == 0);
|
|
}
|
|
|
|
// Split an install.wim for FAT32 limits
|
|
BOOL WimSplitFile(const char* src, const char* dst)
|
|
{
|
|
int r = 1;
|
|
WIMStruct* wim;
|
|
|
|
if ((src == NULL) || (dst == NULL))
|
|
goto out;
|
|
|
|
wimlib_global_init(0);
|
|
wimlib_set_print_errors(true);
|
|
r = wimlib_open_wimU(src, 0, &wim);
|
|
if (r == 0) {
|
|
wimlib_register_progress_function(wim, WimProgressFunc, NULL);
|
|
r = wimlib_splitU(wim, dst, 4094ULL * MB, WIMLIB_WRITE_FLAG_FSYNC);
|
|
wimlib_free(wim);
|
|
}
|
|
wimlib_global_cleanup();
|
|
|
|
out:
|
|
return (r == 0);
|
|
}
|
|
|
|
BOOL WimApplyImage(const char* image, int index, const char* dst)
|
|
{
|
|
int r = 1;
|
|
WIMStruct* wim;
|
|
|
|
wimlib_global_init(0);
|
|
wimlib_set_print_errors(true);
|
|
|
|
uprintf("Opening: %s:[%d]", image, index);
|
|
r = wimlib_open_wimU(image, 0, &wim);
|
|
if (r == 0) {
|
|
progress_op = OP_FILE_COPY;
|
|
progress_msg = MSG_267;
|
|
wimlib_register_progress_function(wim, WimProgressFunc, NULL);
|
|
r = wimlib_extract_imageU(wim, index, dst, 0);
|
|
wimlib_free(wim);
|
|
} else {
|
|
uprintf("Failed to open '%s': Wimlib error %d", image, r);
|
|
}
|
|
wimlib_global_cleanup();
|
|
return (r == 0);
|
|
}
|
|
|
|
// Mount an ISO or a VHD/VHDX image and provide its size
|
|
// Returns the physical path of the mounted image or NULL on error.
|
|
char* VhdMountImageAndGetSize(const char* path, uint64_t* disk_size)
|
|
{
|
|
VIRTUAL_STORAGE_TYPE vtype = { VIRTUAL_STORAGE_TYPE_DEVICE_ISO, VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT };
|
|
ATTACH_VIRTUAL_DISK_PARAMETERS vparams = { 0 };
|
|
GET_VIRTUAL_DISK_INFO disk_info = { 0 };
|
|
DWORD r;
|
|
wchar_t wtmp[128];
|
|
ULONG size = ARRAYSIZE(wtmp);
|
|
wconvert(path);
|
|
char *ret = NULL, *ext = NULL;
|
|
|
|
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, ".vhd") == 0)
|
|
vtype.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHD;
|
|
|
|
r = OpenVirtualDisk(&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 = AttachVirtualDisk(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 = GetVirtualDiskPhysicalPath(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));
|
|
|
|
if (disk_size != NULL) {
|
|
*disk_size = 0;
|
|
disk_info.Version = GET_VIRTUAL_DISK_INFO_SIZE;
|
|
size = sizeof(disk_info);
|
|
r = GetVirtualDiskInformation(mounted_handle, &size, &disk_info, NULL);
|
|
if (r != ERROR_SUCCESS) {
|
|
SetLastError(r);
|
|
uprintf("Could not obtain virtual size of mounted image '%s': %s", path, WindowsErrorString());
|
|
goto out;
|
|
}
|
|
*disk_size = disk_info.Size.VirtualSize;
|
|
}
|
|
|
|
ret = physical_path;
|
|
|
|
out:
|
|
if (ret == NULL)
|
|
VhdUnmountImage();
|
|
wfree(path);
|
|
return ret;
|
|
}
|
|
|
|
void VhdUnmountImage(void)
|
|
{
|
|
if ((mounted_handle == NULL) || (mounted_handle == INVALID_HANDLE_VALUE))
|
|
goto out;
|
|
|
|
DetachVirtualDisk(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 VhdSaveImageThread(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);
|
|
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 r = ERROR_NOT_FOUND, flags;
|
|
|
|
if_not_assert(img_save->Type == VIRTUAL_STORAGE_TYPE_DEVICE_VHD ||
|
|
img_save->Type == VIRTUAL_STORAGE_TYPE_DEVICE_VHDX)
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
UpdateProgressWithInfoInit(NULL, FALSE);
|
|
|
|
vparams.Version = CREATE_VIRTUAL_DISK_VERSION_2;
|
|
vparams.Version2.UniqueId = GUID_NULL;
|
|
vparams.Version2.BlockSizeInBytes = CREATE_VIRTUAL_DISK_PARAMETERS_DEFAULT_BLOCK_SIZE;
|
|
vparams.Version2.SectorSizeInBytes = CREATE_VIRTUAL_DISK_PARAMETERS_DEFAULT_SECTOR_SIZE;
|
|
vparams.Version2.PhysicalSectorSizeInBytes = SelectedDrive.SectorSize;
|
|
vparams.Version2.SourcePath = wSrc;
|
|
|
|
// When CREATE_VIRTUAL_DISK_FLAG_CREATE_BACKING_STORAGE is specified with
|
|
// a source path, CreateVirtualDisk() automatically clones the source to
|
|
// the virtual disk.
|
|
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_VHD)
|
|
flags |= CREATE_VIRTUAL_DISK_FLAG_FULL_PHYSICAL_ALLOCATION;
|
|
// TODO: Use CREATE_VIRTUAL_DISK_FLAG_PREVENT_WRITES_TO_SOURCE_DISK?
|
|
|
|
overlapped.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
|
|
|
|
// CreateVirtualDisk() does not have an overwrite flag...
|
|
DeleteFileW(wDst);
|
|
|
|
r = CreateVirtualDisk(&vtype, wDst, VIRTUAL_DISK_ACCESS_NONE, NULL,
|
|
flags, 0, (PCREATE_VIRTUAL_DISK_PARAMETERS)&vparams, &overlapped, &handle);
|
|
if (r != ERROR_SUCCESS && r != ERROR_IO_PENDING) {
|
|
SetLastError(r);
|
|
uprintf("Could not create virtual disk: %s", WindowsErrorString());
|
|
goto out;
|
|
}
|
|
|
|
if (r == ERROR_IO_PENDING) {
|
|
while ((r = WaitForSingleObject(overlapped.hEvent, 100)) == WAIT_TIMEOUT) {
|
|
if (IS_ERROR(ErrorStatus) && (SCODE_CODE(ErrorStatus) == ERROR_CANCELLED)) {
|
|
CancelIoEx(handle, &overlapped);
|
|
goto out;
|
|
}
|
|
if (GetVirtualDiskOperationProgress(handle, &overlapped, &vprogress) == ERROR_SUCCESS) {
|
|
if (vprogress.OperationStatus == ERROR_IO_PENDING)
|
|
UpdateProgressWithInfo(OP_FORMAT, MSG_261, vprogress.CurrentValue, vprogress.CompletionValue);
|
|
}
|
|
}
|
|
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("Saved '%s'", img_save->ImagePath);
|
|
|
|
out:
|
|
safe_closehandle(overlapped.hEvent);
|
|
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(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 FfuSaveImageThread(void* param)
|
|
{
|
|
DWORD r;
|
|
IMG_SAVE* img_save = (IMG_SAVE*)param;
|
|
char cmd[MAX_PATH + 128], letters[27], *label;
|
|
|
|
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);
|
|
uprintf("Running command: '%s'", cmd);
|
|
// For detecting typical dism.exe commandline progress report of type:
|
|
// "\r[==== 8.0% ]"
|
|
r = RunCommandWithProgress(cmd, sysnative_dir, TRUE, MSG_261, ".*\r\\[[= ]+([0-9\\.]+)%[= ]+\\].*");
|
|
if (r != 0 && !IS_ERROR(ErrorStatus)) {
|
|
SetLastError(r);
|
|
uprintf("Failed to capture FFU image: %s", WindowsErrorString());
|
|
ErrorStatus = RUFUS_ERROR(SCODE_CODE(r));
|
|
}
|
|
PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)TRUE, 0);
|
|
if (!IS_ERROR(ErrorStatus))
|
|
uprintf("Saved '%s'", img_save->ImagePath);
|
|
safe_free(img_save->DevicePath);
|
|
safe_free(img_save->ImagePath);
|
|
ExitThread(r);
|
|
}
|
|
|
|
BOOL SaveImage(void)
|
|
{
|
|
UINT i;
|
|
static IMG_SAVE img_save;
|
|
char filename[128], letters[27], path[MAX_PATH];
|
|
int DriveIndex = ComboBox_GetCurSel(hDeviceList);
|
|
enum { image_type_vhd = 1, image_type_vhdx = 2, image_type_ffu = 3, image_type_iso = 4 };
|
|
// Add a non-printable zero-width space to UDF *.iso extension to differentiate it from ISO-9660
|
|
static EXT_DECL(img_ext, filename, __VA_GROUP__("*.vhd", "*.vhdx", "*.ffu", "*.iso"),
|
|
__VA_GROUP__(lmprintf(MSG_343), lmprintf(MSG_342), lmprintf(MSG_344), lmprintf(MSG_355)));
|
|
ULARGE_INTEGER free_space;
|
|
|
|
memset(&img_save, 0, sizeof(IMG_SAVE));
|
|
if ((DriveIndex < 0) || (format_thread != NULL))
|
|
return FALSE;
|
|
|
|
static_sprintf(filename, "%s", rufus_drive[DriveIndex].label);
|
|
img_save.DeviceNum = (DWORD)ComboBox_GetItemData(hDeviceList, DriveIndex);
|
|
img_save.DevicePath = GetPhysicalName(img_save.DeviceNum);
|
|
img_ext.count = 2;
|
|
// FFU support requires GPT
|
|
if (has_ffu_support && SelectedDrive.PartitionStyle == PARTITION_STYLE_GPT) {
|
|
img_ext.count += 1;
|
|
} else {
|
|
// Move the ISO extension one place down
|
|
img_ext.extension[2] = img_ext.extension[3];
|
|
img_ext.description[2] = img_ext.description[3];
|
|
}
|
|
// ISO support requires a mounted file system
|
|
if (GetDriveLetters(SelectedDrive.DeviceNumber, letters) && letters[0] != '\0')
|
|
img_ext.count += 1;
|
|
for (i = 1; i <= (UINT)img_ext.count && (safe_strcmp(save_image_type, &img_ext.extension[i - 1][2]) != '\0'); i++);
|
|
if (i > (UINT)img_ext.count)
|
|
i = image_type_vhdx;
|
|
img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, &i);
|
|
if (img_save.ImagePath == NULL)
|
|
goto out;
|
|
// Start from the end of our extension array, since '.vhd' would match for '.vhdx' otherwise
|
|
for (i = (UINT)img_ext.count; (i > 0) && (strstr(img_save.ImagePath, &img_ext.extension[i - 1][1]) == NULL); i--);
|
|
if (i == 0) {
|
|
uprintf("WARNING: Can not determine image type from extension - Saving to uncompressed VHD.");
|
|
i = image_type_vhd;
|
|
} else {
|
|
save_image_type = (char*)&img_ext.extension[i - 1][2];
|
|
WriteSettingStr(SETTING_PREFERRED_SAVE_IMAGE_TYPE, save_image_type);
|
|
}
|
|
switch (i) {
|
|
case image_type_vhd:
|
|
img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_VHD;
|
|
break;
|
|
case image_type_ffu:
|
|
img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_FFU;
|
|
break;
|
|
case image_type_iso:
|
|
// ISO requires oscdimg.exe. If not already present, attempt to download it.
|
|
static_sprintf(path, "%s\\%s\\oscdimg.exe", app_data_dir, FILES_DIR);
|
|
if (!PathFileExistsU(path)) {
|
|
if (MessageBoxExU(hMainDialog, lmprintf(MSG_337, "oscdimg.exe"), lmprintf(MSG_115),
|
|
MB_YESNO | MB_ICONWARNING | MB_IS_RTL, selected_langid) != IDYES)
|
|
goto out;
|
|
IGNORE_RETVAL(_chdirU(app_data_dir));
|
|
IGNORE_RETVAL(_mkdir(FILES_DIR));
|
|
if (DownloadToFileOrBufferEx(OSCDIMG_URL, path, SYMBOL_SERVER_USER_AGENT, NULL, hMainDialog, FALSE) < 64 * KB)
|
|
goto out;
|
|
}
|
|
img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_ISO;
|
|
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);
|
|
ErrorStatus = 0;
|
|
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");
|
|
ErrorStatus = RUFUS_ERROR(ERROR_FILE_TOO_LARGE);
|
|
PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)FALSE, 0);
|
|
goto out;
|
|
}
|
|
}
|
|
// Disable all controls except Cancel
|
|
EnableControls(FALSE, FALSE);
|
|
ErrorStatus = 0;
|
|
InitProgress(TRUE);
|
|
format_thread = CreateThread(NULL, 0, img_save.Type == VIRTUAL_STORAGE_TYPE_DEVICE_FFU ?
|
|
FfuSaveImageThread : (img_save.Type == VIRTUAL_STORAGE_TYPE_DEVICE_ISO ? IsoSaveImageThread : VhdSaveImageThread),
|
|
&img_save, 0, NULL);
|
|
if (format_thread != NULL) {
|
|
uprintf("\r\nSave to image operation started");
|
|
PrintInfo(0, -1);
|
|
SendMessage(hMainDialog, UM_TIMER_START, 0, 0);
|
|
} else {
|
|
uprintf("Unable to start image save thread");
|
|
ErrorStatus = RUFUS_ERROR(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);
|
|
}
|
|
return (format_thread != NULL);
|
|
}
|