Home PC Virtualized
One of my home PCs has crossed the Singularity. I had an old Windows PC from 2003 that I've kept around because it has lots of important stuff on it, including node-locked commercial software. This weekend I duplicated it into a VMWare image and successfully booted it up inside a newer workstation. I don't need the original physical PC anymore, so after taking out the hard drives I'm carting it off to Google's recycling station.
I wish I'd done this since I was a kid. I could have mirrored all my old Apple II data onto a few Macintosh disks and continued to run Drol and Star Blazer inside an emulator, and then when I lost the Mac faith back in 1997 I could have migrated to whatever Mac-on-Windows emulator must have existed back then. True, there's probably little practical value to reading my Magic Window-formatted book reports from seventh grade, but the geek value is enormous.
Mirroring the PC would probably have been easy with VMWare Converter, but I didn't take that path at first. I was cheaping out and going with free Microsoft Virtual PC 2007, which meant I had to create the drive image myself. Here are the steps I took:
- Move all "live" data (stuff I could see myself accessing more than once a decade) onto a file server.
- Uninstall soon-to-be inapplicable software, such as video drivers and CD/DVD authoring applications.
- Defragment the hard drive.
- Boot into a live CD and run GParted to shrink the drive partition as much as possible, leaving just a bit of room so that Windows can still comfortably run.
- Copy the drive partition to a file on a second drive using a command roughly like this:
dd if=/dev/hda1 of=/mnt/other_drive/hda1.img bs=1M - Also copy the MBR and other fluff:
dd if=/dev/hda of=/mnt/other_drive/hda_header.img count=63(I determined the 63 value by using GParted to figue out where Partition 1 started) - Boot back into Windows and assemble the two parts into a single .vhd file. In retrospect, it would have been much easier to calculate the point where hda1 ended and just copy from block 0 all the way to there, but I was learning as I went.
- Try to trick Virtual PC into accepting the new file as a hard drive. After no luck, do a bit of research on the web to find out the format of the last sector in a .vhd. Write a quick C program to generate it (reprinted at end).
- Tack on the final sector. Confirm that Virtual PC now lets me create a virtual machine using this as the drive.
- Boot the virtual PC. See that it hangs on mup.sys. After finding on the web that a mup.sys hang is a symptom of any of the top 1,000 possible things that could go wrong with your computer, give up on Virtual PC and try VMWare Workstation.
- Learn that my particular PC is actually hanging on sptd.sys. Delete that. Try again. Success.
- Now fire up VMWare Converter. Import the working .vhd into VMWare. Clean up the PC and shut it down.
- 7zip the new VMWare machine into a .7z file (getting about 50% compression!). Drop the .7z file onto a file server for safekeeping.
- All done!
Here's the code to generate the .vhd footer for a fixed-size drive (though if you read my instructions start to finish you'll just do what I wish I'd done and avoid Virtual PC):
#include <windows.h> #include <objbase.h>typedef struct {
unsigned char cookie[8];
unsigned char features[4];
unsigned char file_format_version[4];
unsigned char data_offset[8];
unsigned char time_stamp[4];
unsigned char creator_application[4];
unsigned char creator_version[4];
unsigned char creator_host_os[4];
unsigned char original_size[8];
unsigned char current_size[8];
unsigned char disk_geometry[4];
unsigned char disk_type[4];
unsigned char checksum[4];
unsigned char unique_id[16];
unsigned char saved_state[1];
unsigned char reserved[427];
} hard_disk_footer;void write_int16(unsigned char *p, unsigned short val) {
*p++ = (unsigned char)(val>>8);
*p++ = (unsigned char)val;
}void write_int32(unsigned char *p, unsigned int val) {
*p++ = (unsigned char)(val>>24);
*p++ = (unsigned char)(val>>16);
*p++ = (unsigned char)(val>>8);
*p++ = (unsigned char)val;
}void write_int64(unsigned char *p, ULONGLONG val) {
*p++ = (unsigned char)(val>>56);
*p++ = (unsigned char)(val>>48);
*p++ = (unsigned char)(val>>40);
*p++ = (unsigned char)(val>>32);
*p++ = (unsigned char)(val>>24);
*p++ = (unsigned char)(val>>16);
*p++ = (unsigned char)(val>>8);
*p++ = (unsigned char)val;
}int _tmain(int argc, _TCHAR* argv[])
{
hard_disk_footer hdf = { 0 };memcpy(hdf.cookie, "conectix", 8);
write_int32(hdf.features, 2);
write_int32(hdf.file_format_version, 0x00010000);
write_int32(&hdf.data_offset[4], 0xFFFFFFFF);
memcpy(hdf.creator_application, "vpc ", 4);unsigned int totalSectors = 62914560; // 30GB
unsigned int sectorsPerTrack;
unsigned int heads;
unsigned int cylinderTimesHeads;
unsigned int cylinders;if (totalSectors > 65535 * 16 * 255) {
totalSectors = 65535 * 16 * 255;
}
if (totalSectors >= 65535 * 16 * 63) {
sectorsPerTrack = 255;
heads = 16;
cylinderTimesHeads = totalSectors / sectorsPerTrack;
} else {
sectorsPerTrack = 17;
cylinderTimesHeads = totalSectors / sectorsPerTrack;
heads = (cylinderTimesHeads + 1023) / 1024;
if (heads < 4) {
heads = 4;
}
if (cylinderTimesHeads >= (heads * 1024) || heads > 16) {
sectorsPerTrack = 31;
heads = 16;
cylinderTimesHeads = totalSectors / sectorsPerTrack;
}
if (cylinderTimesHeads >= (heads * 1024)) {
sectorsPerTrack = 63;
heads = 16;
cylinderTimesHeads = totalSectors / sectorsPerTrack;
}
}
cylinders = cylinderTimesHeads / heads;write_int16(hdf.disk_geometry, cylinders);
hdf.disk_geometry[2] = (unsigned char)heads;
hdf.disk_geometry[3] = (unsigned char)sectorsPerTrack;write_int32(hdf.disk_type, 2); // fixed
ULONGLONG totalSize = UInt32x32To64(totalSectors, 512);
write_int64(hdf.current_size, totalSize);CoCreateGuid((GUID*)&hdf.unique_id);
unsigned int checksum = 0;
write_int32(hdf.checksum, 0);
unsigned int counter;
unsigned char *p = (unsigned char *)&hdf;
for (counter = 0 ; counter < sizeof(hard_disk_footer); counter++) {
checksum += p[counter];
}
write_int32(hdf.checksum, ~checksum);FILE *f = fopen("e:\\hdr.out", "wb");
fwrite(&hdf, 1, sizeof(hard_disk_footer), f);
return 0;
}

Leave a comment