@@ -1594,6 +1594,159 @@ NTSTATUS EramShutdown(
15941594}
15951595
15961596
1597+ /* VhdSwap32
1598+ Reverse the byte order of a 32-bit value for big-endian storage.
1599+ */
1600+ static ULONG VhdSwap32 (ULONG x )
1601+ {
1602+ return ((x & 0xFFUL ) << 24 )
1603+ | ((x & 0xFF00UL ) << 8 )
1604+ | ((x & 0xFF0000UL ) >> 8 )
1605+ | ((x >> 24 ) & 0xFFUL );
1606+ }
1607+
1608+ /* VhdSwap64
1609+ Reverse the byte order of a 64-bit value for big-endian storage.
1610+ */
1611+ static ULONGLONG VhdSwap64 (ULONGLONG x )
1612+ {
1613+ return ((ULONGLONG )VhdSwap32 ((ULONG )(x & 0xFFFFFFFFULL )) << 32 )
1614+ | (ULONGLONG )VhdSwap32 ((ULONG )(x >> 32 ));
1615+ }
1616+
1617+ /* VhdComputeGeometry
1618+ Compute the 4-byte packed CHS geometry field (big-endian) for a VHD
1619+ of the given byte size using the Microsoft-specified algorithm.
1620+ */
1621+ static ULONG VhdComputeGeometry (ULONGLONG diskSize )
1622+ {
1623+ ULONG totalSectors , spt , heads , cylTimesHeads , cyls ;
1624+ /* Convert bytes to 512-byte sectors; cap to avoid overflow */
1625+ if (diskSize / 512 > 0xFFFFFFFFULL )
1626+ totalSectors = 0xFFFFFFFFUL ;
1627+ else
1628+ totalSectors = (ULONG )(diskSize / 512 );
1629+ if (totalSectors > 65535UL * 16UL * 255UL )
1630+ totalSectors = 65535UL * 16UL * 255UL ;
1631+ if (totalSectors >= 65535UL * 16UL * 63UL ) {
1632+ spt = 255 ;
1633+ heads = 16 ;
1634+ cylTimesHeads = totalSectors / spt ;
1635+ } else {
1636+ spt = 17 ;
1637+ cylTimesHeads = totalSectors / spt ;
1638+ heads = (cylTimesHeads + 1023 ) / 1024 ;
1639+ if (heads < 4 )
1640+ heads = 4 ;
1641+ if (cylTimesHeads >= (ULONG )heads * 1024 || heads > 16 ) {
1642+ spt = 31 ;
1643+ heads = 16 ;
1644+ cylTimesHeads = totalSectors / spt ;
1645+ }
1646+ if (cylTimesHeads >= (ULONG )heads * 1024 ) {
1647+ spt = 63 ;
1648+ heads = 16 ;
1649+ cylTimesHeads = totalSectors / spt ;
1650+ }
1651+ }
1652+ cyls = cylTimesHeads / heads ;
1653+ if (cyls > 65535 )
1654+ cyls = 65535 ;
1655+ /* Return big-endian: cylinders (16-bit) | heads (8-bit) | spt (8-bit) */
1656+ return VhdSwap32 (((ULONG )(cyls & 0xFFFF ) << 16 )
1657+ | ((ULONG )(heads & 0xFF ) << 8 )
1658+ | (ULONG )(spt & 0xFF ));
1659+ }
1660+
1661+ /* VhdBuildFooter
1662+ Fill the 512-byte buffer pointed to by pFooter with the VHD fixed-disk
1663+ footer for a disk of diskSize bytes. The footer must be written at the
1664+ end of the file, immediately after the raw disk data.
1665+ */
1666+ static VOID VhdBuildFooter (PBYTE pFooter , ULONGLONG diskSize )
1667+ {
1668+ LARGE_INTEGER systemTime ;
1669+ LONGLONG vhdTimestamp ;
1670+ ULONG checksum ;
1671+ ULONG i ;
1672+ ULONGLONG swapped64 ;
1673+ ULONG swapped32 ;
1674+
1675+ /* Zero the entire footer first */
1676+ RtlZeroBytes (pFooter , VHD_FOOTER_SIZE );
1677+
1678+ /* Cookie: "conectix" */
1679+ pFooter [0 ]= 'c' ; pFooter [1 ]= 'o' ; pFooter [2 ]= 'n' ; pFooter [3 ]= 'e' ;
1680+ pFooter [4 ]= 'c' ; pFooter [5 ]= 't' ; pFooter [6 ]= 'i' ; pFooter [7 ]= 'x' ;
1681+
1682+ /* Features: 0x00000002 (big-endian) */
1683+ pFooter [8 ]= 0x00 ; pFooter [9 ]= 0x00 ; pFooter [10 ]= 0x00 ; pFooter [11 ]= 0x02 ;
1684+
1685+ /* File Format Version: 0x00010000 (big-endian) */
1686+ pFooter [12 ]= 0x00 ; pFooter [13 ]= 0x01 ; pFooter [14 ]= 0x00 ; pFooter [15 ]= 0x00 ;
1687+
1688+ /* Data Offset: 0xFFFFFFFFFFFFFFFF (fixed disk, big-endian) */
1689+ pFooter [16 ]= 0xFF ; pFooter [17 ]= 0xFF ; pFooter [18 ]= 0xFF ; pFooter [19 ]= 0xFF ;
1690+ pFooter [20 ]= 0xFF ; pFooter [21 ]= 0xFF ; pFooter [22 ]= 0xFF ; pFooter [23 ]= 0xFF ;
1691+
1692+ /* Time Stamp: seconds since 2000-01-01 00:00:00 UTC (big-endian)
1693+ Windows FILETIME epoch is 1601-01-01; subtract the offset defined in
1694+ VHD_EPOCH_FILETIME_OFFSET to convert to seconds from the VHD epoch. */
1695+ KeQuerySystemTime (& systemTime );
1696+ vhdTimestamp = (systemTime .QuadPart - VHD_EPOCH_FILETIME_OFFSET ) / 10000000LL ;
1697+ if (vhdTimestamp < 0 ) vhdTimestamp = 0 ;
1698+ if (vhdTimestamp > 0xFFFFFFFFLL ) vhdTimestamp = 0xFFFFFFFFLL ;
1699+ swapped32 = VhdSwap32 ((ULONG )vhdTimestamp );
1700+ RtlCopyBytes (pFooter + 24 , & swapped32 , 4 );
1701+
1702+ /* Creator Application: "win " */
1703+ pFooter [28 ]= 'w' ; pFooter [29 ]= 'i' ; pFooter [30 ]= 'n' ; pFooter [31 ]= ' ' ;
1704+
1705+ /* Creator Version: 0x000A0000 (big-endian) */
1706+ pFooter [32 ]= 0x00 ; pFooter [33 ]= 0x0A ; pFooter [34 ]= 0x00 ; pFooter [35 ]= 0x00 ;
1707+
1708+ /* Creator Host OS: "Wi2k" */
1709+ pFooter [36 ]= 'W' ; pFooter [37 ]= 'i' ; pFooter [38 ]= '2' ; pFooter [39 ]= 'k' ;
1710+
1711+ /* Original Size (big-endian) */
1712+ swapped64 = VhdSwap64 (diskSize );
1713+ RtlCopyBytes (pFooter + 40 , & swapped64 , 8 );
1714+
1715+ /* Current Size (big-endian) */
1716+ RtlCopyBytes (pFooter + 48 , & swapped64 , 8 );
1717+
1718+ /* Disk Geometry (packed CHS, big-endian) */
1719+ swapped32 = VhdComputeGeometry (diskSize );
1720+ RtlCopyBytes (pFooter + 56 , & swapped32 , 4 );
1721+
1722+ /* Disk Type: 2 = fixed (big-endian) */
1723+ pFooter [60 ]= 0x00 ; pFooter [61 ]= 0x00 ; pFooter [62 ]= 0x00 ; pFooter [63 ]= 0x02 ;
1724+
1725+ /* Checksum at offset 64 is zero for now; UniqueId derived from disk size */
1726+ pFooter [68 ] = (BYTE )(diskSize >> 56 );
1727+ pFooter [69 ] = (BYTE )(diskSize >> 48 );
1728+ pFooter [70 ] = (BYTE )(diskSize >> 40 );
1729+ pFooter [71 ] = (BYTE )(diskSize >> 32 );
1730+ pFooter [72 ] = (BYTE )(diskSize >> 24 );
1731+ pFooter [73 ] = (BYTE )(diskSize >> 16 );
1732+ pFooter [74 ] = (BYTE )(diskSize >> 8 );
1733+ pFooter [75 ] = (BYTE )(diskSize );
1734+ /* Remaining 8 bytes of UniqueId: "ERAM" + 4 zeros (already zero) */
1735+ pFooter [76 ]= 'E' ; pFooter [77 ]= 'R' ; pFooter [78 ]= 'A' ; pFooter [79 ]= 'M' ;
1736+
1737+ /* Saved State: 0 (already zero); Reserved: zeros (already zero) */
1738+
1739+ /* Compute checksum: one's complement of the 32-bit sum of all footer bytes
1740+ with the checksum field treated as zero (it already is zero). */
1741+ checksum = 0 ;
1742+ for (i = 0 ; i < VHD_FOOTER_SIZE ; i ++ )
1743+ checksum += pFooter [i ];
1744+ checksum = ~checksum ;
1745+ swapped32 = VhdSwap32 (checksum );
1746+ RtlCopyBytes (pFooter + 64 , & swapped32 , 4 );
1747+ }
1748+
1749+
15971750/* EramBackupDisk
15981751 Write the RAM disk contents to the backup file.
15991752 Parameters
@@ -1603,6 +1756,9 @@ NTSTATUS EramShutdown(
16031756 Notes
16041757 Only backs up OS-managed memory (paged/non-paged pool).
16051758 External (OS-unmanaged) memory backup is not supported.
1759+ The backup is written in VHD fixed-disk format: raw disk data followed
1760+ by a 512-byte VHD footer. The resulting file can be mounted directly
1761+ as a .vhd in Windows Disk Management or Hyper-V.
16061762*/
16071763
16081764VOID EramBackupDisk (
@@ -1683,6 +1839,19 @@ VOID EramBackupDisk(
16831839 ByteOffset .QuadPart += uChunkSize ;
16841840 uRemain -= uChunkSize ;
16851841 }
1842+ if (ntStat == STATUS_SUCCESS )
1843+ {
1844+ /* Append the 512-byte VHD fixed-disk footer so the file can be mounted
1845+ directly as a .vhd by Windows Disk Management or Hyper-V. */
1846+ BYTE VhdFooter [VHD_FOOTER_SIZE ];
1847+ VhdBuildFooter (VhdFooter , (ULONGLONG )uTotalSize );
1848+ ntStat = ZwWriteFile (hFile , NULL , NULL , NULL , & IoStat , VhdFooter , VHD_FOOTER_SIZE , & ByteOffset , NULL );
1849+ if (ntStat != STATUS_SUCCESS )
1850+ {
1851+ KdPrint (("EramBackupDisk: ZwWriteFile(VHD footer) failed 0x%x\n" , ntStat ));
1852+ EramReportEvent (pEramExt -> pDevObj , ERAM_ERROR_FUNCTIONERROR , "EramBackupDisk:VHD footer" );
1853+ }
1854+ }
16861855 ZwClose (hFile );
16871856 KdPrint (("EramBackupDisk end\n" ));
16881857}
@@ -1696,6 +1865,9 @@ VOID EramBackupDisk(
16961865 TRUE if restored successfully, FALSE otherwise (no file, size mismatch, I/O error).
16971866 Notes
16981867 Only restores to OS-managed memory (paged/non-paged pool).
1868+ Accepts both the legacy raw format (file size == disk size) and the
1869+ current VHD fixed-disk format (file size == disk size + VHD_FOOTER_SIZE).
1870+ Only the raw disk data is read; the VHD footer is ignored on restore.
16991871*/
17001872
17011873BOOLEAN EramRestoreDisk (
@@ -1764,16 +1936,27 @@ BOOLEAN EramRestoreDisk(
17641936 ZwClose (hFile );
17651937 return FALSE;
17661938 }
1767- /* Verify backup file size matches current disk size */
1939+ /* Accept both raw (legacy) and VHD fixed-disk formats.
1940+ Raw: file size == disk size (no footer)
1941+ VHD: file size == disk size + 512 (VHD footer at end)
1942+ In both cases we read exactly uTotalSize bytes starting at offset 0. */
17681943 uTotalSize = (SIZE_T )pEramExt -> uSizeTotal << PAGE_SIZE_LOG2 ;
1769- if ((SIZE_T )FileInfo .EndOfFile .QuadPart != uTotalSize )
1944+ if ((SIZE_T )FileInfo .EndOfFile .QuadPart == uTotalSize )
1945+ {
1946+ KdPrint (("EramRestoreDisk: raw format detected\n" ));
1947+ }
1948+ else if ((SIZE_T )FileInfo .EndOfFile .QuadPart == uTotalSize + VHD_FOOTER_SIZE )
1949+ {
1950+ KdPrint (("EramRestoreDisk: VHD format detected, footer will be ignored\n" ));
1951+ }
1952+ else
17701953 {
17711954 KdPrint (("EramRestoreDisk: size mismatch backup=%I64u disk=%lu\n" , FileInfo .EndOfFile .QuadPart , (ULONG )uTotalSize ));
17721955 EramReportEvent (pEramExt -> pDevObj , ERAM_ERROR_FUNCTIONERROR , "EramRestoreDisk:size mismatch" );
17731956 ZwClose (hFile );
17741957 return FALSE;
17751958 }
1776- /* Read backup into RAM disk memory in chunks */
1959+ /* Read backup into RAM disk memory in chunks (only disk data, not VHD footer) */
17771960 pBuf = pEramExt -> pPageBase ;
17781961 ByteOffset .QuadPart = 0 ;
17791962 uRemain = uTotalSize ;
@@ -1851,6 +2034,11 @@ VOID EramSetCleanShutdown(
18512034 from a disk formatted by an older ERAM build that omitted this signature,
18522035 which causes Windows' FAT filesystem driver (fastfat.sys) to return
18532036 STATUS_UNRECOGNIZED_VOLUME and refuse to mount the restored volume.
2037+
2038+ Also repairs the MBR partition table entry at bytes 446-461. Without a
2039+ valid partition entry, Windows Disk Management prompts "Initialize Disk"
2040+ when the image is attached as a VHD. The entry is only written if
2041+ dwNumSectors is zero (absent), so a previously correct entry is preserved.
18542042 Parameters
18552043 pEramExt The pointer to an ERAM_EXTENTION structure.
18562044 Return Value
@@ -1861,6 +2049,8 @@ VOID EramRepairBootSector(
18612049 IN PERAM_EXTENSION pEramExt
18622050 )
18632051{
2052+ PBYTE pPart0 ;
2053+ ULONG dwNumSectors ;
18642054 KdPrint (("EramRepairBootSector start\n" ));
18652055 if (pEramExt -> pPageBase == NULL )
18662056 {
@@ -1874,6 +2064,29 @@ VOID EramRepairBootSector(
18742064 pEramExt -> pPageBase [511 ] = 0xAA ;
18752065 KdPrint (("EramRepairBootSector: boot signature written\n" ));
18762066 }
2067+ /* Repair the MBR partition table entry (bytes 446-461).
2068+ ERAM uses a superfloppy layout so the single partition covers the
2069+ entire disk starting at LBA 0. Only write the entry if it is absent
2070+ (dwNumSectors == 0), which is the case for backups made by older
2071+ ERAM builds that did not write a partition table. */
2072+ pPart0 = pEramExt -> pPageBase + 446 ;
2073+ RtlCopyBytes (& dwNumSectors , pPart0 + 12 , sizeof (ULONG ));
2074+ if (dwNumSectors == 0 && pEramExt -> uAllSector != 0 )
2075+ {
2076+ pPart0 [0 ] = 0x80 ; /* active/bootable */
2077+ pPart0 [1 ] = 0x00 ; /* first CHS: head 0 */
2078+ pPart0 [2 ] = 0x01 ; /* first CHS: sector 1 */
2079+ pPart0 [3 ] = 0x00 ; /* first CHS: cylinder 0 */
2080+ /* Partition type: 0x0C = FAT32 LBA; fall back to FAT_size for FAT12/16 */
2081+ pPart0 [4 ] = (pEramExt -> FAT_size == PARTITION_FAT32 ) ? 0x0C : pEramExt -> FAT_size ;
2082+ pPart0 [5 ] = 0xFE ; /* last CHS: use max (LBA mode) */
2083+ pPart0 [6 ] = 0xFF ;
2084+ pPart0 [7 ] = 0xFF ;
2085+ /* pPart0[8..11]: dwStartSector = 0 (already zero — no hidden sectors) */
2086+ RtlCopyBytes (pPart0 + 12 , & pEramExt -> uAllSector , sizeof (ULONG ));
2087+ KdPrint (("EramRepairBootSector: MBR partition entry written (%lu sectors)\n" ,
2088+ pEramExt -> uAllSector ));
2089+ }
18772090 KdPrint (("EramRepairBootSector end\n" ));
18782091}
18792092
@@ -3191,7 +3404,11 @@ VOID PrepareExtFileName(
31913404 Return Value
31923405 No return value.
31933406 Registry Parameter
3194- BackupFile Backup Image Filename.
3407+ BackupFile Backup drive root or full path.
3408+ If set to a drive letter and optional backslash (e.g. "C:" or "C:\"),
3409+ the driver automatically appends "\ramdisk.vhd" so the backup is
3410+ saved as <drive>:\ramdisk.vhd. A full path may also be given (e.g.
3411+ "C:\mybackup.vhd") and is used as-is.
31953412*/
31963413
31973414VOID PrepareBackupFileName (
@@ -3202,6 +3419,7 @@ VOID PrepareBackupFileName(
32023419 /* local variables */
32033420 static WCHAR wszDef [] = L"" ;
32043421 static WCHAR wszBackupStub [] = L"\\??\\" ;
3422+ static const WCHAR wszVhdSuffix [] = L"\\ramdisk.vhd" ;
32053423 RTL_QUERY_REGISTRY_TABLE ParamTable [2 ];
32063424 NTSTATUS ntStat ;
32073425 UNICODE_STRING UniBackupFile ;
@@ -3225,6 +3443,28 @@ VOID PrepareBackupFileName(
32253443 {
32263444 KdPrint (("Eram Warning:RtlQueryRegistryValues failed\n" ));
32273445 }
3446+ /* If BackupFile is just a drive letter (e.g. "C:" or "C:\"), automatically
3447+ append "\ramdisk.vhd" so the backup lands at the root of the chosen drive. */
3448+ if (UniBackupFile .Length >= 2 * sizeof (WCHAR ) &&
3449+ pEramExt -> wszBackupFileMain [1 ] == L':' )
3450+ {
3451+ /* Count significant characters (strip trailing backslashes) */
3452+ USHORT wchCount = UniBackupFile .Length / (USHORT )sizeof (WCHAR );
3453+ while (wchCount > 2 && pEramExt -> wszBackupFileMain [wchCount - 1 ] == L'\\' )
3454+ wchCount -- ;
3455+ if (wchCount == 2 ) /* only "X:" — no filename component */
3456+ {
3457+ /* Append "\ramdisk.vhd" plus its null terminator.
3458+ sizeof(wszVhdSuffix) covers the 13 wide chars: '\','r','a','m',
3459+ 'd','i','s','k','.','v','h','d','\0' = 26 bytes. */
3460+ ULONG suffixBytes = sizeof (wszVhdSuffix );
3461+ if ((ULONG )(2 * sizeof (WCHAR )) + suffixBytes <= (ULONG )UniBackupFile .MaximumLength )
3462+ {
3463+ RtlCopyBytes (pEramExt -> wszBackupFileMain + 2 ,
3464+ wszVhdSuffix , suffixBytes );
3465+ }
3466+ }
3467+ }
32283468 /* Copy the \\?\\ prefix into the prefix field */
32293469#pragma warning(disable : 4127)
32303470 ASSERT (sizeof (pEramExt -> wszBackupFile ) == (sizeof (wszBackupStub ) - sizeof (WCHAR )));
@@ -3748,6 +3988,22 @@ BOOLEAN EramMakeFAT(
37483988 /* End-of-sector marker required by Windows' FAT driver to recognise the volume */
37493989 pBootFat16 -> bsSig2 [0 ] = 0x55 ;
37503990 pBootFat16 -> bsSig2 [1 ] = 0xaa ;
3991+ /* Write MBR partition table entry into the reserved area of the FAT12/16
3992+ boot sector (bytes 446-461 fall inside byResv2[126] for FAT16).
3993+ This lets the image be mounted as a VHD without "Initialize Disk". */
3994+ {
3995+ PBYTE pPart0 = (PBYTE )pBootFat16 + 446 ;
3996+ pPart0 [0 ] = 0x80 ; /* active/bootable */
3997+ pPart0 [1 ] = 0x00 ; /* first CHS: head 0 */
3998+ pPart0 [2 ] = 0x01 ; /* first CHS: sector 1 */
3999+ pPart0 [3 ] = 0x00 ; /* first CHS: cylinder 0 */
4000+ pPart0 [4 ] = pEramExt -> FAT_size ; /* partition type from FAT_size */
4001+ pPart0 [5 ] = 0xFE ; /* last CHS: use max (LBA mode) */
4002+ pPart0 [6 ] = 0xFF ;
4003+ pPart0 [7 ] = 0xFF ;
4004+ /* dwStartSector = 0: partition begins at LBA 0 */
4005+ RtlCopyBytes (pPart0 + 12 , & pEramExt -> uAllSector , sizeof (ULONG ));
4006+ }
37514007 }
37524008 else /* FAT32 */
37534009 {
@@ -3762,6 +4018,20 @@ BOOLEAN EramMakeFAT(
37624018 /* End-of-sector marker required by Windows' FAT driver to recognise the volume */
37634019 pBootFat32 -> bsSig2 [0 ] = 0x55 ;
37644020 pBootFat32 -> bsSig2 [1 ] = 0xaa ;
4021+ /* Write MBR partition table entry.
4022+ ERAM uses a superfloppy layout (FAT boot sector at LBA 0, bsHiddenSecs=0).
4023+ Without a partition entry, Windows Disk Management prompts to "Initialize Disk"
4024+ when the image is attached as a VHD. Partition type 0x0C = FAT32 LBA. */
4025+ pBootFat32 -> Parts [0 ].byBootInd = 0x80 ; /* active/bootable */
4026+ pBootFat32 -> Parts [0 ].byFirstHead = 0x00 ; /* first CHS: head 0 */
4027+ pBootFat32 -> Parts [0 ].byFirstSector = 0x01 ; /* first CHS: sector 1 */
4028+ pBootFat32 -> Parts [0 ].byFirstTrack = 0x00 ; /* first CHS: cylinder 0 */
4029+ pBootFat32 -> Parts [0 ].byFileSystem = 0x0C ; /* FAT32 with LBA (INT 13h ext) */
4030+ pBootFat32 -> Parts [0 ].byLastHead = 0xFE ; /* last CHS: use max (LBA mode) */
4031+ pBootFat32 -> Parts [0 ].byLastSector = 0xFF ;
4032+ pBootFat32 -> Parts [0 ].byLastTrack = 0xFF ;
4033+ pBootFat32 -> Parts [0 ].dwStartSector = 0 ; /* partition starts at LBA 0 */
4034+ pBootFat32 -> Parts [0 ].dwNumSectors = pEramExt -> uAllSector ;
37654035 /* Write the FSINFO sector */
37664036 pFsInfoSector = (PFSINFO_SECTOR )((PBYTE )pBootFat32 + pBootFat32 -> BPB_fat32 .wFsInfoSector * SECTOR );
37674037 pFsInfoSector -> FSInfo_Sig = 0x41615252 ; /* RRaA */
0 commit comments