FAT driver: Recalculate FAT structure placement when the guest changes the BPB, allow mounting an unformatted partition as a FAT drive letter in a way that allows FORMAT.COM to later make a valid filesystem

This commit is contained in:
Jonathan Campbell 2023-09-08 17:18:16 -07:00
parent c08ce26926
commit 41f94f776f
3 changed files with 396 additions and 152 deletions

View File

@ -12,6 +12,18 @@ Next version:
file would attempt to mount the .bat file, not the disk image.
- IMGMAKE: If -nofs and -bat was specified, the generated .bat file
will refer to "IMGMAKE 2" instead of "IMGMAKE C"
- DOS FAT driver: When the guest sends in a new BIOS Parameter Block,
recalculate the disk locations and FAT type properly instead of
assuming that FORMAT.COM is using the same format already present.
- DOS FAT driver: If the partition is unformatted and looks as if
freshly written by FDISK, then instead of failing to mount, mount
the partition instead as an unformatted partition and fail file
I/O until a BPB is set and the partition formatted by FORMAT.COM.
This matches MS-DOS behavior and it makes it possible to mount an
image, FDISK it, restart, FORMAT.COM the unformatted partition,
and end up with a working drive letter (just like MS-DOS). So far
verified against MS-DOS 6.22 and Windows 98 versions of FDISK,
FORMAT, SYS, and SCANDISK.
2023.09.01
- Disable by default message confirmation after snapshot and AVI video

View File

