DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/ints/bios_memdisk.cpp
00001 /*
00002 *
00003 *  Copyright (c) 2018 Shane Krueger
00004 *
00005 *  This program is free software; you can redistribute it and/or modify
00006 *  it under the terms of the GNU General Public License as published by
00007 *  the Free Software Foundation; either version 2 of the License, or
00008 *  (at your option) any later version.
00009 *
00010 *  This program is distributed in the hope that it will be useful,
00011 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 *  GNU General Public License for more details.
00014 *
00015  *  You should have received a copy of the GNU General Public License along
00016  *  with this program; if not, write to the Free Software Foundation, Inc.,
00017  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "dosbox.h"
00021 #include "callback.h"
00022 #include "bios.h"
00023 #include "bios_disk.h"
00024 #include "regs.h"
00025 #include "mem.h"
00026 #include "dos_inc.h" /* for Drives[] */
00027 #include "../dos/drives.h"
00028 #include "mapper.h"
00029 
00030 /* imageDiskMemory simulates a hard drive image or floppy drive image in RAM
00031 *
00032 * It can be initialized as a floppy, using one of the predefined floppy
00033 *   geometries, or can be initialized as a hard drive, accepting either
00034 *   a size in kilobytes or a specific set of chs values to emulate
00035 * It will then split the image into 64k chunks that are allocated on-demand.
00036 * Initially the only RAM required is for the chunk map
00037 * The image is effectively intialized to all zeros, as each chunk is zeroed
00038 *   upon allocation
00039 * Writes of all zeros do not allocate memory if none has yet been assigned
00040 *
00041 */
00042 
00043 // Create a hard drive image of a specified size; automatically select c/h/s
00044 imageDiskMemory::imageDiskMemory(Bit32u imgSizeK) : imageDisk(ID_MEMORY) {
00045         //notes:
00046         //  this code always returns HARD DRIVES with 512 byte sectors
00047         //  the code will round up in case it cannot make an exact match
00048         //  it enforces a minimum drive size of 32kb, since a hard drive cannot be formatted as FAT12 with a smaller parition
00049         //  the code works properly throughout the range of a 32-bit unsigned integer, however:
00050         //    a) for drives requesting more than 8,225,280kb, the number of cylinders will exceed 1024
00051         //    b) for drives requesting ULONG_MAX kb, the drive it creates will be slightly larger than ULONG_MAX kb, due to rounding
00052     
00053         //BIOS and MBR c/h/s limits:      1024 / 255 /  63     8,225,280kb
00054         //ATA c/h/s limits:              65536 /  16 / 255   267,386,880kb
00055         //combined limits:                1024 /  16 /  63       516,096kb
00056 
00057         //since this is a virtual drive, we are not restricted to ATA limits, so go with BIOS/MBR limits
00058 
00059         //it targets c/h/s values as follows:
00060         //  minimum:                         4 /   1 /  16            32kb
00061         //  through:                      1024 /   1 /  16         8,192kb
00062         //  through:                      1024 /  16 /  16       131,072kb
00063         //  through:                      1024 /  16 /  63       516,096kb
00064         //  max FAT16 drive size:         1024 / 130 /  63     4,193,280kb
00065         //  through:                      1024 / 255 /  63     8,225,280kb
00066         //  maximum:                    534699 / 255 /  63             4tb
00067         
00068         //use 32kb as minimum drive size, since it is not possible to format a smaller drive that has 16 sectors
00069         if (imgSizeK < 32) imgSizeK = 32;
00070         //set the sector size to 512 bytes
00071         Bit32u sector_size = 512;
00072         //calculate the total number of sectors on the drive (imgSizeK * 2)
00073         Bit64u total_sectors = ((Bit64u)imgSizeK * 1024 + sector_size - 1) / sector_size;
00074 
00075         //calculate cylinders, sectors, and heads according to the targets above
00076         Bit32u sectors, heads, cylinders;
00077         if (total_sectors <= 1024 * 16 * 16) {
00078                 sectors = 16;
00079                 heads = (Bit32u)((total_sectors + (1024 * sectors - 1)) / (1024 * sectors));
00080                 cylinders = (Bit32u)((total_sectors + (sectors * heads - 1)) / (sectors * heads));
00081         }
00082         else if (total_sectors <= 1024 * 16 * 63) {
00083                 heads = 16;
00084                 sectors = (Bit32u)((total_sectors + (1024 * heads - 1)) / (1024 * heads));
00085                 cylinders = (Bit32u)((total_sectors + (sectors * heads - 1)) / (sectors * heads));
00086         }
00087         else if (total_sectors <= 1024 * 255 * 63) {
00088                 sectors = 63;
00089                 heads = (Bit32u)((total_sectors + (1024 * sectors - 1)) / (1024 * sectors));
00090                 cylinders = (Bit32u)((total_sectors + (sectors * heads - 1)) / (sectors * heads));
00091         }
00092         else {
00093                 sectors = 63;
00094                 heads = 255;
00095                 cylinders = (Bit32u)((total_sectors + (sectors * heads - 1)) / (sectors * heads));
00096         }
00097 
00098         LOG_MSG("Creating ramdrive as C/H/S %u/%u/%u with %u bytes/sector\n",
00099                 (unsigned int)cylinders, (unsigned int)heads, (unsigned int)sectors, (unsigned int)sector_size);
00100 
00101         diskGeo diskParams;
00102         diskParams.secttrack = sectors;
00103         diskParams.cylcount = cylinders;
00104         diskParams.headscyl = heads;
00105         diskParams.bytespersect = sector_size;
00106         diskParams.biosval = 0;
00107         diskParams.ksize = imgSizeK;
00108         diskParams.mediaid = 0xF0;
00109         diskParams.rootentries = 512;
00110         diskParams.biosval = 0;
00111         diskParams.sectcluster = 1;
00112         init(diskParams, true, 0);
00113 }
00114 
00115 // Create a floppy image of a specified geometry
00116 imageDiskMemory::imageDiskMemory(const diskGeo& floppyGeometry) : imageDisk(ID_MEMORY) {
00117         init(floppyGeometry, false, 0);
00118 }
00119 
00120 // Create a hard drive image of a specified geometry
00121 imageDiskMemory::imageDiskMemory(Bit16u cylinders, Bit16u heads, Bit16u sectors, Bit16u sector_size) : imageDisk(ID_MEMORY) {
00122         diskGeo diskParams;
00123         diskParams.secttrack = sectors;
00124         diskParams.cylcount = cylinders;
00125         diskParams.headscyl = heads;
00126         diskParams.bytespersect = sector_size;
00127         diskParams.biosval = 0;
00128         diskParams.ksize = 0;
00129         diskParams.mediaid = 0xF0;
00130         diskParams.rootentries = 512;
00131         diskParams.biosval = 0;
00132         diskParams.sectcluster = 1;
00133         init(diskParams, true, 0);
00134 }
00135 
00136 // Create a copy-on-write memory image of an existing image
00137 imageDiskMemory::imageDiskMemory(imageDisk* underlyingImage) : imageDisk(ID_MEMORY) {
00138         diskGeo diskParams;
00139         Bit32u heads, cylinders, sectors, bytesPerSector;
00140         underlyingImage->Get_Geometry(&heads, &cylinders, &sectors, &bytesPerSector);
00141         diskParams.headscyl = (Bit16u)heads;
00142         diskParams.cylcount = (Bit16u)cylinders;
00143         diskParams.secttrack = (Bit16u)sectors;
00144         diskParams.bytespersect = (Bit16u)bytesPerSector;
00145         diskParams.biosval = 0;
00146         diskParams.ksize = 0;
00147         diskParams.mediaid = 0xF0;
00148         diskParams.rootentries = 0;
00149         diskParams.biosval = 0;
00150         diskParams.sectcluster = 0;
00151         init(diskParams, true, underlyingImage);
00152 }
00153 
00154 // Internal initialization code to create a image of a specified geometry
00155 void imageDiskMemory::init(diskGeo diskParams, bool isHardDrive, imageDisk* underlyingImage) {
00156         //initialize internal variables in case we fail out
00157         this->total_sectors = 0;
00158         this->underlyingImage = underlyingImage;
00159         if (underlyingImage) underlyingImage->Addref();
00160 
00161         //calculate the total number of sectors on the drive, and check for overflows
00162         Bit64u absoluteSectors = (Bit64u)diskParams.cylcount * (Bit64u)diskParams.headscyl;
00163         if (absoluteSectors > 0x100000000u) {
00164                 LOG_MSG("Image size too large in imageDiskMemory constructor.\n");
00165                 return;
00166         }
00167         absoluteSectors *= (Bit64u)diskParams.secttrack;
00168         if (absoluteSectors > 0x100000000u) {
00169                 LOG_MSG("Image size too large in imageDiskMemory constructor.\n");
00170                 return;
00171         }
00172         //make sure that the number of total sectors on the drive is not zero
00173         if (absoluteSectors == 0) {
00174                 LOG_MSG("Image size too small in imageDiskMemory constructor.\n");
00175                 return;
00176         }
00177 
00178         //calculate total size of the drive in kilobytes, and check for overflow
00179         Bit64u diskSizeK = ((Bit64u)diskParams.headscyl * (Bit64u)diskParams.cylcount * (Bit64u)diskParams.secttrack * (Bit64u)diskParams.bytespersect + (Bit64u)1023) / (Bit64u)1024;
00180         if (diskSizeK >= 0x100000000u)
00181         {
00182                 LOG_MSG("Image size too large in imageDiskMemory constructor.\n");
00183                 return;
00184         }
00185 
00186         //split the sectors into chunks, which are allocated together on an as-needed basis
00187         //assuming a maximum of 63 heads and 255 sectors, this formula gives a nice chunk size that scales with the drive size
00188         //for any drives under 64mb, the chunks are 8kb each, and the map consumes 1/1000 of the total drive size
00189         //then the chunk size grows up to a 8gb drive, while the map consumes 30-60k of memory (on 64bit PCs)
00190         //with a 8gb drive, the chunks are about 1mb each, and the map consumes about 60k of memory 
00191         //and for larger drives (with over 1023 cylinders), the chunks remain at 1mb each while the map grows
00192         this->sectors_per_chunk = (diskParams.headscyl + 7u) / 8u * diskParams.secttrack;
00193         this->total_chunks = (Bit32u)((absoluteSectors + sectors_per_chunk - 1u) / sectors_per_chunk);
00194         this->chunk_size = sectors_per_chunk * diskParams.bytespersect;
00195         //allocate a map of chunks that have been allocated and their memory addresses
00196         ChunkMap = (Bit8u**)malloc(total_chunks * sizeof(Bit8u*));
00197         if (ChunkMap == NULL) {
00198                 LOG_MSG("Error allocating memory map in imageDiskMemory constructor for %lu clusters.\n", (unsigned long)total_chunks);
00199                 return;
00200         }
00201         //clear memory map
00202         memset((void*)ChunkMap, 0, total_chunks * sizeof(Bit8u*));
00203 
00204         //set internal variables
00205         this->diskname = "ram drive";
00206         this->heads = diskParams.headscyl;
00207         this->cylinders = diskParams.cylcount;
00208         this->sectors = diskParams.secttrack;
00209         this->sector_size = diskParams.bytespersect;
00210         this->diskSizeK = diskSizeK;
00211         this->total_sectors = (Bit32u)absoluteSectors;
00212         this->reserved_cylinders = 0;
00213         this->hardDrive = isHardDrive;
00214         this->floppyInfo = diskParams;
00215         this->active = true;
00216 }
00217 
00218 // imageDiskMemory destructor will release all allocated memory
00219 imageDiskMemory::~imageDiskMemory() {
00220         //quit if the map is already not allocated
00221         if (!active) return;
00222         //release the underlying image
00223         if (this->underlyingImage) this->underlyingImage->Release();
00224         //loop through each chunk and release it if it has been allocated
00225         for (Bit32u i = 0; i < total_chunks; i++) {
00226                 Bit8u* chunk = ChunkMap[i];
00227                 if (chunk) free(chunk);
00228         }
00229         //release the memory map
00230         free(ChunkMap);
00231         //reset internal variables
00232         ChunkMap = 0;
00233         total_sectors = 0;
00234         active = false;
00235 }
00236 
00237 // Return the BIOS type of the floppy image, or 0 for hard drives
00238 Bit8u imageDiskMemory::GetBiosType(void) {
00239         return this->hardDrive ? 0 : this->floppyInfo.biosval;
00240 }
00241 
00242 void imageDiskMemory::Set_Geometry(Bit32u setHeads, Bit32u setCyl, Bit32u setSect, Bit32u setSectSize) {
00243         if (setHeads != this->heads || setCyl != this->cylinders || setSect != this->sectors || setSectSize != this->sector_size) {
00244                 LOG_MSG("imageDiskMemory::Set_Geometry not implemented");
00245                 //validate geometry and resize ramdrive
00246         }
00247 }
00248 
00249 // Read a specific sector from the ramdrive
00250 Bit8u imageDiskMemory::Read_AbsoluteSector(Bit32u sectnum, void * data) {
00251         //sector number is a zero-based offset
00252 
00253         //verify the sector number is valid
00254         if (sectnum >= total_sectors) {
00255                 LOG_MSG("Invalid sector number in Read_AbsoluteSector for sector %lu.\n", (unsigned long)sectnum);
00256                 return 0x05;
00257         }
00258 
00259         //calculate which chunk the sector is located within, and which sector within the chunk
00260         Bit32u chunknum, chunksect;
00261         chunknum = sectnum / sectors_per_chunk;
00262         chunksect = sectnum % sectors_per_chunk;
00263 
00264         //retrieve the memory address of the chunk
00265         Bit8u* datalocation;
00266         datalocation = ChunkMap[chunknum];
00267 
00268         //if the chunk has not yet been allocated, return underlying image if any, or else zeros
00269         if (datalocation == 0) {
00270                 if (this->underlyingImage) {
00271                         return this->underlyingImage->Read_AbsoluteSector(sectnum, data);
00272                 }
00273                 memset(data, 0, sector_size);
00274                 return 0x00;
00275         }
00276 
00277         //update the address to the specific sector within the chunk
00278         datalocation = &datalocation[chunksect * sector_size];
00279 
00280         //copy the data to the output and return success
00281         memcpy(data, datalocation, sector_size);
00282         return 0x00;
00283 }
00284 
00285 // Write a specific sector from the ramdrive
00286 Bit8u imageDiskMemory::Write_AbsoluteSector(Bit32u sectnum, const void * data) {
00287         //sector number is a zero-based offset
00288 
00289         //verify the sector number is valid
00290         if (sectnum >= total_sectors) {
00291                 LOG_MSG("Invalid sector number in Write_AbsoluteSector for sector %lu.\n", (unsigned long)sectnum);
00292                 return 0x05;
00293         }
00294 
00295         //calculate which chunk the sector is located within, and which sector within the chunk
00296         Bit32u chunknum, chunksect;
00297         chunknum = sectnum / sectors_per_chunk;
00298         chunksect = sectnum % sectors_per_chunk;
00299 
00300         //retrieve the memory address of the chunk
00301         Bit8u* datalocation;
00302         datalocation = ChunkMap[chunknum];
00303 
00304         //if the chunk has not yet been allocated, allocate the chunk
00305         if (datalocation == NULL) {
00306                 //if no underlying image, first check if we are actually saving anything
00307                 if (this->underlyingImage == NULL) {
00308                         Bit8u anyData = 0;
00309                         for (Bit32u i = 0; i < sector_size; i++) {
00310                                 anyData |= ((Bit8u*)data)[i];
00311                         }
00312                         //if it's all zeros, return success
00313                         if (anyData == 0) return 0x00;
00314                 }
00315 
00316                 //allocate a new memory chunk
00317                 datalocation = (Bit8u*)malloc(chunk_size);
00318                 if (datalocation == NULL) {
00319                         LOG_MSG("Could not allocate memory in Write_AbsoluteSector for sector %lu.\n", (unsigned long)sectnum);
00320                         return 0x05;
00321                 }
00322                 //save the memory chunk address within the memory map
00323                 ChunkMap[chunknum] = datalocation;
00324                 //initialize the memory chunk to all zeros (since we are only writing to a single sector within this chunk)
00325                 memset((void*)datalocation, 0, chunk_size);
00326                 //if there is an underlying image, read from the underlying image to fill the chunk
00327                 if (this->underlyingImage) {
00328                         Bit32u chunkFirstSector = chunknum * this->sectors_per_chunk;
00329                         Bit32u sectorsToCopy = this->sectors_per_chunk;
00330                         //if this is the last chunk, don't read past the end of the original image
00331                         if ((chunknum + 1) == this->total_chunks) sectorsToCopy = this->total_sectors - chunkFirstSector;
00332                         //copy the sectors
00333                         Bit8u* target = datalocation;
00334                         for (Bit32u i = 0; i < sectorsToCopy; i++) {
00335                                 this->underlyingImage->Read_AbsoluteSector(i + chunkFirstSector, target);
00336                                 datalocation += this->sector_size;
00337                         }
00338                 }
00339         }
00340 
00341         //update the address to the specific sector within the chunk
00342         datalocation = &datalocation[chunksect * sector_size];
00343 
00344         //write the sector to the chunk and return success
00345         memcpy(datalocation, data, sector_size);
00346         return 0x00;
00347 }
00348 
00349 // Parition and format the ramdrive
00350 Bit8u imageDiskMemory::Format() {
00351         //verify that the geometry of the drive is valid
00352         if (this->sector_size != 512) {
00353                 LOG_MSG("imageDiskMemory::Format only designed for disks with 512-byte sectors.\n");
00354                 return 0x01;
00355         }
00356         if (this->sectors > 63) {
00357                 LOG_MSG("imageDiskMemory::Format only designed for disks with <= 63 sectors.\n");
00358                 return 0x02;
00359         }
00360         if (this->heads > 255) {
00361                 LOG_MSG("imageDiskMemory::Format only designed for disks with <= 255 heads.\n");
00362                 return 0x03;
00363         }
00364         if (this->cylinders <= this->Get_Reserved_Cylinders()) {
00365                 LOG_MSG("Invalid number of reserved cylinders in imageDiskMemory::Format\n");
00366                 return 0x06;
00367         }
00368         Bit32u reported_cylinders = this->cylinders - this->Get_Reserved_Cylinders();
00369         if (reported_cylinders > 1024) {
00370                 LOG_MSG("imageDiskMemory::Format only designed for disks with <= 1024 cylinders.\n");
00371                 return 0x04;
00372         }
00373         Bit32u reported_total_sectors = reported_cylinders * this->heads * this->sectors;
00374 
00375         //plan the drive
00376         //write a MBR?
00377         bool writeMBR = this->hardDrive;
00378         Bit32u partitionStart = writeMBR ? this->sectors : 0; //must be aligned with the start of a head (multiple of this->sectors)
00379         Bit32u partitionLength = reported_total_sectors - partitionStart; //must be aligned with the start of a head (multiple of this->sectors)
00380         //figure out the media id
00381         Bit8u mediaID = this->hardDrive ? 0xF8 : this->floppyInfo.mediaid;
00382         //figure out the number of root entries and minimum number of sectors per cluster
00383         Bit32u root_ent = this->hardDrive ? 512 : this->floppyInfo.rootentries;
00384         Bit32u sectors_per_cluster = this->hardDrive ? 4 : this->floppyInfo.sectcluster; //fat requires 2k clusters minimum on hard drives
00385 
00386         //calculate the number of:
00387         //  root sectors
00388         //  FAT sectors
00389         //  reserved sectors
00390         //  sectors per cluster
00391         //  if this is a FAT16 or FAT12 drive
00392         Bit32u root_sectors;
00393         bool isFat16;
00394         Bit32u fatSectors;
00395         Bit32u reservedSectors;
00396         if (!this->CalculateFAT(partitionStart, partitionLength, this->hardDrive, root_ent, &root_sectors, &sectors_per_cluster, &isFat16, &fatSectors, &reservedSectors)) {
00397                 LOG_MSG("imageDiskMemory::Format could not calculate FAT sectors.\n");
00398                 return 0x05;
00399         }
00400 
00401         LOG_MSG("Formatting FAT%u %s drive C/H/S %u/%u/%u with %u bytes/sector, %u root entries, %u-byte clusters, media id 0x%X\n",
00402                 (unsigned int)(isFat16 ? 16 : 12), this->hardDrive ? "hard" : "floppy",
00403                 (unsigned int)reported_cylinders, (unsigned int)this->heads, (unsigned int)this->sectors, (unsigned int)this->sector_size,
00404                 (unsigned int)root_ent, (unsigned int)(sectors_per_cluster * this->sector_size), (unsigned int)mediaID);
00405 
00406         //write MBR if applicable
00407         Bit8u sbuf[512];
00408         if (writeMBR) {
00409                 // initialize sbuf with the freedos MBR
00410                 memcpy(sbuf, freedos_mbr, 512);
00411                 // active partition
00412                 sbuf[0x1be] = 0x80;
00413                 //ASSUMING that partitionStart==this->sectors;
00414                 // start head - head 0 has the partition table, head 1 first partition
00415                 sbuf[0x1bf] = this->heads > 1 ? 1 : 0;
00416                 // start sector with bits 8-9 of start cylinder in bits 6-7
00417                 sbuf[0x1c0] = this->heads > 1 ? 1 : (this->sectors > 1 ? 2 : 1);
00418                 // start cylinder bits 0-7
00419                 sbuf[0x1c1] = this->heads > 1 || this->sectors > 1 ? 0 : 1;
00420                 // OS indicator: DOS what else ;)
00421                 sbuf[0x1c2] = 0x06;
00422                 //ASSUMING that partitionLength==(reported_total_sectors-partitionStart)
00423                 // end head (0-based)
00424                 sbuf[0x1c3] = this->heads - 1;
00425                 // end sector with bits 8-9 of end cylinder (0-based) in bits 6-7
00426                 sbuf[0x1c4] = this->sectors | (((this->cylinders - 1 - this->Get_Reserved_Cylinders()) & 0x300) >> 2);
00427                 // end cylinder (0-based) bits 0-7
00428                 sbuf[0x1c5] = (this->cylinders - 1 - this->Get_Reserved_Cylinders()) & 0xFF;
00429                 // sectors preceding partition1 (one head)
00430                 host_writed(&sbuf[0x1c6], this->sectors);
00431                 // length of partition1, align to chs value
00432                 // ASSUMING that partitionLength aligns to chs value
00433                 host_writed(&sbuf[0x1ca], partitionLength);
00434 
00435                 // write partition table
00436                 this->Write_AbsoluteSector(0, sbuf);
00437         }
00438 
00439         // initialize boot sector values
00440         memset(sbuf, 0, 512);
00441         // TODO boot code jump
00442         sbuf[0] = 0xEB; sbuf[1] = 0x3c; sbuf[2] = 0x90;
00443         // OEM
00444         sprintf((char*)&sbuf[0x03], "MSDOS5.0");
00445         // bytes per sector: always 512
00446         host_writew(&sbuf[0x0b], 512);
00447         // sectors per cluster: 1,2,4,8,16,...
00448         sbuf[0x0d] = sectors_per_cluster;
00449         // reserved sectors: 1 for floppies (the boot sector), or align to 4k boundary for hard drives (not required to align to 4k boundary)
00450         host_writew(&sbuf[0x0e], reservedSectors);
00451         // Number of FATs - always 2
00452         sbuf[0x10] = 2;
00453         // Root directory entries
00454         host_writew(&sbuf[0x11], root_ent);
00455         // total sectors (<= 65535)
00456         if (partitionLength < 0x10000u) host_writew(&sbuf[0x13], (Bit16u)partitionLength);
00457         // media descriptor
00458         sbuf[0x15] = mediaID;
00459         // sectors per FAT
00460         host_writew(&sbuf[0x16], fatSectors);
00461         // sectors per track
00462         host_writew(&sbuf[0x18], this->sectors);
00463         // heads
00464         host_writew(&sbuf[0x1a], this->heads);
00465         // hidden sectors
00466         host_writed(&sbuf[0x1c], partitionStart);
00467         // sectors (>= 65536)
00468         if (partitionLength >= 0x10000u) host_writed(&sbuf[0x20], partitionLength);
00469         // BIOS drive
00470         sbuf[0x24] = this->hardDrive ? 0x80 : 0x00;
00471         // ext. boot signature
00472         sbuf[0x26] = 0x29;
00473         // volume serial number
00474         // let's use the BIOS time (cheap, huh?)
00475         host_writed(&sbuf[0x27], mem_readd(BIOS_TIMER));
00476         // Volume label
00477         sprintf((char*)&sbuf[0x2b], "RAMDISK    ");
00478         // file system type
00479         sprintf((char*)&sbuf[0x36], isFat16 ? "FAT16   " : "FAT12   ");
00480         // boot sector signature (indicates disk is bootable)
00481         host_writew(&sbuf[0x1fe], 0xAA55);
00482 
00483         // write the boot sector
00484         this->Write_AbsoluteSector(partitionStart, sbuf);
00485 
00486         // initialize the FATs and root sectors
00487         memset(sbuf, 0, sector_size);
00488         // erase both FATs and the root directory sectors
00489         for (Bit32u i = partitionStart + 1; i < partitionStart + reservedSectors + fatSectors + fatSectors + root_sectors; i++) {
00490                 this->Write_AbsoluteSector(i, sbuf);
00491         }
00492         // set the special markers for cluster 0 and cluster 1
00493         if (isFat16) {
00494                 host_writed(&sbuf[0], 0xFFFFFF00u | mediaID);
00495         }
00496         else {
00497                 host_writed(&sbuf[0], 0xFFFF00u | mediaID);
00498         }
00499         this->Write_AbsoluteSector(partitionStart + reservedSectors, sbuf);
00500         this->Write_AbsoluteSector(partitionStart + reservedSectors + fatSectors, sbuf);
00501 
00502         //success
00503         return 0x00;
00504 }
00505 
00506 // Calculate the number of sectors per cluster, the number of sectors per fat, and if it is FAT12/FAT16
00507 // Note that sectorsPerCluster is required to be set to the minimum, which will be 2 for certain types of floppies
00508 bool imageDiskMemory::CalculateFAT(Bit32u partitionStartingSector, Bit32u partitionLength, bool isHardDrive, Bit32u rootEntries, Bit32u* rootSectors, Bit32u* sectorsPerCluster, bool* isFat16, Bit32u* fatSectors, Bit32u* reservedSectors) {
00509         //check for null references
00510         if (rootSectors == NULL || sectorsPerCluster == NULL || isFat16 == NULL || fatSectors == NULL || reservedSectors == NULL) return false;
00511         //make sure variables all make sense
00512         switch (*sectorsPerCluster) {
00513                 case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: break;
00514                 default:
00515                         LOG_MSG("Invalid number of sectors per cluster\n");
00516                         return false;
00517         }
00518         if (((Bit64u)partitionStartingSector + partitionLength) > 0xfffffffful) {
00519                 LOG_MSG("Invalid partition size\n");
00520                 return false;
00521         }
00522         if ((rootEntries > (isHardDrive ? 512u : 240u)) ||
00523                 (rootEntries / 16) * 16 != rootEntries ||
00524                 rootEntries == 0) {
00525                 LOG_MSG("Invalid number of root entries\n");
00526                 return false;
00527         }
00528         //set the number of root sectors
00529         *rootSectors = rootEntries * 32 / 512;
00530         //make sure there is a minimum number of sectors available
00531         //  minimum sectors = root sectors + 1 for boot sector + 1 for fat #1 + 1 for fat #2 + 1 cluster for data + add 7 for hard drives due to allow for 4k alignment
00532         if (partitionLength < (*rootSectors + 3 + *sectorsPerCluster + (isHardDrive ? 7 : 0))) {
00533                 LOG_MSG("Partition too small to format\n");
00534                 return false;
00535         }
00536 
00537 
00538         //set the minimum number of reserved sectors
00539         //the first sector of a partition is always reserved, of course
00540         *reservedSectors = 1;
00541         //if this is a hard drive, we will align to a 4kb boundary,
00542         //  so set (partitionStartingSector + reservedSectors) to an even number.
00543         //Additional alignment can be made by increasing the number of reserved sectors,
00544         //  or by increasing the reservedSectors value -- both done after the calculation
00545         if (isHardDrive && ((partitionStartingSector + *reservedSectors) % 2 == 1)) (*reservedSectors)++;
00546 
00547 
00548         //compute the number of fat sectors and data sectors
00549         Bit32u dataSectors;
00550         Bit32u dataClusters;
00551         //first try FAT12 - compute the minimum number of fat sectors necessary using the minimum number of sectors per cluster
00552         *fatSectors = (((Bit64u)partitionLength - *reservedSectors - *rootSectors + 2) * 3 + *sectorsPerCluster * 1024 + 5) / (*sectorsPerCluster * 1024 + 6);
00553         dataSectors = partitionLength - *reservedSectors - *rootSectors - *fatSectors - *fatSectors;
00554         dataClusters = dataSectors / *sectorsPerCluster;
00555         //check if this calculation makes the drive too big to fit within a FAT12 drive
00556         if (dataClusters >= 4085) {
00557                 //so instead let's calculate for FAT16, and increase the number of sectors per cluster if necessary
00558                 //note: changing the drive to FAT16 will reducing the number of data sectors, which might change the drive
00559                 //  from a FAT16 drive to a FAT12 drive.  however, the only difference is that there are unused fat sectors
00560                 //  allocated.  we cannot allocate them, or else the drive will change back to a FAT16 drive, so, just leave it as-is
00561                 do {
00562                         //compute the minimum number of fat sectors necessary starting with the minimum number of sectors per cluster
00563                         //  the +2 is for the two reserved data sectors (sector #0 and sector #1) which are not stored on the drive, but are in the map
00564                         //  rounds up and assumes 512 byte sector size
00565                         *fatSectors = ((Bit64u)partitionLength - *reservedSectors - *rootSectors + 2 + (*sectorsPerCluster * 256 + 1)) / (*sectorsPerCluster * 256 + 2);
00566 
00567                         //compute the number of data sectors and clusters with this arrangement
00568                         dataSectors = partitionLength - *reservedSectors - *rootSectors - *fatSectors - *fatSectors;
00569                         dataClusters = dataSectors / *sectorsPerCluster;
00570 
00571                         //check and see if this makes a valid FAT16 drive
00572                         if (dataClusters < 65525) break;
00573 
00574                         //otherwise, double the number of sectors per cluster and try again
00575                         *sectorsPerCluster <<= 1;
00576                 } while (true);
00577                 //determine if the drive is too large for FAT16
00578                 if (*sectorsPerCluster >= 256) {
00579                         LOG_MSG("Partition too large to format as FAT16\n");
00580                         return false;
00581                 }
00582         }
00583 
00584         //add padding to align hard drives to 4kb boundary
00585         if (isHardDrive) {
00586                 //update the reserved sector count
00587                 *reservedSectors = (partitionStartingSector + *reservedSectors + *rootSectors + *fatSectors + *fatSectors) % 8;
00588                 if (*reservedSectors == 0) *reservedSectors = 8;
00589                 //recompute the number of data sectors
00590                 dataSectors = partitionLength - *reservedSectors - *rootSectors - *fatSectors - *fatSectors;
00591                 dataClusters = dataSectors / *sectorsPerCluster;
00592 
00593                 //note: by reducing the number of data sectors, the drive might have changed from a FAT16 drive to a FAT12 drive.
00594                 //  however, the only difference is that there are unused fat sectors allocated.  we cannot allocate them, or else
00595                 //  the drive alignment will change again, and most likely the drive would change back into a FAT16 drive.
00596                 //  so, just leave it as-is
00597         }
00598 
00599         //determine whether the drive is FAT16 or not
00600         //note: this is AFTER adding padding for 4kb alignment
00601         *isFat16 = (dataClusters >= 4085);
00602 
00603         //success
00604         return true;
00605 }