DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/dos/dos_files.cpp
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 <string.h>
00021 #include <stdlib.h>
00022 #include <time.h>
00023 #include <ctype.h>
00024 #if defined(WIN32) && defined(__MINGW32__)
00025 # include <malloc.h>
00026 #endif
00027 
00028 #include "dosbox.h"
00029 #include "bios.h"
00030 #include "mem.h"
00031 #include "regs.h"
00032 #include "dos_inc.h"
00033 #include "drives.h"
00034 #include "cross.h"
00035 #include "control.h"
00036 #include "dos_network2.h"
00037 
00038 #define DOS_FILESTART 4
00039 
00040 #define FCB_SUCCESS     0
00041 #define FCB_READ_NODATA 1
00042 #define FCB_READ_PARTIAL 3
00043 #define FCB_ERR_NODATA  1
00044 #define FCB_ERR_EOF     3
00045 #define FCB_ERR_WRITE   1
00046 
00047 extern bool log_int21;
00048 extern bool log_fileio;
00049 extern int dos_clipboard_device_access;
00050 extern char *dos_clipboard_device_name;
00051 
00052 Bitu DOS_FILES = 127;
00053 DOS_File ** Files = NULL;
00054 DOS_Drive * Drives[DOS_DRIVES] = {NULL};
00055 bool force_sfn = false;
00056 int sdrive = 0;
00057 
00058 /* This is the LFN filefind handle that is currently being used, with normal values between
00059  * 0 and 254 for LFN calls. The value LFN_FILEFIND_INTERNAL and LFN_FILEFIND_IMG are used
00060  * internally by LFN and image handling functions. For non-LFN calls the value is fixed to
00061  * be LFN_FILEFIND_NONE as defined in drives.h. */
00062 int lfn_filefind_handle = LFN_FILEFIND_NONE;
00063 
00064 bool shiftjis_lead_byte(int c);
00065 
00066 Bit8u DOS_GetDefaultDrive(void) {
00067 //      return DOS_SDA(DOS_SDA_SEG,DOS_SDA_OFS).GetDrive();
00068         Bit8u d = DOS_SDA(DOS_SDA_SEG,DOS_SDA_OFS).GetDrive();
00069         if( d != dos.current_drive ) LOG(LOG_DOSMISC,LOG_ERROR)("SDA drive %d not the same as dos.current_drive %d",d,dos.current_drive);
00070         return dos.current_drive;
00071 }
00072 
00073 void DOS_SetDefaultDrive(Bit8u drive) {
00074 //      if (drive<=DOS_DRIVES && ((drive<2) || Drives[drive])) DOS_SDA(DOS_SDA_SEG,DOS_SDA_OFS).SetDrive(drive);
00075         if (drive<DOS_DRIVES && ((drive<2) || Drives[drive])) {dos.current_drive = drive; DOS_SDA(DOS_SDA_SEG,DOS_SDA_OFS).SetDrive(drive);}
00076 }
00077 
00078 bool DOS_MakeName(char const * const name,char * const fullname,Bit8u * drive) {
00079         if(!name || *name == 0 || *name == ' ') {
00080                 /* Both \0 and space are seperators and
00081                  * empty filenames report file not found */
00082                 DOS_SetError(DOSERR_FILE_NOT_FOUND);
00083                 return false;
00084         }
00085         char names[LFN_NAMELENGTH];
00086         strcpy(names,name);
00087         char * name_int = names;
00088         if (strlen(names)==14 && name_int[1]==':' && name_int[2]!='\\' && name_int[9]==' ' && name_int[10]=='.') {
00089                 for (unsigned int i=0;i<strlen(names);i++)
00090                         if (i<10 && name_int[i]==32) {
00091                                 name_int[i]='.';
00092                                 name_int[i+1]=name_int[11]==32?0:toupper(name_int[11]);
00093                                 name_int[i+2]=name_int[12]==32?0:toupper(name_int[12]);
00094                                 name_int[i+3]=name_int[13]==32?0:toupper(name_int[13]);
00095                                 name_int[i+4]=0;
00096                                 break;
00097                         } else if (i<10) name_int[i]=toupper(name_int[i]);
00098         }
00099         char tempdir[DOS_PATHLENGTH];
00100         char upname[DOS_PATHLENGTH];
00101     Bitu r,w, q=0;
00102         /* First get the drive */
00103         *drive = DOS_GetDefaultDrive();
00104         while (name_int[0]=='"') {
00105                 q++;
00106                 name_int++;
00107         }
00108         if (name_int[1]==':') {
00109                 *drive=(name_int[0] | 0x20)-'a';
00110                 name_int+=2;
00111         }
00112         if (*drive>=DOS_DRIVES || !Drives[*drive]) { 
00113                 DOS_SetError(DOSERR_PATH_NOT_FOUND);
00114                 return false; 
00115         }
00116         r=0;w=0;
00117         while (r<DOS_PATHLENGTH && name_int[r]!=0) {
00118                 Bit8u c=(Bit8u)name_int[r++];
00119                 if (c=='/') c='\\';
00120                 else if (c=='"') {q++;continue;}
00121                 else if (uselfn&&!force_sfn) {
00122                         if (c==' ' && q/2*2 == q) continue;
00123                 } else {
00124                         if ((c>='a') && (c<='z')) c-=32;
00125                         else if (c==' ') continue; /* should be separator */
00126                 }
00127                 upname[w++]=(char)c;
00128         if (IS_PC98_ARCH && shiftjis_lead_byte(c) && r<DOS_PATHLENGTH) {
00129             /* The trailing byte is NOT ASCII and SHOULD NOT be converted to uppercase like ASCII */
00130             upname[w++]=name_int[r++];
00131         }
00132         }
00133         while (r>0 && name_int[r-1]==' ') r--;
00134         if (r>=DOS_PATHLENGTH) { DOS_SetError(DOSERR_PATH_NOT_FOUND);return false; }
00135         upname[w]=0;
00136 
00137         /* Now parse the new file name to make the final filename */
00138         if (upname[0]!='\\') strcpy(fullname,Drives[*drive]->curdir);
00139         else fullname[0]=0;
00140         Bit32u lastdir=0;Bit32u t=0;
00141         while (fullname[t]!=0) {
00142                 if ((fullname[t]=='\\') && (fullname[t+1]!=0)) lastdir=t;
00143                 t++;
00144         }
00145         r=0;w=0;
00146         tempdir[0]=0;
00147         bool stop=false;
00148         while (!stop) {
00149                 if (upname[r]==0) stop=true;
00150                 if ((upname[r]=='\\') || (upname[r]==0)){
00151                         tempdir[w]=0;
00152                         if (tempdir[0]==0) { w=0;r++;continue;}
00153                         if (strcmp(tempdir,".")==0) {
00154                                 tempdir[0]=0;                   
00155                                 w=0;r++;
00156                                 continue;
00157                         }
00158 
00159                         Bit32s iDown;
00160                         bool dots = true;
00161                         Bit32s templen=(Bit32s)strlen(tempdir);
00162                         for(iDown=0;(iDown < templen) && dots;iDown++)
00163                                 if(tempdir[iDown] != '.')
00164                                         dots = false;
00165 
00166                         // only dots?
00167                         if (dots && (templen > 1)) {
00168                                 Bit32s cDots = templen - 1;
00169                                 for(iDown=(Bit32s)strlen(fullname)-1;iDown>=0;iDown--) {
00170                                         if(fullname[iDown]=='\\' || iDown==0) {
00171                                                 lastdir = (Bit32u)iDown;
00172                                                 cDots--;
00173                                                 if(cDots==0)
00174                                                         break;
00175                                         }
00176                                 }
00177                                 fullname[lastdir]=0;
00178                                 t=0;lastdir=0;
00179                                 while (fullname[t]!=0) {
00180                                         if ((fullname[t]=='\\') && (fullname[t+1]!=0)) lastdir=t;
00181                                         t++;
00182                                 }
00183                                 tempdir[0]=0;
00184                                 w=0;r++;
00185                                 continue;
00186                         }
00187                         
00188 
00189                         lastdir=(Bit32u)strlen(fullname);
00190 
00191                         if (lastdir!=0) strcat(fullname,"\\");
00192                         if (!uselfn||force_sfn) {
00193                                 char * ext=strchr(tempdir,'.');
00194                                 if (ext) {
00195                                         if(strchr(ext+1,'.')) { 
00196                                         //another dot in the extension =>file not found
00197                                         //Or path not found depending on wether 
00198                                         //we are still in dir check stage or file stage
00199                                                 if(stop)
00200                                                         DOS_SetError(DOSERR_FILE_NOT_FOUND);
00201                                                 else
00202                                                         DOS_SetError(DOSERR_PATH_NOT_FOUND);
00203                                                 return false;
00204                                         }
00205                                         
00206                                         ext[4] = 0;
00207                                         if((strlen(tempdir) - strlen(ext)) > 8) memmove(tempdir + 8, ext, 5);
00208                 } else {
00209                     tempdir[8] = 0;
00210                 }
00211                         }
00212 
00213                         if (strlen(fullname)+strlen(tempdir)>=DOS_PATHLENGTH) {
00214                                 DOS_SetError(DOSERR_PATH_NOT_FOUND);return false;
00215                         }
00216                    
00217                         strcat(fullname,tempdir);
00218                         tempdir[0]=0;
00219                         w=0;r++;
00220                         continue;
00221                 }
00222                 tempdir[w++]=upname[r++];
00223         }
00224         return true;    
00225 }
00226 
00227 bool DOS_GetSFNPath(char const * const path,char * SFNPath,bool LFN) {
00228     char pdir[LFN_NAMELENGTH+4], *p;
00229     Bit8u drive;char fulldir[DOS_PATHLENGTH],LFNPath[CROSS_LEN];
00230     char name[DOS_NAMELENGTH_ASCII], lname[LFN_NAMELENGTH];
00231     Bit32u size;Bit16u date;Bit16u time;Bit8u attr;
00232     if (!DOS_MakeName(path,fulldir,&drive)) return false;
00233     sprintf(SFNPath,"%c:\\",drive+'A');
00234     strcpy(LFNPath,SFNPath);
00235     p = fulldir;
00236     if (*p==0) return true;
00237         RealPt save_dta=dos.dta();
00238         dos.dta(dos.tables.tempdta);
00239         DOS_DTA dta(dos.dta());
00240         int fbak=lfn_filefind_handle;
00241     for (char *s = strchr(p,'\\'); s != NULL; s = strchr(p,'\\')) {
00242                 *s = 0;
00243                 if (SFNPath[strlen(SFNPath)-1]=='\\')
00244                         sprintf(pdir,"\"%s%s\"",SFNPath,p);
00245                 else
00246                         sprintf(pdir,"\"%s\\%s\"",SFNPath,p);
00247                 if (!strrchr(p,'*') && !strrchr(p,'?')) {
00248                         *s = '\\';
00249                         p = s + 1;
00250                         lfn_filefind_handle=LFN_FILEFIND_INTERNAL;
00251                         if (DOS_FindFirst(pdir,0xffff & DOS_ATTR_DIRECTORY & ~DOS_ATTR_VOLUME,false)) {
00252                                 lfn_filefind_handle=fbak;
00253                                 dta.GetResult(name,lname,size,date,time,attr);
00254                                 strcat(SFNPath,name);
00255                                 strcat(LFNPath,lname);
00256                                 strcat(SFNPath,"\\");
00257                                 strcat(LFNPath,"\\");
00258                         }
00259                         else {
00260                                 lfn_filefind_handle=fbak;
00261                                 dos.dta(save_dta);
00262                                 return false;
00263                         }
00264                 } else {
00265                         strcat(SFNPath,p);
00266                         strcat(LFNPath,p);
00267                         strcat(SFNPath,"\\");
00268                         strcat(LFNPath,"\\");
00269                         *s = '\\';
00270                         p = s + 1;
00271                         break;
00272                 }
00273     }
00274     if (p != 0) {
00275                 sprintf(pdir,"\"%s%s\"",SFNPath,p);
00276                 lfn_filefind_handle=LFN_FILEFIND_INTERNAL;
00277                 if (!strrchr(p,'*')&&!strrchr(p,'?')&&DOS_FindFirst(pdir,0xffff & ~DOS_ATTR_VOLUME,false)) {
00278                         dta.GetResult(name,lname,size,date,time,attr);
00279                         strcat(SFNPath,name);
00280                         strcat(LFNPath,lname);
00281                 } else {
00282                         strcat(SFNPath,p);
00283                         strcat(LFNPath,p);
00284                 }
00285                 lfn_filefind_handle=fbak;
00286     }
00287         dos.dta(save_dta);
00288     if (LFN) strcpy(SFNPath,LFNPath);
00289     return true;
00290 }
00291 
00292 bool DOS_GetCurrentDir(Bit8u drive,char * const buffer, bool LFN) {
00293     if (drive==0) drive=DOS_GetDefaultDrive();
00294     else drive--;
00295 
00296     if ((drive>=DOS_DRIVES) || (!Drives[drive])) {
00297         DOS_SetError(DOSERR_INVALID_DRIVE);
00298         return false;
00299     }
00300 
00301     if (LFN && uselfn) {
00302         char cdir[DOS_PATHLENGTH+8],ldir[DOS_PATHLENGTH];
00303 
00304         if (strchr(Drives[drive]->curdir,' '))
00305             sprintf(cdir,"\"%c:\\%s\"",drive+'A',Drives[drive]->curdir);
00306         else
00307             sprintf(cdir,"%c:\\%s",drive+'A',Drives[drive]->curdir);
00308 
00309         if (!DOS_GetSFNPath(cdir,ldir,true))
00310             return false;
00311 
00312         strcpy(buffer,ldir+3);
00313         if (DOS_GetSFNPath(cdir,ldir,false))
00314             strcpy(Drives[drive]->curdir,ldir+3);
00315     } else {
00316         strcpy(buffer,Drives[drive]->curdir);
00317     }
00318 
00319     return true;
00320 }
00321 
00322 bool DOS_ChangeDir(char const * const dir) {
00323         Bit8u drive;char fulldir[DOS_PATHLENGTH];
00324         const char * testdir=dir;
00325         if (strlen(testdir) && testdir[1]==':') testdir+=2;
00326         size_t len=strlen(testdir);
00327         if (!len) {
00328                 DOS_SetError(DOSERR_PATH_NOT_FOUND);
00329                 return false;
00330         }
00331         if (!DOS_MakeName(dir,fulldir,&drive)) return false;
00332         if (strlen(fulldir) && testdir[len-1]=='\\') {
00333                 DOS_SetError(DOSERR_PATH_NOT_FOUND);
00334                 return false;
00335         }
00336         
00337         if (Drives[drive]->TestDir(fulldir)) {
00338                 strcpy(Drives[drive]->curdir,fulldir);
00339                 return true;
00340         } else {
00341                 DOS_SetError(DOSERR_PATH_NOT_FOUND);
00342         }
00343         return false;
00344 }
00345 
00346 bool DOS_MakeDir(char const * const dir) {
00347         Bit8u drive;char fulldir[DOS_PATHLENGTH];
00348         size_t len = strlen(dir);
00349         if(!len || dir[len-1] == '\\') {
00350                 DOS_SetError(DOSERR_PATH_NOT_FOUND);
00351                 return false;
00352         }
00353         if (!DOS_MakeName(dir,fulldir,&drive)) return false;
00354         while (strlen(fulldir)&&(*(fulldir+strlen(fulldir)-1)=='.'||*(fulldir+strlen(fulldir)-1)==' ')) *(fulldir+strlen(fulldir)-1)=0;
00355         if(Drives[drive]->MakeDir(fulldir)) return true;
00356 
00357         /* Determine reason for failing */
00358         if(Drives[drive]->TestDir(fulldir)) 
00359                 DOS_SetError(DOSERR_ACCESS_DENIED);
00360         else
00361                 DOS_SetError(DOSERR_PATH_NOT_FOUND);
00362         return false;
00363 }
00364 
00365 bool DOS_RemoveDir(char const * const dir) {
00366 /* We need to do the test before the removal as can not rely on
00367  * the host to forbid removal of the current directory.
00368  * We never change directory. Everything happens in the drives.
00369  */
00370         Bit8u drive;char fulldir[DOS_PATHLENGTH];
00371         if (!DOS_MakeName(dir,fulldir,&drive)) return false;
00372         /* Check if exists */
00373         if(!Drives[drive]->TestDir(fulldir)) {
00374                 DOS_SetError(DOSERR_PATH_NOT_FOUND);
00375                 return false;
00376         }
00377         /* See if it's current directory */
00378     char currdir[DOS_PATHLENGTH]= { 0 }, lcurrdir[DOS_PATHLENGTH]= { 0 };
00379     DOS_GetCurrentDir(drive + 1 ,currdir, false);
00380     DOS_GetCurrentDir(drive + 1 ,lcurrdir, true);
00381     if(strcasecmp(currdir,fulldir) == 0 || (uselfn && strcasecmp(lcurrdir,fulldir) == 0)) {
00382                 DOS_SetError(DOSERR_REMOVE_CURRENT_DIRECTORY);
00383                 return false;
00384         }
00385 
00386         if(Drives[drive]->RemoveDir(fulldir)) return true;
00387 
00388         /* Failed. We know it exists and it's not the current dir */
00389         /* Assume non empty */
00390         DOS_SetError(DOSERR_ACCESS_DENIED);
00391         return false;
00392 }
00393 
00394 bool DOS_Rename(char const * const oldname,char const * const newname) {
00395         Bit8u driveold;char fullold[DOS_PATHLENGTH];
00396         Bit8u drivenew;char fullnew[DOS_PATHLENGTH];
00397         if (!DOS_MakeName(oldname,fullold,&driveold)) return false;
00398         if (!DOS_MakeName(newname,fullnew,&drivenew)) return false;
00399         while (strlen(fullnew)&&(*(fullnew+strlen(fullnew)-1)=='.'||*(fullnew+strlen(fullnew)-1)==' ')) *(fullnew+strlen(fullnew)-1)=0;
00400 
00401         /* No tricks with devices */
00402         bool clip=false;
00403         if ( (DOS_FindDevice(oldname) != DOS_DEVICES) ||
00404              (DOS_FindDevice(newname) != DOS_DEVICES) ) {
00405 #if defined (WIN32)
00406         if (!control->SecureMode()&&(dos_clipboard_device_access==3||dos_clipboard_device_access==4)) {
00407                 if (DOS_FindDevice(oldname) == DOS_DEVICES) {
00408             const char* find_last;
00409                         find_last=strrchr(fullnew,'\\');
00410                         if (find_last==NULL) find_last=fullnew;
00411                         else find_last++;
00412                         if (!strcasecmp(find_last, *dos_clipboard_device_name?dos_clipboard_device_name:"CLIP$"))
00413                                 clip=true;
00414                 }
00415         }
00416 #endif
00417                 if (!clip) {
00418                         DOS_SetError(DOSERR_FILE_NOT_FOUND);
00419                         return false;
00420                 }
00421         }
00422         /* Must be on the same drive */
00423         if(driveold != drivenew) {
00424                 DOS_SetError(DOSERR_NOT_SAME_DEVICE);
00425                 return false;
00426         }
00427         Bit16u attr;
00428         /* Source must exist, check for path ? */
00429         if (!Drives[driveold]->GetFileAttr( fullold, &attr ) ) {
00430                 DOS_SetError(DOSERR_FILE_NOT_FOUND);
00431                 return false;
00432         }
00433         /*Test if target exists => no access */
00434         if(Drives[drivenew]->GetFileAttr(fullnew,&attr)&&!(uselfn&&!force_sfn&&strcmp(fullold, fullnew)&&!strcasecmp(fullold, fullnew))) {
00435                 DOS_SetError(DOSERR_ACCESS_DENIED);
00436                 return false;
00437         }
00438 
00439 #if defined (WIN32)
00440         if (clip) {
00441                 Bit16u sourceHandle, targetHandle, toread = 0x8000;
00442                 static Bit8u buffer[0x8000];
00443                 bool failed = false;
00444                 if (DOS_OpenFile(oldname,OPEN_READ,&sourceHandle) && DOS_OpenFile(newname,OPEN_WRITE,&targetHandle)) {
00445                         do {
00446                                 if (!DOS_ReadFile(sourceHandle,buffer,&toread) || !DOS_WriteFile(targetHandle,buffer,&toread)) failed=true;
00447                         } while (toread == 0x8000);
00448                         if (!DOS_CloseFile(sourceHandle)||!DOS_CloseFile(targetHandle)) failed=true;
00449                 } else failed=true;
00450                 if (!failed&&Drives[drivenew]->FileUnlink(fullold)) return true;
00451         } else
00452 #endif
00453                 if (Drives[drivenew]->Rename(fullold,fullnew)) return true;
00454         /* If it still fails, which error should we give ? PATH NOT FOUND or EACCESS */
00455         if (dos.errorcode!=DOSERR_ACCESS_DENIED&&dos.errorcode!=DOSERR_WRITE_PROTECTED) {
00456                 LOG(LOG_FILES,LOG_NORMAL)("Rename fails for %s to %s, no proper errorcode returned.",oldname,newname);
00457                 DOS_SetError(DOSERR_FILE_NOT_FOUND);
00458         }
00459         return false;
00460 }
00461 
00462 bool DOS_FindFirst(char * search,Bit16u attr,bool fcb_findfirst) {
00463         LOG(LOG_FILES,LOG_NORMAL)("file search attributes %X name %s",attr,search);
00464         DOS_DTA dta(dos.dta());
00465         Bit8u drive;char fullsearch[DOS_PATHLENGTH];
00466         char dir[DOS_PATHLENGTH];char pattern[DOS_PATHLENGTH];
00467         size_t len = strlen(search);
00468         if(len && search[len - 1] == '\\' && !( (len > 2) && (search[len - 2] == ':') && (attr == DOS_ATTR_VOLUME) )) { 
00469                 //Dark Forces installer, but c:\ is allright for volume labels(exclusively set)
00470                 DOS_SetError(DOSERR_NO_MORE_FILES);
00471                 return false;
00472         }
00473         if (!DOS_MakeName(search,fullsearch,&drive)) return false;
00474         //Check for devices. FindDevice checks for leading subdir as well
00475         bool device = (DOS_FindDevice(search) != DOS_DEVICES);
00476 
00477         /* Split the search in dir and pattern */
00478         char * find_last;
00479         find_last=strrchr(fullsearch,'\\');
00480         if (!find_last) {       /*No dir */
00481                 strcpy(pattern,fullsearch);
00482                 dir[0]=0;
00483         } else {
00484                 *find_last=0;
00485                 strcpy(pattern,find_last+1);
00486                 strcpy(dir,fullsearch);
00487         }
00488 
00489         sdrive=drive;
00490         dta.SetupSearch(drive,(Bit8u)attr,pattern);
00491 
00492         if(device) {
00493                 find_last = strrchr(pattern,'.');
00494                 if(find_last) *find_last = 0;
00495                 //TODO use current date and time
00496         dta.SetResult(pattern,pattern,0,0,0,DOS_ATTR_DEVICE);
00497                 LOG(LOG_DOSMISC,LOG_WARN)("finding device %s",pattern);
00498                 return true;
00499         }
00500    
00501         if (Drives[drive]->FindFirst(dir,dta,fcb_findfirst)) return true;
00502         
00503         return false;
00504 }
00505 
00506 bool DOS_FindNext(void) {
00507         DOS_DTA dta(dos.dta());
00508         Bit8u i = dta.GetSearchDrive();
00509         if(uselfn && (i >= DOS_DRIVES || !Drives[i])) i=sdrive;
00510         if(i >= DOS_DRIVES || !Drives[i]) {
00511                 /* Corrupt search. */
00512                 LOG(LOG_FILES,LOG_ERROR)("Corrupt search!!!!");
00513                 DOS_SetError(DOSERR_NO_MORE_FILES); 
00514                 return false;
00515         } 
00516         if (Drives[i]->FindNext(dta)) return true;
00517         return false;
00518 }
00519 
00520 
00521 bool DOS_ReadFile(Bit16u entry,Bit8u * data,Bit16u * amount,bool fcb) {
00522 #if defined(WIN32) && !defined(__MINGW32__)
00523         if(Network_IsActiveResource(entry))
00524                 return Network_ReadFile(entry,data,amount);
00525 #endif
00526         Bit32u handle = fcb?entry:RealHandle(entry);
00527         if (handle>=DOS_FILES) {
00528                 DOS_SetError(DOSERR_INVALID_HANDLE);
00529                 return false;
00530         }
00531         if (!Files[handle] || !Files[handle]->IsOpen()) {
00532                 DOS_SetError(DOSERR_INVALID_HANDLE);
00533                 return false;
00534         }
00535 
00536     if (log_fileio) {
00537         LOG(LOG_FILES, LOG_DEBUG)("Reading %d bytes from %s ", *amount, Files[handle]->name);
00538     }
00539 /*
00540         if ((Files[handle]->flags & 0x0f) == OPEN_WRITE)) {
00541                 DOS_SetError(DOSERR_INVALID_HANDLE);
00542                 return false;
00543         }
00544 */
00545         Bit16u toread=*amount;
00546         bool ret=Files[handle]->Read(data,&toread);
00547         *amount=toread;
00548         return ret;
00549 }
00550 
00551 bool DOS_WriteFile(Bit16u entry,Bit8u * data,Bit16u * amount,bool fcb) {
00552 #if defined(WIN32) && !defined(__MINGW32__)
00553         if(Network_IsActiveResource(entry))
00554                 return Network_WriteFile(entry,data,amount);
00555 #endif
00556         Bit32u handle = fcb?entry:RealHandle(entry);
00557         if (handle>=DOS_FILES) {
00558                 DOS_SetError(DOSERR_INVALID_HANDLE);
00559                 return false;
00560         }
00561         if (!Files[handle] || !Files[handle]->IsOpen()) {
00562                 DOS_SetError(DOSERR_INVALID_HANDLE);
00563                 return false;
00564         }
00565 
00566     if (log_fileio) {
00567         LOG(LOG_FILES, LOG_DEBUG)("Writing %d bytes to %s", *amount, Files[handle]->name);
00568     }
00569 /*
00570         if ((Files[handle]->flags & 0x0f) == OPEN_READ)) {
00571                 DOS_SetError(DOSERR_INVALID_HANDLE);
00572                 return false;
00573         }
00574 */
00575         Bit16u towrite=*amount;
00576         bool ret=Files[handle]->Write(data,&towrite);
00577         *amount=towrite;
00578         return ret;
00579 }
00580 
00581 bool DOS_SeekFile(Bit16u entry,Bit32u * pos,Bit32u type,bool fcb) {
00582         Bit32u handle = fcb?entry:RealHandle(entry);
00583         if (handle>=DOS_FILES) {
00584                 DOS_SetError(DOSERR_INVALID_HANDLE);
00585                 return false;
00586         }
00587         if (!Files[handle] || !Files[handle]->IsOpen()) {
00588                 DOS_SetError(DOSERR_INVALID_HANDLE);
00589                 return false;
00590         }
00591         return Files[handle]->Seek(pos,type);
00592 }
00593 
00594 /* ert, 20100711: Locking extensions */
00595 bool DOS_LockFile(Bit16u entry,Bit8u mode,Bit32u pos,Bit32u size) {
00596         Bit32u handle=RealHandle(entry);
00597         if (handle>=DOS_FILES) {
00598                 DOS_SetError(DOSERR_INVALID_HANDLE);
00599                 return false;
00600         }
00601         if (!Files[handle] || !Files[handle]->IsOpen()) {
00602                 DOS_SetError(DOSERR_INVALID_HANDLE);
00603                 return false;
00604         }
00605 #ifdef WIN32
00606         return Files[handle]->LockFile(mode,pos,size);
00607 #else
00608     (void)mode;//UNUSED
00609     (void)size;//UNUSED
00610     (void)pos;//UNUSED
00611         return true;
00612 #endif
00613 }
00614 
00615 bool DOS_CloseFile(Bit16u entry, bool fcb) {
00616 #if defined(WIN32) && !defined(__MINGW32__)
00617         if(Network_IsActiveResource(entry))
00618                 return Network_CloseFile(entry);
00619 #endif
00620         Bit32u handle = fcb?entry:RealHandle(entry);
00621         if (handle>=DOS_FILES) {
00622                 DOS_SetError(DOSERR_INVALID_HANDLE);
00623                 return false;
00624         }
00625         if (!Files[handle]) {
00626                 DOS_SetError(DOSERR_INVALID_HANDLE);
00627                 return false;
00628         }
00629     if (Files[handle]->IsOpen()) {
00630         if (log_fileio) {
00631             LOG(LOG_FILES, LOG_NORMAL)("Closing file %s", Files[handle]->name);
00632         }
00633         Files[handle]->Close();
00634         }
00635 
00636         DOS_PSP psp(dos.psp());
00637         if (!fcb) psp.SetFileHandle(entry,0xff);
00638 
00639         if (Files[handle]->RemoveRef()<=0) {
00640                 delete Files[handle];
00641                 Files[handle]=0;
00642         }
00643         return true;
00644 }
00645 
00646 bool DOS_FlushFile(Bit16u entry) {
00647         Bit32u handle=RealHandle(entry);
00648         if (handle>=DOS_FILES) {
00649                 DOS_SetError(DOSERR_INVALID_HANDLE);
00650                 return false;
00651         }
00652         if (!Files[handle] || !Files[handle]->IsOpen()) {
00653                 DOS_SetError(DOSERR_INVALID_HANDLE);
00654                 return false;
00655         }
00656 
00657         LOG(LOG_DOSMISC,LOG_DEBUG)("FFlush used.");
00658 
00659     Files[handle]->Flush();
00660         return true;
00661 }
00662 
00663 static bool PathExists(char const * const name) {
00664         const char* leading = strrchr(name,'\\');
00665         if(!leading) return true;
00666         char temp[CROSS_LEN];
00667         strcpy(temp,name);
00668         char * lead = strrchr(temp,'\\');
00669         if (lead == temp) return true;
00670         *lead = 0;
00671         Bit8u drive;char fulldir[DOS_PATHLENGTH];
00672         if (!DOS_MakeName(temp,fulldir,&drive)) return false;
00673         if(!Drives[drive]->TestDir(fulldir)) return false;
00674         return true;
00675 }
00676 
00677 
00678 bool DOS_CreateFile(char const * name,Bit16u attributes,Bit16u * entry,bool fcb) {
00679         // Creation of a device is the same as opening it
00680         // Tc201 installer
00681         if (DOS_FindDevice(name) != DOS_DEVICES)
00682                 return DOS_OpenFile(name, OPEN_READ, entry, fcb);
00683 
00684         LOG(LOG_FILES,LOG_NORMAL)("file create attributes %X file %s",attributes,name);
00685         char fullname[DOS_PATHLENGTH];Bit8u drive;
00686         DOS_PSP psp(dos.psp());
00687         if (!DOS_MakeName(name,fullname,&drive)) return false;
00688         while (strlen(fullname)&&(*(fullname+strlen(fullname)-1)=='.'||*(fullname+strlen(fullname)-1)==' ')) *(fullname+strlen(fullname)-1)=0;
00689 
00690         /* Check for a free file handle */
00691         Bit8u handle=(Bit8u)DOS_FILES;Bit8u i;
00692         for (i=0;i<DOS_FILES;i++) {
00693                 if (!Files[i]) {
00694                         handle=i;
00695                         break;
00696                 }
00697         }
00698         if (handle==DOS_FILES) {
00699                 DOS_SetError(DOSERR_TOO_MANY_OPEN_FILES);
00700                 return false;
00701         }
00702         /* We have a position in the main table now find one in the psp table */
00703         *entry = fcb?handle:psp.FindFreeFileEntry();
00704         if (*entry==0xff) {
00705                 DOS_SetError(DOSERR_TOO_MANY_OPEN_FILES);
00706                 return false;
00707         }
00708         /* Don't allow directories to be created */
00709         if (attributes&DOS_ATTR_DIRECTORY) {
00710                 DOS_SetError(DOSERR_ACCESS_DENIED);
00711                 return false;
00712         }
00713         bool foundit=Drives[drive]->FileCreate(&Files[handle],fullname,attributes);
00714         if (foundit) { 
00715                 if (Files[handle]) {
00716                         Files[handle]->SetDrive(drive);
00717                         Files[handle]->AddRef();
00718                         Files[handle]->drive = drive;
00719                 }
00720                 if (!fcb) psp.SetFileHandle(*entry,handle);
00721                 if (Files[handle]) Drives[drive]->EmptyCache();
00722                 return true;
00723         } else {
00724                 if(!PathExists(name)) DOS_SetError(DOSERR_PATH_NOT_FOUND); 
00725                 else DOS_SetError(DOSERR_FILE_NOT_FOUND);
00726                 return false;
00727         }
00728 }
00729 
00730 bool DOS_OpenFile(char const * name,Bit8u flags,Bit16u * entry,bool fcb) {
00731 #if defined(WIN32) && !defined(__MINGW32__)
00732         if(Network_IsNetworkResource(const_cast<char *>(name)))
00733                 return Network_OpenFile(const_cast<char *>(name),flags,entry);
00734 #endif
00735         /* First check for devices */
00736         if (flags>2) LOG(LOG_FILES,LOG_NORMAL)("Special file open command %X file %s",flags,name); // FIXME: Why? Is there something about special opens DOSBox doesn't handle properly?
00737         else LOG(LOG_FILES,LOG_NORMAL)("file open command %X file %s",flags,name);
00738 
00739         DOS_PSP psp(dos.psp());
00740         Bit16u attr = 0;
00741         Bit8u devnum = DOS_FindDevice(name);
00742         bool device = (devnum != DOS_DEVICES);
00743         if(!device && DOS_GetFileAttr(name,&attr)) {
00744         //DON'T ALLOW directories to be openened.(skip test if file is device).
00745                 if((attr & DOS_ATTR_DIRECTORY) || (attr & DOS_ATTR_VOLUME)){
00746                         DOS_SetError(DOSERR_ACCESS_DENIED);
00747                         return false;
00748                 }
00749         }
00750 
00751         char fullname[DOS_PATHLENGTH];Bit8u drive;Bit8u i;
00752         /* First check if the name is correct */
00753         if (!DOS_MakeName(name,fullname,&drive)) return false;
00754         Bit8u handle=255;               
00755         /* Check for a free file handle */
00756         for (i=0;i<DOS_FILES;i++) {
00757                 if (!Files[i]) {
00758                         handle=i;
00759                         break;
00760                 }
00761         }
00762         if (handle==255) {
00763                 DOS_SetError(DOSERR_TOO_MANY_OPEN_FILES);
00764                 return false;
00765         }
00766         /* We have a position in the main table now find one in the psp table */
00767         *entry = fcb?handle:psp.FindFreeFileEntry();
00768 
00769         if (*entry==0xff) {
00770                 DOS_SetError(DOSERR_TOO_MANY_OPEN_FILES);
00771                 return false;
00772         }
00773         bool exists=false;
00774         if (device) {
00775                 Files[handle]=new DOS_Device(*Devices[devnum]);
00776         } else {
00777         exists=Drives[drive]->FileOpen(&Files[handle],fullname,flags) || Drives[drive]->FileOpen(&Files[handle],upcase(fullname),flags);
00778                 if (exists) Files[handle]->SetDrive(drive);
00779         }
00780         if (exists || device ) { 
00781                 Files[handle]->AddRef();
00782                 psp.SetFileHandle(*entry,handle);
00783                 Files[handle]->drive = drive;
00784                 return true;
00785         } else {
00786                 //Test if file exists, but opened in read-write mode (and writeprotected)
00787                 if(((flags&3) != OPEN_READ) && Drives[drive]->FileExists(fullname))
00788                         DOS_SetError(DOSERR_ACCESS_DENIED);
00789                 else {
00790                         if(!PathExists(name)) DOS_SetError(DOSERR_PATH_NOT_FOUND); 
00791                         else DOS_SetError(DOSERR_FILE_NOT_FOUND);
00792                 }
00793                 return false;
00794         }
00795 }
00796 
00797 bool DOS_OpenFileExtended(char const * name, Bit16u flags, Bit16u createAttr, Bit16u action, Bit16u *entry, Bit16u* status) {
00798 // FIXME: Not yet supported : Bit 13 of flags (int 0x24 on critical error)
00799         Bit16u result = 0;
00800         if (action==0) {
00801                 // always fail setting
00802                 DOS_SetError(DOSERR_FUNCTION_NUMBER_INVALID);
00803                 return false;
00804         } else {
00805                 if (((action & 0x0f)>2) || ((action & 0xf0)>0x10)) {
00806                         // invalid action parameter
00807                         DOS_SetError(DOSERR_FUNCTION_NUMBER_INVALID);
00808                         return false;
00809                 }
00810         }
00811         if (DOS_OpenFile(name, (Bit8u)(flags&0xff), entry)) {
00812                 // File already exists
00813                 switch (action & 0x0f) {
00814                         case 0x00:              // failed
00815                                 DOS_SetError(DOSERR_FILE_ALREADY_EXISTS);
00816                                 return false;
00817                         case 0x01:              // file open (already done)
00818                                 result = 1;
00819                                 break;
00820                         case 0x02:              // replace
00821                                 DOS_CloseFile(*entry);
00822                                 if (!DOS_CreateFile(name, createAttr, entry)) return false;
00823                                 result = 3;
00824                                 break;
00825                         default:
00826                                 DOS_SetError(DOSERR_FUNCTION_NUMBER_INVALID);
00827                                 E_Exit("DOS: OpenFileExtended: Unknown action.");
00828                                 break;
00829                 }
00830         } else {
00831                 // File doesn't exist
00832                 if ((action & 0xf0)==0) {
00833                         // uses error code from failed open
00834                         return false;
00835                 }
00836                 // Create File
00837                 if (!DOS_CreateFile(name, createAttr, entry)) {
00838                         // uses error code from failed create
00839                         return false;
00840                 }
00841                 result = 2;
00842         }
00843         // success
00844         *status = result;
00845         return true;
00846 }
00847 
00848 bool DOS_UnlinkFile(char const * const name) {
00849         char fullname[DOS_PATHLENGTH];Bit8u drive;
00850     // An existing device returns an access denied error
00851     if (log_fileio) {
00852         LOG(LOG_FILES, LOG_NORMAL)("Deleting file %s", name);
00853     }
00854     if (DOS_FindDevice(name) != DOS_DEVICES) {
00855                 DOS_SetError(DOSERR_ACCESS_DENIED);
00856                 return false;
00857         }
00858         if (!DOS_MakeName(name,fullname,&drive)) return false;
00859         if(Drives[drive]->FileUnlink(fullname)){
00860                 return true;
00861         } else if(uselfn&&!force_sfn&&(strchr(fullname, '*')||strchr(fullname, '?'))) { // Wildcard delete as used by MS-DOS 7+ "DEL *.*" in LFN mode
00862                 char dir[DOS_PATHLENGTH], temp[DOS_PATHLENGTH], fname[DOS_PATHLENGTH], spath[DOS_PATHLENGTH], pattern[DOS_PATHLENGTH];
00863                 if (!DOS_Canonicalize(name,fullname)||!strlen(fullname)) {
00864                         DOS_SetError(DOSERR_PATH_NOT_FOUND);
00865                         return false;
00866                 }
00867                 if (!strchr(name,'\"')||!DOS_GetSFNPath(("\""+std::string(fullname)+"\"").c_str(), fname, false))
00868                         strcpy(fname, fullname);
00869                 char * find_last=strrchr(fname,'\\');
00870                 if (!find_last) {
00871                         dir[0]=0;
00872                         strcpy(pattern, fname);
00873                 } else {
00874                         *find_last=0;
00875                         strcpy(dir,fname);
00876                         strcpy(pattern, find_last+1);
00877                 }
00878                 int k=0;
00879                 for (int i=0;i<(int)strlen(pattern);i++)
00880                         if (pattern[i]!='\"')
00881                                 pattern[k++]=pattern[i];
00882                 pattern[k]=0;
00883                 RealPt save_dta=dos.dta();
00884                 dos.dta(dos.tables.tempdta);
00885                 DOS_DTA dta(dos.dta());
00886                 std::vector<std::string> cdirs;
00887                 cdirs.clear();
00888                 strcpy(spath, dir);
00889                 if (!DOS_GetSFNPath(dir, spath, false)) strcpy(spath, dir);
00890                 if (!strlen(spath)||spath[strlen(spath)-1]!='\\') strcat(spath, "\\");
00891                 std::string pfull=std::string(spath)+std::string(pattern);
00892                 int fbak=lfn_filefind_handle;
00893                 lfn_filefind_handle=LFN_FILEFIND_INTERNAL;
00894                 bool ret=DOS_FindFirst((char *)((pfull.length()&&pfull[0]=='"'?"":"\"")+pfull+(pfull.length()&&pfull[pfull.length()-1]=='"'?"":"\"")).c_str(),0xffu & ~DOS_ATTR_VOLUME & ~DOS_ATTR_DIRECTORY);
00895                 if (ret) do {
00896                         char find_name[DOS_NAMELENGTH_ASCII],lfind_name[LFN_NAMELENGTH];
00897                         Bit16u find_date,find_time;Bit32u find_size;Bit8u find_attr;
00898                         dta.GetResult(find_name,lfind_name,find_size,find_date,find_time,find_attr);
00899                         if (!(find_attr & DOS_ATTR_DIRECTORY)&&strlen(find_name)&&!strchr(find_name, '*')&&!strchr(find_name, '?')) {
00900                                 strcpy(temp, dir);
00901                                 if (strlen(temp)&&temp[strlen(temp)-1]!='\\') strcat(temp, "\\");
00902                                 strcat(temp, find_name);
00903                                 cdirs.push_back(std::string(temp));
00904                         }
00905                 } while ((ret=DOS_FindNext())==true);
00906                 lfn_filefind_handle=fbak;
00907                 bool removed=false;
00908                 while (!cdirs.empty()) {
00909                         if (DOS_UnlinkFile(cdirs.begin()->c_str()))
00910                                 removed=true;
00911                         cdirs.erase(cdirs.begin());
00912                 }
00913                 dos.dta(save_dta);
00914                 if (removed)
00915                         return true;
00916                 else {
00917                         if (dos.errorcode!=DOSERR_ACCESS_DENIED&&dos.errorcode!=DOSERR_WRITE_PROTECTED) DOS_SetError(DOSERR_FILE_NOT_FOUND);
00918                         return false;
00919                 }
00920         } else {
00921                 if (dos.errorcode!=DOSERR_ACCESS_DENIED&&dos.errorcode!=DOSERR_WRITE_PROTECTED) DOS_SetError(DOSERR_FILE_NOT_FOUND);
00922                 return false;
00923         }
00924 }
00925 
00926 bool DOS_GetFileAttr(char const * const name,Bit16u * attr) {
00927         char fullname[DOS_PATHLENGTH];Bit8u drive;
00928         if (!DOS_MakeName(name,fullname,&drive)) return false;
00929 #if defined (WIN32)
00930         if (!control->SecureMode()&&dos_clipboard_device_access) {
00931         const char* find_last;
00932                 find_last=strrchr(fullname,'\\');
00933                 if (find_last==NULL) find_last=fullname;
00934                 else find_last++;
00935                 if (!strcasecmp(find_last, *dos_clipboard_device_name?dos_clipboard_device_name:"CLIP$"))
00936                         return true;
00937         }
00938 #endif
00939         if (Drives[drive]->GetFileAttr(fullname,attr)) {
00940                 return true;
00941         } else {
00942                 DOS_SetError(DOSERR_FILE_NOT_FOUND);
00943                 return false;
00944         }
00945 }
00946 
00947 bool DOS_GetFileAttrEx(char const* const name, struct stat *status, Bit8u hdrive)
00948 {
00949         char fullname[DOS_PATHLENGTH];
00950         Bit8u drive;
00951         bool usehdrive=/*hdrive>=0&&(always true)*/hdrive<DOS_FILES;
00952         if (usehdrive)
00953                 strcpy(fullname,name);
00954         else if (!DOS_MakeName(name, fullname, &drive))
00955                 return false;
00956         return Drives[usehdrive?hdrive:drive]->GetFileAttrEx(fullname, status);
00957 }
00958 
00959 unsigned long DOS_GetCompressedFileSize(char const* const name)
00960 {
00961         char fullname[DOS_PATHLENGTH];
00962         Bit8u drive;
00963         if (!DOS_MakeName(name, fullname, &drive))
00964                 return false;
00965         return Drives[drive]->GetCompressedSize(fullname);
00966 }
00967 
00968 #if defined (WIN32)
00969 HANDLE DOS_CreateOpenFile(char const* const name)
00970 {
00971         char fullname[DOS_PATHLENGTH];
00972         Bit8u drive;
00973         if (!DOS_MakeName(name, fullname, &drive))
00974                 return INVALID_HANDLE_VALUE;
00975         return Drives[drive]->CreateOpenFile(fullname);
00976 }
00977 #endif
00978 
00979 bool DOS_SetFileAttr(char const * const name,Bit16u attr) 
00980 // returns false when using on cdrom (stonekeep)
00981 {
00982         char fullname[DOS_PATHLENGTH];Bit8u drive;
00983         if (!DOS_MakeName(name,fullname,&drive)) return false;  
00984         if (strncmp(Drives[drive]->GetInfo(),"CDRom ",6)==0 || strncmp(Drives[drive]->GetInfo(),"isoDrive ",9)==0) {
00985                 DOS_SetError(DOSERR_ACCESS_DENIED);
00986                 return false;
00987         }
00988 
00989         /* This function must prevent changing a file into a directory, volume label into a file, etc.
00990          * Also Windows 95 setup likes to create WINBOOT.INI as a file and then chattr it into a directory for some stupid reason. */
00991         Bit16u old_attr;
00992         if (!Drives[drive]->GetFileAttr(fullname,&old_attr)) {
00993                 DOS_SetError(DOSERR_FILE_NOT_FOUND);
00994                 return false;
00995         }
00996 
00997         if ((old_attr ^ attr) & DOS_ATTR_VOLUME) { /* change in volume label attribute */
00998                 LOG(LOG_DOSMISC,LOG_WARN)("Attempted to change volume label attribute of '%s' with SetFileAttr",name);
00999                 return false;
01000         }
01001 
01002         if ((old_attr ^ attr) & DOS_ATTR_DIRECTORY) /* change in directory attribute (ex: Windows 95 SETUP.EXE vs WINBOOT.INI) */
01003                 LOG(LOG_DOSMISC,LOG_WARN)("Attempted to change directory attribute of '%s' with SetFileAttr",name);
01004 
01005         /* define what cannot be changed */
01006         const Bit16u attr_mask = (DOS_ATTR_VOLUME|DOS_ATTR_DIRECTORY);
01007 
01008         attr = (attr & ~attr_mask) | (old_attr & attr_mask);
01009 
01010         return Drives[drive]->SetFileAttr(fullname,attr);
01011 }
01012 
01013 bool DOS_Canonicalize(char const * const name,char * const big) {
01014 //TODO Add Better support for devices and shit but will it be needed i doubt it :) 
01015         Bit8u drive;
01016         char fullname[DOS_PATHLENGTH];
01017         if (!DOS_MakeName(name,fullname,&drive)) return false;
01018         big[0]=drive+'A';
01019         big[1]=':';
01020         big[2]='\\';
01021         strcpy(&big[3],fullname);
01022         return true;
01023 }
01024 
01025 #ifdef _MSC_VER
01026 # define MIN(a,b) ((a) < (b) ? (a) : (b))
01027 # define MAX(a,b) ((a) > (b) ? (a) : (b))
01028 #else
01029 # define MIN(a,b) std::min(a,b)
01030 # define MAX(a,b) std::max(a,b)
01031 #endif
01032 
01033 /* Common routine to take larger allocation information (such as FAT32) and convert it to values
01034  * that are suitable for use with older DOS programs that pre-date FAT32 and partitions 2GB or larger. 
01035  * This is what Windows 95 OSR2 and higher do with FAT32 partitions anyway, as documented by Microsoft. */
01036 bool DOS_CommonFAT32FAT16DiskSpaceConv(
01037                 Bit16u * bytes,Bit8u * sectors,Bit16u * clusters,Bit16u * free,
01038                 const Bit32u bytes32,const Bit32u sectors32,const Bit32u clusters32,const Bit32u free32) {
01039         Bit32u cdiv = 1;
01040 
01041         if (sectors32 > 128 || bytes32 > 0x8000)
01042                 return false;
01043 
01044         /* This function is for the old API. It is necessary to adjust the values so that they never overflow
01045          * 16-bit unsigned integers and never multiply out to a number greater than just under 2GB. Because
01046          * old DOS programs use 32-bit signed integers for disk total/free and FAT12/FAT16 filesystem limitations. */
01047         /* NTS: Make sure (bytes per sector * sectors per cluster) is less than 0x10000, or else FORMAT.COM will
01048          * crash with divide by zero or produce incorrect results when run with "FORMAT /S" */
01049         while ((clusters32 > 0xFFFFu || free32 > 0xFFFFu) && (sectors32 * cdiv) <= 64u && (bytes32 * sectors32 * cdiv) < 0x8000/*Needed for FORMAT.COM*/)
01050                 cdiv *= 2u;
01051 
01052         /* The old API must never report more than just under 2GB for total and free */
01053         const Bit32u clust2gb = (Bit32u)0x7FFF8000ul / (Bit32u)bytes32 / (sectors32 * cdiv);
01054 
01055         *bytes = bytes32;
01056         *sectors = sectors32 * cdiv;
01057         *clusters = (Bit16u)MIN(MIN(clusters32 / cdiv,clust2gb),0xFFFFu);
01058         *free = (Bit16u)MIN(MIN(free32 / cdiv,clust2gb),0xFFFFu);
01059         return true;
01060 }
01061 
01062 bool DOS_GetFreeDiskSpace(Bit8u drive,Bit16u * bytes,Bit8u * sectors,Bit16u * clusters,Bit16u * free) {
01063         if (drive==0) drive=DOS_GetDefaultDrive();
01064         else drive--;
01065         if ((drive>=DOS_DRIVES) || (!Drives[drive])) {
01066                 DOS_SetError(DOSERR_INVALID_DRIVE);
01067                 return false;
01068         }
01069 
01070         {
01071                 Bit32u bytes32,sectors32,clusters32,free32;
01072                 if ((dos.version.major > 7 || (dos.version.major == 7 && dos.version.minor >= 10)) &&
01073                         Drives[drive]->AllocationInfo32(&bytes32,&sectors32,&clusters32,&free32) &&
01074                         DOS_CommonFAT32FAT16DiskSpaceConv(bytes,sectors,clusters,free,bytes32,sectors32,clusters32,free32))
01075                         return true;
01076         }
01077 
01078         if (Drives[drive]->AllocationInfo(bytes,sectors,clusters,free))
01079                 return true;
01080 
01081         return false;
01082 }
01083 
01084 bool DOS_GetFreeDiskSpace32(Bit8u drive,Bit32u * bytes,Bit32u * sectors,Bit32u * clusters,Bit32u * free) {
01085         if (drive==0) drive=DOS_GetDefaultDrive();
01086         else drive--;
01087         if ((drive>=DOS_DRIVES) || (!Drives[drive])) {
01088                 DOS_SetError(DOSERR_INVALID_DRIVE);
01089                 return false;
01090         }
01091 
01092         if ((dos.version.major > 7 || (dos.version.major == 7 && dos.version.minor >= 10)) && Drives[drive]->AllocationInfo32(bytes,sectors,clusters,free))
01093                 return true;
01094 
01095         {
01096                 Bit8u sectors8;
01097                 Bit16u bytes16,clusters16,free16;
01098                 if (Drives[drive]->AllocationInfo(&bytes16,&sectors8,&clusters16,&free16)) {
01099                         *free = free16;
01100                         *bytes = bytes16;
01101                         *sectors = sectors8;
01102                         *clusters = clusters16;
01103                         return true;
01104                 }
01105         }
01106 
01107         return false;
01108 }
01109 
01110 bool DOS_DuplicateEntry(Bit16u entry,Bit16u * newentry) {
01111         // Dont duplicate console handles
01112 /*      if (entry<=STDPRN) {
01113                 *newentry = entry;
01114                 return true;
01115         };
01116 */      
01117         Bit8u handle=RealHandle(entry);
01118         if (handle>=DOS_FILES) {
01119                 DOS_SetError(DOSERR_INVALID_HANDLE);
01120                 return false;
01121         }
01122         if (!Files[handle] || !Files[handle]->IsOpen()) {
01123                 DOS_SetError(DOSERR_INVALID_HANDLE);
01124                 return false;
01125         }
01126         DOS_PSP psp(dos.psp());
01127         *newentry = psp.FindFreeFileEntry();
01128         if (*newentry==0xff) {
01129                 DOS_SetError(DOSERR_TOO_MANY_OPEN_FILES);
01130                 return false;
01131         }
01132         Files[handle]->AddRef();        
01133         psp.SetFileHandle(*newentry,handle);
01134         return true;
01135 }
01136 
01137 bool DOS_ForceDuplicateEntry(Bit16u entry,Bit16u newentry) {
01138         if(entry == newentry) {
01139                 DOS_SetError(DOSERR_INVALID_HANDLE);
01140                 return false;
01141         }
01142         Bit8u orig = RealHandle(entry);
01143         if (orig >= DOS_FILES) {
01144                 DOS_SetError(DOSERR_INVALID_HANDLE);
01145                 return false;
01146         }
01147         if (!Files[orig] || !Files[orig]->IsOpen()) {
01148                 DOS_SetError(DOSERR_INVALID_HANDLE);
01149                 return false;
01150         }
01151         Bit8u newone = RealHandle(newentry);
01152         if (newone < DOS_FILES && Files[newone]) {
01153                 DOS_CloseFile(newentry);
01154         }
01155         DOS_PSP psp(dos.psp());
01156         Files[orig]->AddRef();
01157         psp.SetFileHandle(newentry,orig);
01158         return true;
01159 }
01160 
01161 
01162 bool DOS_CreateTempFile(char * const name,Bit16u * entry) {
01163         size_t namelen=strlen(name);
01164         char * tempname=name+namelen;
01165         if (namelen==0) {
01166                 // temp file created in root directory
01167                 tempname[0]='\\';
01168                 tempname++;
01169         } else {
01170                 if ((name[namelen-1]!='\\') && (name[namelen-1]!='/')) {
01171                         tempname[0]='\\';
01172                         tempname++;
01173                 }
01174         }
01175         dos.errorcode=0;
01176         /* add random crap to the end of the name and try to open */
01177         do {
01178                 Bit32u i;
01179                 for (i=0;i<8;i++) {
01180                         tempname[i]=(rand()%26)+'A';
01181                 }
01182                 tempname[8]=0;
01183         } while ((!DOS_CreateFile(name,0,entry)) && (dos.errorcode==DOSERR_FILE_ALREADY_EXISTS));
01184         if (dos.errorcode) return false;
01185         return true;
01186 }
01187 
01188 char DOS_ToUpper(char c) {
01189         unsigned char uc = *reinterpret_cast<unsigned char*>(&c);
01190         if (uc > 0x60 && uc < 0x7B) uc -= 0x20;
01191         else if (uc > 0x7F && uc < 0xA5) {
01192                 const unsigned char t[0x25] = { 
01193                         0x00, 0x9a, 0x45, 0x41, 0x8E, 0x41, 0x8F, 0x80, 0x45, 0x45, 0x45, 0x49, 0x49, 0x49, 0x00, 0x00,
01194                         0x00, 0x92, 0x00, 0x4F, 0x99, 0x4F, 0x55, 0x55, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
01195                 0x41, 0x49, 0x4F, 0x55, 0xA5};
01196                         if (t[uc - 0x80]) uc = t[uc-0x80];
01197         }
01198         char sc = *reinterpret_cast<char*>(&uc);
01199         return sc;
01200 }
01201 
01202 #define FCB_SEP ":;,=+"
01203 #define ILLEGAL ":.;,=+ \t/\"[]<>|"
01204 
01205 static bool isvalid(const char in){
01206         const char ill[]=ILLEGAL;    
01207         return (Bit8u(in)>0x1F) && (!strchr(ill,in));
01208 }
01209 
01210 #define PARSE_SEP_STOP          0x01
01211 #define PARSE_DFLT_DRIVE        0x02
01212 #define PARSE_BLNK_FNAME        0x04
01213 #define PARSE_BLNK_FEXT         0x08
01214 
01215 #define PARSE_RET_NOWILD        0
01216 #define PARSE_RET_WILD          1
01217 #define PARSE_RET_BADDRIVE      0xff
01218 
01219 Bit8u FCB_Parsename(Bit16u seg,Bit16u offset,Bit8u parser ,char *string, Bit8u *change) {
01220     const char* string_begin = string;
01221         Bit8u ret=0;
01222         if (!(parser & PARSE_DFLT_DRIVE)) {
01223                 // default drive forced, this intentionally invalidates an extended FCB
01224                 mem_writeb(PhysMake(seg,offset),0);
01225         }
01226         DOS_FCB fcb(seg,offset,false);  // always a non-extended FCB
01227         bool hasdrive,hasname,hasext,finished;
01228         hasdrive=hasname=hasext=finished=false;
01229         Bitu index=0;
01230         Bit8u fill=' ';
01231 /* First get the old data from the fcb */
01232 #ifdef _MSC_VER
01233 #pragma pack (1)
01234 #endif
01235         union {
01236                 struct {
01237                         char drive[2];
01238                         char name[9];
01239                         char ext[4];
01240                 } GCC_ATTRIBUTE (packed) part;
01241                 char full[DOS_FCBNAME];
01242         } fcb_name;
01243 #ifdef _MSC_VER
01244 #pragma pack()
01245 #endif
01246         /* Get the old information from the previous fcb */
01247         fcb.GetName(fcb_name.full);
01248         fcb_name.part.drive[0]-='A'-1;fcb_name.part.drive[1]=0;
01249         fcb_name.part.name[8]=0;fcb_name.part.ext[3]=0;
01250         /* strip leading spaces */
01251         while((*string==' ')||(*string=='\t')) string++;
01252 
01253         /* Strip of the leading separator */
01254         if((parser & PARSE_SEP_STOP) && *string) {
01255                 char sep[] = FCB_SEP;char a[2];
01256                 a[0] = *string;a[1] = '\0';
01257                 if (strcspn(a,sep) == 0) string++;
01258         } 
01259 
01260         /* Skip following spaces as well */
01261         while((*string==' ')||(*string=='\t')) string++;
01262 
01263         /* Check for a drive */
01264         if (string[1]==':') {
01265                 unsigned char d = *reinterpret_cast<unsigned char*>(&string[0]);
01266                 if (!isvalid(ascii_toupper(d))) {string += 2; goto savefcb;} //TODO check (for ret value)
01267                 fcb_name.part.drive[0]=0;
01268                 hasdrive=true;
01269                 if (isalpha(d) && Drives[ascii_toupper(d)-'A']) { //Floppies under dos always exist, but don't bother with that at this level
01270                         ; //THIS* was here
01271                 } else ret=0xff;
01272                 fcb_name.part.drive[0]=DOS_ToUpper(string[0])-'A'+1; //Always do THIS* and continue parsing, just return the right code
01273                 string+=2;
01274         }
01275 
01276         /* Check for extension only file names */
01277         if (string[0] == '.') {string++;goto checkext;}
01278 
01279         /* do nothing if not a valid name */
01280         if(!isvalid(string[0])) goto savefcb;
01281 
01282         hasname=true;finished=false;fill=' ';index=0;
01283         /* Copy the name */     
01284         while (true) {
01285                 unsigned char nc = *reinterpret_cast<unsigned char*>(&string[0]);
01286                 if (IS_PC98_ARCH && shiftjis_lead_byte(nc)) {
01287                 /* Shift-JIS is NOT ASCII and SHOULD NOT be converted to uppercase like ASCII */
01288                 fcb_name.part.name[index]=(char)nc;
01289                 string++;
01290                 index++;
01291                 if (index >= 8) break;
01292 
01293                 /* should be trailing byte of Shift-JIS */
01294                 if (nc < 32 || nc >= 127) continue;
01295 
01296                 fcb_name.part.name[index]=(char)nc;
01297             }
01298                 else
01299                 {
01300                         char ncs = (char)ascii_toupper(nc); //Should use DOS_ToUpper, but then more calls need to be changed.
01301                         if (ncs == '*') { //Handle *
01302                                 fill = '?';
01303                                 ncs = '?';
01304                         }
01305                         if (ncs == '?' && !ret && index < 8) ret = 1; //Don't override bad drive
01306                         if (!isvalid(ncs)) { //Fill up the name.
01307                                 while(index < 8) 
01308                                         fcb_name.part.name[index++] = (char)fill;
01309                                 break;
01310                         }
01311                         if (index < 8) { 
01312                                 fcb_name.part.name[index++] = (char)((fill == '?')?fill:ncs);
01313                         }
01314                 }
01315                 string++;
01316         }
01317         if (!(string[0]=='.')) goto savefcb;
01318         string++;
01319 checkext:
01320         /* Copy the extension */
01321         hasext=true;finished=false;fill=' ';index=0;
01322         while (true) {
01323                 unsigned char nc = *reinterpret_cast<unsigned char*>(&string[0]);
01324                 if (IS_PC98_ARCH && shiftjis_lead_byte(nc)) {
01325                 /* Shift-JIS is NOT ASCII and SHOULD NOT be converted to uppercase like ASCII */
01326                 fcb_name.part.ext[index]=(char)nc;
01327                 string++;
01328                 index++;
01329                 if (index >= 3) break;
01330 
01331                 /* should be trailing byte of Shift-JIS */
01332                 if (nc < 32u || nc >= 127u) continue;
01333 
01334                 fcb_name.part.ext[index]=(char)nc;
01335             }
01336                 else
01337                 {
01338                         char ncs = (char)ascii_toupper(nc);
01339                         if (ncs == '*') { //Handle *
01340                                 fill = '?';
01341                                 ncs = '?';
01342                         }
01343                         if (ncs == '?' && !ret && index < 3) ret = 1;
01344                         if (!isvalid(ncs)) { //Fill up the name.
01345                                 while(index < 3) 
01346                                         fcb_name.part.ext[index++] = (char)fill;
01347                                 break;
01348                         }
01349                         if (index < 3) { 
01350                                 fcb_name.part.ext[index++] = (char)((fill=='?')?fill:ncs);
01351                         }
01352                 }
01353                 string++;
01354         }
01355 savefcb:
01356         if (!hasdrive && !(parser & PARSE_DFLT_DRIVE)) fcb_name.part.drive[0] = 0;
01357         if (!hasname && !(parser & PARSE_BLNK_FNAME)) strcpy(fcb_name.part.name,"        ");
01358         if (!hasext && !(parser & PARSE_BLNK_FEXT)) strcpy(fcb_name.part.ext,"   ");
01359         fcb.SetName((unsigned char)fcb_name.part.drive[0],fcb_name.part.name,fcb_name.part.ext);
01360         fcb.ClearBlockRecsize(); //Undocumented bonus work.
01361         *change=(Bit8u)(string-string_begin);
01362         return ret;
01363 }
01364 
01365 static void DTAExtendNameVolumeLabel(const char* const name, char* const filename, char* const ext) {
01366     size_t i,s;
01367 
01368     i=0;
01369     s=0;
01370     while (i < 8 && name[s] != 0) filename[i++] = name[s++];
01371     while (i < 8) filename[i++] = ' ';
01372 
01373     i=0;
01374     while (i < 3 && name[s] != 0) ext[i++] = name[s++];
01375     while (i < 3) ext[i++] = ' ';
01376 }
01377 
01378 static void DTAExtendName(char * const name,char * const filename,char * const ext) {
01379         char * find=strchr(name,'.');
01380         if (find && find!=name) {
01381                 strcpy(ext,find+1);
01382                 *find=0;
01383         } else ext[0]=0;
01384         strcpy(filename,name);
01385         size_t i;
01386         for (i=strlen(name);i<8;i++) filename[i]=' ';
01387         filename[8]=0;
01388         for (i=strlen(ext);i<3;i++) ext[i]=' ';
01389         ext[3]=0;
01390 }
01391 
01392 static void SaveFindResult(DOS_FCB & find_fcb) {
01393         DOS_DTA find_dta(dos.tables.tempdta);
01394     char name[DOS_NAMELENGTH_ASCII],lname[LFN_NAMELENGTH];
01395     Bit32u size;Bit16u date;Bit16u time;Bit8u attr;Bit8u drive;
01396         char file_name[9];char ext[4];
01397         find_dta.GetResult(name,lname,size,date,time,attr);
01398         drive=find_fcb.GetDrive()+1;
01399         Bit8u find_attr = DOS_ATTR_ARCHIVE;
01400         find_fcb.GetAttr(find_attr); /* Gets search attributes if extended */
01401         /* Create a correct file and extention */
01402     if (attr & DOS_ATTR_VOLUME)
01403         DTAExtendNameVolumeLabel(name,file_name,ext);
01404     else
01405         DTAExtendName(name,file_name,ext);      
01406 
01407     DOS_FCB fcb(RealSeg(dos.dta()),RealOff(dos.dta()));//TODO
01408         fcb.Create(find_fcb.Extended());
01409         fcb.SetName(drive,file_name,ext);
01410         fcb.SetAttr(find_attr);      /* Only adds attribute if fcb is extended */
01411         fcb.SetResult(size,date,time,attr);
01412 }
01413 
01414 bool DOS_FCBCreate(Bit16u seg,Bit16u offset) { 
01415         DOS_FCB fcb(seg,offset);
01416         char shortname[DOS_FCBNAME];Bit16u handle;
01417         Bit8u attr = DOS_ATTR_ARCHIVE;
01418         fcb.GetAttr(attr);
01419         if (!attr) attr = DOS_ATTR_ARCHIVE; //Better safe than sorry 
01420 
01421     if (attr & DOS_ATTR_VOLUME) {
01422             fcb.GetVolumeName(shortname);
01423         return Drives[fcb.GetDrive()]->FileCreate(NULL,shortname,attr);
01424     }
01425 
01426         fcb.GetName(shortname);
01427         if (!DOS_CreateFile(shortname,attr,&handle,true)) return false;
01428         fcb.FileOpen((Bit8u)handle);
01429         return true;
01430 }
01431 
01432 bool DOS_FCBOpen(Bit16u seg,Bit16u offset) { 
01433         DOS_FCB fcb(seg,offset);
01434         char shortname[DOS_FCBNAME];Bit16u handle;
01435         fcb.GetName(shortname);
01436 
01437         /* Search for file if name has wildcards */
01438         if (strpbrk(shortname,"*?")) {
01439                 LOG(LOG_FCB,LOG_WARN)("Wildcards in filename");
01440                 if (!DOS_FCBFindFirst(seg,offset)) return false;
01441                 DOS_DTA find_dta(dos.tables.tempdta);
01442                 DOS_FCB find_fcb(RealSeg(dos.tables.tempdta),RealOff(dos.tables.tempdta));
01443                 char name[DOS_NAMELENGTH_ASCII],lname[LFN_NAMELENGTH],file_name[9],ext[4];
01444                 Bit32u size;Bit16u date,time;Bit8u attr;
01445                 find_dta.GetResult(name,lname,size,date,time,attr);
01446                 DTAExtendName(name,file_name,ext);
01447                 find_fcb.SetName(fcb.GetDrive()+1,file_name,ext);
01448                 find_fcb.GetName(shortname);
01449         }
01450 
01451         /* First check if the name is correct */
01452         Bit8u drive;
01453         char fullname[DOS_PATHLENGTH];
01454         if (!DOS_MakeName(shortname,fullname,&drive)) return false;
01455         
01456         /* Check, if file is already opened */
01457         for (Bit8u i = 0;i < DOS_FILES;i++) {
01458                 if (Files[i] && Files[i]->IsOpen() && Files[i]->IsName(fullname)) {
01459                         Files[i]->AddRef();
01460                         fcb.FileOpen(i);
01461                         return true;
01462                 }
01463         }
01464         
01465         if (!DOS_OpenFile(shortname,OPEN_READWRITE,&handle,true)) return false;
01466         fcb.FileOpen((Bit8u)handle);
01467         return true;
01468 }
01469 
01470 bool DOS_FCBClose(Bit16u seg,Bit16u offset) {
01471         DOS_FCB fcb(seg,offset);
01472         if(!fcb.Valid()) return false;
01473         Bit8u fhandle;
01474         fcb.FileClose(fhandle);
01475         DOS_CloseFile(fhandle,true);
01476         return true;
01477 }
01478 
01479 bool DOS_FCBFindFirst(Bit16u seg,Bit16u offset) {
01480         DOS_FCB fcb(seg,offset);
01481         RealPt old_dta=dos.dta();dos.dta(dos.tables.tempdta);
01482         char name[DOS_FCBNAME];fcb.GetName(name);
01483         Bit8u attr = DOS_ATTR_ARCHIVE;
01484         fcb.GetAttr(attr); /* Gets search attributes if extended */
01485         bool ret=DOS_FindFirst(name,attr,true);
01486         dos.dta(old_dta);
01487         if (ret) SaveFindResult(fcb);
01488         return ret;
01489 }
01490 
01491 bool DOS_FCBFindNext(Bit16u seg,Bit16u offset) {
01492         DOS_FCB fcb(seg,offset);
01493         RealPt old_dta=dos.dta();dos.dta(dos.tables.tempdta);
01494         bool ret=DOS_FindNext();
01495         dos.dta(old_dta);
01496         if (ret) SaveFindResult(fcb);
01497         return ret;
01498 }
01499 
01500 Bit8u DOS_FCBRead(Bit16u seg,Bit16u offset,Bit16u recno) {
01501         DOS_FCB fcb(seg,offset);
01502         Bit8u fhandle,cur_rec;Bit16u cur_block,rec_size;
01503         fcb.GetSeqData(fhandle,rec_size);
01504         if (fhandle==0xff && rec_size!=0) {
01505                 if (!DOS_FCBOpen(seg,offset)) return FCB_READ_NODATA;
01506                 LOG(LOG_FCB,LOG_WARN)("Reopened closed FCB");
01507                 fcb.GetSeqData(fhandle,rec_size);
01508         }
01509         if (rec_size == 0) {
01510                 rec_size = 128;
01511                 fcb.SetSeqData(fhandle,rec_size);
01512         }
01513         fcb.GetRecord(cur_block,cur_rec);
01514         Bit32u pos=((cur_block*128u)+cur_rec)*rec_size;
01515         if (!DOS_SeekFile(fhandle,&pos,DOS_SEEK_SET,true)) return FCB_READ_NODATA; 
01516         Bit16u toread=rec_size;
01517         if (!DOS_ReadFile(fhandle,dos_copybuf,&toread,true)) return FCB_READ_NODATA;
01518         if (toread==0) return FCB_READ_NODATA;
01519         if (toread < rec_size) { //Zero pad copybuffer to rec_size
01520                 Bitu i = toread;
01521                 while(i < rec_size) dos_copybuf[i++] = 0;
01522         }
01523         MEM_BlockWrite(Real2Phys(dos.dta())+(PhysPt)(recno*rec_size),dos_copybuf,rec_size);
01524         if (++cur_rec>127u) { cur_block++;cur_rec=0; }
01525         fcb.SetRecord(cur_block,cur_rec);
01526         if (toread==rec_size) return FCB_SUCCESS;
01527         return FCB_READ_PARTIAL;
01528 }
01529 
01530 Bit8u DOS_FCBWrite(Bit16u seg,Bit16u offset,Bit16u recno) {
01531         DOS_FCB fcb(seg,offset);
01532         Bit8u fhandle,cur_rec;Bit16u cur_block,rec_size;
01533         fcb.GetSeqData(fhandle,rec_size);
01534         if (fhandle==0xffu && rec_size!=0u) {
01535                 if (!DOS_FCBOpen(seg,offset)) return FCB_READ_NODATA;
01536                 LOG(LOG_FCB,LOG_WARN)("Reopened closed FCB");
01537                 fcb.GetSeqData(fhandle,rec_size);
01538         }
01539         if (rec_size == 0) {
01540                 rec_size = 128;
01541                 fcb.SetSeqData(fhandle,rec_size);
01542         }
01543         fcb.GetRecord(cur_block,cur_rec);
01544         Bit32u pos=((cur_block*128u)+cur_rec)*rec_size;
01545         if (!DOS_SeekFile(fhandle,&pos,DOS_SEEK_SET,true)) return FCB_ERR_WRITE; 
01546         MEM_BlockRead(Real2Phys(dos.dta())+(PhysPt)(recno*rec_size),dos_copybuf,rec_size);
01547         Bit16u towrite=rec_size;
01548         if (!DOS_WriteFile(fhandle,dos_copybuf,&towrite,true)) return FCB_ERR_WRITE;
01549         Bit32u size;Bit16u date,time;
01550         fcb.GetSizeDateTime(size,date,time);
01551         if (pos+towrite>size) size=pos+towrite;
01552         //time doesn't keep track of endofday
01553         date = DOS_PackDate(dos.date.year,dos.date.month,dos.date.day);
01554         Bit32u ticks = mem_readd(BIOS_TIMER);
01555         Bit32u seconds = (ticks*10u)/182u;
01556         Bit16u hour = (Bit16u)(seconds/3600u);
01557         Bit16u min = (Bit16u)((seconds % 3600u)/60u);
01558         Bit16u sec = (Bit16u)(seconds % 60u);
01559         time = DOS_PackTime(hour,min,sec);
01560         Files[fhandle]->time = time;
01561         Files[fhandle]->date = date;
01562         fcb.SetSizeDateTime(size,date,time);
01563         if (++cur_rec>127u) { cur_block++;cur_rec=0; }  
01564         fcb.SetRecord(cur_block,cur_rec);
01565         return FCB_SUCCESS;
01566 }
01567 
01568 Bit8u DOS_FCBIncreaseSize(Bit16u seg,Bit16u offset) {
01569         DOS_FCB fcb(seg,offset);
01570         Bit8u fhandle,cur_rec;Bit16u cur_block,rec_size;
01571         fcb.GetSeqData(fhandle,rec_size);
01572         fcb.GetRecord(cur_block,cur_rec);
01573         Bit32u pos=((cur_block*128u)+cur_rec)*rec_size;
01574         if (!DOS_SeekFile(fhandle,&pos,DOS_SEEK_SET,true)) return FCB_ERR_WRITE; 
01575         Bit16u towrite=0;
01576         if (!DOS_WriteFile(fhandle,dos_copybuf,&towrite,true)) return FCB_ERR_WRITE;
01577         Bit32u size;Bit16u date,time;
01578         fcb.GetSizeDateTime(size,date,time);
01579         if (pos+towrite>size) size=pos+towrite;
01580         //time doesn't keep track of endofday
01581         date = DOS_PackDate(dos.date.year,dos.date.month,dos.date.day);
01582         Bit32u ticks = mem_readd(BIOS_TIMER);
01583         Bit32u seconds = (ticks*10u)/182u;
01584         Bit16u hour = (Bit16u)(seconds/3600u);
01585         Bit16u min = (Bit16u)((seconds % 3600u)/60u);
01586         Bit16u sec = (Bit16u)(seconds % 60u);
01587         time = DOS_PackTime(hour,min,sec);
01588         Files[fhandle]->time = time;
01589         Files[fhandle]->date = date;
01590         fcb.SetSizeDateTime(size,date,time);
01591         fcb.SetRecord(cur_block,cur_rec);
01592         return FCB_SUCCESS;
01593 }
01594 
01595 Bit8u DOS_FCBRandomRead(Bit16u seg,Bit16u offset,Bit16u * numRec,bool restore) {
01596 /* if restore is true :random read else random blok read. 
01597  * random read updates old block and old record to reflect the random data
01598  * before the read!!!!!!!!! and the random data is not updated! (user must do this)
01599  * Random block read updates these fields to reflect the state after the read!
01600  */
01601         DOS_FCB fcb(seg,offset);
01602         Bit32u random;
01603         Bit16u old_block=0;
01604         Bit8u old_rec=0;
01605         Bit8u error=0;
01606         Bit16u count;
01607 
01608         /* Set the correct record from the random data */
01609         fcb.GetRandom(random);
01610         fcb.SetRecord((Bit16u)(random / 128u),(Bit8u)(random & 127u));
01611         if (restore) fcb.GetRecord(old_block,old_rec);//store this for after the read.
01612         // Read records
01613         for (count=0; count<*numRec; count++) {
01614                 error = DOS_FCBRead(seg,offset,count);
01615                 if (error!=FCB_SUCCESS) break;
01616         }
01617         if (error==FCB_READ_PARTIAL) count++;   //partial read counts
01618         *numRec=count;
01619         Bit16u new_block;Bit8u new_rec;
01620         fcb.GetRecord(new_block,new_rec);
01621         if (restore) fcb.SetRecord(old_block,old_rec);
01622         /* Update the random record pointer with new position only when restore is false*/
01623         if(!restore) fcb.SetRandom(new_block*128u+new_rec); 
01624         return error;
01625 }
01626 
01627 Bit8u DOS_FCBRandomWrite(Bit16u seg,Bit16u offset,Bit16u * numRec,bool restore) {
01628 /* see FCB_RandomRead */
01629         DOS_FCB fcb(seg,offset);
01630         Bit32u random;
01631         Bit16u old_block=0;
01632         Bit8u old_rec=0;
01633         Bit8u error=0;
01634 
01635         /* Set the correct record from the random data */
01636         fcb.GetRandom(random);
01637         fcb.SetRecord((Bit16u)(random / 128u),(Bit8u)(random & 127u));
01638         if (restore) fcb.GetRecord(old_block,old_rec);
01639         if (*numRec > 0) {
01640                 Bit16u count;
01641                 /* Write records */
01642                 for (count=0; count<*numRec; count++) {
01643                         error = DOS_FCBWrite(seg,offset,count);// dos_fcbwrite return 0 false when true...
01644                         if (error!=FCB_SUCCESS) break;
01645                 }
01646                 *numRec=count;
01647         } else {
01648                 DOS_FCBIncreaseSize(seg,offset);
01649         }
01650         Bit16u new_block;Bit8u new_rec;
01651         fcb.GetRecord(new_block,new_rec);
01652         if (restore) fcb.SetRecord(old_block,old_rec);
01653         /* Update the random record pointer with new position only when restore is false */
01654         if (!restore) fcb.SetRandom(new_block*128u+new_rec); 
01655         return error;
01656 }
01657 
01658 bool DOS_FCBGetFileSize(Bit16u seg,Bit16u offset) {
01659         char shortname[DOS_PATHLENGTH];Bit16u entry;
01660         DOS_FCB fcb(seg,offset);
01661         fcb.GetName(shortname);
01662         if (!DOS_OpenFile(shortname,OPEN_READ,&entry,true)) return false;
01663         Bit32u size = 0;
01664         Files[entry]->Seek(&size,DOS_SEEK_END);
01665         DOS_CloseFile(entry,true);
01666 
01667         Bit8u handle; Bit16u rec_size;
01668         fcb.GetSeqData(handle,rec_size);
01669         if (rec_size == 0) rec_size = 128; //Use default if missing.
01670 
01671         Bit32u random=(size/rec_size);
01672         if (size % rec_size) random++;
01673         fcb.SetRandom(random);
01674         return true;
01675 }
01676 
01677 bool DOS_FCBDeleteFile(Bit16u seg,Bit16u offset){
01678 /* Special case: ????????.??? and DOS_ATTR_VOLUME */
01679     {
01680         DOS_FCB fcb(seg,offset);
01681         Bit8u attr = 0;
01682         fcb.GetAttr(attr);
01683         Bit8u drive = fcb.GetDrive();
01684         std::string label = Drives[drive]->GetLabel();
01685 
01686         if (attr & DOS_ATTR_VOLUME) {
01687         char shortname[DOS_FCBNAME];
01688             fcb.GetVolumeName(shortname);
01689 
01690             if (!strcmp(shortname,"???????????")) {
01691                 if (!label.empty()) {
01692                     Drives[drive]->SetLabel("",false,true);
01693                     LOG(LOG_DOSMISC,LOG_NORMAL)("FCB delete volume label");
01694                     return true;
01695                 }
01696             }
01697             else {
01698                 /* MS-DOS 6.22 LABEL.EXE will explicitly request to delete the volume label by the volume label not ????????.???? */
01699                 if (!label.empty()) {
01700                     while (label.length() < 11) label += ' ';
01701                     if (!memcmp(label.c_str(),shortname,11)) {
01702                         Drives[drive]->SetLabel("",false,true);
01703                         LOG(LOG_DOSMISC,LOG_NORMAL)("FCB delete volume label deleted");
01704                         return true;
01705                     }
01706                 }
01707             }
01708 
01709             LOG(LOG_DOSMISC,LOG_NORMAL)("FCB delete volume label not found (current='%s' asking='%s')",label.c_str(),shortname);
01710             DOS_SetError(DOSERR_FILE_NOT_FOUND); // right?
01711             return false;
01712         }
01713     }
01714 
01715 /* FCB DELETE honours wildcards. it will return true if one or more
01716  * files get deleted. 
01717  * To get this: the dta is set to temporary dta in which found files are
01718  * stored. This can not be the tempdta as that one is used by fcbfindfirst
01719  */
01720         RealPt old_dta=dos.dta();dos.dta(dos.tables.tempdta_fcbdelete);
01721         RealPt new_dta=dos.dta();
01722         bool nextfile = false;
01723         bool return_value = false;
01724         nextfile = DOS_FCBFindFirst(seg,offset);
01725         DOS_FCB fcb(RealSeg(new_dta),RealOff(new_dta));
01726         while(nextfile) {
01727                 char shortname[DOS_FCBNAME] = { 0 };
01728                 fcb.GetName(shortname);
01729                 bool res=DOS_UnlinkFile(shortname);
01730                 if(!return_value && res) return_value = true; //at least one file deleted
01731                 nextfile = DOS_FCBFindNext(seg,offset);
01732         }
01733         dos.dta(old_dta);  /*Restore dta */
01734         return return_value;
01735 }
01736 
01737 char* trimString(char* str);
01738 
01739 bool DOS_FCBRenameFile(Bit16u seg, Bit16u offset){
01740         DOS_FCB fcbold(seg,offset);
01741     DOS_FCB fcbnew(seg,offset);
01742     fcbnew.SetPtPhys(fcbnew.GetPtPhys()+0x10u);//HACK: FCB NEW memory offset is affected by whether FCB OLD is extended
01743     if(!fcbold.Valid()) return false;
01744         char oldname[DOS_FCBNAME];
01745         char newname[DOS_FCBNAME];
01746         fcbold.GetName(oldname);fcbnew.GetName(newname);
01747 
01748     {
01749         Bit8u drive = fcbold.GetDrive();
01750         std::string label = Drives[drive]->GetLabel();
01751         Bit8u attr = 0;
01752 
01753         fcbold.GetAttr(attr);
01754         /* According to RBIL and confirmed with SETLABEL.ASM in DOSLIB2, you can rename a volume label dirent as well with this function */
01755         if (attr & DOS_ATTR_VOLUME) {
01756             fcbold.GetVolumeName(oldname);
01757             fcbnew.GetVolumeName(newname);
01758 
01759             for (unsigned int i=0;i < 11;i++)
01760                 oldname[i] = toupper(oldname[i]);
01761 
01762             trimString(oldname);
01763             trimString(newname);
01764 
01765             if (!label.empty()) {
01766                 if (!strcmp(oldname,"???????????") || label == oldname) {
01767                     Drives[drive]->SetLabel(newname,false,true);
01768                     LOG(LOG_DOSMISC,LOG_NORMAL)("FCB rename volume label to '%s' from '%s'",newname,oldname);
01769                     return true;
01770                 }
01771                 else {
01772                     LOG(LOG_DOSMISC,LOG_NORMAL)("FCB rename volume label rejected, does not match current label '%s' from '%s'",newname,oldname);
01773                     DOS_SetError(DOSERR_FILE_NOT_FOUND); // right?
01774                     return false;
01775                 }
01776             }
01777             else {
01778                 LOG(LOG_DOSMISC,LOG_NORMAL)("FCB rename volume label rejected, no label set");
01779                 DOS_SetError(DOSERR_FILE_NOT_FOUND); // right?
01780                 return false;
01781             }
01782         }
01783     }
01784 
01785         /* Check, if sourcefile is still open. This was possible in DOS, but modern oses don't like this */
01786         Bit8u drive; char fullname[DOS_PATHLENGTH];
01787         if (!DOS_MakeName(oldname,fullname,&drive)) return false;
01788         
01789         DOS_PSP psp(dos.psp());
01790         for (Bit8u i=0;i<DOS_FILES;i++) {
01791                 if (Files[i] && Files[i]->IsOpen() && Files[i]->IsName(fullname)) {
01792                         Bit16u handle = psp.FindEntryByHandle(i);
01793                         //(more than once maybe)
01794                         if (handle == 0xFFu) {
01795                                 DOS_CloseFile(i,true);
01796                         } else {
01797                                 DOS_CloseFile(handle);
01798                         }
01799                 }
01800         }
01801 
01802         /* Rename the file */
01803         return DOS_Rename(oldname,newname);
01804 }
01805 
01806 void DOS_FCBSetRandomRecord(Bit16u seg, Bit16u offset) {
01807         DOS_FCB fcb(seg,offset);
01808         Bit16u block;Bit8u rec;
01809         fcb.GetRecord(block,rec);
01810         fcb.SetRandom(block*128u+rec);
01811 }
01812 
01813 
01814 bool DOS_FileExists(char const * const name) {
01815         char fullname[DOS_PATHLENGTH];Bit8u drive;
01816         if (!DOS_MakeName(name,fullname,&drive)) return false;
01817         return Drives[drive]->FileExists(fullname);
01818 }
01819 
01820 bool DOS_GetAllocationInfo(Bit8u drive,Bit16u * _bytes_sector,Bit8u * _sectors_cluster,Bit16u * _total_clusters) {
01821         if (!drive) drive =  DOS_GetDefaultDrive();
01822         else drive--;
01823         if (drive >= DOS_DRIVES || !Drives[drive]) {
01824                 DOS_SetError(DOSERR_INVALID_DRIVE);
01825                 return false;
01826         }
01827         Bit16u _free_clusters;
01828         Drives[drive]->AllocationInfo(_bytes_sector,_sectors_cluster,_total_clusters,&_free_clusters);
01829         SegSet16(ds,RealSeg(dos.tables.mediaid));
01830         reg_bx=RealOff(dos.tables.mediaid+drive*dos.tables.dpb_size);
01831         return true;
01832 }
01833 
01834 bool DOS_SetDrive(Bit8u drive) {
01835         if (Drives[drive]) {
01836                 DOS_SetDefaultDrive(drive);
01837                 return true;
01838         } else {
01839                 return false;
01840         }
01841 }
01842 
01843 bool DOS_GetFileDate(Bit16u entry, Bit16u* otime, Bit16u* odate) {
01844         Bit32u handle=RealHandle(entry);
01845         if (handle>=DOS_FILES) {
01846                 DOS_SetError(DOSERR_INVALID_HANDLE);
01847                 return false;
01848         }
01849         if (!Files[handle] || !Files[handle]->IsOpen()) {
01850                 DOS_SetError(DOSERR_INVALID_HANDLE);
01851                 return false;
01852         }
01853         if (!Files[handle]->UpdateDateTimeFromHost()) {
01854                 DOS_SetError(DOSERR_INVALID_HANDLE);
01855                 return false; 
01856         }
01857         *otime = Files[handle]->time;
01858         *odate = Files[handle]->date;
01859         return true;
01860 }
01861 
01862 bool DOS_SetFileDate(Bit16u entry, Bit16u ntime, Bit16u ndate)
01863 {
01864         Bit32u handle=RealHandle(entry);
01865         if (handle>=DOS_FILES) {
01866                 DOS_SetError(DOSERR_INVALID_HANDLE);
01867                 return false;
01868         }
01869         if (!Files[handle]) {
01870                 DOS_SetError(DOSERR_INVALID_HANDLE);
01871                 return false;
01872         }
01873         Files[handle]->time = ntime;
01874         Files[handle]->date = ndate;
01875         Files[handle]->newtime = true;
01876 
01877         return true;
01878 }
01879 
01880 void DOS_SetupFiles (void) {
01881         /* Setup the File Handles */
01882         Files = new DOS_File * [DOS_FILES];
01883         Bit32u i;
01884         for (i=0;i<DOS_FILES;i++) {
01885                 Files[i]=0;
01886         }
01887         /* Setup the Virtual Disk System */
01888         for (i=0;i<DOS_DRIVES;i++) {
01889                 Drives[i]=0;
01890         }
01891         Drives[25]=new Virtual_Drive();
01892 }
01893 
01894 // save state support
01895 void DOS_File::SaveState( std::ostream& stream )
01896 {
01897         Bit32u file_namelen, seek_pos;
01898 
01899 
01900         file_namelen = strlen( name );
01901         seek_pos = GetSeekPos();
01902 
01903         //******************************************
01904         //******************************************
01905         //******************************************
01906 
01907         // - pure data
01908         WRITE_POD( &file_namelen, file_namelen );
01909         WRITE_POD_SIZE( name, file_namelen );
01910 
01911         WRITE_POD( &flags, flags );
01912         WRITE_POD( &open, open );
01913 
01914         WRITE_POD( &attr, attr );
01915         WRITE_POD( &time, time );
01916         WRITE_POD( &date, date );
01917         WRITE_POD( &refCtr, refCtr );
01918         WRITE_POD( &hdrive, hdrive );
01919 
01920         //******************************************
01921         //******************************************
01922         //******************************************
01923 
01924         // - reloc ptr
01925         WRITE_POD( &seek_pos, seek_pos );
01926 }
01927 
01928 
01929 void DOS_File::LoadState( std::istream& stream, bool pop )
01930 {
01931         Bit32u file_namelen, seek_pos;
01932         char *file_name;
01933 
01934         //******************************************
01935         //******************************************
01936         //******************************************
01937 
01938         // - pure data
01939         READ_POD( &file_namelen, file_namelen );
01940         file_name = (char*)alloca( file_namelen * sizeof(char) );
01941         READ_POD_SIZE( file_name, file_namelen );
01942 
01943         READ_POD( &flags, flags );
01944         READ_POD( &open, open );
01945 
01946         READ_POD( &attr, attr );
01947         READ_POD( &time, time );
01948         READ_POD( &date, date );
01949         READ_POD( &refCtr, refCtr );
01950         READ_POD( &hdrive, hdrive );
01951 
01952         //******************************************
01953         //******************************************
01954         //******************************************
01955 
01956         // - reloc ptr
01957         READ_POD( &seek_pos, seek_pos );
01958 
01959         if (pop)
01960                 return;
01961 
01962         if( open ) Seek( &seek_pos, DOS_SEEK_SET );
01963         else Close();
01964 }
01965 
01966 extern bool dos_kernel_disabled;
01967 
01968 void POD_Save_DOS_Files( std::ostream& stream )
01969 {
01970         if (!dos_kernel_disabled) {
01971                 // 1. Do drives first (directories -> files)
01972                 // 2. Then files next
01973 
01974                 for( int lcv=0; lcv<DOS_DRIVES; lcv++ ) {
01975                         Bit8u drive_valid;
01976 
01977                         drive_valid = 0;
01978                         if( Drives[lcv] == 0 ) drive_valid = 0xff;
01979 
01980                         //**********************************************
01981                         //**********************************************
01982                         //**********************************************
01983 
01984                         // - reloc ptr
01985                         WRITE_POD( &drive_valid, drive_valid );
01986 
01987                         if( drive_valid == 0xff ) continue;
01988                         Drives[lcv]->SaveState(stream);
01989                 }
01990 
01991                 for( unsigned int lcv=0; lcv<DOS_FILES; lcv++ ) {
01992                         Bit8u file_valid;
01993                         char *file_name;
01994                         Bit8u file_namelen, file_drive, file_flags;
01995 
01996                         file_valid = 0;
01997                         if( !Files[lcv] || Files[lcv]->GetName() == NULL ) file_valid = 0xff;
01998                         else {
01999                                 if( strcmp( Files[lcv]->GetName(), "NUL" ) == 0 ) file_valid = 0xfe;//earth 2140 needs this
02000                                 if( strcmp( Files[lcv]->GetName(), "CON" ) == 0 ) file_valid = 0xfe;
02001                                 if( strcmp( Files[lcv]->GetName(), "LPT1" ) == 0 ) file_valid = 0xfe;
02002                                 if( strcmp( Files[lcv]->GetName(), "PRN" ) == 0 ) file_valid = 0xfe;
02003                                 if( strcmp( Files[lcv]->GetName(), "AUX" ) == 0 ) file_valid = 0xfe;
02004                                 if( strcmp( Files[lcv]->GetName(), "EMMXXXX0" ) == 0 ) file_valid = 0xfe;//raiden needs this
02005                         }
02006 
02007                         // - reloc ptr
02008                         WRITE_POD( &file_valid, file_valid );
02009                         // system files
02010                         if( file_valid == 0xff ) continue;
02011                         if( file_valid == 0xfe ) {
02012                                 WRITE_POD( &Files[lcv]->refCtr, Files[lcv]->refCtr );
02013                                 continue;
02014                         }
02015 
02016                         //**********************************************
02017                         //**********************************************
02018                         //**********************************************
02019 
02020                         file_namelen = strlen( Files[lcv]->name );
02021                         file_name = (char *) alloca( file_namelen );
02022                         strcpy( file_name, Files[lcv]->name );
02023 
02024                         file_drive = Files[lcv]->GetDrive();
02025                         file_flags = Files[lcv]->flags;
02026 
02027                         // - Drives->FileOpen vars (repeat copy)
02028                         WRITE_POD( &file_namelen, file_namelen );
02029                         WRITE_POD_SIZE( file_name, file_namelen );
02030 
02031                         WRITE_POD( &file_drive, file_drive );
02032                         WRITE_POD( &file_flags, file_flags );
02033 
02034 
02035                         Files[lcv]->SaveState(stream);
02036                 }
02037         }
02038 }
02039 
02040 
02041 void POD_Load_DOS_Files( std::istream& stream )
02042 {
02043         if (!dos_kernel_disabled) {
02044                 // 1. Do drives first (directories -> files)
02045                 // 2. Then files next
02046 
02047                 for( int lcv=0; lcv<DOS_DRIVES; lcv++ ) {
02048                         Bit8u drive_valid;
02049 
02050                         // - reloc ptr
02051                         READ_POD( &drive_valid, drive_valid );
02052                         if( drive_valid == 0xff ) continue;
02053 
02054                         if( Drives[lcv] ) Drives[lcv]->LoadState(stream);
02055                 }
02056 
02057                 //Alien Carnage - game creates and unlinks temp files
02058                 //Here are two situations
02059                 //1. Game already unlinked temp file, but information about file is still in Files[] and we saved it. In this case we must only pop old data from stream by loading. This is fixed.
02060                 //2. Game still did not unlink file, We saved this information. Then was game restarted and temp files were removed. Then we try load save state, but we don't have temp file. This is not fixed
02061                 DOS_File *dummy = NULL;
02062 
02063                 for( unsigned int lcv=0; lcv<DOS_FILES; lcv++ ) {
02064                         Bit8u file_valid;
02065                         char *file_name;
02066                         Bit8u file_namelen, file_drive, file_flags;
02067 
02068                         // - reloc ptr
02069                         READ_POD( &file_valid, file_valid );
02070 
02071                         // ignore system files
02072                         if( file_valid == 0xfe ) {
02073                                 READ_POD( &Files[lcv]->refCtr, Files[lcv]->refCtr );
02074                                 continue;
02075                         }
02076 
02077                         // shutdown old file
02078                         if( Files[lcv] && Files[lcv]->GetName() != NULL ) {
02079                                 // invalid file state - abort
02080                                 if( strcmp( Files[lcv]->GetName(), "NUL" ) == 0 ) break;
02081                                 if( strcmp( Files[lcv]->GetName(), "CON" ) == 0 ) break;
02082                                 if( strcmp( Files[lcv]->GetName(), "LPT1" ) == 0 ) break;
02083                                 if( strcmp( Files[lcv]->GetName(), "PRN" ) == 0 ) break;
02084                                 if( strcmp( Files[lcv]->GetName(), "AUX" ) == 0 ) break;
02085                                 if( strcmp( Files[lcv]->GetName(), "EMMXXXX0" ) == 0 ) break;//raiden needs this
02086 
02087 
02088                                 if( Files[lcv]->IsOpen() ) Files[lcv]->Close();
02089                                 if (Files[lcv]->RemoveRef()<=0) {
02090                                         delete Files[lcv];
02091                                 }
02092                                 Files[lcv]=0;
02093                         }
02094 
02095                         // ignore NULL file
02096                         if( file_valid == 0xff ) continue;
02097 
02098                         //**********************************************
02099                         //**********************************************
02100                         //**********************************************
02101 
02102                         // - Drives->FileOpen vars (repeat copy)
02103 
02104                         READ_POD( &file_namelen, file_namelen );
02105                         file_name = (char *) alloca( file_namelen );
02106                         READ_POD_SIZE( file_name, file_namelen );
02107 
02108                         READ_POD( &file_drive, file_drive );
02109                         READ_POD( &file_flags, file_flags );
02110 
02111 
02112                         // NOTE: Must open regardless to get 'new' DOS_File class
02113                         Drives[file_drive]->FileOpen( &Files[lcv], file_name, file_flags );
02114 
02115                         if( Files[lcv] ) {
02116                                 Files[lcv]->LoadState(stream, false);
02117                         } else {
02118                                 //Alien carnage ->pop data for invalid file from stream
02119                                 if (dummy == NULL) {
02120                                         dummy = new localFile();
02121                                 }
02122                                 dummy->LoadState(stream, true);
02123                         };
02124                 }
02125 
02126                 if (dummy != NULL) {
02127                         delete dummy;
02128                 }
02129         }
02130 }