@ -598,6 +598,7 @@ uint32_t fatFile::GetSeekPos() {
}
uint32_t fatDrive::getClustFirstSect(uint32_t clustNum) {
if (unformatted) return 0;
return ((clustNum - 2) * BPB.v.BPB_SecPerClus) + firstDataSector;
}
@ -607,6 +608,8 @@ uint32_t fatDrive::getClusterValue(uint32_t clustNum) {
uint32_t fatentoff;
uint32_t clustValue=0;
if (unformatted) return 0xFFFFFFFFu;
switch(fattype) {
case FAT12:
fatoffset = clustNum + (clustNum / 2);
@ -669,6 +672,8 @@ void fatDrive::setClusterValue(uint32_t clustNum, uint32_t clustValue) {
uint32_t fatsectnum;
uint32_t fatentoff;
if (unformatted) return;
switch(fattype) {
case FAT12:
fatoffset = clustNum + (clustNum / 2);
@ -740,6 +745,8 @@ void fatDrive::setClusterValue(uint32_t clustNum, uint32_t clustValue) {
}
bool fatDrive::getEntryName(const char *fullname, char *entname) {
if (unformatted) return false;
char dirtoken[DOS_PATHLENGTH];
char * findDir;
@ -772,6 +779,8 @@ bool fatDrive::getEntryName(const char *fullname, char *entname) {
}
void fatDrive::UpdateBootVolumeLabel(const char *label) {
if (unformatted) return;
FAT_BootSector bootbuffer = {};
if (BPB.v.BPB_BootSig == 0x28 || BPB.v.BPB_BootSig == 0x29) {
@ -794,6 +803,8 @@ void fatDrive::UpdateBootVolumeLabel(const char *label) {
}
void fatDrive::SetLabel(const char *label, bool /*iscdrom*/, bool /*updatable*/) {
if (unformatted) return;
direntry sectbuf[MAX_DIRENTS_PER_SECTOR]; /* 16 directory entries per 512 byte sector */
uint32_t dirClustNumber;
uint32_t logentsector; /* Logical entry sector */
@ -903,6 +914,8 @@ nextfile:
* first call the clear() method before calling. After the call, copy off the value because the next call to FindNextInternal
* by any part of this code will obliterate the result with a new result. */
bool fatDrive::getFileDirEntry(char const * const filename, direntry * useEntry, uint32_t * dirClust, uint32_t * subEntry,bool dirOk) {
if (unformatted) return false;
size_t len = strlen(filename);
char dirtoken[DOS_PATHLENGTH];
uint32_t currentClust = 0; /* FAT12/FAT16 root directory */
@ -966,6 +979,8 @@ bool fatDrive::getFileDirEntry(char const * const filename, direntry * useEntry,
}
bool fatDrive::getDirClustNum(const char *dir, uint32_t *clustNum, bool parDir) {
if (unformatted) return false;
uint32_t len = (uint32_t)strlen(dir);
char dirtoken[DOS_PATHLENGTH];
direntry foundEntry;
@ -1077,14 +1092,17 @@ uint32_t fatDrive::getSectorSize(void) {
}
uint32_t fatDrive::getClusterSize(void) {
if (unformatted) return 0;
return (unsigned int)BPB.v.BPB_SecPerClus * (unsigned int)BPB.v.BPB_BytsPerSec;
}
uint32_t fatDrive::getAbsoluteSectFromBytePos(uint32_t startClustNum, uint32_t bytePos,clusterChainMemory *ccm) {
if (unformatted) return 0;
return getAbsoluteSectFromChain(startClustNum, bytePos / BPB.v.BPB_BytsPerSec,ccm);
}
bool fatDrive::iseofFAT(const uint32_t cv) const {
if (unformatted) return true;
switch(fattype) {
case FAT12: return cv < 2 || cv >= 0xff8;
case FAT16: return cv < 2 || cv >= 0xfff8;
@ -1096,6 +1114,7 @@ bool fatDrive::iseofFAT(const uint32_t cv) const {
}
uint32_t fatDrive::getAbsoluteSectFromChain(uint32_t startClustNum, uint32_t logicalSector,clusterChainMemory *ccm) {
if (unformatted) return 0;
uint32_t targClust = (uint32_t)(logicalSector / BPB.v.BPB_SecPerClus);
uint32_t sectClust = (uint32_t)(logicalSector % BPB.v.BPB_SecPerClus);
uint32_t indxClust = (uint32_t)0;
@ -1147,6 +1166,7 @@ uint32_t fatDrive::getAbsoluteSectFromChain(uint32_t startClustNum, uint32_t log
}
void fatDrive::deleteClustChain(uint32_t startCluster, uint32_t bytePos) {
if (unformatted) return;
if (startCluster < 2) return; /* do not corrupt the FAT media ID. The file has no chain. Do nothing. */
uint32_t clustSize = getClusterSize();
@ -1226,6 +1246,7 @@ void fatDrive::deleteClustChain(uint32_t startCluster, uint32_t bytePos) {
}
uint32_t fatDrive::appendCluster(uint32_t startCluster) {
if (unformatted) return 0;
if (startCluster < 2) return 0; /* do not corrupt the FAT media ID. The file has no chain. Do nothing. */
uint32_t currentClust = startCluster;
@ -1270,6 +1291,7 @@ uint32_t fatDrive::appendCluster(uint32_t startCluster) {
}
bool fatDrive::allocateCluster(uint32_t useCluster, uint32_t prevCluster) {
if (unformatted) return false;
/* Can't allocate cluster #0 */
if(useCluster == 0) return false;
@ -1506,7 +1528,7 @@ void fatDrive::UpdateDPB(unsigned char dos_drive) {
void fatDrive::fatDriveInit(const char *sysFilename, uint32_t bytesector, uint32_t cylsector, uint32_t headscyl, uint32_t cylinders, uint64_t filesize, const std::vector<std::string> &options) {
uint32_t startSector = 0,countSector = 0;
bool pc98_512_to_1024_allow = false;
int opt_partition_index = -1;
int opt_partition_index = -1;
bool is_hdd = (filesize > 2880);
physToLogAdj = 0;
@ -1517,29 +1539,29 @@ void fatDrive::fatDriveInit(const char *sysFilename, uint32_t bytesector, uint32
return;
}
for (const auto &opt : options) {
size_t equ = opt.find_first_of('=');
std::string name,value;
for (const auto &opt : options) {
size_t equ = opt.find_first_of('=');
std::string name,value;
if (equ != std::string::npos) {
name = opt.substr(0,equ);
value = opt.substr(equ+1);
}
else {
name = opt;
value.clear();
}
if (equ != std::string::npos) {
name = opt.substr(0,equ);
value = opt.substr(equ+1);
}
else {
name = opt;
value.clear();
}
if (name == "partidx") {
if (!value.empty())
opt_partition_index = (int)atol(value.c_str());
}
else {
LOG(LOG_DOSMISC,LOG_DEBUG)("FAT: option '%s' = '%s' ignored, unknown",name.c_str(),value.c_str());
}
if (name == "partidx") {
if (!value.empty())
opt_partition_index = (int)atol(value.c_str());
}
else {
LOG(LOG_DOSMISC,LOG_DEBUG)("FAT: option '%s' = '%s' ignored, unknown",name.c_str(),value.c_str());
}
// LOG_MSG("'%s' = '%s'",name.c_str(),value.c_str());
}
// LOG_MSG("'%s' = '%s'",name.c_str(),value.c_str());
}
loadedDisk->Addref();
bool isipl1 = false;
@ -1701,15 +1723,144 @@ void fatDrive::fatDriveInit(const char *sysFilename, uint32_t bytesector, uint32
loadedDisk->Read_AbsoluteSector(0+partSectOff,&bootbuffer);
/* If the sector is full of 0xF6, the partition is brand new and was just created with Microsoft FDISK.EXE (Windows 98 behavior)
* and therefore there is NO FAT filesystem here. We'll go farther and check if all bytes are just the same. */
{
* and therefore there is NO FAT filesystem here. We'll go farther and check if all bytes are just the same.
*
* MS-DOS behavior is to let the drive letter appear, but any attempt to access it results in errors like "invalid media type" until
* you run FORMAT.COM on it, which will then set up the filesystem and allow it to work. */
if (partSectOff != 0) { /* hard drives only */
unsigned int i=1;
while (i < 128 && ((uint8_t*)(&bootbuffer))[0] == ((uint8_t*)(&bootbuffer))[i]) i++;
if (i == 128) {
LOG_MSG("Boot sector appears to have been created by FDISK.EXE but not formatted");
created_successfully = false;
LOG_MSG("Boot sector appears to have been created by FDISK.EXE but not formatted. Allowing mount but filesystem access will not work until formatted.");
sector_size = loadedDisk->getSectSize();
firstRootDirSect = 0;
CountOfClusters = 0;
firstDataSector = 0;
unformatted = true;
cwdDirCluster = 0;
memset(fatSectBuffer,0,1024);
curFatSect = 0xffffffff;
strcpy(info, "fatDrive ");
strcat(info, wpcolon&&strlen(sysFilename)>1&&sysFilename[0]==':'?sysFilename+1:sysFilename);
/* NTS: Real MS-DOS behavior is that an unformatted partition is inaccessible but INT 21h AX=440Dh CX=0x0860 (Get Device Parameters)
* has a ready-made BPB for use in formatting the partition. FORMAT.COM then formats the filesystem based on the MS-DOS kernel's
* recommended BPB. It's not FORMAT.COM that decides the layout it is MS-DOS itself. */
uint64_t part_sectors;
if (partSectOff != 0 && partSectSize != 0) {
part_sectors = partSectSize;
}
else {
part_sectors = GetSectorCount();
}
/* If the partition is 1GB or larger and emulating MS-DOS 7.10 or higher, default FAT32 */
if ((part_sectors*uint64_t(sector_size)) >= (1000ull*2048ull) && (dos.version.major > 7 || (dos.version.major == 7 && dos.version.minor >= 10))) {
BPB.v32.BPB_BytsPerSec = sector_size;
BPB.v32.BPB_SecPerClus = 8; // 4096 byte clusters
BPB.v32.BPB_RsvdSecCnt = 32;
BPB.v32.BPB_NumFATs = 2;
BPB.v32.BPB_RootEntCnt = 0;
BPB.v32.BPB_TotSec16 = 0;
BPB.v32.BPB_Media = 0xF8;
BPB.v32.BPB_SecPerTrk = loadedDisk->sectors;
BPB.v32.BPB_NumHeads = loadedDisk->heads;
BPB.v32.BPB_HiddSec = partSectOff;
BPB.v32.BPB_TotSec32 = part_sectors;
BPB.v32.BPB_ExtFlags = 0;
BPB.v32.BPB_FSVer = 0;
BPB.v32.BPB_RootClus = 2;
BPB.v32.BPB_FSInfo = 0;
BPB.v32.BPB_BkBootSec = 0;
while (BPB.v32.BPB_SecPerClus < 64 && (part_sectors / uint64_t(BPB.v32.BPB_SecPerClus)) > uint64_t(0x880000u))
BPB.v32.BPB_SecPerClus *= 2u;
/* Get size of root dir in sectors */
uint32_t DataSectors;
DataSectors = (Bitu)BPB.v32.BPB_TotSec32 - ((Bitu)BPB.v32.BPB_RsvdSecCnt + ((Bitu)BPB.v32.BPB_NumFATs * (Bitu)BPB.v32.BPB_FATSz32));
CountOfClusters = DataSectors / BPB.v32.BPB_SecPerClus;
BPB.v32.BPB_FATSz32 = uint32_t((CountOfClusters * 4ull) + uint64_t(sector_size) - 1ull) / uint64_t(sector_size);
assert(CountOfClusters >= 0xFFF8);
assert(BPB.is_fat32());
fattype = FAT32;
}
else {
BPB.v.BPB_BytsPerSec = sector_size;
BPB.v.BPB_SecPerClus = 1; // 512 byte clusters
BPB.v.BPB_RsvdSecCnt = 1;
BPB.v.BPB_NumFATs = 2;
BPB.v.BPB_RootEntCnt = 96;
if (part_sectors > 65535ul) {
BPB.v.BPB_TotSec16 = 0;
BPB.v.BPB_TotSec32 = part_sectors;
}
else {
BPB.v.BPB_TotSec16 = part_sectors;
BPB.v.BPB_TotSec32 = 0;
}
BPB.v.BPB_Media = 0xF8;
BPB.v.BPB_SecPerTrk = loadedDisk->sectors;
BPB.v.BPB_NumHeads = loadedDisk->heads;
BPB.v.BPB_HiddSec = partSectOff;
if ((part_sectors*uint64_t(sector_size)) >= (31ull*1024ull*1024ull)) { // 31MB
fattype = FAT16;
while (BPB.v.BPB_SecPerClus < 32 && (part_sectors / uint64_t(BPB.v.BPB_SecPerClus)) > uint64_t(0x8800u))
BPB.v.BPB_SecPerClus *= 2u;
}
else {
fattype = FAT12;
while (BPB.v.BPB_SecPerClus < 64 && (part_sectors / uint64_t(BPB.v.BPB_SecPerClus)) > uint64_t(0x880u))
BPB.v.BPB_SecPerClus *= 2u;
}
/* Get size of root dir in sectors */
uint32_t RootDirSectors;
uint32_t DataSectors;
RootDirSectors = ((BPB.v.BPB_RootEntCnt * 32u) + (BPB.v.BPB_BytsPerSec - 1u)) / BPB.v.BPB_BytsPerSec;
if (BPB.v.BPB_TotSec16 != 0)
DataSectors = (Bitu)BPB.v.BPB_TotSec16 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors);
else
DataSectors = (Bitu)BPB.v.BPB_TotSec32 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors);
CountOfClusters = DataSectors / BPB.v.BPB_SecPerClus;
if (fattype == FAT16)
BPB.v.BPB_FATSz16 = uint32_t((CountOfClusters * 2ull) + uint64_t(sector_size) - 1ull) / uint64_t(sector_size);
else
BPB.v.BPB_FATSz16 = uint32_t((((CountOfClusters+1ull)/2ull) * 3ull) + uint64_t(sector_size) - 1ull) / uint64_t(sector_size);
if (BPB.v.BPB_TotSec16 != 0)
DataSectors = (Bitu)BPB.v.BPB_TotSec16 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors);
else
DataSectors = (Bitu)BPB.v.BPB_TotSec32 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors);
CountOfClusters = DataSectors / BPB.v.BPB_SecPerClus;
if (fattype == FAT12) {
assert(CountOfClusters < 0xFF8);
}
else if (fattype == FAT16) {
assert(CountOfClusters < 0xFFF8);
assert(CountOfClusters >= 0xFF8);
}
assert(!BPB.is_fat32());
}
return;
}
}
@ -1793,9 +1944,9 @@ void fatDrive::fatDriveInit(const char *sysFilename, uint32_t bytesector, uint32
/* DEBUG */
LOG(LOG_DOSMISC,LOG_DEBUG)("FAT: BPB says %u sectors/track %u heads %u bytes/sector",
BPB.v.BPB_SecPerTrk,
BPB.v.BPB_NumHeads,
BPB.v.BPB_BytsPerSec);
BPB.v.BPB_SecPerTrk,
BPB.v.BPB_NumHeads,
BPB.v.BPB_BytsPerSec);
/* NTS: PC-98 floppies (the 1024 byte/sector format) do not have magic bytes */
if (fatDrive::getSectSize() == 512 && !IS_PC98_ARCH) {
@ -1808,35 +1959,35 @@ void fatDrive::fatDriveInit(const char *sysFilename, uint32_t bytesector, uint32
}
}
/* NTS: Some HDI images of PC-98 games do in fact have BPB_NumHeads == 0. Some like "Amaranth 5" have BPB_SecPerTrk == 0 too! */
if (!IS_PC98_ARCH) {
/* a clue that we're not really looking at FAT is invalid or weird values in the boot sector */
if (BPB.v.BPB_SecPerTrk == 0 || (BPB.v.BPB_SecPerTrk > ((filesize <= 3000) ? 40 : 255)) ||
(BPB.v.BPB_NumHeads > ((filesize <= 3000) ? 64 : 255))) {
LOG_MSG("Rejecting image, boot sector has weird values not consistent with FAT filesystem");
created_successfully = false;
return;
}
}
/* NTS: Some HDI images of PC-98 games do in fact have BPB_NumHeads == 0. Some like "Amaranth 5" have BPB_SecPerTrk == 0 too! */
if (!IS_PC98_ARCH) {
/* a clue that we're not really looking at FAT is invalid or weird values in the boot sector */
if (BPB.v.BPB_SecPerTrk == 0 || (BPB.v.BPB_SecPerTrk > ((filesize <= 3000) ? 40 : 255)) ||
(BPB.v.BPB_NumHeads > ((filesize <= 3000) ? 64 : 255))) {
LOG_MSG("Rejecting image, boot sector has weird values not consistent with FAT filesystem");
created_successfully = false;
return;
}
}
/* work at this point in logical sectors */
/* work at this point in logical sectors */
sector_size = loadedDisk->getSectSize();
/* Many HDI images indicate a disk format of 256 or 512 bytes per sector combined with a FAT filesystem
* that indicates 1024 bytes per sector. */
if (pc98_512_to_1024_allow &&
BPB.v.BPB_BytsPerSec != fatDrive::getSectSize() &&
BPB.v.BPB_BytsPerSec > fatDrive::getSectSize() &&
(BPB.v.BPB_BytsPerSec % fatDrive::getSectSize()) == 0) {
unsigned int ratioshift = 1;
/* Many HDI images indicate a disk format of 256 or 512 bytes per sector combined with a FAT filesystem
* that indicates 1024 bytes per sector. */
if (pc98_512_to_1024_allow &&
BPB.v.BPB_BytsPerSec != fatDrive::getSectSize() &&
BPB.v.BPB_BytsPerSec > fatDrive::getSectSize() &&
(BPB.v.BPB_BytsPerSec % fatDrive::getSectSize()) == 0) {
unsigned int ratioshift = 1;
while ((unsigned int)(BPB.v.BPB_BytsPerSec >> ratioshift) > fatDrive::getSectSize())
ratioshift++;
while ((unsigned int)(BPB.v.BPB_BytsPerSec >> ratioshift) > fatDrive::getSectSize())
ratioshift++;
unsigned int ratio = 1u << ratioshift;
unsigned int ratio = 1u << ratioshift;
LOG_MSG("Disk indicates %u bytes/sector, FAT filesystem indicates %u bytes/sector. Ratio=%u:1 shift=%u",
fatDrive::getSectSize(),BPB.v.BPB_BytsPerSec,ratio,ratioshift);
LOG_MSG("Disk indicates %u bytes/sector, FAT filesystem indicates %u bytes/sector. Ratio=%u:1 shift=%u",
fatDrive::getSectSize(),BPB.v.BPB_BytsPerSec,ratio,ratioshift);
if ((unsigned int)(BPB.v.BPB_BytsPerSec >> ratioshift) == fatDrive::getSectSize()) {
assert(ratio >= 2);
@ -1854,18 +2005,18 @@ void fatDrive::fatDriveInit(const char *sysFilename, uint32_t bytesector, uint32
}
/* Sanity checks */
/* NTS: DOSBox-X *does* support non-standard sector sizes, though not in IBM PC mode and not through INT 13h.
* In DOSBox-X INT 13h emulation will enforce the standard (512 byte) sector size.
* In PC-98 mode mounting disk images requires "non-standard" sector sizes because PC-98 floppies (other
* than ones formatted 1.44MB) generally use 1024 bytes/sector and MAY use 128 or 256 bytes per sector. */
/* NTS: Loosen geometry checks for PC-98 mode, for two reasons. One, is that the geometry check will fail
* when logical vs physical sector translation is involved, since it is apparently common for PC-98 HDI
* images to be formatted with 256, 512, 1024, or in rare cases even 2048 bytes per sector, yet the FAT
* file format will report a sector size that is a power of 2 multiple of the disk sector size. The
* most common appears to be 512 byte/sector HDI images formatted with 1024 byte/sector FAT filesystems.
*
* Second, there are some HDI images that are valid yet the FAT filesystem reports a head count of 0
* for some reason (Touhou Project) */
/* NTS: DOSBox-X *does* support non-standard sector sizes, though not in IBM PC mode and not through INT 13h.
* In DOSBox-X INT 13h emulation will enforce the standard (512 byte) sector size.
* In PC-98 mode mounting disk images requires "non-standard" sector sizes because PC-98 floppies (other
* than ones formatted 1.44MB) generally use 1024 bytes/sector and MAY use 128 or 256 bytes per sector. */
/* NTS: Loosen geometry checks for PC-98 mode, for two reasons. One, is that the geometry check will fail
* when logical vs physical sector translation is involved, since it is apparently common for PC-98 HDI
* images to be formatted with 256, 512, 1024, or in rare cases even 2048 bytes per sector, yet the FAT
* file format will report a sector size that is a power of 2 multiple of the disk sector size. The
* most common appears to be 512 byte/sector HDI images formatted with 1024 byte/sector FAT filesystems.
*
* Second, there are some HDI images that are valid yet the FAT filesystem reports a head count of 0
* for some reason (Touhou Project) */
if ((BPB.v.BPB_SecPerClus == 0) ||
(BPB.v.BPB_NumFATs == 0) ||
(BPB.v.BPB_NumHeads == 0 && !IS_PC98_ARCH) ||
@ -1882,53 +2033,53 @@ void fatDrive::fatDriveInit(const char *sysFilename, uint32_t bytesector, uint32
return;
}
/* Sanity check: Root directory count is nonzero if FAT16/FAT12, or is zero if FAT32 */
if (BPB.is_fat32()) {
if (BPB.v.BPB_RootEntCnt != 0) {
LOG_MSG("Sanity check fail: Root directory count != 0 and not FAT32");
created_successfully = false;
return;
}
}
else {
if (BPB.v.BPB_RootEntCnt == 0) {
LOG_MSG("Sanity check fail: Root directory count == 0 and not FAT32");
created_successfully = false;
return;
}
}
/* Sanity check: Root directory count is nonzero if FAT16/FAT12, or is zero if FAT32 */
if (BPB.is_fat32()) {
if (BPB.v.BPB_RootEntCnt != 0) {
LOG_MSG("Sanity check fail: Root directory count != 0 and not FAT32");
created_successfully = false;
return;
}
}
else {
if (BPB.v.BPB_RootEntCnt == 0) {
LOG_MSG("Sanity check fail: Root directory count == 0 and not FAT32");
created_successfully = false;
return;
}
}
/* too much of this code assumes 512 bytes per sector or more.
* MS-DOS itself as I understand it relies on bytes per sector being a power of 2.
* this is to protect against errant FAT structures and to help prep this code
* later to work with the 1024 bytes/sector used by PC-98 floppy formats.
* When done, this code should be able to then handle the FDI/FDD images
* PC-98 games are normally distributed in on the internet.
*
* The value "128" comes from the smallest sector size possible on the floppy
* controller of MS-DOS based systems. */
/* NTS: Power of 2 test: A number is a power of 2 if (x & (x - 1)) == 0
*
* 15 15 & 14 01111 AND 01110 RESULT: 01110 (15)
* 16 16 & 15 10000 AND 01111 RESULT: 00000 (0)
* 17 17 & 16 10001 AND 10000 RESULT: 10000 (16) */
if (BPB.v.BPB_BytsPerSec < 128 || BPB.v.BPB_BytsPerSec > SECTOR_SIZE_MAX ||
(BPB.v.BPB_BytsPerSec & (BPB.v.BPB_BytsPerSec - 1)) != 0/*not a power of 2*/) {
LOG_MSG("FAT bytes/sector value %u not supported",BPB.v.BPB_BytsPerSec);
/* too much of this code assumes 512 bytes per sector or more.
* MS-DOS itself as I understand it relies on bytes per sector being a power of 2.
* this is to protect against errant FAT structures and to help prep this code
* later to work with the 1024 bytes/sector used by PC-98 floppy formats.
* When done, this code should be able to then handle the FDI/FDD images
* PC-98 games are normally distributed in on the internet.
*
* The value "128" comes from the smallest sector size possible on the floppy
* controller of MS-DOS based systems. */
/* NTS: Power of 2 test: A number is a power of 2 if (x & (x - 1)) == 0
*
* 15 15 & 14 01111 AND 01110 RESULT: 01110 (15)
* 16 16 & 15 10000 AND 01111 RESULT: 00000 (0)
* 17 17 & 16 10001 AND 10000 RESULT: 10000 (16) */
if (BPB.v.BPB_BytsPerSec < 128 || BPB.v.BPB_BytsPerSec > SECTOR_SIZE_MAX ||
(BPB.v.BPB_BytsPerSec & (BPB.v.BPB_BytsPerSec - 1)) != 0/*not a power of 2*/) {
LOG_MSG("FAT bytes/sector value %u not supported",BPB.v.BPB_BytsPerSec);
created_successfully = false;
return;
}
return;
}
/* another fault of this code is that it assumes the sector size of the medium matches
* the BPB_BytsPerSec value of the MS-DOS filesystem. if they don't match, problems
* will result. */
if (BPB.v.BPB_BytsPerSec != fatDrive::getSectSize()) {
LOG_MSG("FAT bytes/sector %u does not match disk image bytes/sector %u",
(unsigned int)BPB.v.BPB_BytsPerSec,
(unsigned int)fatDrive::getSectSize());
/* another fault of this code is that it assumes the sector size of the medium matches
* the BPB_BytsPerSec value of the MS-DOS filesystem. if they don't match, problems
* will result. */
if (BPB.v.BPB_BytsPerSec != fatDrive::getSectSize()) {
LOG_MSG("FAT bytes/sector %u does not match disk image bytes/sector %u",
(unsigned int)BPB.v.BPB_BytsPerSec,
(unsigned int)fatDrive::getSectSize());
created_successfully = false;
return;
}
return;
}
/* Filesystem must be contiguous to use absolute sectors, otherwise CHS will be used */
absolute = IS_PC98_ARCH || ((BPB.v.BPB_NumHeads == headscyl) && (BPB.v.BPB_SecPerTrk == cylsector));
@ -1940,53 +2091,53 @@ void fatDrive::fatDriveInit(const char *sysFilename, uint32_t bytesector, uint32
uint32_t RootDirSectors;
uint32_t DataSectors;
if (BPB.is_fat32()) {
/* FAT32 requires use of TotSec32, TotSec16 must be zero. */
if (BPB.v.BPB_TotSec32 == 0) {
LOG_MSG("BPB_TotSec32 == 0 and FAT32 BPB, not valid");
created_successfully = false;
return;
}
if (BPB.v32.BPB_RootClus < 2) {
LOG_MSG("BPB_RootClus == 0 and FAT32 BPB, not valid");
created_successfully = false;
return;
}
if (BPB.v.BPB_FATSz16 != 0) {
LOG_MSG("BPB_FATSz16 != 0 and FAT32 BPB, not valid");
created_successfully = false;
return;
}
if (BPB.v32.BPB_FATSz32 == 0) {
LOG_MSG("BPB_FATSz32 == 0 and FAT32 BPB, not valid");
created_successfully = false;
return;
}
if (BPB.is_fat32()) {
/* FAT32 requires use of TotSec32, TotSec16 must be zero. */
if (BPB.v.BPB_TotSec32 == 0) {
LOG_MSG("BPB_TotSec32 == 0 and FAT32 BPB, not valid");
created_successfully = false;
return;
}
if (BPB.v32.BPB_RootClus < 2) {
LOG_MSG("BPB_RootClus == 0 and FAT32 BPB, not valid");
created_successfully = false;
return;
}
if (BPB.v.BPB_FATSz16 != 0) {
LOG_MSG("BPB_FATSz16 != 0 and FAT32 BPB, not valid");
created_successfully = false;
return;
}
if (BPB.v32.BPB_FATSz32 == 0) {
LOG_MSG("BPB_FATSz32 == 0 and FAT32 BPB, not valid");
created_successfully = false;
return;
}
RootDirSectors = 0; /* FAT32 root directory has it's own allocation chain, instead of a fixed location */
DataSectors = (Bitu)BPB.v.BPB_TotSec32 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v32.BPB_FATSz32) + (Bitu)RootDirSectors);
CountOfClusters = DataSectors / BPB.v.BPB_SecPerClus;
firstDataSector = ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v32.BPB_FATSz32) + (Bitu)RootDirSectors) + (Bitu)partSectOff;
firstRootDirSect = 0;
}
else {
if (BPB.v.BPB_FATSz16 == 0) {
LOG_MSG("BPB_FATSz16 == 0 and not FAT32 BPB, not valid");
created_successfully = false;
return;
}
RootDirSectors = 0; /* FAT32 root directory has it's own allocation chain, instead of a fixed location */
DataSectors = (Bitu)BPB.v.BPB_TotSec32 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v32.BPB_FATSz32) + (Bitu)RootDirSectors);
CountOfClusters = DataSectors / BPB.v.BPB_SecPerClus;
firstDataSector = ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v32.BPB_FATSz32) + (Bitu)RootDirSectors) + (Bitu)partSectOff;
firstRootDirSect = 0;
}
else {
if (BPB.v.BPB_FATSz16 == 0) {
LOG_MSG("BPB_FATSz16 == 0 and not FAT32 BPB, not valid");
created_successfully = false;
return;
}
RootDirSectors = ((BPB.v.BPB_RootEntCnt * 32u) + (BPB.v.BPB_BytsPerSec - 1u)) / BPB.v.BPB_BytsPerSec;
RootDirSectors = ((BPB.v.BPB_RootEntCnt * 32u) + (BPB.v.BPB_BytsPerSec - 1u)) / BPB.v.BPB_BytsPerSec;
if (BPB.v.BPB_TotSec16 != 0)
DataSectors = (Bitu)BPB.v.BPB_TotSec16 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors);
else
DataSectors = (Bitu)BPB.v.BPB_TotSec32 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors);
if (BPB.v.BPB_TotSec16 != 0)
DataSectors = (Bitu)BPB.v.BPB_TotSec16 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors);
else
DataSectors = (Bitu)BPB.v.BPB_TotSec32 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors);
CountOfClusters = DataSectors / BPB.v.BPB_SecPerClus;
firstDataSector = ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors) + (Bitu)partSectOff;
firstRootDirSect = (Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)partSectOff;
}
CountOfClusters = DataSectors / BPB.v.BPB_SecPerClus;
firstDataSector = ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors) + (Bitu)partSectOff;
firstRootDirSect = (Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)partSectOff;
}
if(CountOfClusters < 4085) {
/* Volume is FAT12 */
@ -2027,6 +2178,8 @@ bool fatDrive::AllocationInfo32(uint32_t * _bytes_sector,uint32_t * _sectors_clu
uint32_t countFree = 0;
uint32_t i;
if (unformatted) return false;
for(i=0;i<CountOfClusters;i++) {
if(!getClusterValue(i+2))
countFree++;
@ -2041,6 +2194,8 @@ bool fatDrive::AllocationInfo32(uint32_t * _bytes_sector,uint32_t * _sectors_clu
}
bool fatDrive::AllocationInfo(uint16_t *_bytes_sector, uint8_t *_sectors_cluster, uint16_t *_total_clusters, uint16_t *_free_clusters) {
if (unformatted) return false;
if (BPB.is_fat32()) {
uint32_t bytes32,sectors32,clusters32,free32;
if (AllocationInfo32(&bytes32,&sectors32,&clusters32,&free32) &&
@ -2070,6 +2225,9 @@ bool fatDrive::AllocationInfo(uint16_t *_bytes_sector, uint8_t *_sectors_cluster
uint32_t fatDrive::getFirstFreeClust(void) {
uint32_t i;
if (unformatted) return 0;
for(i=searchFreeCluster;i<CountOfClusters;i++) {
if(!getClusterValue(i+2)) return ((searchFreeCluster=i)+2);
}
@ -2095,6 +2253,7 @@ const FAT_BootSector::bpb_union_t &fatDrive::GetBPB(void) { return BPB; }
void fatDrive::SetBPB(const FAT_BootSector::bpb_union_t &bpb) {
if (readonly) return;
unformatted = false;
BPB.v.BPB_BytsPerSec = bpb.v.BPB_BytsPerSec;
BPB.v.BPB_SecPerClus = bpb.v.BPB_SecPerClus;
BPB.v.BPB_RsvdSecCnt = bpb.v.BPB_RsvdSecCnt;
@ -2132,16 +2291,54 @@ void fatDrive::SetBPB(const FAT_BootSector::bpb_union_t &bpb) {
BPB.v32.BPB_BkBootSec = bpb.v32.BPB_BkBootSec;
}
FAT_BootSector bootbuffer = {};
loadedDisk->Read_AbsoluteSector(0+partSectOff,&bootbuffer);
FAT_BootSector bootbuffer = {};
loadedDisk->Read_AbsoluteSector(0+partSectOff,&bootbuffer);
if (BPB.is_fat32()) bootbuffer.bpb.v32=BPB.v32;
bootbuffer.bpb.v=BPB.v;
loadedDisk->Write_AbsoluteSector(0+partSectOff,&bootbuffer);
loadedDisk->Write_AbsoluteSector(0+partSectOff,&bootbuffer);
/* Get size of root dir in sectors */
uint32_t RootDirSectors;
uint32_t DataSectors;
if (BPB.is_fat32()) {
RootDirSectors = 0; /* FAT32 root directory has it's own allocation chain, instead of a fixed location */
DataSectors = (Bitu)BPB.v.BPB_TotSec32 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v32.BPB_FATSz32) + (Bitu)RootDirSectors);
CountOfClusters = DataSectors / BPB.v.BPB_SecPerClus;
firstDataSector = ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v32.BPB_FATSz32) + (Bitu)RootDirSectors) + (Bitu)partSectOff;
firstRootDirSect = 0;
}
else {
RootDirSectors = ((BPB.v.BPB_RootEntCnt * 32u) + (BPB.v.BPB_BytsPerSec - 1u)) / BPB.v.BPB_BytsPerSec;
if (BPB.v.BPB_TotSec16 != 0)
DataSectors = (Bitu)BPB.v.BPB_TotSec16 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors);
else
DataSectors = (Bitu)BPB.v.BPB_TotSec32 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors);
CountOfClusters = DataSectors / BPB.v.BPB_SecPerClus;
firstDataSector = ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors) + (Bitu)partSectOff;
firstRootDirSect = (Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)partSectOff;
}
if(CountOfClusters < 4085) {
/* Volume is FAT12 */
LOG_MSG("Mounted FAT volume is now FAT12 with %d clusters", CountOfClusters);
fattype = FAT12;
} else if (CountOfClusters < 65525 || !bpb.is_fat32()) {
LOG_MSG("Mounted FAT volume is now FAT16 with %d clusters", CountOfClusters);
fattype = FAT16;
} else {
LOG_MSG("Mounted FAT volume is now FAT32 with %d clusters", CountOfClusters);
fattype = FAT32;
}
}
bool fatDrive::FileCreate(DOS_File **file, const char *name, uint16_t attributes) {
const char *lfn = NULL;
if (unformatted) return false;
if (readonly) {
DOS_SetError(DOSERR_WRITE_PROTECTED);
return false;
@ -2243,6 +2440,8 @@ bool fatDrive::FileCreate(DOS_File **file, const char *name, uint16_t attributes
}
bool fatDrive::FileExists(const char *name) {
if (unformatted) return false;
direntry fileEntry = {};
uint32_t dummy1, dummy2;
uint16_t save_errorcode = dos.errorcode;
@ -2252,6 +2451,8 @@ bool fatDrive::FileExists(const char *name) {
}
bool fatDrive::FileOpen(DOS_File **file, const char *name, uint32_t flags) {
if (unformatted) return false;
direntry fileEntry = {};
uint32_t dirClust, subEntry;
@ -2280,6 +2481,8 @@ bool fatDrive::FileStat(const char * /*name*/, FileStat_Block *const /*stat_bloc
}
bool fatDrive::FileUnlink(const char * name) {
if (unformatted) return false;
if (readonly) {
DOS_SetError(DOSERR_WRITE_PROTECTED);
return false;
@ -2327,6 +2530,8 @@ bool fatDrive::FileUnlink(const char * name) {
}
bool fatDrive::FindFirst(const char *_dir, DOS_DTA &dta,bool fcb_findfirst) {
if (unformatted) return false;
direntry dummyClust = {};
// volume label searches always affect root directory, no matter the current directory, at least with FCBs
@ -2414,6 +2619,8 @@ static void copyDirEntry(const direntry *src, direntry *dst) {
}
bool fatDrive::FindNextInternal(uint32_t dirClustNumber, DOS_DTA &dta, direntry *foundEntry) {
if (unformatted) return false;
direntry sectbuf[MAX_DIRENTS_PER_SECTOR]; /* 16 directory entries per 512 byte sector */
uint32_t logentsector; /* Logical entry sector */
uint32_t entryoffset; /* Index offset within sector */
@ -2676,6 +2883,8 @@ nextfile:
}
bool fatDrive::FindNext(DOS_DTA &dta) {
if (unformatted) return false;
direntry dummyClust = {};
return FindNextInternal(lfn_filefind_handle>=LFN_FILEFIND_MAX?dta.GetDirIDCluster():(dnum[lfn_filefind_handle]?dnum[lfn_filefind_handle]:0), dta, &dummyClust);
@ -2683,6 +2892,8 @@ bool fatDrive::FindNext(DOS_DTA &dta) {
bool fatDrive::SetFileAttr(const char *name, uint16_t attr) {
if (unformatted) return false;
if (readonly) {
DOS_SetError(DOSERR_WRITE_PROTECTED);
return false;
@ -2706,6 +2917,8 @@ bool fatDrive::SetFileAttr(const char *name, uint16_t attr) {
}
bool fatDrive::GetFileAttr(const char *name, uint16_t *attr) {
if (unformatted) return false;
direntry fileEntry = {};
uint32_t dirClust, subEntry;
@ -2748,6 +2961,8 @@ unsigned long fatDrive::GetSerial() {
}
bool fatDrive::directoryBrowse(uint32_t dirClustNumber, direntry *useEntry, int32_t entNum, int32_t start/*=0*/) {
if (unformatted) return false;
direntry sectbuf[MAX_DIRENTS_PER_SECTOR]; /* 16 directory entries per 512 byte sector */
uint32_t tmpsector;
@ -2783,6 +2998,8 @@ bool fatDrive::directoryBrowse(uint32_t dirClustNumber, direntry *useEntry, int3
}
bool fatDrive::directoryChange(uint32_t dirClustNumber, const direntry *useEntry, int32_t entNum) {
if (unformatted) return false;
direntry sectbuf[MAX_DIRENTS_PER_SECTOR]; /* 16 directory entries per 512 byte sector */
uint32_t tmpsector = 0;
@ -2817,6 +3034,8 @@ bool fatDrive::directoryChange(uint32_t dirClustNumber, const direntry *useEntry
}
bool fatDrive::addDirectoryEntry(uint32_t dirClustNumber, const direntry& useEntry,const char *lfn) {
if (unformatted) return false;
direntry sectbuf[MAX_DIRENTS_PER_SECTOR]; /* 16 directory entries per 512 byte sector */
uint32_t tmpsector;
uint16_t dirPos = 0;
@ -2977,6 +3196,8 @@ void fatDrive::zeroOutCluster(uint32_t clustNumber) {
}
bool fatDrive::MakeDir(const char *dir) {
if (unformatted) return false;
const char *lfn = NULL;
if (readonly) {
@ -3080,6 +3301,8 @@ bool fatDrive::MakeDir(const char *dir) {
}
bool fatDrive::RemoveDir(const char *dir) {
if (unformatted) return false;
if (readonly) {
DOS_SetError(DOSERR_WRITE_PROTECTED);
return false;
@ -3149,6 +3372,8 @@ bool fatDrive::RemoveDir(const char *dir) {
}
bool fatDrive::Rename(const char * oldname, const char * newname) {
if (unformatted) return false;
const char *lfn = NULL;
if (readonly) {
@ -3235,6 +3460,8 @@ bool fatDrive::Rename(const char * oldname, const char * newname) {
}
bool fatDrive::TestDir(const char *dir) {
if (unformatted) return false;
uint32_t dummyClust;
/* root directory is directory */
@ -3244,14 +3471,17 @@ bool fatDrive::TestDir(const char *dir) {
}
uint32_t fatDrive::GetPartitionOffset(void) {
if (unformatted) return 0;
return partSectOff;
}
uint32_t fatDrive::GetFirstClusterOffset(void) {
if (unformatted) return 0;
return firstDataSector - partSectOff;
}
uint32_t fatDrive::GetHighestClusterNumber(void) {
if (unformatted) return 0;
return CountOfClusters + 1ul;
}

View File

@ -519,6 +519,8 @@ public:
virtual uint32_t GetPartitionOffset(void);
virtual uint32_t GetFirstClusterOffset(void);
virtual uint32_t GetHighestClusterNumber(void);
bool unformatted = false;
};
PhysPt DOS_Get_DPB(unsigned int dos_drive);