DOSBox-X
|
00001 /* 00002 * Copyright (C) 2002-2020 The DOSBox Team 00003 * 00004 * This program is free software; you can redistribute it and/or modify 00005 * it under the terms of the GNU General Public License as published by 00006 * the Free Software Foundation; either version 2 of the License, or 00007 * (at your option) any later version. 00008 * 00009 * This program is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 * GNU General Public License for more details. 00013 * 00014 * You should have received a copy of the GNU General Public License along 00015 * with this program; if not, write to the Free Software Foundation, Inc., 00016 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00017 */ 00018 00019 00020 #include <stdio.h> 00021 #include <stdlib.h> 00022 #include <string.h> 00023 #include <time.h> 00024 #include "dosbox.h" 00025 #include "dos_inc.h" 00026 #include "drives.h" 00027 #include "support.h" 00028 #include "cross.h" 00029 #include "bios.h" 00030 #include "bios_disk.h" 00031 #include "qcow2_disk.h" 00032 #include "bitop.h" 00033 00034 #include <algorithm> 00035 00036 #define IMGTYPE_FLOPPY 0 00037 #define IMGTYPE_ISO 1 00038 #define IMGTYPE_HDD 2 00039 00040 #define FAT12 0 00041 #define FAT16 1 00042 #define FAT32 2 00043 00044 static Bit16u dpos[256]; 00045 static Bit32u dnum[256]; 00046 extern bool wpcolon, force_sfn; 00047 extern int lfn_filefind_handle; 00048 00049 bool filename_not_8x3(const char *n) { 00050 unsigned int i; 00051 00052 i = 0; 00053 while (*n != 0) { 00054 if (*n == '.') break; 00055 if (*n<=32||*n==127||*n=='"'||*n=='+'||*n=='='||*n==','||*n==';'||*n==':'||*n=='<'||*n=='>'||*n=='['||*n==']'||*n=='|'||*n=='?'||*n=='*') return true; 00056 i++; 00057 n++; 00058 } 00059 if (i > 8) return true; 00060 if (*n == 0) return false; /* made it past 8 or less normal chars and end of string: normal */ 00061 00062 /* skip dot */ 00063 assert(*n == '.'); 00064 n++; 00065 00066 i = 0; 00067 while (*n != 0) { 00068 if (*n == '.') return true; /* another '.' means LFN */ 00069 if (*n<=32||*n==127||*n=='"'||*n=='+'||*n=='='||*n==','||*n==';'||*n==':'||*n=='<'||*n=='>'||*n=='['||*n==']'||*n=='|'||*n=='?'||*n=='*') return true; 00070 i++; 00071 n++; 00072 } 00073 if (i > 3) return true; 00074 00075 return false; /* it is 8.3 case */ 00076 } 00077 00078 /* Assuming an LFN call, if the name is not strict 8.3 uppercase, return true. 00079 * If the name is strict 8.3 uppercase like "FILENAME.TXT" there is no point making an LFN because it is a waste of space */ 00080 bool filename_not_strict_8x3(const char *n) { 00081 if (filename_not_8x3(n)) return true; 00082 for (unsigned int i=0; i<strlen(n); i++) 00083 if (n[i]>='a' && n[i]<='z') 00084 return true; 00085 return false; /* it is strict 8.3 upper case */ 00086 } 00087 00088 char sfn[DOS_NAMELENGTH_ASCII]; 00089 /* Generate 8.3 names from LFNs, with tilde usage (from ~1 to ~9999). */ 00090 char* fatDrive::Generate_SFN(const char *path, const char *name) { 00091 if (!filename_not_8x3(name)) { 00092 strcpy(sfn, name); 00093 upcase(sfn); 00094 return sfn; 00095 } 00096 char lfn[LFN_NAMELENGTH+1], fullname[DOS_PATHLENGTH+DOS_NAMELENGTH_ASCII], *n; 00097 if (name==NULL||!*name) return NULL; 00098 if (strlen(name)>LFN_NAMELENGTH) { 00099 strncpy(lfn, name, LFN_NAMELENGTH); 00100 lfn[LFN_NAMELENGTH]=0; 00101 } else 00102 strcpy(lfn, name); 00103 if (!strlen(lfn)) return NULL; 00104 direntry fileEntry = {}; 00105 Bit32u dirClust, subEntry; 00106 unsigned int k=1, i, t=10000; 00107 while (k<10000) { 00108 n=lfn; 00109 if (t>strlen(n)||k==1||k==10||k==100||k==1000) { 00110 i=0; 00111 *sfn=0; 00112 while (*n == '.'||*n == ' ') n++; 00113 while (strlen(n)&&(*(n+strlen(n)-1)=='.'||*(n+strlen(n)-1)==' ')) *(n+strlen(n)-1)=0; 00114 while (*n != 0 && *n != '.' && i<(k<10?6u:(k<100?5u:(k<1000?4:3u)))) { 00115 if (*n == ' ') { 00116 n++; 00117 continue; 00118 } 00119 if (*n=='"'||*n=='+'||*n=='='||*n==','||*n==';'||*n==':'||*n=='<'||*n=='>'||*n=='['||*n==']'||*n=='|'||*n=='?'||*n=='*') { 00120 sfn[i++]='_'; 00121 n++; 00122 } else 00123 sfn[i++]=toupper(*(n++)); 00124 } 00125 sfn[i++]='~'; 00126 t=i; 00127 } else 00128 i=t; 00129 if (k<10) 00130 sfn[i++]='0'+k; 00131 else if (k<100) { 00132 sfn[i++]='0'+(k/10); 00133 sfn[i++]='0'+(k%10); 00134 } else if (k<1000) { 00135 sfn[i++]='0'+(k/100); 00136 sfn[i++]='0'+((k%100)/10); 00137 sfn[i++]='0'+(k%10); 00138 } else { 00139 sfn[i++]='0'+(k/1000); 00140 sfn[i++]='0'+((k%1000)/100); 00141 sfn[i++]='0'+((k%100)/10); 00142 sfn[i++]='0'+(k%10); 00143 } 00144 if (t>strlen(n)||k==1||k==10||k==100||k==1000) { 00145 char *p=strrchr(n, '.'); 00146 if (p!=NULL) { 00147 sfn[i++]='.'; 00148 n=p+1; 00149 while (*n == '.') n++; 00150 int j=0; 00151 while (*n != 0 && j++<3) { 00152 if (*n == ' ') { 00153 n++; 00154 continue; 00155 } 00156 if (*n=='"'||*n=='+'||*n=='='||*n==','||*n==';'||*n==':'||*n=='<'||*n=='>'||*n=='['||*n==']'||*n=='|'||*n=='?'||*n=='*') { 00157 sfn[i++]='_'; 00158 n++; 00159 } else 00160 sfn[i++]=toupper(*(n++)); 00161 } 00162 } 00163 sfn[i++]=0; 00164 } 00165 strcpy(fullname, path); 00166 strcat(fullname, sfn); 00167 if(!getFileDirEntry(fullname, &fileEntry, &dirClust, &subEntry,/*dirOk*/true)) return sfn; 00168 k++; 00169 } 00170 return NULL; 00171 } 00172 00173 class fatFile : public DOS_File { 00174 public: 00175 fatFile(const char* name, Bit32u startCluster, Bit32u fileLen, fatDrive *useDrive); 00176 bool Read(Bit8u * data,Bit16u * size); 00177 bool Write(const Bit8u * data,Bit16u * size); 00178 bool Seek(Bit32u * pos,Bit32u type); 00179 bool Close(); 00180 Bit16u GetInformation(void); 00181 void Flush(void); 00182 bool UpdateDateTimeFromHost(void); 00183 Bit32u GetSeekPos(void); 00184 Bit32u firstCluster; 00185 Bit32u seekpos = 0; 00186 Bit32u filelength; 00187 Bit32u currentSector = 0; 00188 Bit32u curSectOff = 0; 00189 Bit8u sectorBuffer[SECTOR_SIZE_MAX]; 00190 /* Record of where in the directory structure this file is located */ 00191 Bit32u dirCluster = 0; 00192 Bit32u dirIndex = 0; 00193 00194 bool modified = false; 00195 bool loadedSector = false; 00196 fatDrive *myDrive; 00197 00198 #if 0/*unused*/ 00199 private: 00200 enum { NONE,READ,WRITE } last_action; 00201 Bit16u info; 00202 #endif 00203 }; 00204 00205 void time_t_to_DOS_DateTime(Bit16u &t,Bit16u &d,time_t unix_time) { 00206 const struct tm *tm = localtime(&unix_time); 00207 if (tm == NULL) return; 00208 00209 /* NTS: tm->tm_year = years since 1900, 00210 * tm->tm_mon = months since January therefore January == 0 00211 * tm->tm_mday = day of the month, starting with 1 */ 00212 00213 t = ((unsigned int)tm->tm_hour << 11u) + ((unsigned int)tm->tm_min << 5u) + ((unsigned int)tm->tm_sec >> 1u); 00214 d = (((unsigned int)tm->tm_year - 80u) << 9u) + (((unsigned int)tm->tm_mon + 1u) << 5u) + (unsigned int)tm->tm_mday; 00215 } 00216 00217 /* IN - char * filename: Name in regular filename format, e.g. bob.txt */ 00218 /* OUT - char * filearray: Name in DOS directory format, eleven char, e.g. bob txt */ 00219 static void convToDirFile(const char *filename, char *filearray) { 00220 Bit32u charidx = 0; 00221 Bit32u flen,i; 00222 flen = (Bit32u)strlen(filename); 00223 memset(filearray, 32, 11); 00224 for(i=0;i<flen;i++) { 00225 if(charidx >= 11) break; 00226 if(filename[i] != '.') { 00227 filearray[charidx] = filename[i]; 00228 charidx++; 00229 } else { 00230 charidx = 8; 00231 } 00232 } 00233 } 00234 00235 fatFile::fatFile(const char* /*name*/, Bit32u startCluster, Bit32u fileLen, fatDrive *useDrive) : firstCluster(startCluster), filelength(fileLen), myDrive(useDrive) { 00236 Bit32u seekto = 0; 00237 open = true; 00238 memset(§orBuffer[0], 0, sizeof(sectorBuffer)); 00239 00240 if(filelength > 0) { 00241 Seek(&seekto, DOS_SEEK_SET); 00242 } 00243 } 00244 00245 void fatFile::Flush(void) { 00246 if (loadedSector) { 00247 myDrive->writeSector(currentSector, sectorBuffer); 00248 loadedSector = false; 00249 } 00250 00251 if (modified || newtime) { 00252 direntry tmpentry = {}; 00253 00254 myDrive->directoryBrowse(dirCluster, &tmpentry, (Bit32s)dirIndex); 00255 00256 if (newtime) { 00257 tmpentry.modTime = time; 00258 tmpentry.modDate = date; 00259 } 00260 else { 00261 Bit16u ct,cd; 00262 00263 time_t_to_DOS_DateTime(/*&*/ct,/*&*/cd,::time(NULL)); 00264 00265 tmpentry.modTime = ct; 00266 tmpentry.modDate = cd; 00267 } 00268 00269 myDrive->directoryChange(dirCluster, &tmpentry, (Bit32s)dirIndex); 00270 modified = false; 00271 newtime = false; 00272 } 00273 } 00274 00275 bool fatFile::Read(Bit8u * data, Bit16u *size) { 00276 if ((this->flags & 0xf) == OPEN_WRITE) { // check if file opened in write-only mode 00277 DOS_SetError(DOSERR_ACCESS_DENIED); 00278 return false; 00279 } 00280 Bit16u sizedec, sizecount; 00281 if(seekpos >= filelength) { 00282 *size = 0; 00283 return true; 00284 } 00285 00286 if (!loadedSector) { 00287 currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos); 00288 if(currentSector == 0) { 00289 /* EOC reached before EOF */ 00290 *size = 0; 00291 loadedSector = false; 00292 return true; 00293 } 00294 curSectOff = seekpos % myDrive->getSectorSize(); 00295 myDrive->readSector(currentSector, sectorBuffer); 00296 loadedSector = true; 00297 } 00298 00299 sizedec = *size; 00300 sizecount = 0; 00301 while(sizedec != 0) { 00302 if(seekpos >= filelength) { 00303 *size = sizecount; 00304 return true; 00305 } 00306 data[sizecount++] = sectorBuffer[curSectOff++]; 00307 seekpos++; 00308 if(curSectOff >= myDrive->getSectorSize()) { 00309 currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos); 00310 if(currentSector == 0) { 00311 /* EOC reached before EOF */ 00312 //LOG_MSG("EOC reached before EOF, seekpos %d, filelen %d", seekpos, filelength); 00313 *size = sizecount; 00314 loadedSector = false; 00315 return true; 00316 } 00317 curSectOff = 0; 00318 myDrive->readSector(currentSector, sectorBuffer); 00319 loadedSector = true; 00320 //LOG_MSG("Reading absolute sector at %d for seekpos %d", currentSector, seekpos); 00321 } 00322 --sizedec; 00323 } 00324 *size =sizecount; 00325 return true; 00326 } 00327 00328 bool fatFile::Write(const Bit8u * data, Bit16u *size) { 00329 if ((this->flags & 0xf) == OPEN_READ) { // check if file opened in read-only mode 00330 DOS_SetError(DOSERR_ACCESS_DENIED); 00331 return false; 00332 } 00333 00334 direntry tmpentry = {}; 00335 Bit16u sizedec, sizecount; 00336 sizedec = *size; 00337 sizecount = 0; 00338 00339 if(seekpos < filelength && *size == 0) { 00340 /* Truncate file to current position */ 00341 myDrive->deleteClustChain(firstCluster, seekpos); 00342 filelength = seekpos; 00343 if (filelength == 0) firstCluster = 0; /* A file of length zero has a starting cluster of zero as well */ 00344 modified = true; 00345 goto finalizeWrite; 00346 } 00347 00348 if(seekpos > filelength) { 00349 /* Extend file to current position */ 00350 Bit32u clustSize = myDrive->getClusterSize(); 00351 if(filelength == 0) { 00352 firstCluster = myDrive->getFirstFreeClust(); 00353 if(firstCluster == 0) goto finalizeWrite; // out of space 00354 myDrive->allocateCluster(firstCluster, 0); 00355 filelength = clustSize; 00356 } 00357 00358 /* round up */ 00359 filelength += clustSize - 1; 00360 filelength -= filelength % clustSize; 00361 00362 /* add clusters until the file length is correct */ 00363 while(filelength < seekpos) { 00364 if(myDrive->appendCluster(firstCluster) == 0) goto finalizeWrite; // out of space 00365 filelength += clustSize; 00366 } 00367 assert(filelength < (seekpos+clustSize)); 00368 00369 /* limit file length to seekpos, then bail out if write count is zero */ 00370 modified = true; 00371 if(filelength > seekpos) filelength = seekpos; 00372 if(*size == 0) goto finalizeWrite; 00373 } 00374 00375 while(sizedec != 0) { 00376 /* Increase filesize if necessary */ 00377 if(seekpos >= filelength) { 00378 if(filelength == 0) { 00379 firstCluster = myDrive->getFirstFreeClust(); 00380 if(firstCluster == 0) goto finalizeWrite; // out of space 00381 myDrive->allocateCluster(firstCluster, 0); 00382 currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos); 00383 if (currentSector == 0) { 00384 /* I guess allocateCluster() didn't work after all. This check is necessary to prevent 00385 * this conditon from treating the BOOT SECTOR as a file. */ 00386 LOG(LOG_DOSMISC,LOG_WARN)("FAT file write: unable to allocate first cluster, erroring out"); 00387 goto finalizeWrite; 00388 } 00389 myDrive->readSector(currentSector, sectorBuffer); 00390 loadedSector = true; 00391 } 00392 if (!loadedSector) { 00393 currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos); 00394 if(currentSector == 0) { 00395 /* EOC reached before EOF - try to increase file allocation */ 00396 myDrive->appendCluster(firstCluster); 00397 /* Try getting sector again */ 00398 currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos); 00399 if(currentSector == 0) { 00400 /* No can do. lets give up and go home. We must be out of room */ 00401 goto finalizeWrite; 00402 } 00403 } 00404 curSectOff = seekpos % myDrive->getSectorSize(); 00405 myDrive->readSector(currentSector, sectorBuffer); 00406 loadedSector = true; 00407 } 00408 filelength = seekpos+1; 00409 } 00410 --sizedec; 00411 modified = true; 00412 sectorBuffer[curSectOff++] = data[sizecount++]; 00413 seekpos++; 00414 if(curSectOff >= myDrive->getSectorSize()) { 00415 if(loadedSector) myDrive->writeSector(currentSector, sectorBuffer); 00416 loadedSector = false; 00417 00418 if (sizedec == 0) goto finalizeWrite; 00419 00420 currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos); 00421 if(currentSector == 0) { 00422 /* EOC reached before EOF - try to increase file allocation */ 00423 myDrive->appendCluster(firstCluster); 00424 /* Try getting sector again */ 00425 currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos); 00426 if(currentSector == 0) { 00427 /* No can do. lets give up and go home. We must be out of room */ 00428 goto finalizeWrite; 00429 } 00430 } 00431 curSectOff = 0; 00432 myDrive->readSector(currentSector, sectorBuffer); 00433 loadedSector = true; 00434 } 00435 } 00436 if(curSectOff>0 && loadedSector) myDrive->writeSector(currentSector, sectorBuffer); 00437 00438 finalizeWrite: 00439 myDrive->directoryBrowse(dirCluster, &tmpentry, (Bit32s)dirIndex); 00440 tmpentry.entrysize = filelength; 00441 00442 if (myDrive->GetBPB().is_fat32()) 00443 tmpentry.SetCluster32(firstCluster); 00444 else 00445 tmpentry.loFirstClust = (Bit16u)firstCluster; 00446 00447 myDrive->directoryChange(dirCluster, &tmpentry, (Bit32s)dirIndex); 00448 00449 *size =sizecount; 00450 return true; 00451 } 00452 00453 bool fatFile::Seek(Bit32u *pos, Bit32u type) { 00454 Bit32s seekto=0; 00455 00456 switch(type) { 00457 case DOS_SEEK_SET: 00458 seekto = (Bit32s)*pos; 00459 break; 00460 case DOS_SEEK_CUR: 00461 /* Is this relative seek signed? */ 00462 seekto = (Bit32s)*pos + (Bit32s)seekpos; 00463 break; 00464 case DOS_SEEK_END: 00465 seekto = (Bit32s)filelength + (Bit32s)*pos; 00466 break; 00467 } 00468 // LOG_MSG("Seek to %d with type %d (absolute value %d)", *pos, type, seekto); 00469 00470 if(seekto<0) seekto = 0; 00471 seekpos = (Bit32u)seekto; 00472 currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos); 00473 if (currentSector == 0) { 00474 /* not within file size, thus no sector is available */ 00475 loadedSector = false; 00476 } else { 00477 curSectOff = seekpos % myDrive->getSectorSize(); 00478 myDrive->readSector(currentSector, sectorBuffer); 00479 loadedSector = true; 00480 } 00481 *pos = seekpos; 00482 return true; 00483 } 00484 00485 bool fatFile::Close() { 00486 /* Flush buffer */ 00487 if (loadedSector) myDrive->writeSector(currentSector, sectorBuffer); 00488 00489 if (modified || newtime) { 00490 direntry tmpentry = {}; 00491 00492 myDrive->directoryBrowse(dirCluster, &tmpentry, (Bit32s)dirIndex); 00493 00494 if (newtime) { 00495 tmpentry.modTime = time; 00496 tmpentry.modDate = date; 00497 } 00498 else { 00499 Bit16u ct,cd; 00500 00501 time_t_to_DOS_DateTime(/*&*/ct,/*&*/cd,::time(NULL)); 00502 00503 tmpentry.modTime = ct; 00504 tmpentry.modDate = cd; 00505 } 00506 00507 myDrive->directoryChange(dirCluster, &tmpentry, (Bit32s)dirIndex); 00508 } 00509 00510 return false; 00511 } 00512 00513 Bit16u fatFile::GetInformation(void) { 00514 return 0; 00515 } 00516 00517 bool fatFile::UpdateDateTimeFromHost(void) { 00518 return true; 00519 } 00520 00521 Bit32u fatFile::GetSeekPos() { 00522 return seekpos; 00523 } 00524 00525 Bit32u fatDrive::getClustFirstSect(Bit32u clustNum) { 00526 return ((clustNum - 2) * BPB.v.BPB_SecPerClus) + firstDataSector; 00527 } 00528 00529 Bit32u fatDrive::getClusterValue(Bit32u clustNum) { 00530 Bit32u fatoffset=0; 00531 Bit32u fatsectnum; 00532 Bit32u fatentoff; 00533 Bit32u clustValue=0; 00534 00535 switch(fattype) { 00536 case FAT12: 00537 fatoffset = clustNum + (clustNum / 2); 00538 break; 00539 case FAT16: 00540 fatoffset = clustNum * 2; 00541 break; 00542 case FAT32: 00543 fatoffset = clustNum * 4; 00544 break; 00545 } 00546 fatsectnum = BPB.v.BPB_RsvdSecCnt + (fatoffset / BPB.v.BPB_BytsPerSec) + partSectOff; 00547 fatentoff = fatoffset % BPB.v.BPB_BytsPerSec; 00548 00549 if (BPB.is_fat32()) { 00550 if (fatsectnum >= (BPB.v.BPB_RsvdSecCnt + BPB.v32.BPB_FATSz32 + partSectOff)) { 00551 LOG(LOG_DOSMISC,LOG_ERROR)("Attempt to read cluster entry from FAT that out of range (outside the FAT table) cluster %u",(unsigned int)clustNum); 00552 return 0; 00553 } 00554 } 00555 else { 00556 if (fatsectnum >= (BPB.v.BPB_RsvdSecCnt + BPB.v.BPB_FATSz16 + partSectOff)) { 00557 LOG(LOG_DOSMISC,LOG_ERROR)("Attempt to read cluster entry from FAT that out of range (outside the FAT table) cluster %u",(unsigned int)clustNum); 00558 return 0; 00559 } 00560 } 00561 00562 assert((BPB.v.BPB_BytsPerSec * (Bitu)2) <= sizeof(fatSectBuffer)); 00563 00564 if(curFatSect != fatsectnum) { 00565 /* Load two sectors at once for FAT12 */ 00566 readSector(fatsectnum, &fatSectBuffer[0]); 00567 if (fattype==FAT12) 00568 readSector(fatsectnum+1, &fatSectBuffer[BPB.v.BPB_BytsPerSec]); 00569 curFatSect = fatsectnum; 00570 } 00571 00572 switch(fattype) { 00573 case FAT12: 00574 clustValue = *((Bit16u *)&fatSectBuffer[fatentoff]); 00575 if(clustNum & 0x1) { 00576 clustValue >>= 4; 00577 } else { 00578 clustValue &= 0xfff; 00579 } 00580 break; 00581 case FAT16: 00582 clustValue = *((Bit16u *)&fatSectBuffer[fatentoff]); 00583 break; 00584 case FAT32: 00585 clustValue = *((Bit32u *)&fatSectBuffer[fatentoff]) & 0x0FFFFFFFul; /* Well, actually it's FAT28. Upper 4 bits are "reserved". */ 00586 break; 00587 } 00588 00589 return clustValue; 00590 } 00591 00592 void fatDrive::setClusterValue(Bit32u clustNum, Bit32u clustValue) { 00593 Bit32u fatoffset=0; 00594 Bit32u fatsectnum; 00595 Bit32u fatentoff; 00596 00597 switch(fattype) { 00598 case FAT12: 00599 fatoffset = clustNum + (clustNum / 2); 00600 break; 00601 case FAT16: 00602 fatoffset = clustNum * 2; 00603 break; 00604 case FAT32: 00605 fatoffset = clustNum * 4; 00606 break; 00607 } 00608 fatsectnum = BPB.v.BPB_RsvdSecCnt + (fatoffset / BPB.v.BPB_BytsPerSec) + partSectOff; 00609 fatentoff = fatoffset % BPB.v.BPB_BytsPerSec; 00610 00611 if (BPB.is_fat32()) { 00612 if (fatsectnum >= (BPB.v.BPB_RsvdSecCnt + BPB.v32.BPB_FATSz32 + partSectOff)) { 00613 LOG(LOG_DOSMISC,LOG_ERROR)("Attempt to write cluster entry from FAT that out of range (outside the FAT table) cluster %u",(unsigned int)clustNum); 00614 return; 00615 } 00616 } 00617 else { 00618 if (fatsectnum >= (BPB.v.BPB_RsvdSecCnt + BPB.v.BPB_FATSz16 + partSectOff)) { 00619 LOG(LOG_DOSMISC,LOG_ERROR)("Attempt to write cluster entry from FAT that out of range (outside the FAT table) cluster %u",(unsigned int)clustNum); 00620 return; 00621 } 00622 } 00623 00624 assert((BPB.v.BPB_BytsPerSec * (Bitu)2) <= sizeof(fatSectBuffer)); 00625 00626 if(curFatSect != fatsectnum) { 00627 /* Load two sectors at once for FAT12 */ 00628 readSector(fatsectnum, &fatSectBuffer[0]); 00629 if (fattype==FAT12) 00630 readSector(fatsectnum+1, &fatSectBuffer[BPB.v.BPB_BytsPerSec]); 00631 curFatSect = fatsectnum; 00632 } 00633 00634 switch(fattype) { 00635 case FAT12: { 00636 Bit16u tmpValue = *((Bit16u *)&fatSectBuffer[fatentoff]); 00637 if(clustNum & 0x1) { 00638 clustValue &= 0xfff; 00639 clustValue <<= 4; 00640 tmpValue &= 0xf; 00641 tmpValue |= (Bit16u)clustValue; 00642 00643 } else { 00644 clustValue &= 0xfff; 00645 tmpValue &= 0xf000; 00646 tmpValue |= (Bit16u)clustValue; 00647 } 00648 *((Bit16u *)&fatSectBuffer[fatentoff]) = tmpValue; 00649 break; 00650 } 00651 case FAT16: 00652 *((Bit16u *)&fatSectBuffer[fatentoff]) = (Bit16u)clustValue; 00653 break; 00654 case FAT32: 00655 *((Bit32u *)&fatSectBuffer[fatentoff]) = clustValue; 00656 break; 00657 } 00658 for(unsigned int fc=0;fc<BPB.v.BPB_NumFATs;fc++) { 00659 writeSector(fatsectnum + (fc * (BPB.is_fat32() ? BPB.v32.BPB_FATSz32 : BPB.v.BPB_FATSz16)), &fatSectBuffer[0]); 00660 if (fattype==FAT12) { 00661 if (fatentoff >= (BPB.v.BPB_BytsPerSec-1U)) 00662 writeSector(fatsectnum+1u+(fc * (BPB.is_fat32() ? BPB.v32.BPB_FATSz32 : BPB.v.BPB_FATSz16)), &fatSectBuffer[BPB.v.BPB_BytsPerSec]); 00663 } 00664 } 00665 } 00666 00667 bool fatDrive::getEntryName(const char *fullname, char *entname) { 00668 char dirtoken[DOS_PATHLENGTH]; 00669 00670 char * findDir; 00671 char * findFile; 00672 strcpy(dirtoken,fullname); 00673 00674 //LOG_MSG("Testing for filename %s", fullname); 00675 findDir = strtok(dirtoken,"\\"); 00676 if (findDir==NULL) { 00677 return true; // root always exists 00678 } 00679 findFile = findDir; 00680 while(findDir != NULL) { 00681 findFile = findDir; 00682 findDir = strtok(NULL,"\\"); 00683 } 00684 int j=0; 00685 for (int i=0; i<(int)strlen(findFile); i++) 00686 if (findFile[i]!=' '&&findFile[i]!='"'&&findFile[i]!='+'&&findFile[i]!='='&&findFile[i]!=','&&findFile[i]!=';'&&findFile[i]!=':'&&findFile[i]!='<'&&findFile[i]!='>'&&findFile[i]!='['&&findFile[i]!=']'&&findFile[i]!='|'&&findFile[i]!='?'&&findFile[i]!='*') findFile[j++]=findFile[i]; 00687 findFile[j]=0; 00688 if (strlen(findFile)>12) 00689 strncpy(entname, findFile, 12); 00690 else 00691 strcpy(entname, findFile); 00692 upcase(entname); 00693 return true; 00694 } 00695 00696 void fatDrive::UpdateBootVolumeLabel(const char *label) { 00697 FAT_BootSector bootbuffer = {}; 00698 00699 if (BPB.v.BPB_BootSig == 0x28 || BPB.v.BPB_BootSig == 0x29) { 00700 unsigned int i = 0; 00701 00702 loadedDisk->Read_AbsoluteSector(0+partSectOff,&bootbuffer); 00703 00704 while (i < 11 && *label != 0) bootbuffer.bpb.v.BPB_VolLab[i++] = toupper(*label++); 00705 while (i < 11) bootbuffer.bpb.v.BPB_VolLab[i++] = ' '; 00706 00707 loadedDisk->Write_AbsoluteSector(0+partSectOff,&bootbuffer); 00708 } 00709 } 00710 00711 void fatDrive::SetLabel(const char *label, bool /*iscdrom*/, bool /*updatable*/) { 00712 direntry sectbuf[MAX_DIRENTS_PER_SECTOR]; /* 16 directory entries per 512 byte sector */ 00713 Bit32u dirClustNumber; 00714 Bit32u logentsector; /* Logical entry sector */ 00715 Bit32u entryoffset; /* Index offset within sector */ 00716 Bit32u tmpsector; 00717 Bit16u dirPos = 0; 00718 00719 size_t dirent_per_sector = getSectSize() / sizeof(direntry); 00720 assert(dirent_per_sector <= MAX_DIRENTS_PER_SECTOR); 00721 assert((dirent_per_sector * sizeof(direntry)) <= SECTOR_SIZE_MAX); 00722 00723 if(!getDirClustNum("\\", &dirClustNumber, false)) 00724 return; 00725 00726 nextfile: 00727 logentsector = (Bit32u)((size_t)dirPos / dirent_per_sector); 00728 entryoffset = (Bit32u)((size_t)dirPos % dirent_per_sector); 00729 00730 if(dirClustNumber==0) { 00731 assert(!BPB.is_fat32()); 00732 if(dirPos >= BPB.v.BPB_RootEntCnt) return; 00733 tmpsector = firstRootDirSect+logentsector; 00734 } else { 00735 /* A zero sector number can't happen */ 00736 tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector); 00737 /* A zero sector number can't happen - we need to allocate more room for this directory*/ 00738 if(tmpsector == 0) { 00739 if (*label == 0) return; // removing volume label, so stop now 00740 Bit32u newClust; 00741 newClust = appendCluster(dirClustNumber); 00742 if(newClust == 0) return; 00743 zeroOutCluster(newClust); 00744 /* Try again to get tmpsector */ 00745 tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector); 00746 if(tmpsector == 0) return; /* Give up if still can't get more room for directory */ 00747 } 00748 readSector(tmpsector,sectbuf); 00749 } 00750 readSector(tmpsector,sectbuf); 00751 dirPos++; 00752 00753 if (dos.version.major >= 7 || uselfn) { 00754 /* skip LFN entries */ 00755 if ((sectbuf[entryoffset].attrib & 0x3F) == 0x0F) 00756 goto nextfile; 00757 } 00758 00759 if (*label != 0) { 00760 /* adding a volume label */ 00761 if (sectbuf[entryoffset].entryname[0] == 0x00 || 00762 sectbuf[entryoffset].entryname[0] == 0xE5) { 00763 memset(§buf[entryoffset],0,sizeof(sectbuf[entryoffset])); 00764 sectbuf[entryoffset].attrib = DOS_ATTR_VOLUME; 00765 { 00766 unsigned int j = 0; 00767 const char *s = label; 00768 while (j < 11 && *s != 0) sectbuf[entryoffset].entryname[j++] = toupper(*s++); 00769 while (j < 11) sectbuf[entryoffset].entryname[j++] = ' '; 00770 } 00771 writeSector(tmpsector,sectbuf); 00772 labelCache.SetLabel(label, false, true); 00773 UpdateBootVolumeLabel(label); 00774 return; 00775 } 00776 } 00777 else { 00778 if (sectbuf[entryoffset].entryname[0] == 0x00) 00779 return; 00780 if (sectbuf[entryoffset].entryname[0] == 0xe5) 00781 goto nextfile; 00782 00783 if (sectbuf[entryoffset].attrib & DOS_ATTR_VOLUME) { 00784 /* TODO: There needs to be a way for FCB delete to erase the volume label by name instead 00785 * of just picking the first one */ 00786 /* found one */ 00787 sectbuf[entryoffset].entryname[0] = 0xe5; 00788 writeSector(tmpsector,sectbuf); 00789 labelCache.SetLabel("", false, true); 00790 UpdateBootVolumeLabel("NO NAME"); 00791 return; 00792 } 00793 } 00794 00795 goto nextfile; 00796 } 00797 00798 /* NTS: This function normally will only return files. Every element of the path that is a directory is entered into. 00799 * If every element is a directory, then this code will fail to locate anything. 00800 * 00801 * If dirOk is set, and all path elements are directories, it will stop at the last one and look it up as if a file. 00802 * The purpose is to clean up this FAT driver by eliminating all the ridiculous "look up getFileDirEntry but if it fails 00803 * do a whole different code path that looks it up as if directory" copy-pasta in this code that complicates some functions 00804 * like the Rename() method. 00805 * 00806 * useEntry is filled with the SFN direntry of the first search result. dirClust is filled in with the starting cluster of 00807 * the parent directory. Note that even if dirOk is set and the result is a directory, dirClust is the parent directory of 00808 * that directory. subEntry is the dirent index into the directory. 00809 * 00810 * As a side effect of using FindNextInternal, variable lfnRange will be either cleared or filled in with the subEntry range 00811 * of dirents that contain the LFN entries (needed for deletion, renaming, rmdir, etc). Not all paths set or clear it, so 00812 * first call the clear() method before calling. After the call, copy off the value because the next call to FindNextInternal 00813 * by any part of this code will obliterate the result with a new result. */ 00814 bool fatDrive::getFileDirEntry(char const * const filename, direntry * useEntry, Bit32u * dirClust, Bit32u * subEntry,bool dirOk) { 00815 size_t len = strlen(filename); 00816 char dirtoken[DOS_PATHLENGTH]; 00817 Bit32u currentClust = 0; /* FAT12/FAT16 root directory */ 00818 00819 direntry foundEntry; 00820 char * findDir; 00821 char * findFile; 00822 strcpy(dirtoken,filename); 00823 findFile=dirtoken; 00824 00825 if (BPB.is_fat32()) { 00826 /* Set to FAT32 root directory */ 00827 currentClust = BPB.v32.BPB_RootClus; 00828 } 00829 00830 int fbak=lfn_filefind_handle; 00831 lfn_filefind_handle=uselfn?LFN_FILEFIND_IMG:LFN_FILEFIND_NONE; 00832 /* Skip if testing in root directory */ 00833 if ((len>0) && (filename[len-1]!='\\')) { 00834 //LOG_MSG("Testing for filename %s", filename); 00835 findDir = strtok(dirtoken,"\\"); 00836 findFile = findDir; 00837 while(findDir != NULL) { 00838 imgDTA->SetupSearch(0,DOS_ATTR_DIRECTORY,findDir); 00839 imgDTA->SetDirID(0); 00840 00841 findFile = findDir; 00842 if(!FindNextInternal(currentClust, *imgDTA, &foundEntry)) break; 00843 else { 00844 //Found something. See if it's a directory (findfirst always finds regular files) 00845 char find_name[DOS_NAMELENGTH_ASCII],lfind_name[LFN_NAMELENGTH]; 00846 Bit16u find_date,find_time;Bit32u find_size;Bit8u find_attr; 00847 imgDTA->GetResult(find_name,lfind_name,find_size,find_date,find_time,find_attr); 00848 if(!(find_attr & DOS_ATTR_DIRECTORY)) break; 00849 00850 char * findNext; 00851 findNext = strtok(NULL,"\\"); 00852 if (findNext == NULL && dirOk) break; /* dirOk means that if the last element is a directory, then refer to the directory itself */ 00853 findDir = findNext; 00854 } 00855 00856 if (BPB.is_fat32()) 00857 currentClust = foundEntry.Cluster32(); 00858 else 00859 currentClust = foundEntry.loFirstClust; 00860 } 00861 } else { 00862 /* Set to root directory */ 00863 } 00864 00865 /* Search found directory for our file */ 00866 imgDTA->SetupSearch(0,0x7 | (dirOk ? DOS_ATTR_DIRECTORY : 0),findFile); 00867 imgDTA->SetDirID(0); 00868 if(!FindNextInternal(currentClust, *imgDTA, &foundEntry)) {lfn_filefind_handle=fbak;return false;} 00869 lfn_filefind_handle=fbak; 00870 00871 memcpy(useEntry, &foundEntry, sizeof(direntry)); 00872 *dirClust = (Bit32u)currentClust; 00873 *subEntry = ((Bit32u)imgDTA->GetDirID()-1); 00874 return true; 00875 } 00876 00877 bool fatDrive::getDirClustNum(const char *dir, Bit32u *clustNum, bool parDir) { 00878 Bit32u len = (Bit32u)strlen(dir); 00879 char dirtoken[DOS_PATHLENGTH]; 00880 direntry foundEntry; 00881 strcpy(dirtoken,dir); 00882 00883 int fbak=lfn_filefind_handle; 00884 /* Skip if testing for root directory */ 00885 if ((len>0) && (dir[len-1]!='\\')) { 00886 Bit32u currentClust = 0; 00887 00888 if (BPB.is_fat32()) { 00889 /* Set to FAT32 root directory */ 00890 currentClust = BPB.v32.BPB_RootClus; 00891 } 00892 00893 //LOG_MSG("Testing for dir %s", dir); 00894 char * findDir = strtok(dirtoken,"\\"); 00895 while(findDir != NULL) { 00896 lfn_filefind_handle=uselfn?LFN_FILEFIND_IMG:LFN_FILEFIND_NONE; 00897 imgDTA->SetupSearch(0,DOS_ATTR_DIRECTORY,findDir); 00898 imgDTA->SetDirID(0); 00899 findDir = strtok(NULL,"\\"); 00900 if(parDir && (findDir == NULL)) {lfn_filefind_handle=fbak;break;} 00901 00902 if(!FindNextInternal(currentClust, *imgDTA, &foundEntry)) { 00903 lfn_filefind_handle=fbak; 00904 return false; 00905 } else { 00906 char find_name[DOS_NAMELENGTH_ASCII],lfind_name[LFN_NAMELENGTH]; 00907 Bit16u find_date,find_time;Bit32u find_size;Bit8u find_attr; 00908 imgDTA->GetResult(find_name,lfind_name,find_size,find_date,find_time,find_attr); 00909 lfn_filefind_handle=fbak; 00910 if(!(find_attr &DOS_ATTR_DIRECTORY)) return false; 00911 } 00912 if (BPB.is_fat32()) 00913 currentClust = foundEntry.Cluster32(); 00914 else 00915 currentClust = foundEntry.loFirstClust; 00916 } 00917 *clustNum = currentClust; 00918 } else if (BPB.is_fat32()) { 00919 /* Set to FAT32 root directory */ 00920 *clustNum = BPB.v32.BPB_RootClus; 00921 } else { 00922 /* Set to root directory */ 00923 *clustNum = 0; 00924 } 00925 return true; 00926 } 00927 00928 Bit8u fatDrive::readSector(Bit32u sectnum, void * data) { 00929 if (absolute) return Read_AbsoluteSector(sectnum, data); 00930 assert(!IS_PC98_ARCH); 00931 Bit32u cylindersize = (unsigned int)BPB.v.BPB_NumHeads * (unsigned int)BPB.v.BPB_SecPerTrk; 00932 Bit32u cylinder = sectnum / cylindersize; 00933 sectnum %= cylindersize; 00934 Bit32u head = sectnum / BPB.v.BPB_SecPerTrk; 00935 Bit32u sector = sectnum % BPB.v.BPB_SecPerTrk + 1L; 00936 return loadedDisk->Read_Sector(head, cylinder, sector, data); 00937 } 00938 00939 Bit8u fatDrive::writeSector(Bit32u sectnum, void * data) { 00940 if (absolute) return Write_AbsoluteSector(sectnum, data); 00941 assert(!IS_PC98_ARCH); 00942 Bit32u cylindersize = (unsigned int)BPB.v.BPB_NumHeads * (unsigned int)BPB.v.BPB_SecPerTrk; 00943 Bit32u cylinder = sectnum / cylindersize; 00944 sectnum %= cylindersize; 00945 Bit32u head = sectnum / BPB.v.BPB_SecPerTrk; 00946 Bit32u sector = sectnum % BPB.v.BPB_SecPerTrk + 1L; 00947 return loadedDisk->Write_Sector(head, cylinder, sector, data); 00948 } 00949 00950 Bit32u fatDrive::getSectorSize(void) { 00951 return BPB.v.BPB_BytsPerSec; 00952 } 00953 00954 Bit32u fatDrive::getClusterSize(void) { 00955 return (unsigned int)BPB.v.BPB_SecPerClus * (unsigned int)BPB.v.BPB_BytsPerSec; 00956 } 00957 00958 Bit32u fatDrive::getAbsoluteSectFromBytePos(Bit32u startClustNum, Bit32u bytePos) { 00959 return getAbsoluteSectFromChain(startClustNum, bytePos / BPB.v.BPB_BytsPerSec); 00960 } 00961 00962 Bit32u fatDrive::getAbsoluteSectFromChain(Bit32u startClustNum, Bit32u logicalSector) { 00963 Bit32s skipClust = (Bit32s)(logicalSector / BPB.v.BPB_SecPerClus); 00964 Bit32u sectClust = (Bit32u)(logicalSector % BPB.v.BPB_SecPerClus); 00965 00966 /* startClustNum == 0 means the file is (likely) zero length and has no allocation chain yet. 00967 * Nothing to map. Without this check, this code would permit the FAT file reader/writer to 00968 * treat the ROOT DIRECTORY as a file (with disasterous results) 00969 * 00970 * [https://github.com/joncampbell123/dosbox-x/issues/1517] */ 00971 if (startClustNum == 0) return 0; 00972 00973 Bit32u currentClust = startClustNum; 00974 00975 while(skipClust!=0) { 00976 bool isEOF = false; 00977 Bit32u testvalue = getClusterValue(currentClust); 00978 if(testvalue == 0) { 00979 /* What the crap? Cluster is already empty - BAIL! */ 00980 LOG(LOG_DOSMISC,LOG_ERROR)("End of cluster chain and cluster value at the end is zero."); 00981 return 0; 00982 } 00983 switch(fattype) { 00984 case FAT12: 00985 if(testvalue >= 0xff8) isEOF = true; 00986 break; 00987 case FAT16: 00988 if(testvalue >= 0xfff8) isEOF = true; 00989 break; 00990 case FAT32: 00991 if(testvalue >= 0x0ffffff8) isEOF = true; /* FAT32 is really FAT28 with 4 reserved bits */ 00992 break; 00993 } 00994 if((isEOF) && (skipClust>=1)) { 00995 //LOG_MSG("End of cluster chain reached before end of logical sector seek!"); 00996 if (skipClust == 1 && fattype == FAT12) { 00997 //break; 00998 LOG(LOG_DOSMISC,LOG_ERROR)("End of cluster chain reached, but maybe good afterall ?"); 00999 } 01000 return 0; 01001 } 01002 currentClust = testvalue; 01003 --skipClust; 01004 } 01005 01006 /* this should not happen! */ 01007 assert(currentClust != 0); 01008 01009 return (getClustFirstSect(currentClust) + sectClust); 01010 } 01011 01012 void fatDrive::deleteClustChain(Bit32u startCluster, Bit32u bytePos) { 01013 if (startCluster < 2) return; /* do not corrupt the FAT media ID. The file has no chain. Do nothing. */ 01014 01015 Bit32u clustSize = getClusterSize(); 01016 Bit32u endClust = (bytePos + clustSize - 1) / clustSize; 01017 Bit32u countClust = 1; 01018 01019 Bit32u currentClust = startCluster; 01020 Bit32u eofClust = 0; 01021 01022 switch(fattype) { 01023 case FAT12: 01024 eofClust = 0xff8; 01025 break; 01026 case FAT16: 01027 eofClust = 0xfff8; 01028 break; 01029 case FAT32: 01030 eofClust = 0x0ffffff8; 01031 break; 01032 default: 01033 abort(); 01034 break; 01035 } 01036 01037 /* chain preservation */ 01038 while (countClust < endClust) { 01039 Bit32u testvalue = getClusterValue(currentClust); 01040 if (testvalue == 0) { 01041 LOG(LOG_DOSMISC,LOG_WARN)("deleteClusterChain startCluster=%u countClust=%u endClust=%u currentClust=%u testvalue=%u eof=%u unexpected zero cluster value in FAT table", 01042 (unsigned int)startCluster,(unsigned int)countClust,(unsigned int)endClust,(unsigned int)currentClust,(unsigned int)testvalue,(unsigned int)eofClust); 01043 return; 01044 } 01045 else if (testvalue >= eofClust) 01046 return; /* Allocation chain is already shorter than intended */ 01047 01048 currentClust = testvalue; 01049 countClust++; 01050 } 01051 01052 /* cut the chain here, write EOF. 01053 * This condition will NOT occur if bytePos == 0 (i.e. complete file truncation) 01054 * because countClust == 1 and endClust == 0 */ 01055 if (countClust == endClust) { 01056 Bit32u testvalue = getClusterValue(currentClust); 01057 if (testvalue == 0) { 01058 LOG(LOG_DOSMISC,LOG_WARN)("deleteClusterChain startCluster=%u countClust=%u endClust=%u currentClust=%u testvalue=%u eof=%u unexpected zero cluster value in FAT table", 01059 (unsigned int)startCluster,(unsigned int)countClust,(unsigned int)endClust,(unsigned int)currentClust,(unsigned int)testvalue,(unsigned int)eofClust); 01060 return; 01061 } 01062 else if (testvalue >= eofClust) 01063 return; /* No need to write EOF because EOF is already there */ 01064 01065 setClusterValue(currentClust,eofClust); 01066 currentClust = testvalue; 01067 countClust++; 01068 } 01069 01070 /* then run the rest of the chain and zero it out */ 01071 while (1) { 01072 Bit32u testvalue = getClusterValue(currentClust); 01073 if (testvalue == 0) { 01074 LOG(LOG_DOSMISC,LOG_WARN)("deleteClusterChain startCluster=%u countClust=%u endClust=%u currentClust=%u testvalue=%u eof=%u unexpected zero cluster value in FAT table", 01075 (unsigned int)startCluster,(unsigned int)countClust,(unsigned int)endClust,(unsigned int)currentClust,(unsigned int)testvalue,(unsigned int)eofClust); 01076 return; 01077 } 01078 01079 setClusterValue(currentClust,0); 01080 currentClust = testvalue; 01081 countClust++; 01082 01083 /* this follows setClusterValue() to make sure the end of the chain is zeroed too */ 01084 if (testvalue >= eofClust) 01085 return; 01086 } 01087 } 01088 01089 Bit32u fatDrive::appendCluster(Bit32u startCluster) { 01090 if (startCluster < 2) return 0; /* do not corrupt the FAT media ID. The file has no chain. Do nothing. */ 01091 01092 Bit32u currentClust = startCluster; 01093 Bit32u eofClust = 0; 01094 01095 switch(fattype) { 01096 case FAT12: 01097 eofClust = 0xff8; 01098 break; 01099 case FAT16: 01100 eofClust = 0xfff8; 01101 break; 01102 case FAT32: 01103 eofClust = 0x0ffffff8; 01104 break; 01105 default: 01106 abort(); 01107 break; 01108 } 01109 01110 while (1) { 01111 Bit32u testvalue = getClusterValue(currentClust); 01112 if (testvalue == 0) { 01113 LOG(LOG_DOSMISC,LOG_WARN)("appendCluster currentClust=%u testvalue=%u eof=%u unexpected zero cluster value in FAT table", 01114 (unsigned int)currentClust,(unsigned int)testvalue,(unsigned int)eofClust); 01115 return 0; 01116 } 01117 else if (testvalue >= eofClust) { 01118 break; /* found it! */ 01119 } 01120 01121 currentClust = testvalue; 01122 } 01123 01124 Bit32u newClust = getFirstFreeClust(); 01125 if(newClust == 0) return 0; /* Drive is full */ 01126 01127 if(!allocateCluster(newClust, currentClust)) return 0; 01128 01129 zeroOutCluster(newClust); 01130 01131 return newClust; 01132 } 01133 01134 bool fatDrive::allocateCluster(Bit32u useCluster, Bit32u prevCluster) { 01135 01136 /* Can't allocate cluster #0 */ 01137 if(useCluster == 0) return false; 01138 01139 if(prevCluster != 0) { 01140 /* Refuse to allocate cluster if previous cluster value is zero (unallocated) */ 01141 if(!getClusterValue(prevCluster)) return false; 01142 01143 /* Point cluster to new cluster in chain */ 01144 setClusterValue(prevCluster, useCluster); 01145 //LOG_MSG("Chaining cluser %d to %d", prevCluster, useCluster); 01146 } 01147 01148 switch(fattype) { 01149 case FAT12: 01150 setClusterValue(useCluster, 0xfff); 01151 break; 01152 case FAT16: 01153 setClusterValue(useCluster, 0xffff); 01154 break; 01155 case FAT32: 01156 setClusterValue(useCluster, 0x0fffffff); 01157 break; 01158 } 01159 return true; 01160 } 01161 01162 fatDrive::~fatDrive() { 01163 if (loadedDisk) { 01164 loadedDisk->Release(); 01165 loadedDisk = NULL; 01166 } 01167 } 01168 01169 /* PC-98 IPL1 partition table entry. 01170 * Taken from GNU Parted source code. 01171 * Maximum 16 entries. */ 01172 #pragma pack(push,1) 01173 struct _PC98RawPartition { 01174 uint8_t mid; /* 0x80 - boot */ 01175 uint8_t sid; /* 0x80 - active */ 01176 uint8_t dum1; /* dummy for padding */ 01177 uint8_t dum2; /* dummy for padding */ 01178 uint8_t ipl_sect; /* IPL sector */ 01179 uint8_t ipl_head; /* IPL head */ 01180 uint16_t ipl_cyl; /* IPL cylinder */ 01181 uint8_t sector; /* starting sector */ 01182 uint8_t head; /* starting head */ 01183 uint16_t cyl; /* starting cylinder */ 01184 uint8_t end_sector; /* end sector */ 01185 uint8_t end_head; /* end head */ 01186 uint16_t end_cyl; /* end cylinder */ 01187 char name[16]; 01188 }; 01189 #pragma pack(pop) 01190 01191 fatDrive::fatDrive(const char* sysFilename, Bit32u bytesector, Bit32u cylsector, Bit32u headscyl, Bit32u cylinders, std::vector<std::string>& options) { 01192 FILE *diskfile; 01193 Bit32u filesize; 01194 unsigned char bootcode[256]; 01195 01196 if(imgDTASeg == 0) { 01197 imgDTASeg = DOS_GetMemory(4,"imgDTASeg"); 01198 imgDTAPtr = RealMake(imgDTASeg, 0); 01199 imgDTA = new DOS_DTA(imgDTAPtr); 01200 } 01201 01202 readonly = wpcolon&&strlen(sysFilename)>1&&sysFilename[0]==':'; 01203 diskfile = fopen64(readonly?sysFilename+1:sysFilename, readonly?"rb":"rb+"); 01204 if(!diskfile) {created_successfully = false;return;} 01205 01206 // all disk I/O is in sector-sized blocks. 01207 // modern OSes have good caching. 01208 // there are plenty of cases where this code aborts, exits, or re-execs itself (such as reboot) 01209 // where stdio buffering can cause loss of data. 01210 setbuf(diskfile,NULL); 01211 01212 QCow2Image::QCow2Header qcow2_header = QCow2Image::read_header(diskfile); 01213 01214 if (qcow2_header.magic == QCow2Image::magic && (qcow2_header.version == 2 || qcow2_header.version == 3)){ 01215 Bit32u cluster_size = 1u << qcow2_header.cluster_bits; 01216 if ((bytesector < 512) || ((cluster_size % bytesector) != 0)){ 01217 created_successfully = false; 01218 return; 01219 } 01220 filesize = (Bit32u)(qcow2_header.size / 1024L); 01221 loadedDisk = new QCow2Disk(qcow2_header, diskfile, (Bit8u *)sysFilename, filesize, bytesector, (filesize > 2880)); 01222 } 01223 else{ 01224 fseeko64(diskfile, 0L, SEEK_SET); 01225 assert(sizeof(bootcode) >= 256); 01226 size_t readResult = fread(bootcode,256,1,diskfile); // look for magic signatures 01227 if (readResult != 1) { 01228 LOG(LOG_IO, LOG_ERROR) ("Reading error in fatDrive constructor\n"); 01229 return; 01230 } 01231 01232 const char *ext = strrchr(sysFilename,'.'), *fname=readonly?sysFilename+1:sysFilename; 01233 01234 if (ext != NULL && !strcasecmp(ext, ".d88")) { 01235 fseeko64(diskfile, 0L, SEEK_END); 01236 filesize = (Bit32u)(ftello64(diskfile) / 1024L); 01237 loadedDisk = new imageDiskD88(diskfile, (Bit8u *)fname, filesize, (filesize > 2880)); 01238 } 01239 else if (!memcmp(bootcode,"VFD1.",5)) { /* FDD files */ 01240 fseeko64(diskfile, 0L, SEEK_END); 01241 filesize = (Bit32u)(ftello64(diskfile) / 1024L); 01242 loadedDisk = new imageDiskVFD(diskfile, (Bit8u *)fname, filesize, (filesize > 2880)); 01243 } 01244 else if (!memcmp(bootcode,"T98FDDIMAGE.R0\0\0",16)) { 01245 fseeko64(diskfile, 0L, SEEK_END); 01246 filesize = (Bit32u)(ftello64(diskfile) / 1024L); 01247 loadedDisk = new imageDiskNFD(diskfile, (Bit8u *)fname, filesize, (filesize > 2880), 0); 01248 } 01249 else if (!memcmp(bootcode,"T98FDDIMAGE.R1\0\0",16)) { 01250 fseeko64(diskfile, 0L, SEEK_END); 01251 filesize = (Bit32u)(ftello64(diskfile) / 1024L); 01252 loadedDisk = new imageDiskNFD(diskfile, (Bit8u *)fname, filesize, (filesize > 2880), 1); 01253 } 01254 else { 01255 fseeko64(diskfile, 0L, SEEK_END); 01256 filesize = (Bit32u)(ftello64(diskfile) / 1024L); 01257 loadedDisk = new imageDisk(diskfile, (Bit8u *)fname, filesize, (filesize > 2880)); 01258 } 01259 } 01260 01261 fatDriveInit(sysFilename, bytesector, cylsector, headscyl, cylinders, filesize, options); 01262 } 01263 01264 fatDrive::fatDrive(imageDisk *sourceLoadedDisk, std::vector<std::string> &options) { 01265 if (sourceLoadedDisk == 0) { 01266 created_successfully = false; 01267 return; 01268 } 01269 created_successfully = true; 01270 01271 if(imgDTASeg == 0) { 01272 imgDTASeg = DOS_GetMemory(4,"imgDTASeg"); 01273 imgDTAPtr = RealMake(imgDTASeg, 0); 01274 imgDTA = new DOS_DTA(imgDTAPtr); 01275 } 01276 01277 loadedDisk = sourceLoadedDisk; 01278 01279 fatDriveInit("", loadedDisk->sector_size, loadedDisk->sectors, loadedDisk->heads, loadedDisk->cylinders, loadedDisk->diskSizeK, options); 01280 } 01281 01282 Bit8u fatDrive::Read_AbsoluteSector(Bit32u sectnum, void * data) { 01283 if (loadedDisk != NULL) { 01284 /* this will only work if the logical sector size is larger than the disk sector size */ 01285 const unsigned int lsz = loadedDisk->getSectSize(); 01286 unsigned int c = sector_size / lsz; 01287 01288 if (c != 0 && (sector_size % lsz) == 0) { 01289 Bit32u ssect = sectnum * c; 01290 01291 while (c-- != 0) { 01292 if (loadedDisk->Read_AbsoluteSector(ssect++,data) != 0) 01293 return 0x05; 01294 01295 data = (void*)((char*)data + lsz); 01296 } 01297 01298 return 0; 01299 } 01300 } 01301 01302 return 0x05; 01303 } 01304 01305 Bit8u fatDrive::Write_AbsoluteSector(Bit32u sectnum, void * data) { 01306 if (loadedDisk != NULL) { 01307 /* this will only work if the logical sector size is larger than the disk sector size */ 01308 const unsigned int lsz = loadedDisk->getSectSize(); 01309 unsigned int c = sector_size / lsz; 01310 01311 if (c != 0 && (sector_size % lsz) == 0) { 01312 Bit32u ssect = sectnum * c; 01313 01314 while (c-- != 0) { 01315 if (loadedDisk->Write_AbsoluteSector(ssect++,data) != 0) 01316 return 0x05; 01317 01318 data = (void*)((char*)data + lsz); 01319 } 01320 01321 return 0; 01322 } 01323 } 01324 01325 return 0x05; 01326 } 01327 01328 Bit32u fatDrive::getSectSize(void) { 01329 return sector_size; 01330 } 01331 01332 void fatDrive::UpdateDPB(unsigned char dos_drive) { 01333 PhysPt ptr = DOS_Get_DPB(dos_drive); 01334 if (ptr != PhysPt(0)) { 01335 mem_writew(ptr+0x02,BPB.v.BPB_BytsPerSec); // +2 = bytes per sector 01336 mem_writeb(ptr+0x04,BPB.v.BPB_SecPerClus - 1); // +4 = highest sector within a cluster 01337 mem_writeb(ptr+0x05,bitop::log2(BPB.v.BPB_SecPerClus)); // +5 = shift count to convert clusters to sectors 01338 mem_writew(ptr+0x06,BPB.v.BPB_RsvdSecCnt); // +6 = number of reserved sectors at start of partition 01339 mem_writeb(ptr+0x08,BPB.v.BPB_NumFATs); // +8 = number of FATs (file allocation tables) 01340 mem_writew(ptr+0x09,BPB.v.BPB_RootEntCnt); // +9 = number of root directory entries 01341 mem_writew(ptr+0x0B,(uint16_t)(firstDataSector-partSectOff));// +11 = number of first sector containing user data 01342 01343 if (BPB.is_fat32()) 01344 mem_writew(ptr+0x0D,0); // Windows 98 behavior 01345 else 01346 mem_writew(ptr+0x0D,(uint16_t)CountOfClusters + 1); // +13 = highest cluster number 01347 01348 mem_writew(ptr+0x0F,(uint16_t)BPB.v.BPB_FATSz16); // +15 = sectors per FAT 01349 01350 if (BPB.is_fat32()) 01351 mem_writew(ptr+0x11,0xFFFF); // Windows 98 behavior 01352 else 01353 mem_writew(ptr+0x11,(uint16_t)(firstRootDirSect-partSectOff));// +17 = sector number of first directory sector 01354 01355 mem_writed(ptr+0x13,0xFFFFFFFF); // +19 = address of device driver header (NOT IMPLEMENTED) Windows 98 behavior 01356 mem_writeb(ptr+0x17,GetMediaByte()); // +23 = media ID byte 01357 mem_writeb(ptr+0x18,0x00); // +24 = disk accessed 01358 mem_writew(ptr+0x1F,0xFFFF); // +31 = number of free clusters or 0xFFFF if unknown 01359 // other fields, not implemented 01360 } 01361 } 01362 01363 void fatDrive::fatDriveInit(const char *sysFilename, Bit32u bytesector, Bit32u cylsector, Bit32u headscyl, Bit32u cylinders, Bit64u filesize, const std::vector<std::string> &options) { 01364 Bit32u startSector = 0,countSector = 0; 01365 bool pc98_512_to_1024_allow = false; 01366 int opt_partition_index = -1; 01367 bool is_hdd = (filesize > 2880); 01368 struct partTable mbrData; 01369 01370 req_ver_major = req_ver_minor = 0; 01371 01372 if(!loadedDisk) { 01373 created_successfully = false; 01374 return; 01375 } 01376 01377 for (const auto &opt : options) { 01378 size_t equ = opt.find_first_of('='); 01379 std::string name,value; 01380 01381 if (equ != std::string::npos) { 01382 name = opt.substr(0,equ); 01383 value = opt.substr(equ+1); 01384 } 01385 else { 01386 name = opt; 01387 value.clear(); 01388 } 01389 01390 if (name == "partidx") { 01391 if (!value.empty()) 01392 opt_partition_index = (int)atol(value.c_str()); 01393 } 01394 else { 01395 LOG(LOG_MISC,LOG_DEBUG)("FAT: option '%s' = '%s' ignored, unknown",name.c_str(),value.c_str()); 01396 } 01397 01398 // LOG_MSG("'%s' = '%s'",name.c_str(),value.c_str()); 01399 } 01400 01401 loadedDisk->Addref(); 01402 01403 { 01404 FAT_BootSector bootbuffer = {}; 01405 01406 if (loadedDisk->getSectSize() > sizeof(bootbuffer)) { 01407 LOG_MSG("Disk sector/bytes (%u) is too large, not attempting FAT filesystem access",loadedDisk->getSectSize()); 01408 created_successfully = false; 01409 return; 01410 } 01411 01412 if(is_hdd) { 01413 /* Set user specified harddrive parameters */ 01414 if (headscyl > 0 && cylinders > 0 && cylsector > 0 && bytesector > 0) 01415 loadedDisk->Set_Geometry(headscyl, cylinders,cylsector, bytesector); 01416 01417 if (loadedDisk->heads == 0 || loadedDisk->sectors == 0 || loadedDisk->cylinders == 0) { 01418 created_successfully = false; 01419 LOG_MSG("No geometry"); 01420 return; 01421 } 01422 01423 loadedDisk->Read_Sector(0,0,1,&mbrData); 01424 01425 if(mbrData.magic1!= 0x55 || mbrData.magic2!= 0xaa) LOG_MSG("Possibly invalid partition table in disk image."); 01426 01427 startSector = 0; 01428 01429 /* PC-98 bootloader support. 01430 * These can be identified by the "IPL1" in the boot sector. 01431 * These boot sectors do not have a valid partition table though the code below might 01432 * pick up a false partition #3 with a zero offset. Partition table is in sector 1 */ 01433 if (!memcmp(mbrData.booter+4,"IPL1",4)) { 01434 unsigned char ipltable[SECTOR_SIZE_MAX]; 01435 unsigned int max_entries = (std::min)(16UL,(unsigned long)(loadedDisk->getSectSize() / sizeof(_PC98RawPartition))); 01436 Bitu i; 01437 01438 LOG_MSG("PC-98 IPL1 signature detected"); 01439 01440 assert(sizeof(_PC98RawPartition) == 32); 01441 01442 memset(ipltable,0,sizeof(ipltable)); 01443 loadedDisk->Read_Sector(0,0,2,ipltable); 01444 01445 if (opt_partition_index >= 0) { 01446 /* user knows best! */ 01447 if ((unsigned int)opt_partition_index >= max_entries) { 01448 LOG_MSG("Partition index out of range"); 01449 created_successfully = false; 01450 return; 01451 } 01452 01453 i = (unsigned int)opt_partition_index; 01454 const _PC98RawPartition *pe = (_PC98RawPartition*)(ipltable+(i * 32)); 01455 01456 /* unfortunately start and end are in C/H/S geometry, so we have to translate. 01457 * this is why it matters so much to read the geometry from the HDI header. 01458 * 01459 * NOTE: C/H/S values in the IPL1 table are similar to IBM PC except that sectors are counted from 0, not 1 */ 01460 startSector = 01461 (pe->cyl * loadedDisk->sectors * loadedDisk->heads) + 01462 (pe->head * loadedDisk->sectors) + 01463 pe->sector; 01464 01465 /* Many HDI images I've encountered so far indicate 512 bytes/sector, 01466 * but then the FAT filesystem itself indicates 1024 bytes per sector. */ 01467 pc98_512_to_1024_allow = true; 01468 01469 { 01470 /* FIXME: What if the label contains SHIFT-JIS? */ 01471 std::string name = std::string(pe->name,sizeof(pe->name)); 01472 01473 LOG_MSG("Using IPL1 entry %lu name '%s' which starts at sector %lu", 01474 (unsigned long)i,name.c_str(),(unsigned long)startSector); 01475 } 01476 } 01477 else { 01478 for (i=0;i < max_entries;i++) { 01479 const _PC98RawPartition *pe = (_PC98RawPartition*)(ipltable+(i * 32)); 01480 01481 if (pe->mid == 0 && pe->sid == 0 && 01482 pe->ipl_sect == 0 && pe->ipl_head == 0 && pe->ipl_cyl == 0 && 01483 pe->sector == 0 && pe->head == 0 && pe->cyl == 0 && 01484 pe->end_sector == 0 && pe->end_head == 0 && pe->end_cyl == 0) 01485 continue; /* unused */ 01486 01487 /* We're looking for MS-DOS partitions. 01488 * I've heard that some other OSes were once ported to PC-98, including Windows NT and OS/2, 01489 * so I would rather not mistake NTFS or HPFS as FAT and cause damage. --J.C. 01490 * FIXME: Is there a better way? */ 01491 if (!strncasecmp(pe->name,"MS-DOS",6) || 01492 !strncasecmp(pe->name,"MSDOS",5) || 01493 !strncasecmp(pe->name,"Windows",7)) { 01494 /* unfortunately start and end are in C/H/S geometry, so we have to translate. 01495 * this is why it matters so much to read the geometry from the HDI header. 01496 * 01497 * NOTE: C/H/S values in the IPL1 table are similar to IBM PC except that sectors are counted from 0, not 1 */ 01498 startSector = 01499 (pe->cyl * loadedDisk->sectors * loadedDisk->heads) + 01500 (pe->head * loadedDisk->sectors) + 01501 pe->sector; 01502 01503 /* Many HDI images I've encountered so far indicate 512 bytes/sector, 01504 * but then the FAT filesystem itself indicates 1024 bytes per sector. */ 01505 pc98_512_to_1024_allow = true; 01506 01507 { 01508 /* FIXME: What if the label contains SHIFT-JIS? */ 01509 std::string name = std::string(pe->name,sizeof(pe->name)); 01510 01511 LOG_MSG("Using IPL1 entry %lu name '%s' which starts at sector %lu", 01512 (unsigned long)i,name.c_str(),(unsigned long)startSector); 01513 break; 01514 } 01515 } 01516 } 01517 } 01518 01519 if (i == max_entries) 01520 LOG_MSG("No partitions found in the IPL1 table"); 01521 } 01522 else { 01523 /* IBM PC master boot record search */ 01524 int m=4; 01525 01526 if (opt_partition_index >= 0) { 01527 /* user knows best! */ 01528 if (opt_partition_index >= 4) { 01529 LOG_MSG("Partition index out of range"); 01530 created_successfully = false; 01531 return; 01532 } 01533 01534 startSector = mbrData.pentry[m=opt_partition_index].absSectStart; 01535 countSector = mbrData.pentry[m=opt_partition_index].partSize; 01536 } 01537 else { 01538 for(m=0;m<4;m++) { 01539 /* Pick the first available partition */ 01540 if (mbrData.pentry[m].parttype == 0x01 || mbrData.pentry[m].parttype == 0x04 || mbrData.pentry[m].parttype == 0x06) { 01541 LOG_MSG("Using partition %d on drive (type 0x%02x); skipping %d sectors", m, mbrData.pentry[m].parttype, mbrData.pentry[m].absSectStart); 01542 startSector = mbrData.pentry[m].absSectStart; 01543 countSector = mbrData.pentry[m].partSize; 01544 break; 01545 } 01546 else if (mbrData.pentry[m].parttype == 0x0E/*FAT16B LBA*/) { 01547 if (dos.version.major >= 7) { /* MS-DOS 7.0 or higher */ 01548 LOG_MSG("Using partition %d on drive (type 0x%02x); skipping %d sectors", m, mbrData.pentry[m].parttype, mbrData.pentry[m].absSectStart); 01549 startSector = mbrData.pentry[m].absSectStart; 01550 countSector = mbrData.pentry[m].partSize; 01551 } else { 01552 req_ver_major = 7; req_ver_minor = 0; 01553 } 01554 break; 01555 } 01556 else if (mbrData.pentry[m].parttype == 0x0B || mbrData.pentry[m].parttype == 0x0C) { /* FAT32 types */ 01557 if (dos.version.major > 7 || (dos.version.major == 7 && dos.version.minor >= 10)) { /* MS-DOS 7.10 or higher */ 01558 LOG_MSG("Using partition %d on drive (type 0x%02x); skipping %d sectors", m, mbrData.pentry[m].parttype, mbrData.pentry[m].absSectStart); 01559 startSector = mbrData.pentry[m].absSectStart; 01560 countSector = mbrData.pentry[m].partSize; 01561 } else { 01562 req_ver_major = 7; req_ver_minor = 10; 01563 } 01564 break; 01565 } 01566 } 01567 } 01568 01569 if(m==4) LOG_MSG("No good partition found in image."); 01570 } 01571 01572 partSectOff = startSector; 01573 partSectSize = countSector; 01574 } else { 01575 /* Get floppy disk parameters based on image size */ 01576 loadedDisk->Get_Geometry(&headscyl, &cylinders, &cylsector, &bytesector); 01577 /* Floppy disks don't have partitions */ 01578 partSectOff = 0; 01579 01580 if (loadedDisk->heads == 0 || loadedDisk->sectors == 0 || loadedDisk->cylinders == 0) { 01581 created_successfully = false; 01582 LOG_MSG("No geometry"); 01583 return; 01584 } 01585 } 01586 01587 BPB = {}; 01588 loadedDisk->Read_AbsoluteSector(0+partSectOff,&bootbuffer); 01589 01590 /* If the sector is full of 0xF6, the partition is brand new and was just created with Microsoft FDISK.EXE (Windows 98 behavior) 01591 * and therefore there is NO FAT filesystem here. We'll go farther and check if all bytes are just the same. */ 01592 { 01593 unsigned int i=1; 01594 01595 while (i < 128 && ((Bit8u*)(&bootbuffer))[0] == ((Bit8u*)(&bootbuffer))[i]) i++; 01596 01597 if (i == 128) { 01598 LOG_MSG("Boot sector appears to have been created by FDISK.EXE but not formatted"); 01599 created_successfully = false; 01600 return; 01601 } 01602 } 01603 01604 if (!is_hdd) { 01605 /* Identify floppy format */ 01606 if ((bootbuffer.BS_jmpBoot[0] == 0x69 || bootbuffer.BS_jmpBoot[0] == 0xe9 || 01607 (bootbuffer.BS_jmpBoot[0] == 0xeb && bootbuffer.BS_jmpBoot[2] == 0x90)) && 01608 (bootbuffer.bpb.v.BPB_Media & 0xf0) == 0xf0) { 01609 /* DOS 2.x or later format, BPB assumed valid */ 01610 01611 if ((bootbuffer.bpb.v.BPB_Media != 0xf0 && !(bootbuffer.bpb.v.BPB_Media & 0x1)) && 01612 (bootbuffer.BS_OEMName[5] != '3' || bootbuffer.BS_OEMName[6] != '.' || bootbuffer.BS_OEMName[7] < '2')) { 01613 /* Fix pre-DOS 3.2 single-sided floppy */ 01614 bootbuffer.bpb.v.BPB_SecPerClus = 1; 01615 } 01616 } else { 01617 /* Read media descriptor in FAT */ 01618 Bit8u sectorBuffer[512]; 01619 loadedDisk->Read_AbsoluteSector(1,§orBuffer); 01620 Bit8u mdesc = sectorBuffer[0]; 01621 01622 if (mdesc >= 0xf8) { 01623 /* DOS 1.x format, create BPB for 160kB floppy */ 01624 bootbuffer.bpb.v.BPB_BytsPerSec = 512; 01625 bootbuffer.bpb.v.BPB_SecPerClus = 1; 01626 bootbuffer.bpb.v.BPB_RsvdSecCnt = 1; 01627 bootbuffer.bpb.v.BPB_NumFATs = 2; 01628 bootbuffer.bpb.v.BPB_RootEntCnt = 64; 01629 bootbuffer.bpb.v.BPB_TotSec16 = 320; 01630 bootbuffer.bpb.v.BPB_Media = mdesc; 01631 bootbuffer.bpb.v.BPB_FATSz16 = 1; 01632 bootbuffer.bpb.v.BPB_SecPerTrk = 8; 01633 bootbuffer.bpb.v.BPB_NumHeads = 1; 01634 bootbuffer.magic1 = 0x55; // to silence warning 01635 bootbuffer.magic2 = 0xaa; 01636 if (!(mdesc & 0x2)) { 01637 /* Adjust for 9 sectors per track */ 01638 bootbuffer.bpb.v.BPB_TotSec16 = 360; 01639 bootbuffer.bpb.v.BPB_FATSz16 = 2; 01640 bootbuffer.bpb.v.BPB_SecPerTrk = 9; 01641 } 01642 if (mdesc & 0x1) { 01643 /* Adjust for 2 sides */ 01644 bootbuffer.bpb.v.BPB_SecPerClus = 2; 01645 bootbuffer.bpb.v.BPB_RootEntCnt = 112; 01646 bootbuffer.bpb.v.BPB_TotSec16 *= 2; 01647 bootbuffer.bpb.v.BPB_NumHeads = 2; 01648 } 01649 } else { 01650 /* Unknown format */ 01651 created_successfully = false; 01652 return; 01653 } 01654 } 01655 } 01656 01657 /* accept BPB.. so far */ 01658 BPB = bootbuffer.bpb; 01659 01660 /* DEBUG */ 01661 LOG(LOG_DOSMISC,LOG_DEBUG)("FAT: BPB says %u sectors/track %u heads %u bytes/sector", 01662 BPB.v.BPB_SecPerTrk, 01663 BPB.v.BPB_NumHeads, 01664 BPB.v.BPB_BytsPerSec); 01665 01666 /* NTS: PC-98 floppies (the 1024 byte/sector format) do not have magic bytes */ 01667 if (fatDrive::getSectSize() == 512 && !IS_PC98_ARCH) { 01668 if ((bootbuffer.magic1 != 0x55) || (bootbuffer.magic2 != 0xaa)) { 01669 /* Not a FAT filesystem */ 01670 LOG_MSG("Loaded image has no valid magicnumbers at the end!"); 01671 created_successfully = false; 01672 return; 01673 } 01674 } 01675 } 01676 01677 /* NTS: Some HDI images of PC-98 games do in fact have BPB_NumHeads == 0. Some like "Amaranth 5" have BPB_SecPerTrk == 0 too! */ 01678 if (!IS_PC98_ARCH) { 01679 /* a clue that we're not really looking at FAT is invalid or weird values in the boot sector */ 01680 if (BPB.v.BPB_SecPerTrk == 0 || (BPB.v.BPB_SecPerTrk > ((filesize <= 3000) ? 40 : 255)) || 01681 (BPB.v.BPB_NumHeads > ((filesize <= 3000) ? 64 : 255))) { 01682 LOG_MSG("Rejecting image, boot sector has weird values not consistent with FAT filesystem"); 01683 created_successfully = false; 01684 return; 01685 } 01686 } 01687 01688 /* work at this point in logical sectors */ 01689 sector_size = loadedDisk->getSectSize(); 01690 01691 /* Many HDI images indicate a disk format of 256 or 512 bytes per sector combined with a FAT filesystem 01692 * that indicates 1024 bytes per sector. */ 01693 if (pc98_512_to_1024_allow && 01694 BPB.v.BPB_BytsPerSec != fatDrive::getSectSize() && 01695 BPB.v.BPB_BytsPerSec > fatDrive::getSectSize() && 01696 (BPB.v.BPB_BytsPerSec % fatDrive::getSectSize()) == 0) { 01697 unsigned int ratioshift = 1; 01698 01699 while ((unsigned int)(BPB.v.BPB_BytsPerSec >> ratioshift) > fatDrive::getSectSize()) 01700 ratioshift++; 01701 01702 unsigned int ratio = 1u << ratioshift; 01703 01704 LOG_MSG("Disk indicates %u bytes/sector, FAT filesystem indicates %u bytes/sector. Ratio=%u:1 shift=%u", 01705 fatDrive::getSectSize(),BPB.v.BPB_BytsPerSec,ratio,ratioshift); 01706 01707 if ((unsigned int)(BPB.v.BPB_BytsPerSec >> ratioshift) == fatDrive::getSectSize()) { 01708 assert(ratio >= 2); 01709 01710 /* we can hack things in place IF the starting sector is an even number */ 01711 if ((partSectOff & (ratio - 1)) == 0) { 01712 partSectOff >>= ratioshift; 01713 startSector >>= ratioshift; 01714 sector_size = BPB.v.BPB_BytsPerSec; 01715 LOG_MSG("Using logical sector size %u",sector_size); 01716 } 01717 else { 01718 LOG_MSG("However there's nothing I can do, because the partition starts on an odd sector"); 01719 } 01720 } 01721 } 01722 01723 /* Sanity checks */ 01724 /* NTS: DOSBox-X *does* support non-standard sector sizes, though not in IBM PC mode and not through INT 13h. 01725 * In DOSBox-X INT 13h emulation will enforce the standard (512 byte) sector size. 01726 * In PC-98 mode mounting disk images requires "non-standard" sector sizes because PC-98 floppies (other 01727 * than ones formatted 1.44MB) generally use 1024 bytes/sector and MAY use 128 or 256 bytes per sector. */ 01728 /* NTS: Loosen geometry checks for PC-98 mode, for two reasons. One, is that the geometry check will fail 01729 * when logical vs physical sector translation is involved, since it is apparently common for PC-98 HDI 01730 * images to be formatted with 256, 512, 1024, or in rare cases even 2048 bytes per sector, yet the FAT 01731 * file format will report a sector size that is a power of 2 multiple of the disk sector size. The 01732 * most common appears to be 512 byte/sector HDI images formatted with 1024 byte/sector FAT filesystems. 01733 * 01734 * Second, there are some HDI images that are valid yet the FAT filesystem reports a head count of 0 01735 * for some reason (Touhou Project) */ 01736 if ((BPB.v.BPB_SecPerClus == 0) || 01737 (BPB.v.BPB_NumFATs == 0) || 01738 (BPB.v.BPB_NumHeads == 0 && !IS_PC98_ARCH) || 01739 (BPB.v.BPB_NumHeads > headscyl && !IS_PC98_ARCH) || 01740 (BPB.v.BPB_SecPerTrk == 0 && !IS_PC98_ARCH) || 01741 (BPB.v.BPB_SecPerTrk > cylsector && !IS_PC98_ARCH)) { 01742 LOG_MSG("Sanity checks failed"); 01743 created_successfully = false; 01744 return; 01745 } 01746 01747 /* Sanity check: Root directory count is nonzero if FAT16/FAT12, or is zero if FAT32 */ 01748 if (BPB.is_fat32()) { 01749 if (BPB.v.BPB_RootEntCnt != 0) { 01750 LOG_MSG("Sanity check fail: Root directory count != 0 and not FAT32"); 01751 created_successfully = false; 01752 return; 01753 } 01754 } 01755 else { 01756 if (BPB.v.BPB_RootEntCnt == 0) { 01757 LOG_MSG("Sanity check fail: Root directory count == 0 and not FAT32"); 01758 created_successfully = false; 01759 return; 01760 } 01761 } 01762 01763 /* too much of this code assumes 512 bytes per sector or more. 01764 * MS-DOS itself as I understand it relies on bytes per sector being a power of 2. 01765 * this is to protect against errant FAT structures and to help prep this code 01766 * later to work with the 1024 bytes/sector used by PC-98 floppy formats. 01767 * When done, this code should be able to then handle the FDI/FDD images 01768 * PC-98 games are normally distributed in on the internet. 01769 * 01770 * The value "128" comes from the smallest sector size possible on the floppy 01771 * controller of MS-DOS based systems. */ 01772 /* NTS: Power of 2 test: A number is a power of 2 if (x & (x - 1)) == 0 01773 * 01774 * 15 15 & 14 01111 AND 01110 RESULT: 01110 (15) 01775 * 16 16 & 15 10000 AND 01111 RESULT: 00000 (0) 01776 * 17 17 & 16 10001 AND 10000 RESULT: 10000 (16) */ 01777 if (BPB.v.BPB_BytsPerSec < 128 || BPB.v.BPB_BytsPerSec > SECTOR_SIZE_MAX || 01778 (BPB.v.BPB_BytsPerSec & (BPB.v.BPB_BytsPerSec - 1)) != 0/*not a power of 2*/) { 01779 LOG_MSG("FAT bytes/sector value %u not supported",BPB.v.BPB_BytsPerSec); 01780 created_successfully = false; 01781 return; 01782 } 01783 01784 /* another fault of this code is that it assumes the sector size of the medium matches 01785 * the BPB_BytsPerSec value of the MS-DOS filesystem. if they don't match, problems 01786 * will result. */ 01787 if (BPB.v.BPB_BytsPerSec != fatDrive::getSectSize()) { 01788 LOG_MSG("FAT bytes/sector %u does not match disk image bytes/sector %u", 01789 (unsigned int)BPB.v.BPB_BytsPerSec, 01790 (unsigned int)fatDrive::getSectSize()); 01791 created_successfully = false; 01792 return; 01793 } 01794 01795 /* Filesystem must be contiguous to use absolute sectors, otherwise CHS will be used */ 01796 absolute = IS_PC98_ARCH || ((BPB.v.BPB_NumHeads == headscyl) && (BPB.v.BPB_SecPerTrk == cylsector)); 01797 LOG(LOG_DOSMISC,LOG_DEBUG)("FAT driver: Using %s sector access",absolute ? "absolute" : "C/H/S"); 01798 01799 /* Determine FAT format, 12, 16 or 32 */ 01800 01801 /* Get size of root dir in sectors */ 01802 Bit32u RootDirSectors; 01803 Bit32u DataSectors; 01804 01805 if (BPB.is_fat32()) { 01806 /* FAT32 requires use of TotSec32, TotSec16 must be zero. */ 01807 if (BPB.v.BPB_TotSec32 == 0) { 01808 LOG_MSG("BPB_TotSec32 == 0 and FAT32 BPB, not valid"); 01809 created_successfully = false; 01810 return; 01811 } 01812 if (BPB.v32.BPB_RootClus < 2) { 01813 LOG_MSG("BPB_RootClus == 0 and FAT32 BPB, not valid"); 01814 created_successfully = false; 01815 return; 01816 } 01817 if (BPB.v.BPB_FATSz16 != 0) { 01818 LOG_MSG("BPB_FATSz16 != 0 and FAT32 BPB, not valid"); 01819 created_successfully = false; 01820 return; 01821 } 01822 if (BPB.v32.BPB_FATSz32 == 0) { 01823 LOG_MSG("BPB_FATSz32 == 0 and FAT32 BPB, not valid"); 01824 created_successfully = false; 01825 return; 01826 } 01827 01828 RootDirSectors = 0; /* FAT32 root directory has it's own allocation chain, instead of a fixed location */ 01829 DataSectors = (Bitu)BPB.v.BPB_TotSec32 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v32.BPB_FATSz32) + (Bitu)RootDirSectors); 01830 CountOfClusters = DataSectors / BPB.v.BPB_SecPerClus; 01831 firstDataSector = ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v32.BPB_FATSz32) + (Bitu)RootDirSectors) + (Bitu)partSectOff; 01832 firstRootDirSect = 0; 01833 } 01834 else { 01835 if (BPB.v.BPB_FATSz16 == 0) { 01836 LOG_MSG("BPB_FATSz16 == 0 and not FAT32 BPB, not valid"); 01837 created_successfully = false; 01838 return; 01839 } 01840 01841 RootDirSectors = ((BPB.v.BPB_RootEntCnt * 32u) + (BPB.v.BPB_BytsPerSec - 1u)) / BPB.v.BPB_BytsPerSec; 01842 01843 if (BPB.v.BPB_TotSec16 != 0) 01844 DataSectors = (Bitu)BPB.v.BPB_TotSec16 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors); 01845 else 01846 DataSectors = (Bitu)BPB.v.BPB_TotSec32 - ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors); 01847 01848 CountOfClusters = DataSectors / BPB.v.BPB_SecPerClus; 01849 firstDataSector = ((Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)RootDirSectors) + (Bitu)partSectOff; 01850 firstRootDirSect = (Bitu)BPB.v.BPB_RsvdSecCnt + ((Bitu)BPB.v.BPB_NumFATs * (Bitu)BPB.v.BPB_FATSz16) + (Bitu)partSectOff; 01851 } 01852 01853 if(CountOfClusters < 4085) { 01854 /* Volume is FAT12 */ 01855 LOG_MSG("Mounted FAT volume is FAT12 with %d clusters", CountOfClusters); 01856 fattype = FAT12; 01857 } else if (CountOfClusters < 65525) { 01858 LOG_MSG("Mounted FAT volume is FAT16 with %d clusters", CountOfClusters); 01859 fattype = FAT16; 01860 } else { 01861 LOG_MSG("Mounted FAT volume is FAT32 with %d clusters", CountOfClusters); 01862 fattype = FAT32; 01863 } 01864 01865 /* just so you know....! */ 01866 if (fattype == FAT32 && (dos.version.major < 7 || (dos.version.major == 7 && dos.version.minor < 10))) { 01867 LOG_MSG("CAUTION: Mounting FAT32 partition when reported DOS version is less than 7.10. Disk formatting/repair utilities may mis-identify the partition."); 01868 } 01869 01870 /* There is no cluster 0, this means we are in the root directory */ 01871 cwdDirCluster = 0; 01872 01873 memset(fatSectBuffer,0,1024); 01874 curFatSect = 0xffffffff; 01875 01876 strcpy(info, "fatDrive "); 01877 strcat(info, wpcolon&&strlen(sysFilename)>1&&sysFilename[0]==':'?sysFilename+1:sysFilename); 01878 } 01879 01880 #ifdef _MSC_VER 01881 # define MIN(a,b) ((a) < (b) ? (a) : (b)) 01882 # define MAX(a,b) ((a) > (b) ? (a) : (b)) 01883 #else 01884 # define MIN(a,b) std::min(a,b) 01885 # define MAX(a,b) std::max(a,b) 01886 #endif 01887 01888 bool fatDrive::AllocationInfo32(Bit32u * _bytes_sector,Bit32u * _sectors_cluster,Bit32u * _total_clusters,Bit32u * _free_clusters) { 01889 Bit32u countFree = 0; 01890 Bit32u i; 01891 01892 for(i=0;i<CountOfClusters;i++) { 01893 if(!getClusterValue(i+2)) 01894 countFree++; 01895 } 01896 01897 *_bytes_sector = getSectSize(); 01898 *_sectors_cluster = BPB.v.BPB_SecPerClus; 01899 *_total_clusters = CountOfClusters; 01900 *_free_clusters = countFree; 01901 01902 return true; 01903 } 01904 01905 bool fatDrive::AllocationInfo(Bit16u *_bytes_sector, Bit8u *_sectors_cluster, Bit16u *_total_clusters, Bit16u *_free_clusters) { 01906 if (BPB.is_fat32()) { 01907 Bit32u bytes32,sectors32,clusters32,free32; 01908 if (AllocationInfo32(&bytes32,§ors32,&clusters32,&free32) && 01909 DOS_CommonFAT32FAT16DiskSpaceConv(_bytes_sector,_sectors_cluster,_total_clusters,_free_clusters,bytes32,sectors32,clusters32,free32)) 01910 return true; 01911 01912 return false; 01913 } 01914 else { 01915 Bit32u countFree = 0; 01916 Bit32u i; 01917 01918 for(i=0;i<CountOfClusters;i++) { 01919 if(!getClusterValue(i+2)) 01920 countFree++; 01921 } 01922 01923 /* FAT12/FAT16 should never allow more than 0xFFF6 clusters and partitions larger than 2GB */ 01924 *_bytes_sector = (Bit16u)getSectSize(); 01925 *_sectors_cluster = BPB.v.BPB_SecPerClus; 01926 *_total_clusters = (Bit16u)MIN(CountOfClusters,0xFFFFu); 01927 *_free_clusters = (Bit16u)MIN(countFree,0xFFFFu); 01928 } 01929 01930 return true; 01931 } 01932 01933 Bit32u fatDrive::getFirstFreeClust(void) { 01934 Bit32u i; 01935 for(i=0;i<CountOfClusters;i++) { 01936 if(!getClusterValue(i+2)) return (i+2); 01937 } 01938 01939 /* No free cluster found */ 01940 return 0; 01941 } 01942 01943 bool fatDrive::isRemote(void) { return false; } 01944 bool fatDrive::isRemovable(void) { return false; } 01945 01946 Bits fatDrive::UnMount(void) { 01947 delete this; 01948 return 0; 01949 } 01950 01951 Bit8u fatDrive::GetMediaByte(void) { return BPB.v.BPB_Media; } 01952 const FAT_BootSector::bpb_union_t &fatDrive::GetBPB(void) { return BPB; } 01953 01954 void fatDrive::SetBPB(const FAT_BootSector::bpb_union_t &bpb) { 01955 if (readonly) return; 01956 BPB.v.BPB_BytsPerSec = bpb.v.BPB_BytsPerSec; 01957 BPB.v.BPB_SecPerClus = bpb.v.BPB_SecPerClus; 01958 BPB.v.BPB_RsvdSecCnt = bpb.v.BPB_RsvdSecCnt; 01959 BPB.v.BPB_NumFATs = bpb.v.BPB_NumFATs; 01960 BPB.v.BPB_RootEntCnt = bpb.v.BPB_RootEntCnt; 01961 BPB.v.BPB_TotSec16 = bpb.v.BPB_TotSec16; 01962 BPB.v.BPB_Media = bpb.v.BPB_Media; 01963 BPB.v.BPB_FATSz16 = bpb.v.BPB_FATSz16; 01964 BPB.v.BPB_SecPerTrk = bpb.v.BPB_SecPerTrk; 01965 BPB.v.BPB_NumHeads = bpb.v.BPB_NumHeads; 01966 BPB.v.BPB_HiddSec = bpb.v.BPB_HiddSec; 01967 BPB.v.BPB_TotSec32 = bpb.v.BPB_TotSec32; 01968 if (!bpb.is_fat32() && (bpb.v.BPB_BootSig == 0x28 || bpb.v.BPB_BootSig == 0x29)) 01969 BPB.v.BPB_VolID = bpb.v.BPB_VolID; 01970 if (bpb.is_fat32() && (bpb.v32.BS_BootSig == 0x28 || bpb.v32.BS_BootSig == 0x29)) 01971 BPB.v32.BS_VolID = bpb.v32.BS_VolID; 01972 if (bpb.is_fat32()) { 01973 BPB.v32.BPB_BytsPerSec = bpb.v32.BPB_BytsPerSec; 01974 BPB.v32.BPB_SecPerClus = bpb.v32.BPB_SecPerClus; 01975 BPB.v32.BPB_RsvdSecCnt = bpb.v32.BPB_RsvdSecCnt; 01976 BPB.v32.BPB_NumFATs = bpb.v32.BPB_NumFATs; 01977 BPB.v32.BPB_RootEntCnt = bpb.v32.BPB_RootEntCnt; 01978 BPB.v32.BPB_TotSec16 = bpb.v32.BPB_TotSec16; 01979 BPB.v32.BPB_Media = bpb.v32.BPB_Media; 01980 BPB.v32.BPB_FATSz32 = bpb.v32.BPB_FATSz32; 01981 BPB.v32.BPB_SecPerTrk = bpb.v32.BPB_SecPerTrk; 01982 BPB.v32.BPB_NumHeads = bpb.v32.BPB_NumHeads; 01983 BPB.v32.BPB_HiddSec = bpb.v32.BPB_HiddSec; 01984 BPB.v32.BPB_TotSec32 = bpb.v32.BPB_TotSec32; 01985 BPB.v32.BPB_FATSz32 = bpb.v32.BPB_FATSz32; 01986 BPB.v32.BPB_ExtFlags = bpb.v32.BPB_ExtFlags; 01987 BPB.v32.BPB_FSVer = bpb.v32.BPB_FSVer; 01988 BPB.v32.BPB_RootClus = bpb.v32.BPB_RootClus; 01989 BPB.v32.BPB_FSInfo = bpb.v32.BPB_FSInfo; 01990 BPB.v32.BPB_BkBootSec = bpb.v32.BPB_BkBootSec; 01991 } 01992 01993 FAT_BootSector bootbuffer = {}; 01994 loadedDisk->Read_AbsoluteSector(0+partSectOff,&bootbuffer); 01995 if (BPB.is_fat32()) bootbuffer.bpb.v32=BPB.v32; 01996 bootbuffer.bpb.v=BPB.v; 01997 loadedDisk->Write_AbsoluteSector(0+partSectOff,&bootbuffer); 01998 } 01999 02000 bool fatDrive::FileCreate(DOS_File **file, const char *name, Bit16u attributes) { 02001 const char *lfn = NULL; 02002 02003 if (readonly) { 02004 DOS_SetError(DOSERR_WRITE_PROTECTED); 02005 return false; 02006 } 02007 direntry fileEntry = {}; 02008 Bit32u dirClust, subEntry; 02009 char dirName[DOS_NAMELENGTH_ASCII]; 02010 char pathName[11], path[DOS_PATHLENGTH]; 02011 02012 Bit16u save_errorcode=dos.errorcode; 02013 02014 if (attributes & DOS_ATTR_VOLUME) { 02015 SetLabel(name,false,true); 02016 return true; 02017 } 02018 if (attributes & DOS_ATTR_DIRECTORY) { 02019 DOS_SetError(DOSERR_ACCESS_DENIED); 02020 return false; 02021 } 02022 02023 /* you cannot create root directory */ 02024 if (*name == 0) { 02025 DOS_SetError(DOSERR_ACCESS_DENIED); 02026 return false; 02027 } 02028 02029 /* Check if file already exists */ 02030 if(getFileDirEntry(name, &fileEntry, &dirClust, &subEntry, true/*dirOk*/)) { 02031 /* You can't create/truncate a directory! */ 02032 if (fileEntry.attrib & DOS_ATTR_DIRECTORY) { 02033 DOS_SetError(DOSERR_ACCESS_DENIED); 02034 return false; 02035 } 02036 02037 /* Truncate file allocation chain */ 02038 { 02039 const Bit32u chk = BPB.is_fat32() ? fileEntry.Cluster32() : fileEntry.loFirstClust; 02040 if(chk != 0) deleteClustChain(chk, 0); 02041 } 02042 /* Update directory entry */ 02043 fileEntry.entrysize=0; 02044 fileEntry.SetCluster32(0); 02045 directoryChange(dirClust, &fileEntry, (Bit32s)subEntry); 02046 } else { 02047 /* Can we even get the name of the file itself? */ 02048 if(!getEntryName(name, &dirName[0])||!strlen(trim(dirName))) return false; 02049 convToDirFile(&dirName[0], &pathName[0]); 02050 02051 /* Can we find the base directory? */ 02052 if(!getDirClustNum(name, &dirClust, true)) return false; 02053 02054 /* NTS: "name" is the full relative path. For LFN creation to work we need only the final element of the path */ 02055 if (uselfn && !force_sfn) { 02056 lfn = strrchr(name,'\\'); 02057 02058 if (lfn != NULL) { 02059 lfn++; /* step past '\' */ 02060 strcpy(path, name); 02061 *(strrchr(path,'\\')+1)=0; 02062 } else { 02063 lfn = name; /* no path elements */ 02064 *path=0; 02065 } 02066 02067 if (filename_not_strict_8x3(lfn)) { 02068 char *sfn=Generate_SFN(path, lfn); 02069 if (sfn!=NULL) convToDirFile(sfn, &pathName[0]); 02070 } else 02071 lfn = NULL; 02072 } 02073 02074 memset(&fileEntry, 0, sizeof(direntry)); 02075 memcpy(&fileEntry.entryname, &pathName[0], 11); 02076 { 02077 Bit16u ct,cd; 02078 time_t_to_DOS_DateTime(/*&*/ct,/*&*/cd,time(NULL)); 02079 fileEntry.modTime = ct; 02080 fileEntry.modDate = cd; 02081 } 02082 fileEntry.attrib = (Bit8u)(attributes & 0xff); 02083 addDirectoryEntry(dirClust, fileEntry, lfn); 02084 02085 /* Check if file exists now */ 02086 if(!getFileDirEntry(name, &fileEntry, &dirClust, &subEntry)) return false; 02087 } 02088 02089 /* Empty file created, now lets open it */ 02090 /* TODO: check for read-only flag and requested write access */ 02091 *file = new fatFile(name, BPB.is_fat32() ? fileEntry.Cluster32() : fileEntry.loFirstClust, fileEntry.entrysize, this); 02092 (*file)->flags=OPEN_READWRITE; 02093 ((fatFile *)(*file))->dirCluster = dirClust; 02094 ((fatFile *)(*file))->dirIndex = subEntry; 02095 /* Maybe modTime and date should be used ? (crt matches findnext) */ 02096 ((fatFile *)(*file))->time = fileEntry.modTime; 02097 ((fatFile *)(*file))->date = fileEntry.modDate; 02098 02099 dos.errorcode=save_errorcode; 02100 return true; 02101 } 02102 02103 bool fatDrive::FileExists(const char *name) { 02104 direntry fileEntry = {}; 02105 Bit32u dummy1, dummy2; 02106 if(!getFileDirEntry(name, &fileEntry, &dummy1, &dummy2)) return false; 02107 return true; 02108 } 02109 02110 bool fatDrive::FileOpen(DOS_File **file, const char *name, Bit32u flags) { 02111 direntry fileEntry = {}; 02112 Bit32u dirClust, subEntry; 02113 02114 /* you cannot open root directory */ 02115 if (*name == 0) { 02116 DOS_SetError(DOSERR_ACCESS_DENIED); 02117 return false; 02118 } 02119 02120 if(!getFileDirEntry(name, &fileEntry, &dirClust, &subEntry)) return false; 02121 /* TODO: check for read-only flag and requested write access */ 02122 *file = new fatFile(name, BPB.is_fat32() ? fileEntry.Cluster32() : fileEntry.loFirstClust, fileEntry.entrysize, this); 02123 (*file)->flags = flags; 02124 ((fatFile *)(*file))->dirCluster = dirClust; 02125 ((fatFile *)(*file))->dirIndex = subEntry; 02126 /* Maybe modTime and date should be used ? (crt matches findnext) */ 02127 ((fatFile *)(*file))->time = fileEntry.modTime; 02128 ((fatFile *)(*file))->date = fileEntry.modDate; 02129 return true; 02130 } 02131 02132 bool fatDrive::FileStat(const char * /*name*/, FileStat_Block *const /*stat_block*/) { 02133 /* TODO: Stub */ 02134 return false; 02135 } 02136 02137 bool fatDrive::FileUnlink(const char * name) { 02138 if (readonly) { 02139 DOS_SetError(DOSERR_WRITE_PROTECTED); 02140 return false; 02141 } 02142 direntry tmpentry = {}; 02143 direntry fileEntry = {}; 02144 Bit32u dirClust, subEntry; 02145 02146 /* you cannot delete root directory */ 02147 if (*name == 0) { 02148 DOS_SetError(DOSERR_ACCESS_DENIED); 02149 return false; 02150 } 02151 02152 lfnRange.clear(); 02153 if(!getFileDirEntry(name, &fileEntry, &dirClust, &subEntry)) return false; /* Do not use dirOk, DOS should never call this unless a file */ 02154 lfnRange_t dir_lfn_range = lfnRange; /* copy down LFN results before they are obliterated by the next call to FindNextInternal. */ 02155 02156 /* delete LFNs */ 02157 if (!dir_lfn_range.empty() && (dos.version.major >= 7 || uselfn)) { 02158 /* last LFN entry should be fileidx */ 02159 assert(dir_lfn_range.dirPos_start < dir_lfn_range.dirPos_end); 02160 if (dir_lfn_range.dirPos_end != subEntry) LOG_MSG("FAT warning: LFN dirPos_end=%u fileidx=%u (mismatch)",dir_lfn_range.dirPos_end,subEntry); 02161 for (unsigned int didx=dir_lfn_range.dirPos_start;didx < dir_lfn_range.dirPos_end;didx++) { 02162 if (directoryBrowse(dirClust,&tmpentry,didx)) { 02163 tmpentry.entryname[0] = 0xe5; 02164 directoryChange(dirClust,&tmpentry,didx); 02165 } 02166 } 02167 } 02168 02169 /* remove primary 8.3 SFN */ 02170 fileEntry.entryname[0] = 0xe5; 02171 directoryChange(dirClust, &fileEntry, (Bit32s)subEntry); 02172 02173 /* delete allocation chain */ 02174 { 02175 const Bit32u chk = BPB.is_fat32() ? fileEntry.Cluster32() : fileEntry.loFirstClust; 02176 if(chk != 0) deleteClustChain(chk, 0); 02177 } 02178 02179 if(getFileDirEntry(name, &fileEntry, &dirClust, &subEntry)) return false; 02180 02181 return true; 02182 } 02183 02184 bool fatDrive::FindFirst(const char *_dir, DOS_DTA &dta,bool /*fcb_findfirst*/) { 02185 direntry dummyClust = {}; 02186 02187 // volume label searches always affect root directory, no matter the current directory, at least with FCBs 02188 if (dta.GetAttr() & DOS_ATTR_VOLUME) { 02189 if(!getDirClustNum("\\", &cwdDirCluster, false)) { 02190 DOS_SetError(DOSERR_PATH_NOT_FOUND); 02191 return false; 02192 } 02193 } 02194 else { 02195 if(!getDirClustNum(_dir, &cwdDirCluster, false)) { 02196 DOS_SetError(DOSERR_PATH_NOT_FOUND); 02197 return false; 02198 } 02199 } 02200 02201 if (lfn_filefind_handle>=LFN_FILEFIND_MAX) { 02202 dta.SetDirID(0); 02203 dta.SetDirIDCluster(cwdDirCluster); 02204 } else { 02205 dpos[lfn_filefind_handle]=0; 02206 dnum[lfn_filefind_handle]=cwdDirCluster; 02207 } 02208 02209 return FindNextInternal(cwdDirCluster, dta, &dummyClust); 02210 } 02211 02212 char* removeTrailingSpaces(char* str) { 02213 char* end = str + strlen(str) - 1; 02214 while (end >= str && *end == ' ') end--; 02215 /* NTS: The loop will exit with 'end' one char behind the last ' ' space character. 02216 * So to ASCIIZ snip off the space, step forward one and overwrite with NUL. 02217 * The loop may end with 'end' one char behind 'ptr' if the string was empty "" 02218 * or nothing but spaces. This is OK because after the step forward, end >= str 02219 * in all cases. */ 02220 *(++end) = '\0'; 02221 return str; 02222 } 02223 02224 char* removeLeadingSpaces(char* str) { 02225 size_t len = strlen(str); 02226 size_t pos = strspn(str," "); 02227 memmove(str,str + pos,len - pos + 1); 02228 return str; 02229 } 02230 02231 char* trimString(char* str) { 02232 return removeTrailingSpaces(removeLeadingSpaces(str)); 02233 } 02234 02235 Bit32u fatDrive::GetSectorCount(void) { 02236 return (loadedDisk->heads * loadedDisk->sectors * loadedDisk->cylinders) - partSectOff; 02237 } 02238 02239 Bit32u fatDrive::GetSectorSize(void) { 02240 return getSectorSize(); 02241 } 02242 02243 Bit8u fatDrive::Read_AbsoluteSector_INT25(Bit32u sectnum, void * data) { 02244 return readSector(sectnum+partSectOff,data); 02245 } 02246 02247 Bit8u fatDrive::Write_AbsoluteSector_INT25(Bit32u sectnum, void * data) { 02248 return writeSector(sectnum+partSectOff,data); 02249 } 02250 02251 bool fatDrive::FindNextInternal(Bit32u dirClustNumber, DOS_DTA &dta, direntry *foundEntry) { 02252 direntry sectbuf[MAX_DIRENTS_PER_SECTOR]; /* 16 directory entries per 512 byte sector */ 02253 Bit32u logentsector; /* Logical entry sector */ 02254 Bit32u entryoffset; /* Index offset within sector */ 02255 Bit32u tmpsector; 02256 Bit8u attrs; 02257 Bit16u dirPos; 02258 char srch_pattern[CROSS_LEN]; 02259 char find_name[DOS_NAMELENGTH_ASCII]; 02260 char lfind_name[LFN_NAMELENGTH+1]; 02261 unsigned int lfn_max_ord = 0; 02262 unsigned char lfn_checksum = 0; 02263 bool lfn_ord_found[0x40]; 02264 char extension[4]; 02265 02266 size_t dirent_per_sector = getSectSize() / sizeof(direntry); 02267 assert(dirent_per_sector <= MAX_DIRENTS_PER_SECTOR); 02268 assert((dirent_per_sector * sizeof(direntry)) <= SECTOR_SIZE_MAX); 02269 02270 dta.GetSearchParams(attrs, srch_pattern,uselfn); 02271 dirPos = lfn_filefind_handle>=LFN_FILEFIND_MAX?dta.GetDirID():dpos[lfn_filefind_handle]; /* NTS: Windows 9x is said to have a 65536 dirent limit even for FAT32, so dirPos as 16-bit is acceptable */ 02272 02273 memset(lfind_name,0,LFN_NAMELENGTH); 02274 lfnRange.clear(); 02275 02276 nextfile: 02277 logentsector = (Bit32u)((size_t)dirPos / dirent_per_sector); 02278 entryoffset = (Bit32u)((size_t)dirPos % dirent_per_sector); 02279 02280 if(dirClustNumber==0) { 02281 if (BPB.is_fat32()) return false; 02282 02283 if(dirPos >= BPB.v.BPB_RootEntCnt) { 02284 if (lfn_filefind_handle<LFN_FILEFIND_MAX) { 02285 dpos[lfn_filefind_handle]=0; 02286 dnum[lfn_filefind_handle]=0; 02287 } 02288 DOS_SetError(DOSERR_NO_MORE_FILES); 02289 return false; 02290 } 02291 readSector(firstRootDirSect+logentsector,sectbuf); 02292 } else { 02293 tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector); 02294 /* A zero sector number can't happen */ 02295 if(tmpsector == 0) { 02296 if (lfn_filefind_handle<LFN_FILEFIND_MAX) { 02297 dpos[lfn_filefind_handle]=0; 02298 dnum[lfn_filefind_handle]=0; 02299 } 02300 DOS_SetError(DOSERR_NO_MORE_FILES); 02301 return false; 02302 } 02303 readSector(tmpsector,sectbuf); 02304 } 02305 dirPos++; 02306 if (lfn_filefind_handle>=LFN_FILEFIND_MAX) dta.SetDirID(dirPos); 02307 else dpos[lfn_filefind_handle]=dirPos; 02308 02309 /* Deleted file entry */ 02310 if (sectbuf[entryoffset].entryname[0] == 0xe5) { 02311 lfind_name[0] = 0; /* LFN code will memset() it in full upon next dirent */ 02312 lfn_max_ord = 0; 02313 lfnRange.clear(); 02314 goto nextfile; 02315 } 02316 02317 /* End of directory list */ 02318 if (sectbuf[entryoffset].entryname[0] == 0x00) { 02319 if (lfn_filefind_handle<LFN_FILEFIND_MAX) { 02320 dpos[lfn_filefind_handle]=0; 02321 dnum[lfn_filefind_handle]=0; 02322 } 02323 DOS_SetError(DOSERR_NO_MORE_FILES); 02324 return false; 02325 } 02326 memset(find_name,0,DOS_NAMELENGTH_ASCII); 02327 memset(extension,0,4); 02328 memcpy(find_name,§buf[entryoffset].entryname[0],8); 02329 memcpy(extension,§buf[entryoffset].entryname[8],3); 02330 02331 if (!(sectbuf[entryoffset].attrib & DOS_ATTR_VOLUME)) { 02332 trimString(&find_name[0]); 02333 trimString(&extension[0]); 02334 } 02335 02336 if (extension[0]!=0) { 02337 if (!(sectbuf[entryoffset].attrib & DOS_ATTR_VOLUME)) { 02338 strcat(find_name, "."); 02339 } 02340 strcat(find_name, extension); 02341 } 02342 02343 if (sectbuf[entryoffset].attrib & DOS_ATTR_VOLUME) 02344 trimString(find_name); 02345 02346 /* Compare attributes to search attributes */ 02347 02348 //TODO What about attrs = DOS_ATTR_VOLUME|DOS_ATTR_DIRECTORY ? 02349 if (attrs == DOS_ATTR_VOLUME) { 02350 if (dos.version.major >= 7 || uselfn) { 02351 /* skip LFN entries */ 02352 if ((sectbuf[entryoffset].attrib & 0x3F) == 0x0F) 02353 goto nextfile; 02354 } 02355 02356 if (!(sectbuf[entryoffset].attrib & DOS_ATTR_VOLUME)) goto nextfile; 02357 labelCache.SetLabel(find_name, false, true); 02358 } else if ((dos.version.major >= 7 || uselfn) && (sectbuf[entryoffset].attrib & 0x3F) == 0x0F) { /* long filename piece */ 02359 struct direntry_lfn *dlfn = (struct direntry_lfn*)(§buf[entryoffset]); 02360 02361 /* assume last entry comes first, because that's how Windows 9x does it and that is how you're supposed to do it according to Microsoft */ 02362 if (dlfn->LDIR_Ord & 0x40) { 02363 lfn_max_ord = (dlfn->LDIR_Ord & 0x3F); /* NTS: First entry has ordinal 1, this is the HIGHEST ordinal in the LFN. The other entries follow in descending ordinal. */ 02364 for (unsigned int i=0;i < 0x40;i++) lfn_ord_found[i] = false; 02365 lfn_checksum = dlfn->LDIR_Chksum; 02366 memset(lfind_name,0,LFN_NAMELENGTH); 02367 lfnRange.clear(); 02368 lfnRange.dirPos_start = dirPos - 1; /* NTS: The code above has already incremented dirPos */ 02369 } 02370 02371 if (lfn_max_ord != 0 && (dlfn->LDIR_Ord & 0x3F) > 0 && (dlfn->LDIR_Ord & 0x3Fu) <= lfn_max_ord && dlfn->LDIR_Chksum == lfn_checksum) { 02372 unsigned int oidx = (dlfn->LDIR_Ord & 0x3Fu) - 1u; 02373 unsigned int stridx = oidx * 13u; 02374 02375 if ((stridx+13u) <= LFN_NAMELENGTH) { 02376 for (unsigned int i=0;i < 5;i++) 02377 lfind_name[stridx+i+0] = (char)(dlfn->LDIR_Name1[i] & 0xFF); 02378 for (unsigned int i=0;i < 6;i++) 02379 lfind_name[stridx+i+5] = (char)(dlfn->LDIR_Name2[i] & 0xFF); 02380 for (unsigned int i=0;i < 2;i++) 02381 lfind_name[stridx+i+11] = (char)(dlfn->LDIR_Name3[i] & 0xFF); 02382 02383 lfn_ord_found[oidx] = true; 02384 } 02385 } 02386 02387 goto nextfile; 02388 } else { 02389 if (~attrs & sectbuf[entryoffset].attrib & (DOS_ATTR_DIRECTORY | DOS_ATTR_VOLUME) ) { 02390 lfind_name[0] = 0; /* LFN code will memset() it in full upon next dirent */ 02391 lfn_max_ord = 0; 02392 lfnRange.clear(); 02393 goto nextfile; 02394 } 02395 } 02396 02397 if (lfn_max_ord != 0) { 02398 bool ok = false; 02399 unsigned int complete = 0; 02400 for (unsigned int i=0;i < lfn_max_ord;i++) complete += lfn_ord_found[i]?1:0; 02401 02402 if (complete == lfn_max_ord) { 02403 unsigned char chk = 0; 02404 for (unsigned int i=0;i < 11;i++) { 02405 chk = ((chk & 1u) ? 0x80u : 0x00u) + (chk >> 1u) + sectbuf[entryoffset].entryname[i]; 02406 } 02407 02408 if (lfn_checksum == chk) { 02409 lfnRange.dirPos_end = dirPos - 1; /* NTS: The code above has already incremented dirPos */ 02410 ok = true; 02411 } 02412 } 02413 02414 if (!ok) { 02415 lfind_name[0] = 0; /* LFN code will memset() it in full upon next dirent */ 02416 lfn_max_ord = 0; 02417 lfnRange.clear(); 02418 } 02419 } 02420 else { 02421 lfind_name[0] = 0; /* LFN code will memset() it in full upon next dirent */ 02422 lfn_max_ord = 0; 02423 lfnRange.clear(); 02424 } 02425 02426 /* Compare name to search pattern. Skip long filename match if no long filename given. */ 02427 if (!(WildFileCmp(find_name,srch_pattern) || (lfn_max_ord != 0 && lfind_name[0] != 0 && LWildFileCmp(lfind_name,srch_pattern)))) { 02428 lfind_name[0] = 0; /* LFN code will memset() it in full upon next dirent */ 02429 lfn_max_ord = 0; 02430 lfnRange.clear(); 02431 goto nextfile; 02432 } 02433 02434 // Drive emulation does not need to require a LFN in case there is no corresponding 8.3 names. 02435 if (lfind_name[0] == 0) strcpy(lfind_name,find_name); 02436 02437 //dta.SetResult(find_name, sectbuf[entryoffset].entrysize, sectbuf[entryoffset].crtDate, sectbuf[entryoffset].crtTime, sectbuf[entryoffset].attrib); 02438 02439 dta.SetResult(find_name, lfind_name, sectbuf[entryoffset].entrysize, sectbuf[entryoffset].modDate, sectbuf[entryoffset].modTime, sectbuf[entryoffset].attrib); 02440 02441 memcpy(foundEntry, §buf[entryoffset], sizeof(direntry)); 02442 02443 return true; 02444 } 02445 02446 bool fatDrive::FindNext(DOS_DTA &dta) { 02447 direntry dummyClust = {}; 02448 02449 return FindNextInternal(lfn_filefind_handle>=LFN_FILEFIND_MAX?dta.GetDirIDCluster():(dnum[lfn_filefind_handle]?dnum[lfn_filefind_handle]:0), dta, &dummyClust); 02450 } 02451 02452 02453 bool fatDrive::SetFileAttr(const char *name, Bit16u attr) { 02454 if (readonly) { 02455 DOS_SetError(DOSERR_WRITE_PROTECTED); 02456 return false; 02457 } 02458 direntry fileEntry = {}; 02459 Bit32u dirClust, subEntry; 02460 02461 /* you cannot set file attr root directory (right?) */ 02462 if (*name == 0) { 02463 DOS_SetError(DOSERR_ACCESS_DENIED); 02464 return false; 02465 } 02466 02467 if(!getFileDirEntry(name, &fileEntry, &dirClust, &subEntry, /*dirOk*/true)) { 02468 return false; 02469 } else { 02470 fileEntry.attrib=(Bit8u)attr; 02471 directoryChange(dirClust, &fileEntry, (Bit32s)subEntry); 02472 } 02473 return true; 02474 } 02475 02476 bool fatDrive::GetFileAttr(const char *name, Bit16u *attr) { 02477 direntry fileEntry = {}; 02478 Bit32u dirClust, subEntry; 02479 02480 /* you CAN get file attr root directory */ 02481 if (*name == 0) { 02482 *attr=DOS_ATTR_DIRECTORY; 02483 return true; 02484 } 02485 02486 if(!getFileDirEntry(name, &fileEntry, &dirClust, &subEntry, /*dirOk*/true)) { 02487 return false; 02488 } else *attr=fileEntry.attrib; 02489 return true; 02490 } 02491 02492 bool fatDrive::GetFileAttrEx(char* name, struct stat *status) { 02493 (void)name; 02494 (void)status; 02495 return false; 02496 } 02497 02498 unsigned long fatDrive::GetCompressedSize(char* name) { 02499 (void)name; 02500 return 0; 02501 } 02502 02503 #if defined (WIN32) 02504 HANDLE fatDrive::CreateOpenFile(const char* name) { 02505 (void)name; 02506 DOS_SetError(1); 02507 return INVALID_HANDLE_VALUE; 02508 } 02509 #endif 02510 02511 unsigned long fatDrive::GetSerial() { 02512 if (BPB.is_fat32()) 02513 return BPB.v32.BS_VolID?BPB.v32.BS_VolID:0x1234; 02514 else 02515 return BPB.v.BPB_VolID?BPB.v.BPB_VolID:0x1234; 02516 } 02517 02518 bool fatDrive::directoryBrowse(Bit32u dirClustNumber, direntry *useEntry, Bit32s entNum, Bit32s start/*=0*/) { 02519 direntry sectbuf[MAX_DIRENTS_PER_SECTOR]; /* 16 directory entries per 512 byte sector */ 02520 Bit32u entryoffset = 0; /* Index offset within sector */ 02521 Bit32u tmpsector; 02522 Bit16u dirPos = 0; 02523 02524 (void)start;//UNUSED 02525 02526 size_t dirent_per_sector = getSectSize() / sizeof(direntry); 02527 assert(dirent_per_sector <= MAX_DIRENTS_PER_SECTOR); 02528 assert((dirent_per_sector * sizeof(direntry)) <= SECTOR_SIZE_MAX); 02529 02530 while(entNum>=0) { 02531 Bit32u logentsector = ((Bit32u)((size_t)dirPos / dirent_per_sector)); /* Logical entry sector */ 02532 entryoffset = ((Bit32u)((size_t)dirPos % dirent_per_sector)); 02533 02534 if(dirClustNumber==0) { 02535 assert(!BPB.is_fat32()); 02536 if(dirPos >= BPB.v.BPB_RootEntCnt) return false; 02537 tmpsector = firstRootDirSect+logentsector; 02538 readSector(tmpsector,sectbuf); 02539 } else { 02540 tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector); 02541 /* A zero sector number can't happen */ 02542 if(tmpsector == 0) return false; 02543 readSector(tmpsector,sectbuf); 02544 } 02545 dirPos++; 02546 02547 02548 /* End of directory list */ 02549 if (sectbuf[entryoffset].entryname[0] == 0x00) return false; 02550 --entNum; 02551 } 02552 02553 memcpy(useEntry, §buf[entryoffset],sizeof(direntry)); 02554 return true; 02555 } 02556 02557 bool fatDrive::directoryChange(Bit32u dirClustNumber, const direntry *useEntry, Bit32s entNum) { 02558 direntry sectbuf[MAX_DIRENTS_PER_SECTOR]; /* 16 directory entries per 512 byte sector */ 02559 Bit32u entryoffset = 0; /* Index offset within sector */ 02560 Bit32u tmpsector = 0; 02561 Bit16u dirPos = 0; 02562 02563 size_t dirent_per_sector = getSectSize() / sizeof(direntry); 02564 assert(dirent_per_sector <= MAX_DIRENTS_PER_SECTOR); 02565 assert((dirent_per_sector * sizeof(direntry)) <= SECTOR_SIZE_MAX); 02566 02567 while(entNum>=0) { 02568 Bit32u logentsector = ((Bit32u)((size_t)dirPos / dirent_per_sector)); /* Logical entry sector */ 02569 entryoffset = ((Bit32u)((size_t)dirPos % dirent_per_sector)); 02570 02571 if(dirClustNumber==0) { 02572 assert(!BPB.is_fat32()); 02573 if(dirPos >= BPB.v.BPB_RootEntCnt) return false; 02574 tmpsector = firstRootDirSect+logentsector; 02575 readSector(tmpsector,sectbuf); 02576 } else { 02577 tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector); 02578 /* A zero sector number can't happen */ 02579 if(tmpsector == 0) return false; 02580 readSector(tmpsector,sectbuf); 02581 } 02582 dirPos++; 02583 02584 02585 /* End of directory list */ 02586 if (sectbuf[entryoffset].entryname[0] == 0x00) return false; 02587 --entNum; 02588 } 02589 if(tmpsector != 0) { 02590 memcpy(§buf[entryoffset], useEntry, sizeof(direntry)); 02591 writeSector(tmpsector, sectbuf); 02592 return true; 02593 } else { 02594 return false; 02595 } 02596 } 02597 02598 bool fatDrive::addDirectoryEntry(Bit32u dirClustNumber, const direntry& useEntry,const char *lfn) { 02599 direntry sectbuf[MAX_DIRENTS_PER_SECTOR]; /* 16 directory entries per 512 byte sector */ 02600 Bit32u tmpsector; 02601 Bit16u dirPos = 0; 02602 unsigned int need = 1; 02603 unsigned int found = 0; 02604 Bit16u dirPosFound = 0; 02605 02606 if (lfn != NULL && *lfn != 0) { 02607 /* 13 characters per LFN entry. 02608 * FIXME: When we convert the LFN to wchar using code page, strlen() prior to conversion will not work, 02609 * convert first then count wchar_t characters. */ 02610 need = (unsigned int)(1 + ((strlen(lfn) + 12) / 13))/*round up*/; 02611 } 02612 02613 size_t dirent_per_sector = getSectSize() / sizeof(direntry); 02614 assert(dirent_per_sector <= MAX_DIRENTS_PER_SECTOR); 02615 assert((dirent_per_sector * sizeof(direntry)) <= SECTOR_SIZE_MAX); 02616 02617 for(;;) { 02618 Bit32u logentsector = ((Bit32u)((size_t)dirPos / dirent_per_sector)); /* Logical entry sector */ 02619 Bit32u entryoffset = ((Bit32u)((size_t)dirPos % dirent_per_sector)); /* Index offset within sector */ 02620 02621 if(dirClustNumber==0) { 02622 assert(!BPB.is_fat32()); 02623 if(dirPos >= BPB.v.BPB_RootEntCnt) return false; 02624 tmpsector = firstRootDirSect+logentsector; 02625 } else { 02626 tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector); 02627 /* A zero sector number can't happen - we need to allocate more room for this directory*/ 02628 if(tmpsector == 0) { 02629 Bit32u newClust; 02630 newClust = appendCluster(dirClustNumber); 02631 if(newClust == 0) return false; 02632 zeroOutCluster(newClust); 02633 /* Try again to get tmpsector */ 02634 tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector); 02635 if(tmpsector == 0) return false; /* Give up if still can't get more room for directory */ 02636 } 02637 } 02638 readSector(tmpsector,sectbuf); 02639 02640 /* Deleted file entry or end of directory list */ 02641 if ((sectbuf[entryoffset].entryname[0] == 0xe5) || (sectbuf[entryoffset].entryname[0] == 0x00)) { 02642 if (found == 0) dirPosFound = dirPos; 02643 02644 if ((++found) >= need) { 02645 sectbuf[entryoffset] = useEntry; 02646 writeSector(tmpsector,sectbuf); 02647 02648 /* Add LFN entries */ 02649 if (need != 1/*LFN*/) { 02650 Bit16u lfnbuf[LFN_NAMELENGTH+13]; /* on disk, LFNs are WCHAR unicode (UCS-16) */ 02651 02652 assert(lfn != NULL); 02653 assert(*lfn != 0); 02654 02655 /* TODO: ANSI LFN convert to wchar here according to code page */ 02656 02657 unsigned int o = 0; 02658 const char *scan = lfn; 02659 02660 while (*scan) { 02661 if (o >= LFN_NAMELENGTH) return false; /* Nope! */ 02662 lfnbuf[o++] = (Bit16u)((unsigned char)(*scan++)); 02663 } 02664 02665 /* on disk, LFNs are padded with 0x0000 followed by a run of 0xFFFF to fill the dirent */ 02666 lfnbuf[o++] = 0x0000; 02667 for (unsigned int i=0;i < 13;i++) lfnbuf[o++] = 0xFFFF; 02668 assert(o <= (LFN_NAMELENGTH+13)); 02669 02670 unsigned char chk = 0; 02671 for (unsigned int i=0;i < 11;i++) { 02672 chk = ((chk & 1u) ? 0x80u : 0x00u) + (chk >> 1u) + useEntry.entryname[i]; 02673 } 02674 02675 dirPos = dirPosFound; 02676 for (unsigned int s=0;s < (need-1u);s++) { 02677 unsigned int lfnsrci = (need-2u-s); 02678 unsigned int lfnsrc = lfnsrci * 13; 02679 02680 logentsector = ((Bit32u)((size_t)dirPos / dirent_per_sector)); /* Logical entry sector */ 02681 entryoffset = ((Bit32u)((size_t)dirPos % dirent_per_sector)); /* Index offset within sector */ 02682 02683 if(dirClustNumber==0) { 02684 assert(!BPB.is_fat32()); 02685 if(dirPos >= BPB.v.BPB_RootEntCnt) return false; 02686 tmpsector = firstRootDirSect+logentsector; 02687 } else { 02688 tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector); 02689 /* A zero sector number can't happen - we need to allocate more room for this directory*/ 02690 if(tmpsector == 0) return false; 02691 } 02692 readSector(tmpsector,sectbuf); 02693 02694 direntry_lfn *dlfn = (direntry_lfn*)(§buf[entryoffset]); 02695 02696 memset(dlfn,0,sizeof(*dlfn)); 02697 02698 dlfn->LDIR_Ord = (s == 0 ? 0x40 : 0x00) + lfnsrci + 1; 02699 dlfn->LDIR_Chksum = chk; 02700 dlfn->attrib = 0x0F; 02701 02702 for (unsigned int i=0;i < 5;i++) dlfn->LDIR_Name1[i] = lfnbuf[lfnsrc++]; 02703 for (unsigned int i=0;i < 6;i++) dlfn->LDIR_Name2[i] = lfnbuf[lfnsrc++]; 02704 for (unsigned int i=0;i < 2;i++) dlfn->LDIR_Name3[i] = lfnbuf[lfnsrc++]; 02705 02706 writeSector(tmpsector,sectbuf); 02707 dirPos++; 02708 } 02709 } 02710 02711 break; 02712 } 02713 } 02714 else { 02715 found = 0; 02716 } 02717 02718 dirPos++; 02719 } 02720 02721 return true; 02722 } 02723 02724 void fatDrive::zeroOutCluster(Bit32u clustNumber) { 02725 Bit8u secBuffer[SECTOR_SIZE_MAX]; 02726 02727 memset(&secBuffer[0], 0, SECTOR_SIZE_MAX); 02728 02729 unsigned int i; 02730 for(i=0;i<BPB.v.BPB_SecPerClus;i++) { 02731 writeSector(getAbsoluteSectFromChain(clustNumber,i), &secBuffer[0]); 02732 } 02733 } 02734 02735 bool fatDrive::MakeDir(const char *dir) { 02736 const char *lfn = NULL; 02737 02738 if (readonly) { 02739 DOS_SetError(DOSERR_WRITE_PROTECTED); 02740 return false; 02741 } 02742 Bit32u dummyClust, dirClust, subEntry; 02743 direntry tmpentry; 02744 char dirName[DOS_NAMELENGTH_ASCII]; 02745 char pathName[11], path[DOS_PATHLENGTH]; 02746 Bit16u ct,cd; 02747 02748 /* you cannot mkdir root directory */ 02749 if (*dir == 0) { 02750 DOS_SetError(DOSERR_ACCESS_DENIED); 02751 return false; 02752 } 02753 02754 /* Can we even get the name of the directory itself? */ 02755 if(!getEntryName(dir, &dirName[0])||!strlen(trim(dirName))) return false; 02756 convToDirFile(&dirName[0], &pathName[0]); 02757 02758 /* Fail to make directory if something of that name already exists */ 02759 if(getFileDirEntry(dir,&tmpentry,&dummyClust,&subEntry,/*dirOk*/true)) return false; 02760 02761 /* Can we find the base directory? */ 02762 if(!getDirClustNum(dir, &dirClust, true)) return false; 02763 02764 dummyClust = getFirstFreeClust(); 02765 /* No more space */ 02766 if(dummyClust == 0) return false; 02767 02768 if(!allocateCluster(dummyClust, 0)) return false; 02769 02770 /* NTS: "dir" is the full relative path. For LFN creation to work we need only the final element of the path */ 02771 if (uselfn && !force_sfn) { 02772 lfn = strrchr(dir,'\\'); 02773 02774 if (lfn != NULL) { 02775 lfn++; /* step past '\' */ 02776 strcpy(path, dir); 02777 *(strrchr(path,'\\')+1)=0; 02778 } else { 02779 lfn = dir; /* no path elements */ 02780 *path=0; 02781 } 02782 02783 if (filename_not_strict_8x3(lfn)) { 02784 char *sfn=Generate_SFN(path, lfn); 02785 if (sfn!=NULL) convToDirFile(sfn, &pathName[0]); 02786 } else 02787 lfn = NULL; 02788 } 02789 02790 zeroOutCluster(dummyClust); 02791 02792 time_t_to_DOS_DateTime(/*&*/ct,/*&*/cd,::time(NULL)); 02793 02794 /* Add the new directory to the base directory */ 02795 memset(&tmpentry,0, sizeof(direntry)); 02796 memcpy(&tmpentry.entryname, &pathName[0], 11); 02797 tmpentry.loFirstClust = (Bit16u)(dummyClust & 0xffff); 02798 tmpentry.hiFirstClust = (Bit16u)(dummyClust >> 16); 02799 tmpentry.attrib = DOS_ATTR_DIRECTORY; 02800 tmpentry.modTime = ct; 02801 tmpentry.modDate = cd; 02802 addDirectoryEntry(dirClust, tmpentry, lfn); 02803 02804 /* Add the [.] and [..] entries to our new directory*/ 02805 /* [.] entry */ 02806 memset(&tmpentry,0, sizeof(direntry)); 02807 memcpy(&tmpentry.entryname, ". ", 11); 02808 tmpentry.loFirstClust = (Bit16u)(dummyClust & 0xffff); 02809 tmpentry.hiFirstClust = (Bit16u)(dummyClust >> 16); 02810 tmpentry.attrib = DOS_ATTR_DIRECTORY; 02811 tmpentry.modTime = ct; 02812 tmpentry.modDate = cd; 02813 addDirectoryEntry(dummyClust, tmpentry); 02814 02815 /* [..] entry */ 02816 memset(&tmpentry,0, sizeof(direntry)); 02817 memcpy(&tmpentry.entryname, ".. ", 11); 02818 if (BPB.is_fat32() && dirClust == BPB.v32.BPB_RootClus) { 02819 /* Windows 98 SCANDISK.EXE considers it an error for the '..' entry of a top level 02820 * directory to point at the actual cluster number of the root directory. The 02821 * correct value is 0 apparently. */ 02822 tmpentry.loFirstClust = (Bit16u)0; 02823 tmpentry.hiFirstClust = (Bit16u)0; 02824 } 02825 else { 02826 tmpentry.loFirstClust = (Bit16u)(dirClust & 0xffff); 02827 tmpentry.hiFirstClust = (Bit16u)(dirClust >> 16); 02828 } 02829 tmpentry.attrib = DOS_ATTR_DIRECTORY; 02830 tmpentry.modTime = ct; 02831 tmpentry.modDate = cd; 02832 addDirectoryEntry(dummyClust, tmpentry); 02833 //if(!getDirClustNum(dir, &dummyClust, false)) return false; 02834 02835 return true; 02836 } 02837 02838 bool fatDrive::RemoveDir(const char *dir) { 02839 if (readonly) { 02840 DOS_SetError(DOSERR_WRITE_PROTECTED); 02841 return false; 02842 } 02843 Bit32u dummyClust, dirClust, subEntry; 02844 direntry tmpentry = {}; 02845 char dirName[DOS_NAMELENGTH_ASCII]; 02846 char pathName[11]; 02847 02848 /* you cannot rmdir root directory */ 02849 if (*dir == 0) { 02850 DOS_SetError(DOSERR_ACCESS_DENIED); 02851 return false; 02852 } 02853 02854 /* Can we even get the name of the directory itself? */ 02855 if(!getEntryName(dir, &dirName[0])||!strlen(trim(dirName))) return false; 02856 convToDirFile(&dirName[0], &pathName[0]); 02857 02858 /* directory must exist */ 02859 lfnRange.clear(); 02860 if(!getFileDirEntry(dir,&tmpentry,&dirClust,&subEntry,/*dirOk*/true)) return false; /* dirClust is parent dir of directory */ 02861 if (!(tmpentry.attrib & DOS_ATTR_DIRECTORY)) return false; 02862 dummyClust = (BPB.is_fat32() ? tmpentry.Cluster32() : tmpentry.loFirstClust); 02863 lfnRange_t dir_lfn_range = lfnRange; /* copy down LFN results before they are obliterated by the next call to FindNextInternal. */ 02864 02865 /* Can't remove root directory */ 02866 if(dummyClust == 0) return false; 02867 if(BPB.is_fat32() && dummyClust==BPB.v32.BPB_RootClus) return false; 02868 02869 /* Check to make sure directory is empty */ 02870 Bit32u filecount = 0; 02871 /* Set to 2 to skip first 2 entries, [.] and [..] */ 02872 Bit32s fileidx = 2; 02873 while(directoryBrowse(dummyClust, &tmpentry, fileidx)) { 02874 /* Check for non-deleted files */ 02875 if(tmpentry.entryname[0] != 0xe5) filecount++; 02876 fileidx++; 02877 } 02878 02879 /* Return if directory is not empty */ 02880 if(filecount > 0) return false; 02881 02882 /* delete LFNs */ 02883 if (!dir_lfn_range.empty() && (dos.version.major >= 7 || uselfn)) { 02884 /* last LFN entry should be fileidx */ 02885 assert(dir_lfn_range.dirPos_start < dir_lfn_range.dirPos_end); 02886 if (dir_lfn_range.dirPos_end != subEntry) LOG_MSG("FAT warning: LFN dirPos_end=%u fileidx=%u (mismatch)",dir_lfn_range.dirPos_end,subEntry); 02887 for (unsigned int didx=dir_lfn_range.dirPos_start;didx < dir_lfn_range.dirPos_end;didx++) { 02888 if (directoryBrowse(dirClust,&tmpentry,didx)) { 02889 tmpentry.entryname[0] = 0xe5; 02890 directoryChange(dirClust,&tmpentry,didx); 02891 } 02892 } 02893 } 02894 02895 /* remove primary 8.3 entry */ 02896 if (!directoryBrowse(dirClust, &tmpentry, subEntry)) return false; 02897 tmpentry.entryname[0] = 0xe5; 02898 if (!directoryChange(dirClust, &tmpentry, subEntry)) return false; 02899 02900 /* delete allocation chain */ 02901 deleteClustChain(dummyClust, 0); 02902 return true; 02903 } 02904 02905 bool fatDrive::Rename(const char * oldname, const char * newname) { 02906 const char *lfn = NULL; 02907 02908 if (readonly) { 02909 DOS_SetError(DOSERR_WRITE_PROTECTED); 02910 return false; 02911 } 02912 02913 /* you cannot rename root directory */ 02914 if (*oldname == 0 || *newname == 0) { 02915 DOS_SetError(DOSERR_ACCESS_DENIED); 02916 return false; 02917 } 02918 02919 direntry fileEntry1 = {}, fileEntry2 = {}; 02920 Bit32u dirClust1, subEntry1, dirClust2, subEntry2; 02921 char dirName2[DOS_NAMELENGTH_ASCII]; 02922 char pathName2[11], path[DOS_PATHLENGTH]; 02923 lfnRange_t dir_lfn_range; 02924 02925 /* Check that old name exists (file or directory) */ 02926 lfnRange.clear(); 02927 if(!getFileDirEntry(oldname, &fileEntry1, &dirClust1, &subEntry1, /*dirOk*/true)) return false; 02928 dir_lfn_range = lfnRange; 02929 02930 /* Check if new name (file or directory) already exists, fail if so */ 02931 if(getFileDirEntry(newname, &fileEntry2, &dirClust2, &subEntry2, /*dirOk*/true)&&!(uselfn&&!force_sfn&&strcmp(oldname, newname)&&!strcasecmp(oldname, newname))) return false; 02932 02933 /* Can we even get the name of the file itself? */ 02934 if(!getEntryName(newname, &dirName2[0])||!strlen(trim(dirName2))) return false; 02935 convToDirFile(&dirName2[0], &pathName2[0]); 02936 02937 /* Can we find the base directory of the new name? (we know the parent dir of oldname in dirClust1) */ 02938 if(!getDirClustNum(newname, &dirClust2, true)) return false; 02939 02940 /* NTS: "newname" is the full relative path. For LFN creation to work we need only the final element of the path */ 02941 if (uselfn && !force_sfn) { 02942 lfn = strrchr(newname,'\\'); 02943 02944 if (lfn != NULL) { 02945 lfn++; /* step past '\' */ 02946 strcpy(path, newname); 02947 *(strrchr(path,'\\')+1)=0; 02948 } else { 02949 lfn = newname; /* no path elements */ 02950 *path=0; 02951 } 02952 02953 if (filename_not_strict_8x3(lfn)) { 02954 char oldchar=fileEntry1.entryname[0]; 02955 fileEntry1.entryname[0] = 0xe5; 02956 directoryChange(dirClust1, &fileEntry1, (Bit32s)subEntry1); 02957 char *sfn=Generate_SFN(path, lfn); 02958 if (sfn!=NULL) convToDirFile(sfn, &pathName2[0]); 02959 fileEntry1.entryname[0] = oldchar; 02960 directoryChange(dirClust1, &fileEntry1, (Bit32s)subEntry1); 02961 } else 02962 lfn = NULL; 02963 } 02964 02965 /* add new dirent */ 02966 memcpy(&fileEntry2, &fileEntry1, sizeof(direntry)); 02967 memcpy(&fileEntry2.entryname, &pathName2[0], 11); 02968 addDirectoryEntry(dirClust2, fileEntry2, lfn); 02969 02970 /* Remove old 8.3 SFN entry */ 02971 fileEntry1.entryname[0] = 0xe5; 02972 directoryChange(dirClust1, &fileEntry1, (Bit32s)subEntry1); 02973 02974 /* remove LFNs of old entry only if emulating LFNs or DOS version 7.0. 02975 * Earlier DOS versions ignore LFNs. */ 02976 if (!dir_lfn_range.empty() && (dos.version.major >= 7 || uselfn)) { 02977 /* last LFN entry should be fileidx */ 02978 assert(dir_lfn_range.dirPos_start < dir_lfn_range.dirPos_end); 02979 if (dir_lfn_range.dirPos_end != subEntry1) LOG_MSG("FAT warning: LFN dirPos_end=%u fileidx=%u (mismatch)",dir_lfn_range.dirPos_end,subEntry1); 02980 for (unsigned int didx=dir_lfn_range.dirPos_start;didx < dir_lfn_range.dirPos_end;didx++) { 02981 if (directoryBrowse(dirClust1,&fileEntry1,didx)) { 02982 fileEntry1.entryname[0] = 0xe5; 02983 directoryChange(dirClust1,&fileEntry1,didx); 02984 } 02985 } 02986 } 02987 02988 return true; 02989 } 02990 02991 bool fatDrive::TestDir(const char *dir) { 02992 Bit32u dummyClust; 02993 02994 /* root directory is directory */ 02995 if (*dir == 0) return true; 02996 02997 return getDirClustNum(dir, &dummyClust, false); 02998 } 02999 03000 Bit32u fatDrive::GetPartitionOffset(void) { 03001 return partSectOff; 03002 } 03003 03004 Bit32u fatDrive::GetFirstClusterOffset(void) { 03005 return firstDataSector - partSectOff; 03006 } 03007 03008 Bit32u fatDrive::GetHighestClusterNumber(void) { 03009 return CountOfClusters + 1ul; 03010 }