/*
 * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/**
 * @file
 *
 * WIM & BCD patching patching
 *
 */

#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <wchar.h>
#include <assert.h>
#include "wimboot.h"
#include "cmdline.h"
#include "vdisk.h"
#include "sha1.h"
#include "wim.h"
#include "patch.h"

/** Directory into which files are injected */
#define WIM_INJECT_DIR "\\Windows\\System32"

struct wim_patch;

/** A region of a patched WIM file */
struct wim_patch_region {
	/** Name */
	const char *name;
	/** Opaque token */
	void *opaque;
	/** Starting offset of region */
	size_t offset;
	/** Length of region */
	size_t len;
	/** Patch region
	 *
	 * @v patch		WIM patch
	 * @v region		Patch region
	 * @v data		Data buffer
	 * @v offset		Relative offset
	 * @v len		Length
	 * @ret rc		Return status code
	 */
	int ( * patch ) ( struct wim_patch *patch,
			  struct wim_patch_region *region,
			  void *data, size_t offset, size_t len );
};

/** Regions of a patched WIM directory containing injected files */
struct wim_patch_dir_regions {
	/** Subdirectory offset within parent entry */
	struct wim_patch_region subdir;
	/** Copy of original directory entries */
	struct wim_patch_region copy;
	/** Injected file directory entries */
	struct wim_patch_region file[VDISK_MAX_FILES];
} __attribute__ (( packed ));

/** Regions of a patched WIM file */
union wim_patch_regions {
	/** Structured list of regions */
	struct {
		/** WIM header */
		struct wim_patch_region header;
		/** Injected file contents */
		struct wim_patch_region file[VDISK_MAX_FILES];
		/** Injected lookup table */
		struct {
			/** Uncompressed copy of original lookup table */
			struct wim_patch_region copy;
			/** Injected boot image metadata lookup table entry */
			struct wim_patch_region boot;
			/** Injected file lookup table entries */
			struct wim_patch_region file[VDISK_MAX_FILES];
		} __attribute__ (( packed )) lookup;
		/** Injected boot image metadata */
		struct {
			/** Uncompressed copy of original metadata */
			struct wim_patch_region copy;
			/** Patched directory containing injected files */
			struct wim_patch_dir_regions dir;
		} __attribute__ (( packed )) boot;
	} __attribute__ (( packed ));
	/** Unstructured list of regions */
	struct wim_patch_region region[0];
};

/** An injected directory entry */
struct wim_patch_dir_entry {
	/** Directory entry */
	struct wim_directory_entry dir;
	/** Name */
	wchar_t name[ VDISK_NAME_LEN + 1 /* wNUL */ ];
} __attribute__ (( packed ));

/** A directory containing injected files */
struct wim_patch_dir {
	/** Name */
	const char *name;
	/** Offset to parent directory entry */
	size_t parent;
	/** Offset to original directory entries */
	size_t offset;
	/** Length of original directory entries (excluding terminator) */
	size_t len;
	/** Offset to modified directory entries */
	size_t subdir;
};

/** A patched WIM file */
struct wim_patch {
	/** Virtual file */
	struct vdisk_file *file;
	/** Patched WIM header */
	struct wim_header header;
	/** Original lookup table */
	struct wim_resource_header lookup;
	/** Original boot image metadata */
	struct wim_resource_header boot;
	/** Original boot index */
	uint32_t boot_index;
	/** Directory containing injected files */
	struct wim_patch_dir dir;
	/** Patched regions */
	union wim_patch_regions regions;
};

/**
 * Align WIM offset to nearest qword
 *
 * @v len		Length
 * @ret len		Aligned length
 */
static size_t wim_align ( size_t len ) {
	return ( ( len + 0x07 ) & ~0x07 );
}

/**
 * Calculate WIM hash
 *
 * @v vfile		Virtual file
 * @v hash		Hash to fill in
 */
static void wim_hash ( struct vdisk_file *vfile, struct wim_hash *hash ) {
	uint8_t ctx[SHA1_CTX_SIZE];
	uint8_t buf[512];
	size_t offset;
	size_t len;

	/* Calculate SHA-1 digest */
	sha1_init ( ctx );
	for ( offset = 0 ; offset < vfile->len ; offset += len ) {

		/* Read block */
		len = ( vfile->len - offset );
		if ( len > sizeof ( buf ) )
			len = sizeof ( buf );
		vfile->read ( vfile, buf, offset, len );

		/* Update digest */
		sha1_update ( ctx, buf, len );
	}
	sha1_final ( ctx, hash->sha1 );
}

