[vhd] add backup to uncompressed VHD image

* Part of #321
* NOTE: This feature will be enabled after the 1.4.8 release
This commit is contained in:
Pete Batard 2014-05-23 01:03:01 +01:00
parent 1e121d2025
commit 04bd5116a7
5 changed files with 329 additions and 51 deletions

View File

@ -1397,12 +1397,12 @@ DWORD WINAPI FormatThread(void* param)
PrintStatus(0, FALSE, MSG_261, format_percent);
UpdateProgress(OP_FORMAT, format_percent);
}
CHECK_FOR_USER_CANCEL;
// Don't overflow our projected size (mostly for VHDs)
if (wb + rSize > iso_report.projected_size) {
rSize = (DWORD)(iso_report.projected_size - wb);
}
for (i=0; i<WRITE_RETRIES; i++) {
CHECK_FOR_USER_CANCEL;
s = WriteFile(hPhysicalDrive, buffer, rSize, &wSize, NULL);
if ((s) && (wSize == rSize))
break;
@ -1631,3 +1631,102 @@ out:
PostMessage(hMainDialog, UM_FORMAT_COMPLETED, 0, 0);
ExitThread(0);
}
DWORD WINAPI SaveImageThread(void* param)
{
BOOL s;
DWORD rSize, wSize, LastRefresh = 0, DriveIndex = (DWORD)(uintptr_t)param;
HANDLE hPhysicalDrive = INVALID_HANDLE_VALUE;
HANDLE hDestImage = INVALID_HANDLE_VALUE;
LARGE_INTEGER li;
uint8_t *buffer = NULL;
uint64_t wb;
int i;
PrintStatus(0, TRUE, MSG_225);
hPhysicalDrive = GetPhysicalHandle(DriveIndex, FALSE, TRUE);
if (hPhysicalDrive == INVALID_HANDLE_VALUE) {
FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_OPEN_FAILED;
goto out;
}
// Write an image file
// We poked the MBR and other stuff, so we need to rewind
li.QuadPart = 0;
if (!SetFilePointerEx(hPhysicalDrive, li, NULL, FILE_BEGIN))
uprintf("Warning: Unable to rewind device position - wrong data might be copied!");
hDestImage = CreateFileU(image_path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
if (hDestImage == INVALID_HANDLE_VALUE) {
uprintf("Could not open image '%s': %s", image_path, WindowsErrorString());
FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_OPEN_FAILED;
goto out;
}
uprintf("Saving to image '%s'...", image_path);
buffer = (uint8_t*)malloc(DD_BUFFER_SIZE);
if (buffer == NULL) {
FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_NOT_ENOUGH_MEMORY;
uprintf("could not allocate buffer");
goto out;
}
// Don't bother trying for something clever, using double buffering overlapped and whatnot:
// With Windows' default optimizations, sync read + sync write for sequential operations
// will be as fast, if not faster, than whatever async scheme you can come up with.
for (wb = 0; ; wb += wSize) {
s = ReadFile(hPhysicalDrive, buffer,
(DWORD)MIN(DD_BUFFER_SIZE, SelectedDrive.DiskSize - wb), &rSize, NULL);
if (!s) {
FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_READ_FAULT;
uprintf("read error: %s", WindowsErrorString());
goto out;
}
if (rSize == 0)
break;
if (GetTickCount() > LastRefresh + 25) {
LastRefresh = GetTickCount();
format_percent = (100.0f*wb)/(1.0f*SelectedDrive.DiskSize);
PrintStatus(0, FALSE, MSG_261, format_percent);
UpdateProgress(OP_FORMAT, format_percent);
}
for (i=0; i<WRITE_RETRIES; i++) {
CHECK_FOR_USER_CANCEL;
s = WriteFile(hDestImage, buffer, rSize, &wSize, NULL);
if ((s) && (wSize == rSize))
break;
if (s)
uprintf("write error: Wrote %d bytes, expected %d bytes\n", wSize, rSize);
else
uprintf("write error: %s", WindowsErrorString());
if (i < WRITE_RETRIES-1) {
li.QuadPart = wb;
SetFilePointerEx(hDestImage, li, NULL, FILE_BEGIN);
uprintf(" RETRYING...\n");
} else {
FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_WRITE_FAULT;
goto out;
}
}
if (i >= WRITE_RETRIES) goto out;
}
if (wb != SelectedDrive.DiskSize) {
uprintf("Error: wrote %llu bytes, expected %llu", wb, SelectedDrive.DiskSize);
FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_WRITE_FAULT;
goto out;
}
uprintf("%llu bytes written", wb);
uprintf("Appending VHD footer...");
if (!AppendVHDFooter(image_path)) {
FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|ERROR_WRITE_FAULT;
goto out;
}
uprintf("Done");
out:
safe_free(buffer);
SendMessage(hISOProgressDlg, UM_PROGRESS_EXIT, 0, 0);
safe_closehandle(hDestImage);
safe_unlockclose(hPhysicalDrive);
PostMessage(hMainDialog, UM_FORMAT_COMPLETED, 0, 0);
ExitThread(0);
}

View File

@ -610,57 +610,58 @@ static BOOL PopulateProperties(int ComboIndex)
/*
* Set up progress bar real estate allocation
*/
static void InitProgress(void)
static void InitProgress(BOOL bOnlyFormat)
{
int i, fs;
float last_end = 0.0f, slots_discrete = 0.0f, slots_analog = 0.0f;
fs = (int)ComboBox_GetItemData(hFileSystem, ComboBox_GetCurSel(hFileSystem));
memset(&nb_slots, 0, sizeof(nb_slots));
memset(&slot_end, 0, sizeof(slot_end));
previous_end = 0.0f;
memset(nb_slots, 0, sizeof(nb_slots));
memset(slot_end, 0, sizeof(slot_end));
previous_end = 0.0f;
nb_slots[OP_ANALYZE_MBR] = 1;
if (IsChecked(IDC_BADBLOCKS)) {
nb_slots[OP_BADBLOCKS] = -1;
}
if (IsChecked(IDC_BOOT)) {
// 1 extra slot for PBR writing
switch (selection_default) {
case DT_WINME:
nb_slots[OP_DOS] = 3+1;
break;
case DT_FREEDOS:
nb_slots[OP_DOS] = 5+1;
break;
case DT_IMG:
nb_slots[OP_DOS] = 0;
break;
case DT_ISO:
nb_slots[OP_DOS] = -1;
break;
default:
nb_slots[OP_DOS] = 2+1;
break;
}
}
if (selection_default == DT_IMG) {
if (bOnlyFormat) {
nb_slots[OP_FORMAT] = -1;
} else {
nb_slots[OP_ZERO_MBR] = 1;
nb_slots[OP_PARTITION] = 1;
nb_slots[OP_FIX_MBR] = 1;
nb_slots[OP_CREATE_FS] =
nb_steps[ComboBox_GetItemData(hFileSystem, ComboBox_GetCurSel(hFileSystem))];
if ( (!IsChecked(IDC_QUICKFORMAT))
|| ((fs == FS_FAT32) && ((SelectedDrive.DiskSize >= LARGE_FAT32_SIZE) || (force_large_fat32))) ) {
nb_slots[OP_FORMAT] = -1;
nb_slots[OP_ANALYZE_MBR] = 1;
if (IsChecked(IDC_BADBLOCKS)) {
nb_slots[OP_BADBLOCKS] = -1;
}
if (IsChecked(IDC_BOOT)) {
// 1 extra slot for PBR writing
switch (selection_default) {
case DT_WINME:
nb_slots[OP_DOS] = 3+1;
break;
case DT_FREEDOS:
nb_slots[OP_DOS] = 5+1;
break;
case DT_IMG:
nb_slots[OP_DOS] = 0;
break;
case DT_ISO:
nb_slots[OP_DOS] = -1;
break;
default:
nb_slots[OP_DOS] = 2+1;
break;
}
}
if (selection_default == DT_IMG) {
nb_slots[OP_FORMAT] = -1;
} else {
nb_slots[OP_ZERO_MBR] = 1;
nb_slots[OP_PARTITION] = 1;
nb_slots[OP_FIX_MBR] = 1;
nb_slots[OP_CREATE_FS] =
nb_steps[ComboBox_GetItemData(hFileSystem, ComboBox_GetCurSel(hFileSystem))];
if ( (!IsChecked(IDC_QUICKFORMAT))
|| ((fs == FS_FAT32) && ((SelectedDrive.DiskSize >= LARGE_FAT32_SIZE) || (force_large_fat32))) ) {
nb_slots[OP_FORMAT] = -1;
}
nb_slots[OP_FINALIZE] = ((selection_default == DT_ISO) && (fs == FS_NTFS))?3:2;
}
nb_slots[OP_FINALIZE] = ((selection_default == DT_ISO) && (fs == FS_NTFS))?3:2;
}
for (i=0; i<OP_MAX; i++) {
@ -1687,6 +1688,53 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA
break;
#ifdef RUFUS_TEST
case IDC_TEST:
if (format_thid != NULL) {
return (INT_PTR)TRUE;
}
FormatStatus = 0;
format_op_in_progress = TRUE;
// Reset all progress bars
SendMessage(hProgress, PBM_SETSTATE, (WPARAM)PBST_NORMAL, 0);
SetTaskbarProgressState(TASKBAR_NORMAL);
SetTaskbarProgressValue(0, MAX_PROGRESS);
SendMessage(hProgress, PBM_SETPOS, 0, 0);
nDeviceIndex = ComboBox_GetCurSel(hDeviceList);
if (nDeviceIndex != CB_ERR) {
if ((IsChecked(IDC_BOOT)) && (!BootCheck())) {
format_op_in_progress = FALSE;
break;
}
GetWindowTextU(hDeviceList, tmp, ARRAYSIZE(tmp));
if (MessageBoxU(hMainDialog, lmprintf(MSG_003, tmp),
APPLICATION_NAME, MB_OKCANCEL|MB_ICONWARNING|MB_IS_RTL) == IDCANCEL) {
format_op_in_progress = FALSE;
break;
}
safe_free(image_path);
image_path = strdup("C:\\Downloads\\my.vhd");
// Disable all controls except cancel
EnableControls(FALSE);
DeviceNum = (DWORD)ComboBox_GetItemData(hDeviceList, nDeviceIndex);
FormatStatus = 0;
InitProgress(TRUE);
format_thid = CreateThread(NULL, 0, SaveImageThread, (LPVOID)(uintptr_t)DeviceNum, 0, NULL);
if (format_thid == NULL) {
uprintf("Unable to start saving thread");
FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|APPERR(ERROR_CANT_START_THREAD);
PostMessage(hMainDialog, UM_FORMAT_COMPLETED, 0, 0);
}
uprintf("\r\nSave to image operation started");
PrintStatus(0, FALSE, -1);
timer = 0;
safe_sprintf(szTimer, sizeof(szTimer), "00:00:00");
SendMessageA(GetDlgItem(hMainDialog, IDC_STATUS), SB_SETTEXTA,
SBT_OWNERDRAW | 1, (LPARAM)szTimer);
SetTimer(hMainDialog, TID_APP_TIMER, 1000, ClockTimer);
}
if (format_thid == NULL)
format_op_in_progress = FALSE;
break;
#endif
case IDC_LANG:
@ -1889,7 +1937,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA
EnableControls(FALSE);
DeviceNum = (DWORD)ComboBox_GetItemData(hDeviceList, nDeviceIndex);
FormatStatus = 0;
InitProgress();
InitProgress(FALSE);
format_thid = CreateThread(NULL, 0, FormatThread, (LPVOID)(uintptr_t)DeviceNum, 0, NULL);
if (format_thid == NULL) {
uprintf("Unable to start formatting thread");

View File

@ -364,7 +364,6 @@ extern BOOL ExtractDOS(const char* path);
extern BOOL ExtractISO(const char* src_iso, const char* dest_dir, BOOL scan);
extern int64_t ExtractISOFile(const char* iso, const char* iso_file, const char* dest_file, DWORD attributes);
extern BOOL InstallSyslinux(DWORD drive_index, char drive_letter);
DWORD WINAPI FormatThread(void* param);
extern BOOL CreateProgress(void);
extern BOOL SetAutorun(const char* path);
extern char* FileDialog(BOOL save, char* path, const ext_t* ext, DWORD options);
@ -388,8 +387,12 @@ extern void parse_update(char* buf, size_t len);
extern BOOL WimExtractCheck(void);
extern BOOL WimExtractFile(const char* wim_image, int index, const char* src, const char* dst);
extern BOOL IsHDImage(const char* path);
extern BOOL AppendVHDFooter(const char* image_path);
extern int IsHDD(DWORD DriveIndex, uint16_t vid, uint16_t pid, const char* strid);
DWORD WINAPI FormatThread(void* param);
DWORD WINAPI SaveImageThread(void* param);
static __inline BOOL UnlockDrive(HANDLE hDrive) {
DWORD size;
return DeviceIoControl(hDrive, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &size, NULL);

View File

@ -32,7 +32,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDD_DIALOG DIALOGEX 12, 12, 206, 329
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Rufus 1.4.8.483"
CAPTION "Rufus 1.4.8.484"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "Start",IDC_START,94,291,50,14
@ -165,7 +165,7 @@ END
RTL_IDD_DIALOG DIALOGEX 12, 12, 206, 329
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_RTLREADING | WS_EX_APPWINDOW | WS_EX_LAYOUTRTL
CAPTION "Rufus 1.4.8.483"
CAPTION "Rufus 1.4.8.484"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "Start",IDC_START,94,291,50,14
@ -427,8 +427,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,4,8,483
PRODUCTVERSION 1,4,8,483
FILEVERSION 1,4,8,484
PRODUCTVERSION 1,4,8,484
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -445,13 +445,13 @@ BEGIN
BEGIN
VALUE "CompanyName", "Akeo Consulting (http://akeo.ie)"
VALUE "FileDescription", "Rufus"
VALUE "FileVersion", "1.4.8.483"
VALUE "FileVersion", "1.4.8.484"
VALUE "InternalName", "Rufus"
VALUE "LegalCopyright", "© 2011-2014 Pete Batard (GPL v3)"
VALUE "LegalTrademarks", "http://www.gnu.org/copyleft/gpl.html"
VALUE "OriginalFilename", "rufus.exe"
VALUE "ProductName", "Rufus"
VALUE "ProductVersion", "1.4.8.483"
VALUE "ProductVersion", "1.4.8.484"
END
END
BLOCK "VarFileInfo"

136
src/vhd.c
View File

@ -20,6 +20,8 @@
#include <windows.h>
#include <stdlib.h>
#include <io.h>
#include <rpc.h>
#include <time.h>
#include "rufus.h"
#include "msapi_utf8.h"
@ -38,15 +40,28 @@
#define VHD_FOOTER_COOKIE { 'c', 'o', 'n', 'e', 'c', 't', 'i', 'x' }
#define VHD_FOOTER_FEATURES_NONE 0x00000000
#define VHD_FOOTER_FEATURES_TEMPORARY 0x00000001
#define VHD_FOOTER_FEATURES_RESERVED 0x00000002
#define VHD_FOOTER_FILE_FORMAT_V1_0 0x00010000
#define VHD_FOOTER_DATA_OFFSET_FIXED_DISK 0xFFFFFFFFFFFFFFFFULL
#define VHD_FOOTER_CREATOR_HOST_OS_WINDOWS { 'W', 'i', '2', 'k' }
#define VHD_FOOTER_CREATOR_HOST_OS_MAC { 'M', 'a', 'c', ' ' }
#define VHD_FOOTER_TYPE_FIXED_HARD_DISK 0x00000002
#define VHD_FOOTER_TYPE_DYNAMIC_HARD_DISK 0x00000003
#define VHD_FOOTER_TYPE_DIFFER_HARD_DISK 0x00000004
#define SECONDS_SINCE_JAN_1ST_2000 946684800
/*
* VHD Fixed HD footer (Big Endian)
* http://download.microsoft.com/download/f/f/e/ffef50a5-07dd-4cf8-aaa3-442c0673a029/Virtual%20Hard%20Disk%20Format%20Spec_10_18_06.doc
* NB: If a dymamic implementation is needed, check the GPL v3 compatible C++ implementation from:
* https://sourceforge.net/p/urbackup/backend/ci/master/tree/fsimageplugin/
*/
#pragma pack(push, 1)
typedef struct vhd_footer {
@ -55,12 +70,19 @@ typedef struct vhd_footer {
uint32_t file_format_version;
uint64_t data_offset;
uint32_t timestamp;
uint32_t creator_app;
char creator_app[4];
uint32_t creator_version;
uint32_t creator_host_os;
char creator_host_os[4];
uint64_t original_size;
uint64_t current_size;
uint32_t disk_geometry;
union {
uint32_t geometry;
struct {
uint16_t cylinders;
uint8_t heads;
uint8_t sectors;
} chs;
} disk_geometry;
uint32_t disk_type;
uint32_t checksum;
uuid_t unique_id;
@ -77,9 +99,11 @@ PF_TYPE_DECL(WINAPI, BOOL, WIMSetTemporaryPath, (HANDLE, PWSTR));
PF_TYPE_DECL(WINAPI, HANDLE, WIMLoadImage, (HANDLE, DWORD));
PF_TYPE_DECL(WINAPI, BOOL, WIMExtractImagePath, (HANDLE, PWSTR, PWSTR, DWORD));
PF_TYPE_DECL(WINAPI, BOOL, WIMCloseHandle, (HANDLE));
PF_TYPE_DECL(RPC_ENTRY, RPC_STATUS, UuidCreate, (UUID __RPC_FAR*));
static BOOL has_wimgapi = FALSE, has_7z = FALSE;
static char sevenzip_path[MAX_PATH];
static const char conectix_str[] = VHD_FOOTER_COOKIE;
static BOOL Get7ZipPath(void)
{
@ -91,13 +115,109 @@ static BOOL Get7ZipPath(void)
return FALSE;
}
BOOL AppendVHDFooter(const char* image_path)
{
const char creator_os[4] = VHD_FOOTER_CREATOR_HOST_OS_WINDOWS;
const char creator_app[4] = { 'r', 'u', 'f', 'u' };
BOOL r = FALSE;
DWORD size;
LARGE_INTEGER li;
HANDLE handle = INVALID_HANDLE_VALUE;
vhd_footer* footer;
uint64_t totalSectors;
uint16_t cylinders = 0;
uint8_t heads, sectorsPerTrack;
uint32_t cylinderTimesHeads;
uint32_t checksum;
size_t i;
PF_INIT(UuidCreate, Rpcrt4);
handle = CreateFileU(image_path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
li.QuadPart = 0;
if ((handle == INVALID_HANDLE_VALUE) || (!SetFilePointerEx(handle, li, &li, FILE_END))) {
uprintf("Could not open image '%s': %s", image_path, WindowsErrorString());
goto out;
}
footer = (vhd_footer*)calloc(1, sizeof(vhd_footer));
if (footer == NULL) {
uprintf("Could not allocate VHD footer");
goto out;
}
memcpy(footer->cookie, conectix_str, sizeof(footer->cookie));
footer->features = bswap_uint32(VHD_FOOTER_FEATURES_RESERVED);
footer->file_format_version = bswap_uint32(VHD_FOOTER_FILE_FORMAT_V1_0);
footer->data_offset = bswap_uint64(VHD_FOOTER_DATA_OFFSET_FIXED_DISK);
footer->timestamp = bswap_uint32(_time32(NULL) - SECONDS_SINCE_JAN_1ST_2000);
memcpy(footer->creator_app, creator_app, sizeof(creator_app));
footer->creator_version = bswap_uint32((rufus_version[0]<<16)|rufus_version[1]);
memcpy(footer->creator_host_os, creator_os, sizeof(creator_os));
footer->original_size = bswap_uint64(li.QuadPart);
footer->current_size = footer->original_size;
footer->disk_type = bswap_uint32(VHD_FOOTER_TYPE_FIXED_HARD_DISK);
if ((pfUuidCreate == NULL) || (pfUuidCreate(&footer->unique_id) != RPC_S_OK))
uprintf("Warning: could not set VHD UUID");
// Compute CHS, as per the VHD specs
totalSectors = li.QuadPart / 512;
if (totalSectors > 65535 * 16 * 255) {
totalSectors = 65535 * 16 * 255;
}
if (totalSectors >= 65535 * 16 * 63) {
sectorsPerTrack = 255;
heads = 16;
cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
} else {
sectorsPerTrack = 17;
cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
heads = (cylinderTimesHeads + 1023) / 1024;
if (heads < 4) {
heads = 4;
}
if (cylinderTimesHeads >= ((uint32_t)heads * 1024) || heads > 16) {
sectorsPerTrack = 31;
heads = 16;
cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
}
if (cylinderTimesHeads >= ((uint32_t)heads * 1024)) {
sectorsPerTrack = 63;
heads = 16;
cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
}
}
cylinders = cylinderTimesHeads / heads;
footer->disk_geometry.chs.cylinders = bswap_uint16(cylinders);
footer->disk_geometry.chs.heads = heads;
footer->disk_geometry.chs.sectors = sectorsPerTrack;
// Compute the VHD footer checksum
for (checksum=0, i=0; i<sizeof(vhd_footer); i++)
checksum += ((uint8_t*)footer)[i];
footer->checksum = bswap_uint32(~checksum);
if (!WriteFile(handle, footer, sizeof(vhd_footer), &size, NULL) || (size != sizeof(vhd_footer))) {
uprintf("Could not write VHD footer: %s", WindowsErrorString());
goto out;
}
r = TRUE;
out:
safe_free(footer);
safe_closehandle(handle);
return r;
}
BOOL IsHDImage(const char* path)
{
const char conectix_str[] = VHD_FOOTER_COOKIE;
HANDLE handle = INVALID_HANDLE_VALUE;
LARGE_INTEGER liImageSize;
vhd_footer* footer = NULL;
DWORD size;
size_t i;
uint32_t checksum, old_checksum;
LARGE_INTEGER ptr;
handle = CreateFileU(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
@ -130,6 +250,14 @@ BOOL IsHDImage(const char* path)
iso_report.is_bootable_img = FALSE;
goto out;
}
// Might as well validate the checksum while we're at it
old_checksum = bswap_uint32(footer->checksum);
footer->checksum = 0;
for (checksum=0, i=0; i<sizeof(vhd_footer); i++)
checksum += ((uint8_t*)footer)[i];
checksum = ~checksum;
if (checksum != old_checksum)
uprintf("Warning: VHD footer seems corrupted (checksum: %04X, expected: %04X)", old_checksum, checksum);
// Need to remove the footer from our payload
uprintf("Image is a Fixed Hard Disk VHD file");
iso_report.is_vhd = TRUE;