1 /*
2  *  Copyright (C) 2002-2021  The DOSBox Team
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License along
15  *  with this program; if not, write to the Free Software Foundation, Inc.,
16  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "drives.h"
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <time.h>
25 
26 #include "bios_disk.h"
27 #include "bios.h"
28 #include "cross.h"
29 #include "dos_inc.h"
30 #include "string_utils.h"
31 #include "support.h"
32 
33 #define IMGTYPE_FLOPPY 0
34 #define IMGTYPE_ISO    1
35 #define IMGTYPE_HDD	   2
36 
37 #define FAT12		   0
38 #define FAT16		   1
39 #define FAT32		   2
40 
41 class fatFile final : public DOS_File {
42 public:
43 	fatFile(const char* name, Bit32u startCluster, Bit32u fileLen, fatDrive *useDrive);
44 	fatFile(const fatFile&) = delete; // prevent copy
45 	fatFile& operator=(const fatFile&) = delete; // prevent assignment
46 	bool Read(Bit8u * data,Bit16u * size);
47 	bool Write(Bit8u * data,Bit16u * size);
48 	bool Seek(Bit32u * pos,Bit32u type);
49 	bool Close();
50 	Bit16u GetInformation(void);
51 	bool UpdateDateTimeFromHost(void);
52 public:
53 	Bit32u firstCluster;
54 	Bit32u seekpos;
55 	Bit32u filelength;
56 	Bit32u currentSector;
57 	Bit32u curSectOff;
58 	Bit8u sectorBuffer[512];
59 	/* Record of where in the directory structure this file is located */
60 	Bit32u dirCluster;
61 	Bit32u dirIndex;
62 
63 	bool loadedSector;
64 	fatDrive *myDrive;
65 };
66 
67 
68 /* IN - char * filename: Name in regular filename format, e.g. bob.txt */
69 /* OUT - char * filearray: Name in DOS directory format, eleven char, e.g. bob     txt */
convToDirFile(char * filename,char * filearray)70 static void convToDirFile(char *filename, char *filearray) {
71 	Bit32u charidx = 0;
72 	Bit32u flen,i;
73 	flen = (Bit32u)strnlen(filename, DOS_NAMELENGTH_ASCII);
74 	memset(filearray, 32, 11);
75 	for(i=0;i<flen;i++) {
76 		if(charidx >= 11) break;
77 		if(filename[i] != '.') {
78 			filearray[charidx] = filename[i];
79 			charidx++;
80 		} else {
81 			charidx = 8;
82 		}
83 	}
84 }
85 
fatFile(const char *,Bit32u startCluster,Bit32u fileLen,fatDrive * useDrive)86 fatFile::fatFile(const char* /*name*/,
87                  Bit32u startCluster,
88                  Bit32u fileLen,
89                  fatDrive *useDrive)
90 	: firstCluster(startCluster),
91 	  seekpos(0),
92 	  filelength(fileLen),
93 	  currentSector(0),
94 	  curSectOff(0),
95 	  sectorBuffer{0},
96 	  dirCluster(0),
97 	  dirIndex(0),
98 	  loadedSector(false),
99 	  myDrive(useDrive)
100 {
101 	Bit32u seekto = 0;
102 	open = true;
103 	if(filelength > 0) {
104 		Seek(&seekto, DOS_SEEK_SET);
105 	}
106 }
107 
Read(Bit8u * data,Bit16u * size)108 bool fatFile::Read(Bit8u * data, Bit16u *size) {
109 	if ((this->flags & 0xf) == OPEN_WRITE) {	// check if file opened in write-only mode
110 		DOS_SetError(DOSERR_ACCESS_DENIED);
111 		return false;
112 	}
113 	Bit16u sizedec, sizecount;
114 	if(seekpos >= filelength) {
115 		*size = 0;
116 		return true;
117 	}
118 
119 	if (!loadedSector) {
120 		currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos);
121 		if(currentSector == 0) {
122 			/* EOC reached before EOF */
123 			*size = 0;
124 			loadedSector = false;
125 			return true;
126 		}
127 		curSectOff = seekpos % myDrive->getSectorSize();
128 		myDrive->readSector(currentSector, sectorBuffer);
129 		loadedSector = true;
130 	}
131 
132 	sizedec = *size;
133 	sizecount = 0;
134 	while(sizedec != 0) {
135 		if(seekpos >= filelength) {
136 			*size = sizecount;
137 			return true;
138 		}
139 		data[sizecount++] = sectorBuffer[curSectOff++];
140 		seekpos++;
141 		if(curSectOff >= myDrive->getSectorSize()) {
142 			currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos);
143 			if(currentSector == 0) {
144 				/* EOC reached before EOF */
145 				//LOG_MSG("EOC reached before EOF, seekpos %d, filelen %d", seekpos, filelength);
146 				*size = sizecount;
147 				loadedSector = false;
148 				return true;
149 			}
150 			curSectOff = 0;
151 			myDrive->readSector(currentSector, sectorBuffer);
152 			loadedSector = true;
153 			//LOG_MSG("Reading absolute sector at %d for seekpos %d", currentSector, seekpos);
154 		}
155 		--sizedec;
156 	}
157 	*size =sizecount;
158 	return true;
159 }
160 
Write(Bit8u * data,Bit16u * size)161 bool fatFile::Write(Bit8u * data, Bit16u *size) {
162 	if ((this->flags & 0xf) == OPEN_READ) {	// check if file opened in read-only mode
163 		DOS_SetError(DOSERR_ACCESS_DENIED);
164 		return false;
165 	}
166 
167 	direntry tmpentry;
168 	Bit16u sizedec, sizecount;
169 	sizedec = *size;
170 	sizecount = 0;
171 
172 	if(seekpos < filelength && *size == 0) {
173 		/* Truncate file to current position */
174 		myDrive->deleteClustChain(firstCluster, seekpos);
175 		filelength = seekpos;
176 		goto finalizeWrite;
177 	}
178 
179 	if(seekpos > filelength) {
180 		/* Extend file to current position */
181 		Bit32u clustSize = myDrive->getClusterSize();
182 		if(filelength == 0) {
183 			firstCluster = myDrive->getFirstFreeClust();
184 			if(firstCluster == 0) goto finalizeWrite; // out of space
185 			myDrive->allocateCluster(firstCluster, 0);
186 			filelength = clustSize;
187 		}
188 		filelength = ((filelength - 1) / clustSize + 1) * clustSize;
189 		while(filelength < seekpos) {
190 			if(myDrive->appendCluster(firstCluster) == 0) goto finalizeWrite; // out of space
191 			filelength += clustSize;
192 		}
193 		if(filelength > seekpos) filelength = seekpos;
194 		if(*size == 0) goto finalizeWrite;
195 	}
196 
197 	while(sizedec != 0) {
198 		/* Increase filesize if necessary */
199 		if(seekpos >= filelength) {
200 			if(filelength == 0) {
201 				firstCluster = myDrive->getFirstFreeClust();
202 				if(firstCluster == 0) goto finalizeWrite; // out of space
203 				myDrive->allocateCluster(firstCluster, 0);
204 				currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos);
205 				myDrive->readSector(currentSector, sectorBuffer);
206 				loadedSector = true;
207 			}
208 			if (!loadedSector) {
209 				currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos);
210 				if(currentSector == 0) {
211 					/* EOC reached before EOF - try to increase file allocation */
212 					myDrive->appendCluster(firstCluster);
213 					/* Try getting sector again */
214 					currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos);
215 					if(currentSector == 0) {
216 						/* No can do. lets give up and go home.  We must be out of room */
217 						goto finalizeWrite;
218 					}
219 				}
220 				curSectOff = seekpos % myDrive->getSectorSize();
221 				myDrive->readSector(currentSector, sectorBuffer);
222 				loadedSector = true;
223 			}
224 			filelength = seekpos+1;
225 		}
226 		sectorBuffer[curSectOff++] = data[sizecount++];
227 		seekpos++;
228 		if(curSectOff >= myDrive->getSectorSize()) {
229 			if(loadedSector) myDrive->writeSector(currentSector, sectorBuffer);
230 
231 			currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos);
232 			if(currentSector == 0) loadedSector = false;
233 			else {
234 				curSectOff = 0;
235 				myDrive->readSector(currentSector, sectorBuffer);
236 				loadedSector = true;
237 			}
238 		}
239 		--sizedec;
240 	}
241 	if(curSectOff>0 && loadedSector) myDrive->writeSector(currentSector, sectorBuffer);
242 
243 finalizeWrite:
244 	myDrive->directoryBrowse(dirCluster, &tmpentry, dirIndex);
245 	tmpentry.entrysize = filelength;
246 	tmpentry.loFirstClust = (Bit16u)firstCluster;
247 	myDrive->directoryChange(dirCluster, &tmpentry, dirIndex);
248 
249 	*size =sizecount;
250 	return true;
251 }
252 
Seek(Bit32u * pos,Bit32u type)253 bool fatFile::Seek(Bit32u *pos, Bit32u type) {
254 	Bit32s seekto=0;
255 
256 	switch(type) {
257 		case DOS_SEEK_SET:
258 			seekto = (Bit32s)*pos;
259 			break;
260 		case DOS_SEEK_CUR:
261 			/* Is this relative seek signed? */
262 			seekto = (Bit32s)*pos + (Bit32s)seekpos;
263 			break;
264 		case DOS_SEEK_END:
265 			seekto = (Bit32s)filelength + (Bit32s)*pos;
266 			break;
267 	}
268 //	LOG_MSG("Seek to %d with type %d (absolute value %d)", *pos, type, seekto);
269 
270 	if(seekto<0) seekto = 0;
271 	seekpos = (Bit32u)seekto;
272 	currentSector = myDrive->getAbsoluteSectFromBytePos(firstCluster, seekpos);
273 	if (currentSector == 0) {
274 		/* not within file size, thus no sector is available */
275 		loadedSector = false;
276 	} else {
277 		curSectOff = seekpos % myDrive->getSectorSize();
278 		myDrive->readSector(currentSector, sectorBuffer);
279 		loadedSector = true;
280 	}
281 	*pos = seekpos;
282 	return true;
283 }
284 
Close()285 bool fatFile::Close() {
286 	/* Flush buffer */
287 	if (loadedSector) myDrive->writeSector(currentSector, sectorBuffer);
288 
289 	return false;
290 }
291 
GetInformation(void)292 Bit16u fatFile::GetInformation(void) {
293 	return 0;
294 }
295 
UpdateDateTimeFromHost(void)296 bool fatFile::UpdateDateTimeFromHost(void) {
297 	return true;
298 }
299 
getClustFirstSect(Bit32u clustNum)300 Bit32u fatDrive::getClustFirstSect(Bit32u clustNum) {
301 	return ((clustNum - 2) * bootbuffer.sectorspercluster) + firstDataSector;
302 }
303 
getClusterValue(Bit32u clustNum)304 Bit32u fatDrive::getClusterValue(Bit32u clustNum) {
305 	Bit32u fatoffset=0;
306 	Bit32u fatsectnum;
307 	Bit32u fatentoff;
308 	Bit32u clustValue=0;
309 
310 	switch(fattype) {
311 		case FAT12:
312 			fatoffset = clustNum + (clustNum / 2);
313 			break;
314 		case FAT16:
315 			fatoffset = clustNum * 2;
316 			break;
317 		case FAT32:
318 			fatoffset = clustNum * 4;
319 			break;
320 	}
321 	fatsectnum = bootbuffer.reservedsectors + (fatoffset / bootbuffer.bytespersector) + partSectOff;
322 	fatentoff = fatoffset % bootbuffer.bytespersector;
323 
324 	if(curFatSect != fatsectnum) {
325 		/* Load two sectors at once for FAT12 */
326 		readSector(fatsectnum, &fatSectBuffer[0]);
327 		if (fattype==FAT12)
328 			readSector(fatsectnum+1, &fatSectBuffer[512]);
329 		curFatSect = fatsectnum;
330 	}
331 
332 	switch(fattype) {
333 		case FAT12:
334 			clustValue = var_read((Bit16u *)&fatSectBuffer[fatentoff]);
335 			if(clustNum & 0x1) {
336 				clustValue >>= 4;
337 			} else {
338 				clustValue &= 0xfff;
339 			}
340 			break;
341 		case FAT16:
342 			clustValue = var_read((Bit16u *)&fatSectBuffer[fatentoff]);
343 			break;
344 		case FAT32:
345 			clustValue = var_read((Bit32u *)&fatSectBuffer[fatentoff]);
346 			break;
347 	}
348 
349 	return clustValue;
350 }
351 
setClusterValue(Bit32u clustNum,Bit32u clustValue)352 void fatDrive::setClusterValue(Bit32u clustNum, Bit32u clustValue) {
353 	Bit32u fatoffset=0;
354 	Bit32u fatsectnum;
355 	Bit32u fatentoff;
356 
357 	switch(fattype) {
358 		case FAT12:
359 			fatoffset = clustNum + (clustNum / 2);
360 			break;
361 		case FAT16:
362 			fatoffset = clustNum * 2;
363 			break;
364 		case FAT32:
365 			fatoffset = clustNum * 4;
366 			break;
367 	}
368 	fatsectnum = bootbuffer.reservedsectors + (fatoffset / bootbuffer.bytespersector) + partSectOff;
369 	fatentoff = fatoffset % bootbuffer.bytespersector;
370 
371 	if(curFatSect != fatsectnum) {
372 		/* Load two sectors at once for FAT12 */
373 		readSector(fatsectnum, &fatSectBuffer[0]);
374 		if (fattype==FAT12)
375 			readSector(fatsectnum+1, &fatSectBuffer[512]);
376 		curFatSect = fatsectnum;
377 	}
378 
379 	switch(fattype) {
380 		case FAT12: {
381 			Bit16u tmpValue = var_read((Bit16u *)&fatSectBuffer[fatentoff]);
382 			if(clustNum & 0x1) {
383 				clustValue &= 0xfff;
384 				clustValue <<= 4;
385 				tmpValue &= 0xf;
386 				tmpValue |= (Bit16u)clustValue;
387 
388 			} else {
389 				clustValue &= 0xfff;
390 				tmpValue &= 0xf000;
391 				tmpValue |= (Bit16u)clustValue;
392 			}
393 			var_write((Bit16u *)&fatSectBuffer[fatentoff], tmpValue);
394 			break;
395 			}
396 		case FAT16:
397 			var_write((Bit16u *)&fatSectBuffer[fatentoff], (Bit16u)clustValue);
398 			break;
399 		case FAT32:
400 			var_write((Bit32u *)&fatSectBuffer[fatentoff], clustValue);
401 			break;
402 	}
403 	for(int fc=0;fc<bootbuffer.fatcopies;fc++) {
404 		writeSector(fatsectnum + (fc * bootbuffer.sectorsperfat), &fatSectBuffer[0]);
405 		if (fattype==FAT12) {
406 			if (fatentoff>=511)
407 				writeSector(fatsectnum+1+(fc * bootbuffer.sectorsperfat), &fatSectBuffer[512]);
408 		}
409 	}
410 }
411 
getEntryName(char * fullname,char * entname)412 bool fatDrive::getEntryName(char *fullname, char *entname) {
413 	char dirtoken[DOS_PATHLENGTH];
414 
415 	char * findDir;
416 	char * findFile;
417 	safe_strcpy(dirtoken, fullname);
418 
419 	//LOG_MSG("Testing for filename %s", fullname);
420 	findDir = strtok(dirtoken,"\\");
421 	if (findDir==NULL) {
422 		return true;	// root always exists
423 	}
424 	findFile = findDir;
425 	while(findDir != NULL) {
426 		findFile = findDir;
427 		findDir = strtok(NULL,"\\");
428 	}
429 
430 	assert(entname);
431 	entname[0] = '\0';
432 	strncat(entname, findFile, DOS_NAMELENGTH_ASCII - 1);
433 	return true;
434 }
435 
getFileDirEntry(char const * const filename,direntry * useEntry,Bit32u * dirClust,Bit32u * subEntry)436 bool fatDrive::getFileDirEntry(char const * const filename, direntry * useEntry, Bit32u * dirClust, Bit32u * subEntry) {
437 	size_t len = strnlen(filename, DOS_PATHLENGTH);
438 	char dirtoken[DOS_PATHLENGTH];
439 	Bit32u currentClust = 0;
440 
441 	direntry foundEntry;
442 	char * findDir;
443 	char * findFile;
444 	safe_strcpy(dirtoken, filename);
445 	findFile=dirtoken;
446 
447 	/* Skip if testing in root directory */
448 	if ((len>0) && (filename[len-1]!='\\')) {
449 		//LOG_MSG("Testing for filename %s", filename);
450 		findDir = strtok(dirtoken,"\\");
451 		findFile = findDir;
452 		while(findDir != NULL) {
453 			imgDTA->SetupSearch(0,DOS_ATTR_DIRECTORY,findDir);
454 			imgDTA->SetDirID(0);
455 
456 			findFile = findDir;
457 			if(!FindNextInternal(currentClust, *imgDTA, &foundEntry)) break;
458 			else {
459 				//Found something. See if it's a directory (findfirst always finds regular files)
460 				char find_name[DOS_NAMELENGTH_ASCII];Bit16u find_date,find_time;Bit32u find_size;Bit8u find_attr;
461 				imgDTA->GetResult(find_name,find_size,find_date,find_time,find_attr);
462 				if(!(find_attr & DOS_ATTR_DIRECTORY)) break;
463 			}
464 
465 			currentClust = foundEntry.loFirstClust;
466 			findDir = strtok(NULL,"\\");
467 		}
468 	} else {
469 		/* Set to root directory */
470 	}
471 
472 	/* Search found directory for our file */
473 	imgDTA->SetupSearch(0,0x7,findFile);
474 	imgDTA->SetDirID(0);
475 	if(!FindNextInternal(currentClust, *imgDTA, &foundEntry)) return false;
476 
477 	memcpy(useEntry, &foundEntry, sizeof(direntry));
478 	*dirClust = currentClust;
479 	*subEntry = ((Bit32u)imgDTA->GetDirID()-1);
480 	return true;
481 }
482 
getDirClustNum(char * dir,Bit32u * clustNum,bool parDir)483 bool fatDrive::getDirClustNum(char *dir, Bit32u *clustNum, bool parDir) {
484 	Bit32u len = (Bit32u)strnlen(dir, DOS_PATHLENGTH);
485 	char dirtoken[DOS_PATHLENGTH];
486 	Bit32u currentClust = 0;
487 	direntry foundEntry;
488 	char * findDir;
489 	safe_strcpy(dirtoken, dir);
490 
491 	/* Skip if testing for root directory */
492 	if ((len>0) && (dir[len-1]!='\\')) {
493 		//LOG_MSG("Testing for dir %s", dir);
494 		findDir = strtok(dirtoken,"\\");
495 		while(findDir != NULL) {
496 			imgDTA->SetupSearch(0,DOS_ATTR_DIRECTORY,findDir);
497 			imgDTA->SetDirID(0);
498 			findDir = strtok(NULL,"\\");
499 			if(parDir && (findDir == NULL)) break;
500 
501 			char find_name[DOS_NAMELENGTH_ASCII];Bit16u find_date,find_time;Bit32u find_size;Bit8u find_attr;
502 			if(!FindNextInternal(currentClust, *imgDTA, &foundEntry)) {
503 				return false;
504 			} else {
505 				imgDTA->GetResult(find_name,find_size,find_date,find_time,find_attr);
506 				if(!(find_attr &DOS_ATTR_DIRECTORY)) return false;
507 			}
508 			currentClust = foundEntry.loFirstClust;
509 
510 		}
511 		*clustNum = currentClust;
512 	} else {
513 		/* Set to root directory */
514 		*clustNum = 0;
515 	}
516 	return true;
517 }
518 
readSector(Bit32u sectnum,void * data)519 Bit8u fatDrive::readSector(Bit32u sectnum, void * data) {
520 	// Guard
521 	if (!loadedDisk) {
522 		return 0;
523 	}
524 
525 	if (absolute) {
526 		return loadedDisk->Read_AbsoluteSector(sectnum, data);
527 	}
528 	Bit32u cylindersize = bootbuffer.headcount * bootbuffer.sectorspertrack;
529 	Bit32u cylinder = sectnum / cylindersize;
530 	sectnum %= cylindersize;
531 	Bit32u head = sectnum / bootbuffer.sectorspertrack;
532 	Bit32u sector = sectnum % bootbuffer.sectorspertrack + 1L;
533 	return loadedDisk->Read_Sector(head, cylinder, sector, data);
534 }
535 
writeSector(Bit32u sectnum,void * data)536 Bit8u fatDrive::writeSector(Bit32u sectnum, void * data) {
537 	// Guard
538 	if (!loadedDisk) {
539 		return 0;
540 	}
541 
542 	if (absolute) {
543 		return loadedDisk->Write_AbsoluteSector(sectnum, data);
544 	}
545 	Bit32u cylindersize = bootbuffer.headcount * bootbuffer.sectorspertrack;
546 	Bit32u cylinder = sectnum / cylindersize;
547 	sectnum %= cylindersize;
548 	Bit32u head = sectnum / bootbuffer.sectorspertrack;
549 	Bit32u sector = sectnum % bootbuffer.sectorspertrack + 1L;
550 	return loadedDisk->Write_Sector(head, cylinder, sector, data);
551 }
552 
getSectorSize(void)553 Bit32u fatDrive::getSectorSize(void) {
554 	return bootbuffer.bytespersector;
555 }
556 
getClusterSize(void)557 Bit32u fatDrive::getClusterSize(void) {
558 	return bootbuffer.sectorspercluster * bootbuffer.bytespersector;
559 }
560 
getAbsoluteSectFromBytePos(Bit32u startClustNum,Bit32u bytePos)561 Bit32u fatDrive::getAbsoluteSectFromBytePos(Bit32u startClustNum, Bit32u bytePos) {
562 	return  getAbsoluteSectFromChain(startClustNum, bytePos / bootbuffer.bytespersector);
563 }
564 
getAbsoluteSectFromChain(Bit32u startClustNum,Bit32u logicalSector)565 Bit32u fatDrive::getAbsoluteSectFromChain(Bit32u startClustNum, Bit32u logicalSector) {
566 	Bit32s skipClust = logicalSector / bootbuffer.sectorspercluster;
567 	Bit32u sectClust = logicalSector % bootbuffer.sectorspercluster;
568 
569 	Bit32u currentClust = startClustNum;
570 	Bit32u testvalue;
571 
572 	while(skipClust!=0) {
573 		bool isEOF = false;
574 		testvalue = getClusterValue(currentClust);
575 		switch(fattype) {
576 			case FAT12:
577 				if(testvalue >= 0xff8) isEOF = true;
578 				break;
579 			case FAT16:
580 				if(testvalue >= 0xfff8) isEOF = true;
581 				break;
582 			case FAT32:
583 				if(testvalue >= 0xfffffff8) isEOF = true;
584 				break;
585 		}
586 		if((isEOF) && (skipClust>=1)) {
587 			//LOG_MSG("End of cluster chain reached before end of logical sector seek!");
588 			if (skipClust == 1 && fattype == FAT12) {
589 				//break;
590 				LOG(LOG_DOSMISC, LOG_ERROR)("End of cluster chain reached, but maybe good after all ?");
591 			}
592 			return 0;
593 		}
594 		currentClust = testvalue;
595 		--skipClust;
596 	}
597 
598 	return (getClustFirstSect(currentClust) + sectClust);
599 }
600 
deleteClustChain(Bit32u startCluster,Bit32u bytePos)601 void fatDrive::deleteClustChain(Bit32u startCluster, Bit32u bytePos) {
602 	Bit32u clustSize = getClusterSize();
603 	Bit32u endClust = (bytePos + clustSize - 1) / clustSize;
604 	Bit32u countClust = 1;
605 
606 	Bit32u testvalue;
607 	Bit32u currentClust = startCluster;
608 	bool isEOF = false;
609 	while(!isEOF) {
610 		testvalue = getClusterValue(currentClust);
611 		if(testvalue == 0) {
612 			/* What the crap?  Cluster is already empty - BAIL! */
613 			break;
614 		}
615 		switch(fattype) {
616 			case FAT12:
617 				if(testvalue >= 0xff8) isEOF = true;
618 				break;
619 			case FAT16:
620 				if(testvalue >= 0xfff8) isEOF = true;
621 				break;
622 			case FAT32:
623 				if(testvalue >= 0xfffffff8) isEOF = true;
624 				break;
625 		}
626 		if(countClust == endClust && !isEOF) {
627 			/* Mark cluster as end */
628 			switch(fattype) {
629 				case FAT12:
630 					setClusterValue(currentClust, 0xfff);
631 					break;
632 				case FAT16:
633 					setClusterValue(currentClust, 0xffff);
634 					break;
635 				case FAT32:
636 					setClusterValue(currentClust, 0xffffffff);
637 					break;
638 			}
639 		} else if(countClust > endClust) {
640 			/* Mark cluster as empty */
641 			setClusterValue(currentClust, 0);
642 		}
643 		if(isEOF) break;
644 		currentClust = testvalue;
645 		countClust++;
646 	}
647 }
648 
appendCluster(Bit32u startCluster)649 Bit32u fatDrive::appendCluster(Bit32u startCluster) {
650 	Bit32u testvalue;
651 	Bit32u currentClust = startCluster;
652 	bool isEOF = false;
653 
654 	while(!isEOF) {
655 		testvalue = getClusterValue(currentClust);
656 		switch(fattype) {
657 			case FAT12:
658 				if(testvalue >= 0xff8) isEOF = true;
659 				break;
660 			case FAT16:
661 				if(testvalue >= 0xfff8) isEOF = true;
662 				break;
663 			case FAT32:
664 				if(testvalue >= 0xfffffff8) isEOF = true;
665 				break;
666 		}
667 		if(isEOF) break;
668 		currentClust = testvalue;
669 	}
670 
671 	Bit32u newClust = getFirstFreeClust();
672 	/* Drive is full */
673 	if(newClust == 0) return 0;
674 
675 	if(!allocateCluster(newClust, currentClust)) return 0;
676 
677 	zeroOutCluster(newClust);
678 
679 	return newClust;
680 }
681 
allocateCluster(Bit32u useCluster,Bit32u prevCluster)682 bool fatDrive::allocateCluster(Bit32u useCluster, Bit32u prevCluster) {
683 
684 	/* Can't allocate cluster #0 */
685 	if(useCluster == 0) return false;
686 
687 	if(prevCluster != 0) {
688 		/* Refuse to allocate cluster if previous cluster value is zero (unallocated) */
689 		if(!getClusterValue(prevCluster)) return false;
690 
691 		/* Point cluster to new cluster in chain */
692 		setClusterValue(prevCluster, useCluster);
693 		//LOG_MSG("Chaining cluser %d to %d", prevCluster, useCluster);
694 	}
695 
696 	switch(fattype) {
697 		case FAT12:
698 			setClusterValue(useCluster, 0xfff);
699 			break;
700 		case FAT16:
701 			setClusterValue(useCluster, 0xffff);
702 			break;
703 		case FAT32:
704 			setClusterValue(useCluster, 0xffffffff);
705 			break;
706 	}
707 	return true;
708 }
709 
fatDrive(const char * sysFilename,Bit32u bytesector,Bit32u cylsector,Bit32u headscyl,Bit32u cylinders,Bit32u startSector)710 fatDrive::fatDrive(const char *sysFilename,
711                    Bit32u bytesector,
712                    Bit32u cylsector,
713                    Bit32u headscyl,
714                    Bit32u cylinders,
715                    Bit32u startSector)
716 	: loadedDisk(nullptr),
717 	  created_successfully(true),
718 	  bootbuffer{{0}, {0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, 0},
719 	  absolute(false),
720 	  fattype(0),
721 	  CountOfClusters(0),
722 	  partSectOff(0),
723 	  firstDataSector(0),
724 	  firstRootDirSect(0),
725 	  cwdDirCluster(0),
726 	  fatSectBuffer{0},
727 	  curFatSect(0)
728 {
729 	FILE *diskfile;
730 	Bit32u filesize;
731 	bool is_hdd;
732 	struct partTable mbrData;
733 
734 	if(imgDTASeg == 0) {
735 		imgDTASeg = DOS_GetMemory(2);
736 		imgDTAPtr = RealMake(imgDTASeg, 0);
737 		imgDTA    = new DOS_DTA(imgDTAPtr);
738 	}
739 
740 	diskfile = fopen_wrap(sysFilename, "rb+");
741 	if (!diskfile) {
742 		created_successfully = false;
743 		return;
744 	}
745 	fseek(diskfile, 0L, SEEK_END);
746 	filesize = (Bit32u)ftell(diskfile) / 1024L;
747 	is_hdd = (filesize > 2880);
748 
749 	/* Load disk image */
750 	loadedDisk.reset(new imageDisk(diskfile, sysFilename, filesize, is_hdd));
751 
752 	if(is_hdd) {
753 		/* Set user specified harddrive parameters */
754 		loadedDisk->Set_Geometry(headscyl, cylinders,cylsector, bytesector);
755 
756 		loadedDisk->Read_Sector(0,0,1,&mbrData);
757 
758 		if(mbrData.magic1!= 0x55 ||	mbrData.magic2!= 0xaa) LOG_MSG("Possibly invalid partition table in disk image.");
759 
760 		startSector = 63;
761 		int m;
762 		for(m=0;m<4;m++) {
763 			/* Pick the first available partition */
764 			if(mbrData.pentry[m].partSize != 0x00) {
765 				mbrData.pentry[m].absSectStart = host_to_le(mbrData.pentry[m].absSectStart);
766 				mbrData.pentry[m].partSize     = host_to_le(mbrData.pentry[m].partSize);
767 				LOG_MSG("Using partition %d on drive; skipping %d sectors", m, mbrData.pentry[m].absSectStart);
768 				startSector = mbrData.pentry[m].absSectStart;
769 				break;
770 			}
771 		}
772 
773 		if(m==4) LOG_MSG("No good partition found in image.");
774 
775 		partSectOff = startSector;
776 	} else {
777 		/* Get floppy disk parameters based on image size */
778 		loadedDisk->Get_Geometry(&headscyl, &cylinders, &cylsector, &bytesector);
779 		/* Floppy disks don't have partitions */
780 		partSectOff = 0;
781 	}
782 
783 	if (bytesector != 512) {
784 		/* Non-standard sector sizes not implemented */
785 		created_successfully = false;
786 		return;
787 	}
788 
789 	loadedDisk->Read_AbsoluteSector(0+partSectOff,&bootbuffer);
790 
791 	bootbuffer.bytespersector    = host_to_le(bootbuffer.bytespersector);
792 	bootbuffer.reservedsectors   = host_to_le(bootbuffer.reservedsectors);
793 	bootbuffer.rootdirentries    = host_to_le(bootbuffer.rootdirentries);
794 	bootbuffer.totalsectorcount  = host_to_le(bootbuffer.totalsectorcount);
795 	bootbuffer.sectorsperfat     = host_to_le(bootbuffer.sectorsperfat);
796 	bootbuffer.sectorspertrack   = host_to_le(bootbuffer.sectorspertrack);
797 	bootbuffer.headcount         = host_to_le(bootbuffer.headcount);
798 	bootbuffer.hiddensectorcount = host_to_le(bootbuffer.hiddensectorcount);
799 	bootbuffer.totalsecdword     = host_to_le(bootbuffer.totalsecdword);
800 
801 	if (!is_hdd) {
802 		/* Identify floppy format */
803 		if ((bootbuffer.nearjmp[0] == 0x69 || bootbuffer.nearjmp[0] == 0xe9 ||
804 			(bootbuffer.nearjmp[0] == 0xeb && bootbuffer.nearjmp[2] == 0x90)) &&
805 			(bootbuffer.mediadescriptor & 0xf0) == 0xf0) {
806 			/* DOS 2.x or later format, BPB assumed valid */
807 
808 			if ((bootbuffer.mediadescriptor != 0xf0 && !(bootbuffer.mediadescriptor & 0x1)) &&
809 				(bootbuffer.oemname[5] != '3' || bootbuffer.oemname[6] != '.' || bootbuffer.oemname[7] < '2')) {
810 				/* Fix pre-DOS 3.2 single-sided floppy */
811 				bootbuffer.sectorspercluster = 1;
812 			}
813 		} else {
814 			/* Read media descriptor in FAT */
815 			Bit8u sectorBuffer[512];
816 			loadedDisk->Read_AbsoluteSector(1,&sectorBuffer);
817 			Bit8u mdesc = sectorBuffer[0];
818 
819 			if (mdesc >= 0xf8) {
820 				/* DOS 1.x format, create BPB for 160kB floppy */
821 				bootbuffer.bytespersector = 512;
822 				bootbuffer.sectorspercluster = 1;
823 				bootbuffer.reservedsectors = 1;
824 				bootbuffer.fatcopies = 2;
825 				bootbuffer.rootdirentries = 64;
826 				bootbuffer.totalsectorcount = 320;
827 				bootbuffer.mediadescriptor = mdesc;
828 				bootbuffer.sectorsperfat = 1;
829 				bootbuffer.sectorspertrack = 8;
830 				bootbuffer.headcount = 1;
831 				bootbuffer.magic1 = 0x55;	// to silence warning
832 				bootbuffer.magic2 = 0xaa;
833 				if (!(mdesc & 0x2)) {
834 					/* Adjust for 9 sectors per track */
835 					bootbuffer.totalsectorcount = 360;
836 					bootbuffer.sectorsperfat = 2;
837 					bootbuffer.sectorspertrack = 9;
838 				}
839 				if (mdesc & 0x1) {
840 					/* Adjust for 2 sides */
841 					bootbuffer.sectorspercluster = 2;
842 					bootbuffer.rootdirentries = 112;
843 					bootbuffer.totalsectorcount *= 2;
844 					bootbuffer.headcount = 2;
845 				}
846 			} else {
847 				/* Unknown format */
848 				created_successfully = false;
849 				return;
850 			}
851 		}
852 	}
853 
854 	if ((bootbuffer.magic1 != 0x55) || (bootbuffer.magic2 != 0xaa)) {
855 		/* Not a FAT filesystem */
856 		LOG_MSG("Loaded image has no valid magicnumbers at the end!");
857 	}
858 
859 	/* Sanity checks */
860 	if ((bootbuffer.sectorsperfat == 0) || // FAT32 not implemented yet
861 		(bootbuffer.bytespersector != 512) || // non-standard sector sizes not implemented
862 		(bootbuffer.sectorspercluster == 0) ||
863 		(bootbuffer.rootdirentries == 0) ||
864 		(bootbuffer.fatcopies == 0) ||
865 		(bootbuffer.headcount == 0) ||
866 		(bootbuffer.headcount > headscyl) ||
867 		(bootbuffer.sectorspertrack == 0) ||
868 		(bootbuffer.sectorspertrack > cylsector)) {
869 		created_successfully = false;
870 		return;
871 	}
872 
873 	/* Filesystem must be contiguous to use absolute sectors, otherwise CHS will be used */
874 	absolute = ((bootbuffer.headcount == headscyl) && (bootbuffer.sectorspertrack == cylsector));
875 
876 	/* Determine FAT format, 12, 16 or 32 */
877 
878 	/* Get size of root dir in sectors */
879 	Bit32u RootDirSectors = ((bootbuffer.rootdirentries * 32) + (bootbuffer.bytespersector - 1)) / bootbuffer.bytespersector;
880 	Bit32u DataSectors;
881 	if(bootbuffer.totalsectorcount != 0) {
882 		DataSectors = bootbuffer.totalsectorcount - (bootbuffer.reservedsectors + (bootbuffer.fatcopies * bootbuffer.sectorsperfat) + RootDirSectors);
883 	} else {
884 		DataSectors = bootbuffer.totalsecdword - (bootbuffer.reservedsectors + (bootbuffer.fatcopies * bootbuffer.sectorsperfat) + RootDirSectors);
885 
886 	}
887 	CountOfClusters = DataSectors / bootbuffer.sectorspercluster;
888 
889 	firstDataSector = (bootbuffer.reservedsectors + (bootbuffer.fatcopies * bootbuffer.sectorsperfat) + RootDirSectors) + partSectOff;
890 	firstRootDirSect = bootbuffer.reservedsectors + (bootbuffer.fatcopies * bootbuffer.sectorsperfat) + partSectOff;
891 
892 	if(CountOfClusters < 4085) {
893 		/* Volume is FAT12 */
894 		LOG_MSG("Mounted FAT volume is FAT12 with %d clusters", CountOfClusters);
895 		fattype = FAT12;
896 	} else if (CountOfClusters < 65525) {
897 		LOG_MSG("Mounted FAT volume is FAT16 with %d clusters", CountOfClusters);
898 		fattype = FAT16;
899 	} else {
900 		LOG_MSG("Mounted FAT volume is FAT32 with %d clusters", CountOfClusters);
901 		fattype = FAT32;
902 	}
903 
904 	/* There is no cluster 0, this means we are in the root directory */
905 	cwdDirCluster = 0;
906 
907 	memset(fatSectBuffer,0,1024);
908 	curFatSect = 0xffffffff;
909 
910 	safe_strcpy(info, "fatDrive ");
911 	safe_strcat(info, sysFilename);
912 }
913 
AllocationInfo(Bit16u * _bytes_sector,Bit8u * _sectors_cluster,Bit16u * _total_clusters,Bit16u * _free_clusters)914 bool fatDrive::AllocationInfo(Bit16u *_bytes_sector, Bit8u *_sectors_cluster, Bit16u *_total_clusters, Bit16u *_free_clusters) {
915 	// Guard
916 	if (!loadedDisk) {
917 		return false;
918 	}
919 
920 	Bit32u hs, cy, sect,sectsize;
921 	Bit32u countFree = 0;
922 	Bit32u i;
923 
924 	loadedDisk->Get_Geometry(&hs, &cy, &sect, &sectsize);
925 	*_bytes_sector = (Bit16u)sectsize;
926 	*_sectors_cluster = bootbuffer.sectorspercluster;
927 
928 	if (CountOfClusters<65536) {
929 		*_total_clusters = (Bit16u)CountOfClusters;
930 	} else {
931 		// maybe some special handling needed for fat32
932 		*_total_clusters = 65535;
933 	}
934 
935 	for (i=0; i<CountOfClusters; i++) {
936 		if (!getClusterValue(i + 2)) {
937 			countFree++;
938 		}
939 	}
940 
941 	if (countFree<65536) {
942 		*_free_clusters = (Bit16u)countFree;
943 	} else {
944 		// maybe some special handling needed for fat32
945 		*_free_clusters = 65535;
946 	}
947 	return true;
948 }
949 
getFirstFreeClust(void)950 Bit32u fatDrive::getFirstFreeClust(void) {
951 	Bit32u i;
952 	for(i=0;i<CountOfClusters;i++) {
953 		if(!getClusterValue(i+2)) return (i+2);
954 	}
955 
956 	/* No free cluster found */
957 	return 0;
958 }
959 
isRemote(void)960 bool fatDrive::isRemote(void) {	return false; }
isRemovable(void)961 bool fatDrive::isRemovable(void) { return false; }
962 
UnMount(void)963 Bits fatDrive::UnMount(void) {
964 	delete this;
965 	return 0;
966 }
967 
GetMediaByte(void)968 Bit8u fatDrive::GetMediaByte(void) {
969 	return loadedDisk ? loadedDisk->GetBiosType() : 0;
970 }
971 
972 // name can be a full DOS path with filename, up-to DOS_PATHLENGTH in length
FileCreate(DOS_File ** file,char * name,Bit16u attributes)973 bool fatDrive::FileCreate(DOS_File **file, char *name, Bit16u attributes) {
974 	direntry fileEntry;
975 	Bit32u dirClust, subEntry;
976 	char dirName[DOS_NAMELENGTH_ASCII];
977 	char pathName[11]; // pathName is actually just the filename, without path
978 
979 	Bit16u save_errorcode=dos.errorcode;
980 
981 	/* Check if file already exists */
982 	if(getFileDirEntry(name, &fileEntry, &dirClust, &subEntry)) {
983 		/* Truncate file */
984 		fileEntry.entrysize=0;
985 		directoryChange(dirClust, &fileEntry, subEntry);
986 		if(fileEntry.loFirstClust != 0) deleteClustChain(fileEntry.loFirstClust, 0);
987 	} else {
988 		/* Can we even get the name of the file itself? */
989 		if(!getEntryName(name, &dirName[0])) return false;
990 		convToDirFile(&dirName[0], &pathName[0]);
991 
992 		/* Can we find the base directory? */
993 		if(!getDirClustNum(name, &dirClust, true)) return false;
994 		memset(&fileEntry, 0, sizeof(direntry));
995 		memcpy(&fileEntry.entryname, &pathName[0], 11);
996 		fileEntry.attrib = (Bit8u)(attributes & 0xff);
997 		addDirectoryEntry(dirClust, fileEntry);
998 
999 		/* Check if file exists now */
1000 		if(!getFileDirEntry(name, &fileEntry, &dirClust, &subEntry)) return false;
1001 	}
1002 
1003 	/* Empty file created, now lets open it */
1004 	/* TODO: check for read-only flag and requested write access */
1005 	*file = new fatFile(name, fileEntry.loFirstClust, fileEntry.entrysize, this);
1006 	(*file)->flags=OPEN_READWRITE;
1007 	((fatFile *)(*file))->dirCluster = dirClust;
1008 	((fatFile *)(*file))->dirIndex = subEntry;
1009 	/* Maybe modTime and date should be used ? (crt matches findnext) */
1010 	((fatFile *)(*file))->time = fileEntry.crtTime;
1011 	((fatFile *)(*file))->date = fileEntry.crtDate;
1012 
1013 	dos.errorcode=save_errorcode;
1014 	return true;
1015 }
1016 
FileExists(const char * name)1017 bool fatDrive::FileExists(const char *name) {
1018 	direntry fileEntry;
1019 	Bit32u dummy1, dummy2;
1020 	Bit16u save_errorcode = dos.errorcode;
1021 	bool found = getFileDirEntry(name, &fileEntry, &dummy1, &dummy2);
1022 	dos.errorcode = save_errorcode;
1023 	return found;
1024 }
1025 
FileOpen(DOS_File ** file,char * name,Bit32u flags)1026 bool fatDrive::FileOpen(DOS_File **file, char *name, Bit32u flags) {
1027 	direntry fileEntry;
1028 	Bit32u dirClust, subEntry;
1029 	if(!getFileDirEntry(name, &fileEntry, &dirClust, &subEntry)) return false;
1030 	/* TODO: check for read-only flag and requested write access */
1031 	*file = new fatFile(name, fileEntry.loFirstClust, fileEntry.entrysize, this);
1032 	(*file)->flags = flags;
1033 	((fatFile *)(*file))->dirCluster = dirClust;
1034 	((fatFile *)(*file))->dirIndex = subEntry;
1035 	/* Maybe modTime and date should be used ? (crt matches findnext) */
1036 	((fatFile *)(*file))->time = fileEntry.crtTime;
1037 	((fatFile *)(*file))->date = fileEntry.crtDate;
1038 	return true;
1039 }
1040 
FileStat(const char *,FileStat_Block * const)1041 bool fatDrive::FileStat(const char * /*name*/, FileStat_Block *const /*stat_block*/) {
1042 	/* TODO: Stub */
1043 	return false;
1044 }
1045 
FileUnlink(char * name)1046 bool fatDrive::FileUnlink(char * name) {
1047 	direntry fileEntry;
1048 	Bit32u dirClust, subEntry;
1049 
1050 	if(!getFileDirEntry(name, &fileEntry, &dirClust, &subEntry)) {
1051 		DOS_SetError(DOSERR_FILE_NOT_FOUND);
1052 		return false;
1053 	}
1054 /*
1055 	Technically correct, but maybe an unwanted obstruction, so inactive for now.
1056 
1057 	if(fileEntry.attrib & (DOS_ATTR_SYSTEM | DOS_ATTR_HIDDEN)) {
1058 		DOS_SetError(DOSERR_FILE_NOT_FOUND);
1059 		return false;
1060 	}
1061 	if(fileEntry.attrib & DOS_ATTR_READ_ONLY) {
1062 		DOS_SetError(DOSERR_ACCESS_DENIED);
1063 		return false;
1064 	}
1065 */
1066 	fileEntry.entryname[0] = 0xe5;
1067 	directoryChange(dirClust, &fileEntry, subEntry);
1068 
1069 	if(fileEntry.loFirstClust != 0) deleteClustChain(fileEntry.loFirstClust, 0);
1070 
1071 	return true;
1072 }
1073 
FindFirst(char * _dir,DOS_DTA & dta,bool)1074 bool fatDrive::FindFirst(char *_dir, DOS_DTA &dta,bool /*fcb_findfirst*/) {
1075 	direntry dummyClust;
1076 #if 0
1077 	Bit8u attr;char pattern[DOS_NAMELENGTH_ASCII];
1078 	dta.GetSearchParams(attr,pattern);
1079 	if(attr==DOS_ATTR_VOLUME) {
1080 		if (strcmp(GetLabel(), "") == 0 ) {
1081 			DOS_SetError(DOSERR_NO_MORE_FILES);
1082 			return false;
1083 		}
1084 		dta.SetResult(GetLabel(),0,0,0,DOS_ATTR_VOLUME);
1085 		return true;
1086 	}
1087 	if(attr & DOS_ATTR_VOLUME) //check for root dir or fcb_findfirst
1088 		LOG(LOG_DOSMISC,LOG_WARN)("findfirst for volumelabel used on fatDrive. Unhandled!!!!!");
1089 #endif
1090 	if(!getDirClustNum(_dir, &cwdDirCluster, false)) {
1091 		DOS_SetError(DOSERR_PATH_NOT_FOUND);
1092 		return false;
1093 	}
1094 	dta.SetDirID(0);
1095 	dta.SetDirIDCluster((Bit16u)(cwdDirCluster&0xffff));
1096 	return FindNextInternal(cwdDirCluster, dta, &dummyClust);
1097 }
1098 
removeTrailingSpaces(char * str,const size_t max_len)1099 char* removeTrailingSpaces(char* str, const size_t max_len) {
1100 	const auto str_len = strnlen(str, max_len);
1101 	if (str_len == 0)
1102 		return str;
1103 
1104 	char* end = str + str_len;
1105 	while((*--end == ' ') && (end > str)) {
1106 		/* do nothing; break on while criteria */
1107 	}
1108 	*++end = '\0';
1109 	return str;
1110 }
1111 
removeLeadingSpaces(char * str,const size_t max_len)1112 char* removeLeadingSpaces(char* str, const size_t max_len) {
1113 	const size_t len = strnlen(str, max_len);
1114 	const size_t pos = strspn(str, " ");
1115 	memmove(str, str + pos, len - pos + 1);
1116 	return str;
1117 }
1118 
trimString(char * str,const size_t max_len)1119 char* trimString(char* str, const size_t max_len) {
1120 	return removeTrailingSpaces(removeLeadingSpaces(str, max_len), max_len);
1121 }
1122 
copyDirEntry(const direntry * src,direntry * dst)1123 static void copyDirEntry(const direntry *src, direntry *dst) {
1124 	memcpy(dst->entryname, src->entryname, sizeof(src->entryname));
1125 	dst->attrib           = host_to_le(src->attrib);
1126 	dst->NTRes            = host_to_le(src->NTRes);
1127 	dst->milliSecondStamp = host_to_le(src->milliSecondStamp);
1128 	dst->crtTime          = host_to_le(src->crtTime);
1129 	dst->crtDate          = host_to_le(src->crtDate);
1130 	dst->accessDate       = host_to_le(src->accessDate);
1131 	dst->hiFirstClust     = host_to_le(src->hiFirstClust);
1132 	dst->modTime          = host_to_le(src->modTime);
1133 	dst->modDate          = host_to_le(src->modDate);
1134 	dst->loFirstClust     = host_to_le(src->loFirstClust);
1135 	dst->entrysize        = host_to_le(src->entrysize);
1136 }
1137 
FindNextInternal(Bit32u dirClustNumber,DOS_DTA & dta,direntry * foundEntry)1138 bool fatDrive::FindNextInternal(Bit32u dirClustNumber, DOS_DTA &dta, direntry *foundEntry) {
1139 	direntry sectbuf[16]; /* 16 directory entries per sector */
1140 	Bit32u logentsector; /* Logical entry sector */
1141 	Bit32u entryoffset;  /* Index offset within sector */
1142 	Bit32u tmpsector;
1143 	Bit8u attrs;
1144 	Bit16u dirPos;
1145 	char srch_pattern[DOS_NAMELENGTH_ASCII];
1146 	char find_name[DOS_NAMELENGTH_ASCII];
1147 	char extension[4];
1148 
1149 	dta.GetSearchParams(attrs, srch_pattern);
1150 	dirPos = dta.GetDirID();
1151 
1152 nextfile:
1153 	logentsector = dirPos / 16;
1154 	entryoffset = dirPos % 16;
1155 
1156 	if(dirClustNumber==0) {
1157 		if(dirPos >= bootbuffer.rootdirentries) {
1158 			DOS_SetError(DOSERR_NO_MORE_FILES);
1159 			return false;
1160 		}
1161 		readSector(firstRootDirSect+logentsector,sectbuf);
1162 	} else {
1163 		tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector);
1164 		/* A zero sector number can't happen */
1165 		if(tmpsector == 0) {
1166 			DOS_SetError(DOSERR_NO_MORE_FILES);
1167 			return false;
1168 		}
1169 		readSector(tmpsector,sectbuf);
1170 	}
1171 	dirPos++;
1172 	dta.SetDirID(dirPos);
1173 
1174 	/* Deleted file entry */
1175 	if (sectbuf[entryoffset].entryname[0] == 0xe5) goto nextfile;
1176 
1177 	/* End of directory list */
1178 	if (sectbuf[entryoffset].entryname[0] == 0x00) {
1179 		DOS_SetError(DOSERR_NO_MORE_FILES);
1180 		return false;
1181 	}
1182 	memset(find_name,0,DOS_NAMELENGTH_ASCII);
1183 	memset(extension,0,4);
1184 	memcpy(find_name,&sectbuf[entryoffset].entryname[0],8);
1185 	memcpy(extension,&sectbuf[entryoffset].entryname[8],3);
1186 	trimString(&find_name[0], sizeof(find_name));
1187 	trimString(&extension[0], sizeof(extension));
1188 
1189 	//if(!(sectbuf[entryoffset].attrib & DOS_ATTR_DIRECTORY))
1190 	if (extension[0]!=0) {
1191 		safe_strcat(find_name, ".");
1192 		safe_strcat(find_name, extension);
1193 	}
1194 
1195 	/* Compare attributes to search attributes */
1196 
1197 	//TODO What about attrs = DOS_ATTR_VOLUME|DOS_ATTR_DIRECTORY ?
1198 	if (attrs == DOS_ATTR_VOLUME) {
1199 		if (!(sectbuf[entryoffset].attrib & DOS_ATTR_VOLUME)) goto nextfile;
1200 		dirCache.SetLabel(find_name, false, true);
1201 	} else {
1202 		if (~attrs & sectbuf[entryoffset].attrib & (DOS_ATTR_DIRECTORY | DOS_ATTR_VOLUME | DOS_ATTR_SYSTEM | DOS_ATTR_HIDDEN) ) goto nextfile;
1203 	}
1204 
1205 
1206 	/* Compare name to search pattern */
1207 	if(!WildFileCmp(find_name,srch_pattern)) goto nextfile;
1208 
1209 	copyDirEntry(&sectbuf[entryoffset], foundEntry);
1210 
1211 	//dta.SetResult(find_name, foundEntry->entrysize, foundEntry->crtDate, foundEntry->crtTime, foundEntry->attrib);
1212 
1213 	dta.SetResult(find_name, foundEntry->entrysize, foundEntry->modDate, foundEntry->modTime, foundEntry->attrib);
1214 
1215 	return true;
1216 }
1217 
FindNext(DOS_DTA & dta)1218 bool fatDrive::FindNext(DOS_DTA &dta) {
1219 	direntry dummyClust;
1220 
1221 	return FindNextInternal(dta.GetDirIDCluster(), dta, &dummyClust);
1222 }
1223 
GetFileAttr(char * name,Bit16u * attr)1224 bool fatDrive::GetFileAttr(char *name, Bit16u *attr) {
1225 	direntry fileEntry;
1226 	Bit32u dirClust, subEntry;
1227 	if(!getFileDirEntry(name, &fileEntry, &dirClust, &subEntry)) {
1228 		char dirName[DOS_NAMELENGTH_ASCII];
1229 		char pathName[11];
1230 
1231 		/* Can we even get the name of the directory itself? */
1232 		if(!getEntryName(name, &dirName[0])) return false;
1233 		convToDirFile(&dirName[0], &pathName[0]);
1234 
1235 		/* Get parent directory starting cluster */
1236 		if(!getDirClustNum(name, &dirClust, true)) return false;
1237 
1238 		/* Find directory entry in parent directory */
1239 		Bit32s fileidx = 2;
1240 		if (dirClust==0) fileidx = 0;	// root directory
1241 		Bit32s last_idx=0;
1242 		while(directoryBrowse(dirClust, &fileEntry, fileidx, last_idx)) {
1243 			if(memcmp(&fileEntry.entryname, &pathName[0], 11) == 0) {
1244 				*attr=fileEntry.attrib;
1245 				return true;
1246 			}
1247 			last_idx=fileidx;
1248 			fileidx++;
1249 		}
1250 		return false;
1251 	} else *attr=fileEntry.attrib;
1252 	return true;
1253 }
1254 
directoryBrowse(Bit32u dirClustNumber,direntry * useEntry,Bit32s entNum,Bit32s start)1255 bool fatDrive::directoryBrowse(Bit32u dirClustNumber, direntry *useEntry, Bit32s entNum, Bit32s start/*=0*/) {
1256 	direntry sectbuf[16];	/* 16 directory entries per sector */
1257 	Bit32u logentsector;	/* Logical entry sector */
1258 	Bit32u entryoffset = 0;	/* Index offset within sector */
1259 	Bit32u tmpsector;
1260 	if ((start<0) || (start>65535)) return false;
1261 	Bit16u dirPos = (Bit16u)start;
1262 	if (entNum<start) return false;
1263 	entNum-=start;
1264 
1265 	while(entNum>=0) {
1266 
1267 		logentsector = dirPos / 16;
1268 		entryoffset = dirPos % 16;
1269 
1270 		if(dirClustNumber==0) {
1271 			if(dirPos >= bootbuffer.rootdirentries) return false;
1272 			tmpsector = firstRootDirSect+logentsector;
1273 			readSector(tmpsector,sectbuf);
1274 		} else {
1275 			tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector);
1276 			/* A zero sector number can't happen */
1277 			if(tmpsector == 0) return false;
1278 			readSector(tmpsector,sectbuf);
1279 		}
1280 		dirPos++;
1281 
1282 
1283 		/* End of directory list */
1284 		if (sectbuf[entryoffset].entryname[0] == 0x00) return false;
1285 		--entNum;
1286 	}
1287 
1288 	copyDirEntry(&sectbuf[entryoffset], useEntry);
1289 	return true;
1290 }
1291 
directoryChange(Bit32u dirClustNumber,direntry * useEntry,Bit32s entNum)1292 bool fatDrive::directoryChange(Bit32u dirClustNumber, direntry *useEntry, Bit32s entNum) {
1293 	direntry sectbuf[16];	/* 16 directory entries per sector */
1294 	Bit32u logentsector;	/* Logical entry sector */
1295 	Bit32u entryoffset = 0;	/* Index offset within sector */
1296 	Bit32u tmpsector = 0;
1297 	Bit16u dirPos = 0;
1298 
1299 	while(entNum>=0) {
1300 
1301 		logentsector = dirPos / 16;
1302 		entryoffset = dirPos % 16;
1303 
1304 		if(dirClustNumber==0) {
1305 			if(dirPos >= bootbuffer.rootdirentries) return false;
1306 			tmpsector = firstRootDirSect+logentsector;
1307 			readSector(tmpsector,sectbuf);
1308 		} else {
1309 			tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector);
1310 			/* A zero sector number can't happen */
1311 			if(tmpsector == 0) return false;
1312 			readSector(tmpsector,sectbuf);
1313 		}
1314 		dirPos++;
1315 
1316 
1317 		/* End of directory list */
1318 		if (sectbuf[entryoffset].entryname[0] == 0x00) return false;
1319 		--entNum;
1320 	}
1321 	if(tmpsector != 0) {
1322 		copyDirEntry(useEntry, &sectbuf[entryoffset]);
1323 		writeSector(tmpsector, sectbuf);
1324 		return true;
1325 	} else {
1326 		return false;
1327 	}
1328 }
1329 
addDirectoryEntry(Bit32u dirClustNumber,direntry useEntry)1330 bool fatDrive::addDirectoryEntry(Bit32u dirClustNumber, direntry useEntry) {
1331 	direntry sectbuf[16]; /* 16 directory entries per sector */
1332 	Bit32u logentsector; /* Logical entry sector */
1333 	Bit32u entryoffset;  /* Index offset within sector */
1334 	Bit32u tmpsector;
1335 	Bit16u dirPos = 0;
1336 
1337 	for(;;) {
1338 
1339 		logentsector = dirPos / 16;
1340 		entryoffset = dirPos % 16;
1341 
1342 		if(dirClustNumber==0) {
1343 			if(dirPos >= bootbuffer.rootdirentries) return false;
1344 			tmpsector = firstRootDirSect+logentsector;
1345 			readSector(tmpsector,sectbuf);
1346 		} else {
1347 			tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector);
1348 			/* A zero sector number can't happen - we need to allocate more room for this directory*/
1349 			if(tmpsector == 0) {
1350 				Bit32u newClust;
1351 				newClust = appendCluster(dirClustNumber);
1352 				if(newClust == 0) return false;
1353 				/* Try again to get tmpsector */
1354 				tmpsector = getAbsoluteSectFromChain(dirClustNumber, logentsector);
1355 				if(tmpsector == 0) return false; /* Give up if still can't get more room for directory */
1356 			}
1357 			readSector(tmpsector,sectbuf);
1358 		}
1359 		dirPos++;
1360 
1361 		/* Deleted file entry or end of directory list */
1362 		if ((sectbuf[entryoffset].entryname[0] == 0xe5) || (sectbuf[entryoffset].entryname[0] == 0x00)) {
1363 			copyDirEntry(&useEntry, &sectbuf[entryoffset]);
1364 			writeSector(tmpsector,sectbuf);
1365 			break;
1366 		}
1367 	}
1368 
1369 	return true;
1370 }
1371 
zeroOutCluster(Bit32u clustNumber)1372 void fatDrive::zeroOutCluster(Bit32u clustNumber) {
1373 	Bit8u secBuffer[512];
1374 
1375 	memset(&secBuffer[0], 0, 512);
1376 
1377 	int i;
1378 	for(i=0;i<bootbuffer.sectorspercluster;i++) {
1379 		writeSector(getAbsoluteSectFromChain(clustNumber,i), &secBuffer[0]);
1380 	}
1381 }
1382 
MakeDir(char * dir)1383 bool fatDrive::MakeDir(char *dir) {
1384 	Bit32u dummyClust, dirClust;
1385 	direntry tmpentry;
1386 	char dirName[DOS_NAMELENGTH_ASCII];
1387 	char pathName[11];
1388 
1389 	/* Can we even get the name of the directory itself? */
1390 	if(!getEntryName(dir, &dirName[0])) return false;
1391 	convToDirFile(&dirName[0], &pathName[0]);
1392 
1393 	/* Fail to make directory if already exists */
1394 	if(getDirClustNum(dir, &dummyClust, false)) return false;
1395 
1396 	dummyClust = getFirstFreeClust();
1397 	/* No more space */
1398 	if(dummyClust == 0) return false;
1399 
1400 	if(!allocateCluster(dummyClust, 0)) return false;
1401 
1402 	zeroOutCluster(dummyClust);
1403 
1404 	/* Can we find the base directory? */
1405 	if(!getDirClustNum(dir, &dirClust, true)) return false;
1406 
1407 	/* Add the new directory to the base directory */
1408 	memset(&tmpentry,0, sizeof(direntry));
1409 	memcpy(&tmpentry.entryname, &pathName[0], 11);
1410 	tmpentry.loFirstClust = (Bit16u)(dummyClust & 0xffff);
1411 	tmpentry.hiFirstClust = (Bit16u)(dummyClust >> 16);
1412 	tmpentry.attrib = DOS_ATTR_DIRECTORY;
1413 	addDirectoryEntry(dirClust, tmpentry);
1414 
1415 	/* Add the [.] and [..] entries to our new directory*/
1416 	/* [.] entry */
1417 	memset(&tmpentry,0, sizeof(direntry));
1418 	memcpy(&tmpentry.entryname, ".          ", 11);
1419 	tmpentry.loFirstClust = (Bit16u)(dummyClust & 0xffff);
1420 	tmpentry.hiFirstClust = (Bit16u)(dummyClust >> 16);
1421 	tmpentry.attrib = DOS_ATTR_DIRECTORY;
1422 	addDirectoryEntry(dummyClust, tmpentry);
1423 
1424 	/* [..] entry */
1425 	memset(&tmpentry,0, sizeof(direntry));
1426 	memcpy(&tmpentry.entryname, "..         ", 11);
1427 	tmpentry.loFirstClust = (Bit16u)(dirClust & 0xffff);
1428 	tmpentry.hiFirstClust = (Bit16u)(dirClust >> 16);
1429 	tmpentry.attrib = DOS_ATTR_DIRECTORY;
1430 	addDirectoryEntry(dummyClust, tmpentry);
1431 
1432 	return true;
1433 }
1434 
RemoveDir(char * dir)1435 bool fatDrive::RemoveDir(char *dir) {
1436 	Bit32u dummyClust, dirClust;
1437 	direntry tmpentry;
1438 	char dirName[DOS_NAMELENGTH_ASCII];
1439 	char pathName[11];
1440 
1441 	/* Can we even get the name of the directory itself? */
1442 	if(!getEntryName(dir, &dirName[0])) return false;
1443 	convToDirFile(&dirName[0], &pathName[0]);
1444 
1445 	/* Get directory starting cluster */
1446 	if(!getDirClustNum(dir, &dummyClust, false)) return false;
1447 
1448 	/* Can't remove root directory */
1449 	if(dummyClust == 0) return false;
1450 
1451 	/* Get parent directory starting cluster */
1452 	if(!getDirClustNum(dir, &dirClust, true)) return false;
1453 
1454 	/* Check to make sure directory is empty */
1455 	Bit32u filecount = 0;
1456 	/* Set to 2 to skip first 2 entries, [.] and [..] */
1457 	Bit32s fileidx = 2;
1458 	while(directoryBrowse(dummyClust, &tmpentry, fileidx)) {
1459 		/* Check for non-deleted files */
1460 		if(tmpentry.entryname[0] != 0xe5) filecount++;
1461 		fileidx++;
1462 	}
1463 
1464 	/* Return if directory is not empty */
1465 	if(filecount > 0) return false;
1466 
1467 	/* Find directory entry in parent directory */
1468 	if (dirClust==0) fileidx = 0;	// root directory
1469 	else fileidx = 2;
1470 	bool found = false;
1471 	while(directoryBrowse(dirClust, &tmpentry, fileidx)) {
1472 		if(memcmp(&tmpentry.entryname, &pathName[0], 11) == 0) {
1473 			found = true;
1474 			tmpentry.entryname[0] = 0xe5;
1475 			directoryChange(dirClust, &tmpentry, fileidx);
1476 			deleteClustChain(dummyClust, 0);
1477 
1478 			break;
1479 		}
1480 		fileidx++;
1481 	}
1482 
1483 	if(!found) return false;
1484 
1485 	return true;
1486 }
1487 
Rename(char * oldname,char * newname)1488 bool fatDrive::Rename(char * oldname, char * newname) {
1489 	direntry fileEntry1;
1490 	Bit32u dirClust1, subEntry1;
1491 	if(!getFileDirEntry(oldname, &fileEntry1, &dirClust1, &subEntry1)) return false;
1492 	/* File to be renamed really exists */
1493 
1494 	direntry fileEntry2;
1495 	Bit32u dirClust2, subEntry2;
1496 
1497 	/* Check if file already exists */
1498 	if(!getFileDirEntry(newname, &fileEntry2, &dirClust2, &subEntry2)) {
1499 		/* Target doesn't exist, can rename */
1500 
1501 		char dirName2[DOS_NAMELENGTH_ASCII];
1502 		char pathName2[11];
1503 		/* Can we even get the name of the file itself? */
1504 		if(!getEntryName(newname, &dirName2[0])) return false;
1505 		convToDirFile(&dirName2[0], &pathName2[0]);
1506 
1507 		/* Can we find the base directory? */
1508 		if(!getDirClustNum(newname, &dirClust2, true)) return false;
1509 		memcpy(&fileEntry2, &fileEntry1, sizeof(direntry));
1510 		memcpy(&fileEntry2.entryname, &pathName2[0], 11);
1511 		addDirectoryEntry(dirClust2, fileEntry2);
1512 
1513 		/* Check if file exists now */
1514 		if(!getFileDirEntry(newname, &fileEntry2, &dirClust2, &subEntry2)) return false;
1515 
1516 		/* Remove old entry */
1517 		fileEntry1.entryname[0] = 0xe5;
1518 		directoryChange(dirClust1, &fileEntry1, subEntry1);
1519 
1520 		return true;
1521 	}
1522 
1523 	/* Target already exists, fail */
1524 	return false;
1525 }
1526 
TestDir(char * dir)1527 bool fatDrive::TestDir(char *dir) {
1528 	Bit32u dummyClust;
1529 	return getDirClustNum(dir, &dummyClust, false);
1530 }
1531