/**
 * Determine whether or not to inject file
 *
 * @v vfile		Virtual file
 * @ret inject		Inject this file
 */
static int wim_inject_file ( struct vdisk_file *vfile ) {
	size_t name_len;
	const char *ext;

	/* Ignore non-existent files */
	if ( ! vfile->read )
		return 0;

	/* Ignore wimboot itself */
	if ( strcasecmp ( vfile->name, "wimboot" ) == 0 )
		return 0;

	/* Ignore bootmgr files */
	if ( strcasecmp ( vfile->name, "bootmgr" ) == 0 )
		return 0;
	if ( strcasecmp ( vfile->name, "bootmgr.exe" ) == 0 )
		return 0;

	/* Ignore BCD files */
	if ( strcasecmp ( vfile->name, "BCD" ) == 0 )
		return 0;

	/* Locate file extension */
	name_len = strlen ( vfile->name );
	ext = ( ( name_len > 4 ) ? ( vfile->name + name_len - 4 ) : "" );

	/* Ignore .wim files */
	if ( strcasecmp ( ext, ".wim" ) == 0 )
		return 0;

	/* Ignore .sdi files */
	if ( strcasecmp ( ext, ".sdi" ) == 0 )
		return 0;

	/* Ignore .efi files */
	if ( strcasecmp ( ext, ".efi" ) == 0 )
		return 0;

	/* Ignore .ttf files */
	if ( strcasecmp ( ext, ".ttf" ) == 0 )
		return 0;

	return 1;
}

/**
 * Patch WIM header
 *
 * @v patch		WIM patch
 * @v region		Patch region
 * @v data		Data buffer
 * @v offset		Relative offset
 * @v len		Length
 * @ret rc		Return status code
 */
static int wim_patch_header ( struct wim_patch *patch,
			      struct wim_patch_region *region,
			      void *data, size_t offset, size_t len ) {
	struct wim_header *header = &patch->header;

	/* Sanity checks */
	assert ( offset < sizeof ( *header ) );
	assert ( len <= ( sizeof ( *header ) - offset ) );

	/* Copy patched header */
	if ( patch->lookup.offset != patch->header.lookup.offset ) {
		DBG2 ( "...patched WIM %s lookup table %#llx->%#llx\n",
		       region->name, patch->lookup.offset,
		       patch->header.lookup.offset );
	}
	if ( patch->boot.offset != patch->header.boot.offset ) {
		DBG2 ( "...patched WIM %s boot metadata %#llx->%#llx\n",
		       region->name, patch->boot.offset,
		       patch->header.boot.offset );
	}
	if ( patch->boot_index != patch->header.boot_index ) {
		DBG2 ( "...patched WIM %s boot index %d->%d\n", region->name,
		       patch->boot_index, patch->header.boot_index );
	}
	memcpy ( data, ( ( ( void * ) &patch->header ) + offset ), len );

	return 0;
}

/**
 * Patch injected file content
 *
 * @v patch		WIM patch
 * @v region		Patch region
 * @v data		Data buffer
 * @v offset		Relative offset
 * @v len		Length
 * @ret rc		Return status code
 */
static int wim_patch_file ( struct wim_patch *patch __unused,
			    struct wim_patch_region *region,
			    void *data, size_t offset, size_t len ) {
	struct vdisk_file *vfile = region->opaque;

	/* Read from file */
	vfile->read ( vfile, data, offset, len );

	return 0;
}

/**
 * Patch uncompressed copy of original lookup table
 *
 * @v patch		WIM patch
 * @v region		Patch region
 * @v data		Data buffer
 * @v offset		Relative offset
 * @v len		Length
 * @ret rc		Return status code
 */
static int wim_patch_lookup_copy ( struct wim_patch *patch,
				   struct wim_patch_region *region __unused,
				   void *data, size_t offset, size_t len ) {
	int rc;

	/* Read original lookup table */
	if ( ( rc = wim_read ( patch->file, &patch->header, &patch->lookup,
			       data, offset, len ) ) != 0 )
		return rc;

	return 0;
}

/**
 * Patch injected boot image metadata lookup table entry
 *
 * @v patch		WIM patch
 * @v region		Patch region
 * @v data		Data buffer
 * @v offset		Relative offset
 * @v len		Length
 * @ret rc		Return status code
 */
