Skip to content

Commit c88b665

Browse files
authored
Merge pull request #43 from Zero3K20/copilot/fix-backup-convert-to-vhd
Fix VHD backup: write MBR partition table entry so disk mounts without "Initialize Disk" prompt
2 parents aae6ed8 + 5cd60ea commit c88b665

2 files changed

Lines changed: 305 additions & 4 deletions

File tree

eram.c

Lines changed: 274 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

16081764
VOID 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

17011873
BOOLEAN 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

31973414
VOID 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

Comments
 (0)