"Fossies" - the Fresh Open Source Software Archive

Member "rufus-3.13/src/vhd.c" (20 Nov 2020, 28157 Bytes) of package /linux/misc/rufus-3.13.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "vhd.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.12_vs_3.13.

    1 /*
    2  * Rufus: The Reliable USB Formatting Utility
    3  * Virtual Disk Handling functions
    4  * Copyright © 2013-2016 Pete Batard <pete@akeo.ie>
    5  *
    6  * This program is free software: you can redistribute it and/or modify
    7  * it under the terms of the GNU General Public License as published by
    8  * the Free Software Foundation, either version 3 of the License, or
    9  * (at your option) any later version.
   10  *
   11  * This program is distributed in the hope that it will be useful,
   12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14  * GNU General Public License for more details.
   15  *
   16  * You should have received a copy of the GNU General Public License
   17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   18  */
   19 
   20 #include <windows.h>
   21 #include <stdlib.h>
   22 #include <io.h>
   23 #include <rpc.h>
   24 #include <time.h>
   25 
   26 #include "rufus.h"
   27 #include "missing.h"
   28 #include "resource.h"
   29 #include "msapi_utf8.h"
   30 
   31 #include "drive.h"
   32 #include "registry.h"
   33 #include "bled/bled.h"
   34 
   35 #define VHD_FOOTER_COOKIE                   { 'c', 'o', 'n', 'e', 'c', 't', 'i', 'x' }
   36 
   37 #define VHD_FOOTER_FEATURES_NONE            0x00000000
   38 #define VHD_FOOTER_FEATURES_TEMPORARY       0x00000001
   39 #define VHD_FOOTER_FEATURES_RESERVED        0x00000002
   40 
   41 #define VHD_FOOTER_FILE_FORMAT_V1_0         0x00010000
   42 
   43 #define VHD_FOOTER_DATA_OFFSET_FIXED_DISK   0xFFFFFFFFFFFFFFFFULL
   44 
   45 #define VHD_FOOTER_CREATOR_HOST_OS_WINDOWS  { 'W', 'i', '2', 'k' }
   46 #define VHD_FOOTER_CREATOR_HOST_OS_MAC      { 'M', 'a', 'c', ' ' }
   47 
   48 #define VHD_FOOTER_TYPE_FIXED_HARD_DISK     0x00000002
   49 #define VHD_FOOTER_TYPE_DYNAMIC_HARD_DISK   0x00000003
   50 #define VHD_FOOTER_TYPE_DIFFER_HARD_DISK    0x00000004
   51 
   52 #define WIM_MAGIC                           0x0000004D4957534DULL   // "MSWIM\0\0\0"
   53 #define WIM_HAS_API_EXTRACT                 1
   54 #define WIM_HAS_7Z_EXTRACT                  2
   55 #define WIM_HAS_API_APPLY                   4
   56 #define WIM_HAS_EXTRACT(r)                  (r & (WIM_HAS_API_EXTRACT|WIM_HAS_7Z_EXTRACT))
   57 
   58 #define SECONDS_SINCE_JAN_1ST_2000          946684800
   59 
   60 /*
   61  * VHD Fixed HD footer (Big Endian)
   62  * http://download.microsoft.com/download/f/f/e/ffef50a5-07dd-4cf8-aaa3-442c0673a029/Virtual%20Hard%20Disk%20Format%20Spec_10_18_06.doc
   63  * NB: If a dymamic implementation is needed, check the GPL v3 compatible C++ implementation from:
   64  * https://sourceforge.net/p/urbackup/backend/ci/master/tree/fsimageplugin/
   65  */
   66 #pragma pack(push, 1)
   67 typedef struct vhd_footer {
   68     char        cookie[8];
   69     uint32_t    features;
   70     uint32_t    file_format_version;
   71     uint64_t    data_offset;
   72     uint32_t    timestamp;
   73     char        creator_app[4];
   74     uint32_t    creator_version;
   75     char        creator_host_os[4];
   76     uint64_t    original_size;
   77     uint64_t    current_size;
   78     union {
   79         uint32_t    geometry;
   80         struct {
   81             uint16_t    cylinders;
   82             uint8_t     heads;
   83             uint8_t     sectors;
   84         } chs;
   85     } disk_geometry;
   86     uint32_t    disk_type;
   87     uint32_t    checksum;
   88     uuid_t      unique_id;
   89     uint8_t     saved_state;
   90     uint8_t     reserved[427];
   91 } vhd_footer;
   92 #pragma pack(pop)
   93 
   94 // WIM API Prototypes
   95 #define WIM_GENERIC_READ            GENERIC_READ
   96 #define WIM_OPEN_EXISTING           OPEN_EXISTING
   97 #define WIM_UNDOCUMENTED_BULLSHIT   0x20000000
   98 PF_TYPE_DECL(WINAPI, HANDLE, WIMCreateFile, (PWSTR, DWORD, DWORD, DWORD, DWORD, PDWORD));
   99 PF_TYPE_DECL(WINAPI, BOOL, WIMSetTemporaryPath, (HANDLE, PWSTR));
  100 PF_TYPE_DECL(WINAPI, HANDLE, WIMLoadImage, (HANDLE, DWORD));
  101 PF_TYPE_DECL(WINAPI, BOOL, WIMMountImage, (PCWSTR, PCWSTR, DWORD, PCWSTR));
  102 PF_TYPE_DECL(WINAPI, BOOL, WIMUnmountImage, (PCWSTR, PCWSTR, DWORD, BOOL));
  103 PF_TYPE_DECL(WINAPI, BOOL, WIMApplyImage, (HANDLE, PCWSTR, DWORD));
  104 PF_TYPE_DECL(WINAPI, BOOL, WIMExtractImagePath, (HANDLE, PWSTR, PWSTR, DWORD));
  105 PF_TYPE_DECL(WINAPI, BOOL, WIMGetImageInformation, (HANDLE, PVOID, PDWORD));
  106 PF_TYPE_DECL(WINAPI, BOOL, WIMCloseHandle, (HANDLE));
  107 PF_TYPE_DECL(WINAPI, DWORD, WIMRegisterMessageCallback, (HANDLE, FARPROC, PVOID));
  108 PF_TYPE_DECL(WINAPI, DWORD, WIMUnregisterMessageCallback, (HANDLE, FARPROC));
  109 PF_TYPE_DECL(RPC_ENTRY, RPC_STATUS, UuidCreate, (UUID __RPC_FAR*));
  110 
  111 uint32_t wim_nb_files, wim_proc_files, wim_extra_files;
  112 HANDLE apply_wim_thread = NULL;
  113 extern int default_thread_priority;
  114 extern BOOL ignore_boot_marker;
  115 
  116 static uint8_t wim_flags = 0;
  117 static wchar_t wmount_path[MAX_PATH] = { 0 };
  118 static char sevenzip_path[MAX_PATH];
  119 static const char conectix_str[] = VHD_FOOTER_COOKIE;
  120 static BOOL count_files;
  121 
  122 static BOOL Get7ZipPath(void)
  123 {
  124     if ( (GetRegistryKeyStr(REGKEY_HKCU, "7-Zip\\Path", sevenzip_path, sizeof(sevenzip_path)))
  125       || (GetRegistryKeyStr(REGKEY_HKLM, "7-Zip\\Path", sevenzip_path, sizeof(sevenzip_path))) ) {
  126         static_strcat(sevenzip_path, "\\7z.exe");
  127         return (_accessU(sevenzip_path, 0) != -1);
  128     }
  129     return FALSE;
  130 }
  131 
  132 BOOL AppendVHDFooter(const char* vhd_path)
  133 {
  134     const char creator_os[4] = VHD_FOOTER_CREATOR_HOST_OS_WINDOWS;
  135     const char creator_app[4] = { 'r', 'u', 'f', 's' };
  136     BOOL r = FALSE;
  137     DWORD size;
  138     LARGE_INTEGER li;
  139     HANDLE handle = INVALID_HANDLE_VALUE;
  140     vhd_footer* footer = NULL;
  141     uint64_t totalSectors;
  142     uint16_t cylinders = 0;
  143     uint8_t heads, sectorsPerTrack;
  144     uint32_t cylinderTimesHeads;
  145     uint32_t checksum;
  146     size_t i;
  147 
  148     PF_INIT(UuidCreate, Rpcrt4);
  149     handle = CreateFileU(vhd_path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL,
  150         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  151     li.QuadPart = 0;
  152     if ((handle == INVALID_HANDLE_VALUE) || (!SetFilePointerEx(handle, li, &li, FILE_END))) {
  153         uprintf("Could not open image '%s': %s", vhd_path, WindowsErrorString());
  154         goto out;
  155     }
  156     footer = (vhd_footer*)calloc(1, sizeof(vhd_footer));
  157     if (footer == NULL) {
  158         uprintf("Could not allocate VHD footer");
  159         goto out;
  160     }
  161 
  162     memcpy(footer->cookie, conectix_str, sizeof(footer->cookie));
  163     footer->features = bswap_uint32(VHD_FOOTER_FEATURES_RESERVED);
  164     footer->file_format_version = bswap_uint32(VHD_FOOTER_FILE_FORMAT_V1_0);
  165     footer->data_offset = bswap_uint64(VHD_FOOTER_DATA_OFFSET_FIXED_DISK);
  166     footer->timestamp = bswap_uint32((uint32_t)(_time64(NULL) - SECONDS_SINCE_JAN_1ST_2000));
  167     memcpy(footer->creator_app, creator_app, sizeof(creator_app));
  168     footer->creator_version = bswap_uint32((rufus_version[0]<<16)|rufus_version[1]);
  169     memcpy(footer->creator_host_os, creator_os, sizeof(creator_os));
  170     footer->original_size = bswap_uint64(li.QuadPart);
  171     footer->current_size = footer->original_size;
  172     footer->disk_type = bswap_uint32(VHD_FOOTER_TYPE_FIXED_HARD_DISK);
  173     if ((pfUuidCreate == NULL) || (pfUuidCreate(&footer->unique_id) != RPC_S_OK))
  174         uprintf("Warning: could not set VHD UUID");
  175 
  176     // Compute CHS, as per the VHD specs
  177     totalSectors = li.QuadPart / 512;
  178     if (totalSectors > 65535 * 16 * 255) {
  179         totalSectors = 65535 * 16 * 255;
  180     }
  181 
  182     if (totalSectors >= 65535 * 16 * 63) {
  183         sectorsPerTrack = 255;
  184         heads = 16;
  185         cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
  186     } else {
  187         sectorsPerTrack = 17;
  188         cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
  189 
  190         heads = (cylinderTimesHeads + 1023) / 1024;
  191 
  192         if (heads < 4) {
  193             heads = 4;
  194         }
  195         if (cylinderTimesHeads >= ((uint32_t)heads * 1024) || heads > 16) {
  196             sectorsPerTrack = 31;
  197             heads = 16;
  198             cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
  199         }
  200         if (cylinderTimesHeads >= ((uint32_t)heads * 1024)) {
  201             sectorsPerTrack = 63;
  202             heads = 16;
  203             cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
  204         }
  205     }
  206     cylinders = cylinderTimesHeads / heads;
  207     footer->disk_geometry.chs.cylinders = bswap_uint16(cylinders);
  208     footer->disk_geometry.chs.heads = heads;
  209     footer->disk_geometry.chs.sectors = sectorsPerTrack;
  210 
  211     // Compute the VHD footer checksum
  212     for (checksum=0, i=0; i<sizeof(vhd_footer); i++)
  213         checksum += ((uint8_t*)footer)[i];
  214     footer->checksum = bswap_uint32(~checksum);
  215 
  216     if (!WriteFileWithRetry(handle, footer, sizeof(vhd_footer), &size, WRITE_RETRIES)) {
  217         uprintf("Could not write VHD footer: %s", WindowsErrorString());
  218         goto out;
  219     }
  220     r = TRUE;
  221 
  222 out:
  223     safe_free(footer);
  224     safe_closehandle(handle);
  225     return r;
  226 }
  227 
  228 typedef struct {
  229     const char* ext;
  230     bled_compression_type type;
  231 } comp_assoc;
  232 
  233 static comp_assoc file_assoc[] = {
  234     { ".zip", BLED_COMPRESSION_ZIP },
  235     { ".Z", BLED_COMPRESSION_LZW },
  236     { ".gz", BLED_COMPRESSION_GZIP },
  237     { ".lzma", BLED_COMPRESSION_LZMA },
  238     { ".bz2", BLED_COMPRESSION_BZIP2 },
  239     { ".xz", BLED_COMPRESSION_XZ },
  240 };
  241 
  242 // For now we consider that an image that matches a known extension is bootable
  243 #define MBR_SIZE 512    // Might need to review this once we see bootable 4k systems
  244 BOOL IsCompressedBootableImage(const char* path)
  245 {
  246     char *p;
  247     unsigned char *buf = NULL;
  248     int i;
  249     BOOL r = FALSE;
  250     int64_t dc;
  251 
  252     img_report.compression_type = BLED_COMPRESSION_NONE;
  253     for (p = (char*)&path[strlen(path)-1]; (*p != '.') && (p != path); p--);
  254 
  255     if (p == path)
  256         return FALSE;
  257 
  258     for (i = 0; i<ARRAYSIZE(file_assoc); i++) {
  259         if (strcmp(p, file_assoc[i].ext) == 0) {
  260             img_report.compression_type = file_assoc[i].type;
  261             buf = malloc(MBR_SIZE);
  262             if (buf == NULL)
  263                 return FALSE;
  264             FormatStatus = 0;
  265             bled_init(_uprintf, NULL, NULL, NULL, NULL, &FormatStatus);
  266             dc = bled_uncompress_to_buffer(path, (char*)buf, MBR_SIZE, file_assoc[i].type);
  267             bled_exit();
  268             if (dc != MBR_SIZE) {
  269                 free(buf);
  270                 return FALSE;
  271             }
  272             r = (buf[0x1FE] == 0x55) && (buf[0x1FF] == 0xAA);
  273             free(buf);
  274             return r;
  275         }
  276     }
  277 
  278     return FALSE;
  279 }
  280 
  281 // 0: non-bootable, 1: bootable, 2: forced bootable
  282 uint8_t IsBootableImage(const char* path)
  283 {
  284     HANDLE handle = INVALID_HANDLE_VALUE;
  285     LARGE_INTEGER liImageSize;
  286     vhd_footer* footer = NULL;
  287     DWORD size;
  288     size_t i;
  289     uint32_t checksum, old_checksum;
  290     uint64_t wim_magic = 0;
  291     LARGE_INTEGER ptr = { 0 };
  292     uint8_t is_bootable_img = 0;
  293 
  294     uprintf("Disk image analysis:");
  295     handle = CreateFileU(path, GENERIC_READ, FILE_SHARE_READ, NULL,
  296         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  297     if (handle == INVALID_HANDLE_VALUE) {
  298         uprintf("  Could not open image '%s'", path);
  299         goto out;
  300     }
  301 
  302     is_bootable_img = IsCompressedBootableImage(path) ? 1 : 0;
  303     if (img_report.compression_type == BLED_COMPRESSION_NONE)
  304         is_bootable_img = AnalyzeMBR(handle, "  Image", FALSE) ? 1 : (ignore_boot_marker ? 2 : 0);
  305 
  306     if (!GetFileSizeEx(handle, &liImageSize)) {
  307         uprintf("  Could not get image size: %s", WindowsErrorString());
  308         goto out;
  309     }
  310     img_report.image_size = (uint64_t)liImageSize.QuadPart;
  311     size = sizeof(wim_magic);
  312     IGNORE_RETVAL(SetFilePointerEx(handle, ptr, NULL, FILE_BEGIN));
  313     img_report.is_windows_img = ReadFile(handle, &wim_magic, size, &size, NULL) && (wim_magic == WIM_MAGIC);
  314     if (img_report.is_windows_img)
  315         goto out;
  316 
  317     size = sizeof(vhd_footer);
  318     if ((img_report.compression_type == BLED_COMPRESSION_NONE) && (img_report.image_size >= (512 + size))) {
  319         footer = (vhd_footer*)malloc(size);
  320         ptr.QuadPart = img_report.image_size - size;
  321         if ( (footer == NULL) || (!SetFilePointerEx(handle, ptr, NULL, FILE_BEGIN)) ||
  322              (!ReadFile(handle, footer, size, &size, NULL)) || (size != sizeof(vhd_footer)) ) {
  323             uprintf("  Could not read VHD footer");
  324             goto out;
  325         }
  326         if (memcmp(footer->cookie, conectix_str, sizeof(footer->cookie)) == 0) {
  327             img_report.image_size -= sizeof(vhd_footer);
  328             if ( (bswap_uint32(footer->file_format_version) != VHD_FOOTER_FILE_FORMAT_V1_0)
  329               || (bswap_uint32(footer->disk_type) != VHD_FOOTER_TYPE_FIXED_HARD_DISK)) {
  330                 uprintf("  Unsupported type of VHD image");
  331                 is_bootable_img = 0;
  332                 goto out;
  333             }
  334             // Might as well validate the checksum while we're at it
  335             old_checksum = bswap_uint32(footer->checksum);
  336             footer->checksum = 0;
  337             for (checksum=0, i=0; i<sizeof(vhd_footer); i++)
  338                 checksum += ((uint8_t*)footer)[i];
  339             checksum = ~checksum;
  340             if (checksum != old_checksum)
  341                 uprintf("  Warning: VHD footer seems corrupted (checksum: %04X, expected: %04X)", old_checksum, checksum);
  342             // Need to remove the footer from our payload
  343             uprintf("  Image is a Fixed Hard Disk VHD file");
  344             img_report.is_vhd = TRUE;
  345         }
  346     }
  347 
  348 out:
  349     safe_free(footer);
  350     safe_closehandle(handle);
  351     return is_bootable_img;
  352 }
  353 
  354 // Find out if we have any way to extract/apply WIM files on this platform
  355 // Returns a bitfield of the methods we can use (1 = Extract using wimgapi, 2 = Extract using 7-Zip, 4 = Apply using wimgapi)
  356 uint8_t WimExtractCheck(BOOL bSilent)
  357 {
  358     PF_INIT(WIMCreateFile, Wimgapi);
  359     PF_INIT(WIMSetTemporaryPath, Wimgapi);
  360     PF_INIT(WIMLoadImage, Wimgapi);
  361     PF_INIT(WIMApplyImage, Wimgapi);
  362     PF_INIT(WIMExtractImagePath, Wimgapi);
  363     PF_INIT(WIMGetImageInformation, Wimgapi);
  364     PF_INIT(WIMRegisterMessageCallback, Wimgapi);
  365     PF_INIT(WIMUnregisterMessageCallback, Wimgapi);
  366     PF_INIT(WIMCloseHandle, Wimgapi);
  367 
  368     if (pfWIMCreateFile && pfWIMSetTemporaryPath && pfWIMLoadImage && pfWIMExtractImagePath && pfWIMCloseHandle)
  369         wim_flags |= WIM_HAS_API_EXTRACT;
  370     if (Get7ZipPath())
  371         wim_flags |= WIM_HAS_7Z_EXTRACT;
  372     if ((wim_flags & WIM_HAS_API_EXTRACT) && pfWIMApplyImage && pfWIMRegisterMessageCallback && pfWIMUnregisterMessageCallback)
  373         wim_flags |= WIM_HAS_API_APPLY;
  374 
  375     suprintf("WIM extraction method(s) supported: %s%s%s", (wim_flags & WIM_HAS_7Z_EXTRACT)?"7-Zip":
  376         ((wim_flags & WIM_HAS_API_EXTRACT)?"":"NONE"),
  377         (WIM_HAS_EXTRACT(wim_flags) == (WIM_HAS_API_EXTRACT|WIM_HAS_7Z_EXTRACT))?", ":
  378         "", (wim_flags & WIM_HAS_API_EXTRACT)?"wimgapi.dll":"");
  379     suprintf("WIM apply method supported: %s", (wim_flags & WIM_HAS_API_APPLY)?"wimgapi.dll":"NONE");
  380     return wim_flags;
  381 }
  382 
  383 // Looks like Microsoft's idea of "mount" for WIM images is to just *extract* all
  384 // files to a mounpoint and pretend it is "mounted", even if you do specify that
  385 // you're not planning to change the content. So, yeah, this is both super slow
  386 // and super wasteful of space... These calls are a complete waste of time.
  387 BOOL WimMountImage(char* pszWimFileName, DWORD dwImageIndex)
  388 {
  389     BOOL r = FALSE;
  390     wconvert(temp_dir);
  391     wconvert(pszWimFileName);
  392     PF_INIT_OR_OUT(WIMMountImage, Wimgapi);
  393 
  394     if (wmount_path[0] != 0) {
  395         uprintf("WimMountImage: An image is already mounted");
  396         goto out;
  397     }
  398     if (GetTempFileNameW(wtemp_dir, L"Rufus", 0, wmount_path) == 0) {
  399         uprintf("WimMountImage: Can not create mount directory");
  400         goto out;
  401     }
  402     DeleteFileW(wmount_path);
  403     if (!CreateDirectoryW(wmount_path, 0)) {
  404         uprintf("WimMountImage: Can not create mount directory");
  405         goto out;
  406     }
  407 
  408     r = pfWIMMountImage(wmount_path, wpszWimFileName, dwImageIndex, NULL);
  409     if (!r)
  410         uprintf("Could not mount %S on %S: %s", wpszWimFileName, wmount_path, WindowsErrorString());
  411 
  412 out:
  413     wfree(temp_dir);
  414     wfree(pszWimFileName);
  415     return r;
  416 }
  417 
  418 BOOL WimUnmountImage(void)
  419 {
  420     BOOL r = FALSE;
  421     PF_INIT_OR_OUT(WIMUnmountImage, Wimgapi);
  422     if (wmount_path[0] == 0) {
  423         uprintf("WimUnmountImage: No image is mounted");
  424         goto out;
  425     }
  426     r = pfWIMUnmountImage(wmount_path, NULL, 0, FALSE);
  427     if (!r)
  428         uprintf("Could not unmount %S: %s", wmount_path, WindowsErrorString());
  429     wmount_path[0] = 0;
  430 out:
  431     return r;
  432 }
  433 
  434 // Extract a file from a WIM image using wimgapi.dll (Windows 7 or later)
  435 // NB: if you want progress from a WIM callback, you must run the WIM API call in its own thread
  436 // (which we don't do here) as it won't work otherwise. Thanks go to Erwan for figuring this out!
  437 BOOL WimExtractFile_API(const char* image, int index, const char* src, const char* dst, BOOL bSilent)
  438 {
  439     static char* index_name = "[1].xml";
  440     BOOL r = FALSE;
  441     DWORD dw = 0;
  442     HANDLE hWim = NULL;
  443     HANDLE hImage = NULL;
  444     HANDLE hFile = NULL;
  445     wchar_t wtemp[MAX_PATH] = {0};
  446     wchar_t* wimage = utf8_to_wchar(image);
  447     wchar_t* wsrc = utf8_to_wchar(src);
  448     wchar_t* wdst = utf8_to_wchar(dst);
  449     char* wim_info;
  450 
  451     PF_INIT_OR_OUT(WIMCreateFile, Wimgapi);
  452     PF_INIT_OR_OUT(WIMSetTemporaryPath, Wimgapi);
  453     PF_INIT_OR_OUT(WIMLoadImage, Wimgapi);
  454     PF_INIT_OR_OUT(WIMExtractImagePath, Wimgapi);
  455     PF_INIT_OR_OUT(WIMCloseHandle, Wimgapi);
  456 
  457     suprintf("Opening: %s:[%d] (API)", image, index);
  458     if (GetTempPathW(ARRAYSIZE(wtemp), wtemp) == 0) {
  459         uprintf("  Could not fetch temp path: %s", WindowsErrorString());
  460         goto out;
  461     }
  462 
  463     // Thanks to dism++ for figuring out that you can use UNDOCUMENTED FLAG 0x20000000
  464     // to open newer install.wim/install.esd images, without running into obnoxious error:
  465     // [0x0000000B] An attempt was made to load a program with an incorrect format.
  466     // No thanks to Microsoft for NOT DOCUMENTING THEIR UTTER BULLSHIT with the WIM API!
  467     hWim = pfWIMCreateFile(wimage, WIM_GENERIC_READ, WIM_OPEN_EXISTING,
  468         (img_report.wininst_version >= SPECIAL_WIM_VERSION) ? WIM_UNDOCUMENTED_BULLSHIT : 0, 0, NULL);
  469     if (hWim == NULL) {
  470         uprintf("  Could not access image: %s", WindowsErrorString());
  471         goto out;
  472     }
  473 
  474     if (!pfWIMSetTemporaryPath(hWim, wtemp)) {
  475         uprintf("  Could not set temp path: %s", WindowsErrorString());
  476         goto out;
  477     }
  478 
  479     suprintf("Extracting: %s (From %s)", dst, src);
  480     if (safe_strcmp(src, index_name) == 0) {
  481         if (!pfWIMGetImageInformation(hWim, &wim_info, &dw)) {
  482             uprintf("  Could not access WIM info: %s", WindowsErrorString());
  483             goto out;
  484         }
  485         hFile = CreateFileW(wdst, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
  486             NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  487         if ((hFile == INVALID_HANDLE_VALUE) || (!WriteFile(hFile, wim_info, dw, &dw, NULL))) {
  488             suprintf("  Could not extract file: %s", WindowsErrorString());
  489             goto out;
  490         }
  491     } else {
  492         hImage = pfWIMLoadImage(hWim, (DWORD)index);
  493         if (hImage == NULL) {
  494             uprintf("  Could not set index: %s", WindowsErrorString());
  495             goto out;
  496         }
  497         if (!pfWIMExtractImagePath(hImage, wsrc, wdst, 0)) {
  498             suprintf("  Could not extract file: %s", WindowsErrorString());
  499             goto out;
  500         }
  501     }
  502     r = TRUE;
  503 
  504 out:
  505     if ((hImage != NULL) || (hWim != NULL)) {
  506         suprintf("Closing: %s", image);
  507         if (hImage != NULL) pfWIMCloseHandle(hImage);
  508         if (hWim != NULL) pfWIMCloseHandle(hWim);
  509     }
  510     safe_closehandle(hFile);
  511     safe_free(wimage);
  512     safe_free(wsrc);
  513     safe_free(wdst);
  514     return r;
  515 }
  516 
  517 // Extract a file from a WIM image using 7-Zip
  518 BOOL WimExtractFile_7z(const char* image, int index, const char* src, const char* dst, BOOL bSilent)
  519 {
  520     int n;
  521     size_t i;
  522     char cmdline[MAX_PATH];
  523     char tmpdst[MAX_PATH];
  524     char index_prefix[] = "#\\";
  525 
  526     suprintf("Opening: %s:[%d] (7-Zip)", image, index);
  527 
  528     if ((image == NULL) || (src == NULL) || (dst == NULL))
  529         return FALSE;
  530 
  531     // If you shove more than 9 images in a WIM, don't come complaining
  532     // that this breaks!
  533     index_prefix[0] = '0' + index;
  534 
  535     suprintf("Extracting: %s (From %s)", dst, src);
  536 
  537     // 7z has a quirk where the image index MUST be specified if a
  538     // WIM has multiple indexes, but it MUST be removed if there is
  539     // only one image. Because of this (and because 7z will not
  540     // return an error code if it can't extract the file), we need
  541     // to issue 2 passes. See github issue #680.
  542     for (n = 0; n < 2; n++) {
  543         static_strcpy(tmpdst, dst);
  544         for (i = strlen(tmpdst) - 1; (i > 0) && (tmpdst[i] != '\\') && (tmpdst[i] != '/'); i--);
  545         tmpdst[i] = 0;
  546 
  547         static_sprintf(cmdline, "\"%s\" -y e \"%s\" %s%s", sevenzip_path,
  548             image, (n == 0) ? index_prefix : "", src);
  549         if (RunCommand(cmdline, tmpdst, FALSE) != 0) {
  550             uprintf("  Could not launch 7z.exe: %s", WindowsErrorString());
  551             return FALSE;
  552         }
  553 
  554         for (i = safe_strlen(src); (i > 0) && (src[i] != '\\') && (src[i] != '/'); i--);
  555         if (i == 0)
  556             static_strcat(tmpdst, "\\");
  557         static_strcat(tmpdst, &src[i]);
  558         if (_access(tmpdst, 0) == 0)
  559             // File was extracted => move on
  560             break;
  561     }
  562 
  563     if (n >= 2) {
  564         suprintf("  7z.exe did not extract %s", tmpdst);
  565         return FALSE;
  566     }
  567 
  568     // coverity[toctou]
  569     if (!MoveFileExU(tmpdst, dst, MOVEFILE_REPLACE_EXISTING)) {
  570         uprintf("  Could not rename %s to %s: %s", tmpdst, dst, WindowsErrorString());
  571         return FALSE;
  572     }
  573 
  574     return TRUE;
  575 }
  576 
  577 // Extract a file from a WIM image
  578 BOOL WimExtractFile(const char* image, int index, const char* src, const char* dst, BOOL bSilent)
  579 {
  580     if ((wim_flags == 0) && (!WIM_HAS_EXTRACT(WimExtractCheck(TRUE))))
  581         return FALSE;
  582     if ((image == NULL) || (src == NULL) || (dst == NULL))
  583         return FALSE;
  584 
  585     // Prefer 7-Zip as, unsurprisingly, it's faster than the Microsoft way,
  586     // but allow fallback if 7-Zip doesn't succeed
  587     return ( ((wim_flags & WIM_HAS_7Z_EXTRACT) && WimExtractFile_7z(image, index, src, dst, bSilent))
  588           || ((wim_flags & WIM_HAS_API_EXTRACT) && WimExtractFile_API(image, index, src, dst, bSilent)) );
  589 }
  590 
  591 // Apply image functionality
  592 static const char *_image, *_dst;
  593 static int _index;
  594 
  595 // From http://msdn.microsoft.com/en-us/library/windows/desktop/dd834960.aspx
  596 // as well as http://www.msfn.org/board/topic/150700-wimgapi-wimmountimage-progressbar/
  597 enum WIMMessage {
  598     WIM_MSG = WM_APP + 0x1476,
  599     WIM_MSG_TEXT,
  600     WIM_MSG_PROGRESS,   // Indicates an update in the progress of an image application.
  601     WIM_MSG_PROCESS,    // Enables the caller to prevent a file or a directory from being captured or applied.
  602     WIM_MSG_SCANNING,   // Indicates that volume information is being gathered during an image capture.
  603     WIM_MSG_SETRANGE,   // Indicates the number of files that will be captured or applied.
  604     WIM_MSG_SETPOS,     // Indicates the number of files that have been captured or applied.
  605     WIM_MSG_STEPIT,     // Indicates that a file has been either captured or applied.
  606     WIM_MSG_COMPRESS,   // Enables the caller to prevent a file resource from being compressed during a capture.
  607     WIM_MSG_ERROR,      // Alerts the caller that an error has occurred while capturing or applying an image.
  608     WIM_MSG_ALIGNMENT,  // Enables the caller to align a file resource on a particular alignment boundary.
  609     WIM_MSG_RETRY,      // Sent when the file is being reapplied because of a network timeout.
  610     WIM_MSG_SPLIT,      // Enables the caller to align a file resource on a particular alignment boundary.
  611     WIM_MSG_FILEINFO,   // Used in conjunction with WimApplyImages()'s WIM_FLAG_FILEINFO flag to provide detailed file info.
  612     WIM_MSG_INFO,       // Sent when an info message is available.
  613     WIM_MSG_WARNING,    // Sent when a warning message is available.
  614     WIM_MSG_CHK_PROCESS,
  615     WIM_MSG_SUCCESS = 0x00000000,
  616     WIM_MSG_ABORT_IMAGE = -1
  617 };
  618 
  619 #define INVALID_CALLBACK_VALUE 0xFFFFFFFF
  620 
  621 #define WIM_FLAG_RESERVED      0x00000001
  622 #define WIM_FLAG_VERIFY        0x00000002
  623 #define WIM_FLAG_INDEX         0x00000004
  624 #define WIM_FLAG_NO_APPLY      0x00000008
  625 #define WIM_FLAG_NO_DIRACL     0x00000010
  626 #define WIM_FLAG_NO_FILEACL    0x00000020
  627 #define WIM_FLAG_SHARE_WRITE   0x00000040
  628 #define WIM_FLAG_FILEINFO      0x00000080
  629 #define WIM_FLAG_NO_RP_FIX     0x00000100
  630 
  631 // Progress callback
  632 DWORD WINAPI WimProgressCallback(DWORD dwMsgId, WPARAM wParam, LPARAM lParam, PVOID pvIgnored)
  633 {
  634     PBOOL pbCancel = NULL;
  635     PWIN32_FIND_DATA pFileData;
  636     const char* level = NULL;
  637     uint64_t size;
  638 
  639     switch (dwMsgId) {
  640     case WIM_MSG_PROGRESS:
  641         // The default WIM progress is useless (freezes at 95%, which is usually when only half
  642         // the files have been processed), so we don't use it
  643 #if 0
  644         PrintInfo(0, MSG_267, (DWORD)wParam);
  645         UpdateProgress(OP_FILE_COPY, 0.98f*(DWORD)wParam);
  646 #endif
  647         break;
  648     case WIM_MSG_PROCESS:
  649         // The amount of files processed is overwhelming (16k+ for a typical image),
  650         // and trying to display it *WILL* slow us down, so we don't.
  651 #if 0
  652         uprintf("%S", (PWSTR)wParam);
  653         PrintStatus(0, MSG_000, str);   // MSG_000 is "%s"
  654 #endif
  655         if (count_files) {
  656             wim_nb_files++;
  657         } else {
  658             // At the end of an actual apply, the WIM API re-lists a bunch of directories it already processed,
  659             // so, even as we try to compensate, we might end up with more entries than counted - ignore those.
  660             if (wim_proc_files < wim_nb_files)
  661                 wim_proc_files++;
  662             else
  663                 wim_extra_files++;
  664             UpdateProgressWithInfo(OP_FILE_COPY, MSG_267, wim_proc_files, wim_nb_files);
  665         }
  666         // Halt on error
  667         if (IS_ERROR(FormatStatus)) {
  668             pbCancel = (PBOOL)lParam;
  669             *pbCancel = TRUE;
  670             break;
  671         }
  672         break;
  673     case WIM_MSG_FILEINFO:
  674         pFileData = (PWIN32_FIND_DATA)lParam;
  675         if (pFileData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  676             uprintf("Creating: %S", (PWSTR)wParam);
  677         } else {
  678             size = (((uint64_t)pFileData->nFileSizeHigh) << 32) + pFileData->nFileSizeLow;
  679             uprintf("Extracting: %S (%s)", (PWSTR)wParam, SizeToHumanReadable(size, FALSE, FALSE));
  680         }
  681         break;
  682     case WIM_MSG_RETRY:
  683         level = "retry";
  684         // fall through
  685     case WIM_MSG_INFO:
  686         if (level == NULL) level = "info";
  687         // fall through
  688     case WIM_MSG_WARNING:
  689         if (level == NULL) level = "warning";
  690         // fall through
  691     case WIM_MSG_ERROR:
  692         if (level == NULL) level = "error";
  693         SetLastError((DWORD)lParam);
  694         uprintf("Apply %s: %S [err = %d]\n", level, (PWSTR)wParam, WindowsErrorString());
  695         break;
  696     }
  697 
  698     return IS_ERROR(FormatStatus)?WIM_MSG_ABORT_IMAGE:WIM_MSG_SUCCESS;
  699 }
  700 
  701 // Apply a WIM image using wimgapi.dll (Windows 7 or later)
  702 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd851944.aspx
  703 // To get progress, we must run this call within its own thread
  704 static DWORD WINAPI WimApplyImageThread(LPVOID param)
  705 {
  706     BOOL r = FALSE;
  707     HANDLE hWim = NULL;
  708     HANDLE hImage = NULL;
  709     wchar_t wtemp[MAX_PATH] = {0};
  710     wchar_t* wimage = utf8_to_wchar(_image);
  711     wchar_t* wdst = utf8_to_wchar(_dst);
  712 
  713     PF_INIT_OR_OUT(WIMRegisterMessageCallback, Wimgapi);
  714     PF_INIT_OR_OUT(WIMCreateFile, Wimgapi);
  715     PF_INIT_OR_OUT(WIMSetTemporaryPath, Wimgapi);
  716     PF_INIT_OR_OUT(WIMLoadImage, Wimgapi);
  717     PF_INIT_OR_OUT(WIMApplyImage, Wimgapi);
  718     PF_INIT_OR_OUT(WIMCloseHandle, Wimgapi);
  719     PF_INIT_OR_OUT(WIMUnregisterMessageCallback, Wimgapi);
  720 
  721     uprintf("Opening: %s:[%d]", _image, _index);
  722 
  723     if (pfWIMRegisterMessageCallback(NULL, (FARPROC)WimProgressCallback, NULL) == INVALID_CALLBACK_VALUE) {
  724         uprintf("  Could not set progress callback: %s", WindowsErrorString());
  725         goto out;
  726     }
  727 
  728     if (GetTempPathW(ARRAYSIZE(wtemp), wtemp) == 0) {
  729         uprintf("  Could not fetch temp path: %s", WindowsErrorString());
  730         goto out;
  731     }
  732 
  733     hWim = pfWIMCreateFile(wimage, WIM_GENERIC_READ, WIM_OPEN_EXISTING,
  734         (img_report.wininst_version >= SPECIAL_WIM_VERSION) ? WIM_UNDOCUMENTED_BULLSHIT : 0, 0, NULL);
  735     if (hWim == NULL) {
  736         uprintf("  Could not access image: %s", WindowsErrorString());
  737         goto out;
  738     }
  739 
  740     if (!pfWIMSetTemporaryPath(hWim, wtemp)) {
  741         uprintf("  Could not set temp path: %s", WindowsErrorString());
  742         goto out;
  743     }
  744 
  745     hImage = pfWIMLoadImage(hWim, (DWORD)_index);
  746     if (hImage == NULL) {
  747         uprintf("  Could not set index: %s", WindowsErrorString());
  748         goto out;
  749     }
  750 
  751     uprintf("Applying Windows image...");
  752     UpdateProgressWithInfoInit(NULL, TRUE);
  753     // Run a first pass using WIM_FLAG_NO_APPLY to count the files
  754     wim_nb_files = 0;
  755     wim_proc_files = 0;
  756     wim_extra_files = 0;
  757     count_files = TRUE;
  758     if (!pfWIMApplyImage(hImage, wdst, WIM_FLAG_NO_APPLY)) {
  759         uprintf("  Could not count the files to apply: %s", WindowsErrorString());
  760         goto out;
  761     }
  762     // The latest Windows 10 ISOs have a ~17.5% discrepancy between the number of
  763     // files and directories actually applied vs. the ones counted when not applying.
  764     // Therefore, we add a 'safe' 20% to our counted files to compensate for yet
  765     // another dismal Microsoft progress reporting API...
  766     wim_nb_files += wim_nb_files / 5;
  767     count_files = FALSE;
  768     // Actual apply
  769     if (!pfWIMApplyImage(hImage, wdst, WIM_FLAG_FILEINFO)) {
  770         uprintf("  Could not apply image: %s", WindowsErrorString());
  771         goto out;
  772     }
  773     // Ensure that we'll pick if need to readjust our 20% above from user reports
  774     if (wim_extra_files > 0)
  775         uprintf("Notice: An extra %d files and directories were applied, from the %d expected",
  776             wim_extra_files, wim_nb_files);
  777     // Re-use extra files as the final progress step
  778     wim_extra_files = (wim_nb_files - wim_proc_files) / 3;
  779     UpdateProgressWithInfo(OP_FILE_COPY, MSG_267, wim_proc_files + wim_extra_files, wim_nb_files);
  780     r = TRUE;
  781 
  782 out:
  783     if ((hImage != NULL) || (hWim != NULL)) {
  784         uprintf("Closing: %s", _image);
  785         if (hImage != NULL) pfWIMCloseHandle(hImage);
  786         if (hWim != NULL) pfWIMCloseHandle(hWim);
  787     }
  788     if (pfWIMUnregisterMessageCallback != NULL)
  789         pfWIMUnregisterMessageCallback(NULL, (FARPROC)WimProgressCallback);
  790     safe_free(wimage);
  791     safe_free(wdst);
  792     ExitThread((DWORD)r);
  793 }
  794 
  795 BOOL WimApplyImage(const char* image, int index, const char* dst)
  796 {
  797     DWORD dw = 0;
  798     _image = image;
  799     _index = index;
  800     _dst = dst;
  801 
  802     apply_wim_thread = CreateThread(NULL, 0, WimApplyImageThread, NULL, 0, NULL);
  803     if (apply_wim_thread == NULL) {
  804         uprintf("Unable to start apply-image thread");
  805         return FALSE;
  806     }
  807     SetThreadPriority(apply_wim_thread, default_thread_priority);
  808     WaitForSingleObject(apply_wim_thread, INFINITE);
  809     if (!GetExitCodeThread(apply_wim_thread, &dw))
  810         dw = 0;
  811     apply_wim_thread = NULL;
  812     return dw;
  813 }