static int wim_patch_lookup_boot ( struct wim_patch *patch,
				   struct wim_patch_region *region __unused,
				   void *data, size_t offset, size_t len ) {
	struct wim_lookup_entry entry;

	/* Sanity checks */
	assert ( offset < sizeof ( entry ) );
	assert ( len <= ( sizeof ( entry ) - offset ) );

	/* Construct lookup table entry */
	memset ( &entry, 0, sizeof ( entry ) );
	memcpy ( &entry.resource, &patch->header.boot,
		 sizeof ( entry.resource ) );

	/* Copy lookup table entry */
	memcpy ( data, ( ( ( void * ) &entry ) + offset ), len );

	return 0;
}

/**
 * Patch injected file lookup table entry
 *
 * @v patch		WIM patch
 * @v region		Patch region
 * @v data		Data buffer
 * @v offset		Relative offset
 * @v len		Length
 * @ret rc		Return status code
 */
static int wim_patch_lookup_file ( struct wim_patch *patch __unused,
				   struct wim_patch_region *region,
				   void *data, size_t offset, size_t len ) {
	struct wim_patch_region *rfile = region->opaque;
	struct vdisk_file *vfile = rfile->opaque;
	struct wim_lookup_entry entry;

	/* Sanity checks */
	assert ( offset < sizeof ( entry ) );
	assert ( len <= ( sizeof ( entry ) - offset ) );

	/* Construct lookup table entry */
	memset ( &entry, 0, sizeof ( entry ) );
	entry.resource.offset = rfile->offset;
	entry.resource.len = vfile->len;
	entry.resource.zlen__flags = entry.resource.len;
	entry.refcnt = 1;
	wim_hash ( vfile, &entry.hash );

	/* Copy lookup table entry */
	memcpy ( data, ( ( ( void * ) &entry ) + offset ), len );
	DBG2 ( "...patched WIM %s %s\n", region->name, vfile->name );

	return 0;
}

/**
 * Patch uncompressed copy of original boot metadata
 *
 * @v patch		WIM patch
 * @v region		Patch region
 * @v data		Data buffer
 * @v offset		Relative offset
 * @v len		Length
 * @ret rc		Return status code
 */
static int wim_patch_boot_copy ( struct wim_patch *patch,
				 struct wim_patch_region *region __unused,
				 void *data, size_t offset, size_t len ) {
	int rc;

	/* Read original boot metadata */
	if ( ( rc = wim_read ( patch->file, &patch->header, &patch->boot,
			       data, offset, len ) ) != 0 )
		return rc;

	return 0;
}

/**
 * Patch subdirectory offset within parent directory entry
 *
 * @v patch		WIM patch
 * @v region		Patch region
 * @v data		Data buffer
 * @v offset		Relative offset
 * @v len		Length
 * @ret rc		Return status code
 */
static int wim_patch_dir_subdir ( struct wim_patch *patch,
				  struct wim_patch_region *region,
				  void *data, size_t offset, size_t len ) {
	struct wim_patch_dir *dir = region->opaque;
	uint64_t subdir = dir->subdir;

	/* Sanity checks */
	assert ( offset < sizeof ( subdir ) );
	assert ( len <= ( sizeof ( subdir ) - offset ) );

	/* Copy subdirectory offset */
	memcpy ( data, ( ( ( void * ) &subdir ) + offset ), len );
	DBG2 ( "...patched WIM %s %s %#llx\n", region->name, dir->name,
	       ( patch->header.boot.offset + subdir ) );

	return 0;
}

/**
 * Patch copy of original directory entries
 *
 * @v patch		WIM patch
 * @v region		Patch region
 * @v data		Data buffer
 * @v offset		Relative offset
 * @v len		Length
 * @ret rc		Return status code
 */
static int wim_patch_dir_copy ( struct wim_patch *patch,
				struct wim_patch_region *region,
				void *data, size_t offset, size_t len ) {
	struct wim_patch_dir *dir = region->opaque;
	int rc;

	/* Read portion of original boot metadata */
	if ( ( rc = wim_read ( patch->file, &patch->header, &patch->boot,
			       data, ( dir->offset + offset ), len ) ) != 0 )
		return rc;

	return 0;
}

/**
 * Patch injected directory entries
 *
 * @v patch		WIM patch
 * @v region		Patch region
 * @v data		Data buffer
 * @v offset		Relative offset
 * @v len		Length
 * @ret rc		Return status code
 */
static int wim_patch_dir_file ( struct wim_patch *patch __unused,
				struct wim_patch_region *region,
				void *data, size_t offset, size_t len ) {
	struct wim_patch_region *rfile = region->opaque;
	struct vdisk_file *vfile = rfile->opaque;
	struct wim_patch_dir_entry entry;
	size_t name_len = strlen ( vfile->name );
	unsigned int i;

	/* Sanity checks */
	assert ( offset < sizeof ( entry ) );
	assert ( len <= ( sizeof ( entry ) - offset ) );

	/* Construct directory entry */
	memset ( &entry, 0, sizeof ( entry ) );
	entry.dir.len = wim_align ( sizeof ( entry ) );
	entry.dir.attributes = WIM_ATTR_NORMAL;
	entry.dir.security = WIM_NO_SECURITY;
	entry.dir.created = WIM_MAGIC_TIME;
	entry.dir.accessed = WIM_MAGIC_TIME;
	entry.dir.written = WIM_MAGIC_TIME;
	wim_hash ( vfile, &entry.dir.hash );
	entry.dir.name_len = ( name_len * sizeof ( entry.name[0] ) );
	for ( i = 0 ; i < name_len ; i++ )
		entry.name[i] = vfile->name[i];

	/* Copy directory entry */
	memcpy ( data, ( ( ( void * ) &entry ) + offset ), len );
	DBG2 ( "...patched WIM %s %s\n", region->name, vfile->name );

	return 0;
}

/**
 * Patch WIM region
 *
 * @v patch		WIM patch
 * @v region		Patch region
 * @v data		Data buffer
 * @v offset		Relative offset
 * @v len		Length
 * @ret rc		Return status code
 */
static int wim_patch_region ( struct wim_patch *patch,
			      struct wim_patch_region *region,
			      void *data, size_t offset, size_t len ) {
	size_t skip;
	int rc;

	/* Skip unused regions */
	if ( ! region->patch )
		return 0;

	/* Skip any data before this region */
	skip = ( ( region->offset > offset ) ?
		 ( region->offset - offset ) : 0 );
	if ( skip >= len )
		return 0;
	data += skip;
	offset += skip;
	len -= skip;

	/* Convert to relative offset within this region */
	offset -= region->offset;

	/* Skip any data after this region */
	if ( offset >= region->len )
		return 0;
	if ( len > ( region->len - offset ) )
		len = ( region->len - offset );

	/* Patch this region */
	if ( ( rc = region->patch ( patch, region, data, offset, len ) ) != 0 )
		return rc;
	DBG2 ( "...patched WIM %s at [%#zx,%#zx)\n", region->name,
	       ( region->offset + offset ), ( region->offset + offset + len ) );

	return 0;
}

/**
 * Construct patched WIM region
 *
 * @v region		Patched region to fill in
 * @v name		Name
 * @v opaque		Opaque data
 * @v offset		Offset
 * @v len		Length
 * @v patch		Patch method
 * @ret offset		Next offset
 */
static inline __attribute__ (( always_inline )) size_t
wim_construct_region ( struct wim_patch_region *region, const char *name,
		       void *opaque, size_t offset, size_t len,
		       int ( * patch ) ( struct wim_patch *patch,
					 struct wim_patch_region *region,
					 void *data, size_t offset,
					 size_t len ) ) {

	DBG ( "...patching WIM %s at [%#zx,%#zx)\n",
	      name, offset, ( offset + len ) );
	region->name = name;
	region->opaque = opaque;
	region->offset = offset;
	region->len = len;
	region->patch = patch;
	return ( offset + len );
}

/**
 * Construct patch WIM directory regions
 *
 * @v patch		WIM patch
 * @v dir		Patched directory
 * @v offset		Offset
 * @v regions		Patched directory regions to fill in
 * @ret offset		Next offset
 */
static size_t wim_construct_dir ( struct wim_patch *patch,
				  struct wim_patch_dir *dir, size_t offset,
				  struct wim_patch_dir_regions *regions ) {
	struct wim_patch_dir_entry *entry;
	struct wim_patch_region *rfile;
	size_t boot_offset = patch->header.boot.offset;
	unsigned int i;

	DBG ( "...patching WIM directory at %#zx from [%#zx,%#zx)\n",
	      ( boot_offset + dir->parent ), ( boot_offset + dir->offset ),
	      ( boot_offset + dir->offset + dir->len ) );

	/* Align directory entries */
	offset = wim_align ( offset );
	dir->subdir = ( offset - patch->header.boot.offset );

	/* Construct injected file directory entries */
	for ( i = 0 ; i < VDISK_MAX_FILES ; i++ ) {
		rfile = &patch->regions.file[i];
		if ( ! rfile->patch )
			continue;
		offset = wim_construct_region ( &regions->file[i], "dir.file",
						rfile, offset,
						sizeof ( *entry ),
						wim_patch_dir_file );
		offset = wim_align ( offset );
	}

	/* Construct copy of original directory entries */
	offset = wim_construct_region ( &regions->copy, dir->name, dir, offset,
					dir->len, wim_patch_dir_copy );

	/* Allow space for directory terminator */
	offset += sizeof ( entry->dir.len );

	/* Construct subdirectory offset within parent directory entry */
	wim_construct_region ( &regions->subdir, "dir.subdir", dir,
			       ( boot_offset + dir->parent +
				 offsetof ( typeof ( entry->dir ), subdir ) ),
			       sizeof ( entry->dir.subdir ),
			       wim_patch_dir_subdir );

	return offset;
}

/**
 * Construct WIM patch
 *
 * @v file		Virtual file
 * @v boot_index	New boot index (or zero)
 * @v inject		Inject files into WIM
 * @v patch		Patch to fill in
 * @ret rc		Return status code
 */
static int wim_construct_patch ( struct vdisk_file *file,
				 unsigned int boot_index, int inject,
				 struct wim_patch *patch ) {
	union wim_patch_regions *regions = &patch->regions;
	struct wim_patch_region *rfile;
	struct wim_resource_header *lookup;
	struct wim_resource_header *boot;
	struct wim_directory_entry direntry;
	struct wim_lookup_entry *entry;
	struct vdisk_file *vfile;
	size_t offset;
	unsigned int injected = 0;
	unsigned int i;
	int rc;

	/* Initialise patch */
	memset ( patch, 0, sizeof ( *patch ) );
	patch->file = file;
	DBG ( "...patching WIM %s\n", file->name );

	/* Reset file length */
	file->xlen = file->len;
	offset = file->len;

	/* Read WIM header */
	if ( ( rc = wim_header ( file, &patch->header ) ) != 0 )
		return rc;
	lookup = &patch->header.lookup;
	boot = &patch->header.boot;

	/* Patch header within original image body */
	wim_construct_region ( &regions->header, "header", NULL, 0,
			       sizeof ( patch->header ), wim_patch_header );

	/* Record original lookup table */
	memcpy ( &patch->lookup, lookup, sizeof ( patch->lookup ) );

	/* Record original metadata for selected boot image (which may
	 * not be the originally selected boot image).
	 */
	if ( ( rc = wim_metadata ( file, &patch->header, boot_index,
				   &patch->boot ) ) != 0 )
		return rc;

	/* Update boot image metadata in patched header, if applicable */
	if ( boot_index )
		memcpy ( boot, &patch->boot, sizeof ( *boot ) );

	/* Record original boot index */
	patch->boot_index = patch->header.boot_index;

	/* Update boot index in patched header, if applicable */
	if ( boot_index )
		patch->header.boot_index = boot_index;

	/* Do nothing more if injection is disabled */
	if ( ! inject )
		return 0;

	/* Construct injected files */
	for ( i = 0 ; i < VDISK_MAX_FILES ; i++ ) {
		vfile = &vdisk_files[i];
		if ( ! wim_inject_file ( vfile ) )
			continue;
		offset = wim_construct_region ( &regions->file[i], vfile->name,
						vfile, offset, vfile->len,
						wim_patch_file );
		injected++;
	}

	/* Do nothing more if no files are injected */
	if ( injected == 0 )
		return 0;

	/* Calculate boot index for injected image */
	if ( ( rc = wim_count ( file, &patch->header, &boot_index ) ) != 0 )
		return rc;
	patch->header.images = ( boot_index + 1 );
	patch->header.boot_index = patch->header.images;

	/* Construct injected lookup table */
	lookup->offset = offset = wim_align ( offset );
	offset = wim_construct_region ( &regions->lookup.copy, "lookup.copy",
					NULL, offset, patch->lookup.len,
					wim_patch_lookup_copy );
	offset = wim_construct_region ( &regions->lookup.boot, "lookup.boot",
					NULL, offset, sizeof ( *entry ),
					wim_patch_lookup_boot );
	for ( i = 0 ; i < VDISK_MAX_FILES ; i++ ) {
		rfile = &regions->file[i];
		if ( ! rfile->patch )
			continue;
		offset = wim_construct_region ( &regions->lookup.file[i],
						"lookup.file", rfile,
						offset, sizeof ( *entry ),
						wim_patch_lookup_file );
	}
	lookup->offset = regions->lookup.copy.offset;
	lookup->len = ( offset - lookup->offset );
	lookup->zlen__flags = lookup->len;

	/* Locate directory containing injected files */
	patch->dir.name = WIM_INJECT_DIR;
	if ( ( rc = wim_path ( file, &patch->header, &patch->boot,
			       L(WIM_INJECT_DIR), &patch->dir.parent,
			       &direntry ) ) != 0 )
		return rc;
	patch->dir.offset = direntry.subdir;
	if ( ( rc = wim_dir_len ( file, &patch->header, &patch->boot,
				  patch->dir.offset, &patch->dir.len ) ) != 0 )
		return rc;

	/* Construct injected boot image metadata */
	boot->offset = offset = wim_align ( offset );
	offset = wim_construct_region ( &regions->boot.copy, "boot.copy",
					NULL, offset, patch->boot.len,
					wim_patch_boot_copy );
	offset = wim_construct_dir ( patch, &patch->dir, offset,
				     &regions->boot.dir );
	boot->len = ( offset - boot->offset );
	boot->zlen__flags = ( boot->len | WIM_RESHDR_METADATA );

	/* Record patched length */
	file->xlen = offset;
	DBG ( "...patching WIM length %#zx->%#zx\n", file->len, file->xlen );

	return 0;
}

/**
 * Patch WIM file
 *
 * @v file		Virtual file
 * @v data		Data buffer
 * @v offset		Offset
 * @v len		Length
 */
void patch_wim ( struct vdisk_file *file, void *data,
				 size_t offset, size_t len ) {
	static struct wim_patch cached_patch;
	struct wim_patch *patch = &cached_patch;
	struct wim_patch_region *region;
	unsigned int boot_index;
	unsigned int i;
	int inject;
	int rc;

	/* Do nothing unless patching is required */
	boot_index = nt_cmdline->index;
	inject = ( ! nt_cmdline->rawwim );
	if ( ( boot_index == 0 ) && ( ! inject ) )
		return;

	/* Update cached patch if required */
	if ( file != patch->file ) {
		if ( ( rc = wim_construct_patch ( file, boot_index, inject,
						  patch ) ) != 0 ) {
			die ( "Could not patch WIM %s\n", file->name );
		}
	}
	patch = &cached_patch;

	/* Patch regions */
	for ( i = 0 ; i < ( sizeof ( patch->regions ) /
			    sizeof ( patch->regions.region[0] ) ) ; i++ ) {
		region = &patch->regions.region[i];
		if ( ( rc = wim_patch_region ( patch, region, data, offset,
					       len ) ) != 0 ) {
			die ( "Could not patch WIM %s %s at [%#zx,%#zx)\n",
			      file->name, region->name, offset,
			      ( offset + len ) );
		}
	}
}

#define BCD_SUFFIX_EXE L".exe"
#define BCD_SUFFIX_EFI L".efi"

#ifdef BIOS
#define BCD_SEARCH BCD_SUFFIX_EFI
#define BCD_REPLACE BCD_SUFFIX_EXE
#else
#define BCD_SEARCH BCD_SUFFIX_EXE
#define BCD_REPLACE BCD_SUFFIX_EFI
#endif

/**
 * Patch BCD file
 *
 * @v vfile		Virtual file
 * @v data		Data buffer
 * @v offset		Offset
 * @v len		Length
 */
void patch_bcd ( struct vdisk_file *vfile __unused, void *data,
				 size_t offset, size_t len ) {
	static const wchar_t search[] = BCD_SEARCH;
	static const wchar_t replace[] = BCD_REPLACE;
	size_t i;

	/* Patch any occurrences of ".exe" to ".efi".  In the common
	 * simple cases, this allows the same BCD file to be used for
	 * both BIOS and UEFI systems.
	 */
	for ( i = 0 ; ( i + sizeof ( search ) ) < len ; i++ ) {
		if ( wcscasecmp ( ( data + i ), search ) == 0 ) {
			memcpy ( ( data + i ), replace, sizeof ( replace ) );
			DBG ( "...patched BCD at %#zx: \"%ls\" to \"%ls\"\n",
				  ( offset + i ), search, replace );
		}
	}
}
