home *** CD-ROM | disk | FTP | other *** search
/ Power Programming / powerprogramming1994.iso / progtool / turbopas / sfmsrc.arc / SFMDOS.INC < prev    next >
Text File  |  1987-11-15  |  46KB  |  1,574 lines

  1. {                         Super File Manager
  2.  
  3.                               SFMDOS.INC
  4.  
  5.                           by David Steiner
  6.                              2035 J Apt. 6
  7.                              Lincoln, NE
  8.  
  9.  
  10.  
  11.    Procedures put in this include file are mostly lower level DOS
  12.      calls and the like.  Very few of them perform any actual input or
  13.      output, the major exception being the CopyEntries procedure.
  14.  
  15.      Most of the very low level routines are functions of type integer.
  16.      These functions will return the error code specified by the Int24Result
  17.      function found in sfmOTHER.inc or an error code that is specific to
  18.      the DOS function used.  These error codes are standard for DOS except
  19.      they have had their high bit set so the ErrorMessage procedure will
  20.      know which error message to print.
  21.      If this code is not 0 (no error) it may then be passed on the the
  22.      ErrorMessage routine to let the user know what happened.
  23.  
  24.    In the interest of consistency, procedures I have written accept drive
  25.      numbers according to A=1, B=2, etc.  DOS, however, is not always so
  26.      helpful and within my procedures the drive specifier passed must often
  27.      be altered by one.  Please keep this in mind when making changes.
  28.      I rather unfortunately wiped out my hard disk's FAT once when I was
  29.      making some relatively minor changes to the directory update functions.
  30.  
  31. }
  32.  
  33. procedure LoadSectors( drv, start, sectors : integer; DTA : Addr_T );
  34.   {
  35.   DOS interrupt $25 performs an absolute disk read.  We are forced
  36.     to use inline code because DOS leaves a copy of the flags register
  37.     on the stack after it returns control.  Because of this 'garbage'
  38.     left on the stack the Turbo procedure Intr will bomb when it attempts
  39.     to return control.
  40.   }
  41. begin
  42.   drv := drv - 1;
  43.   Inline(
  44.     $06                    {        PUSH    ES ; DOS interrupt $25 will     }
  45.     /$1E                   {        PUSH    DS ;   scramble all registers   }
  46.     /$56                   {        PUSH    SI ;   so we'd best save all    }
  47.     /$55                   {        PUSH    BP                              }
  48.     /$52                   {        PUSH    DX                              }
  49.     /$51                   {        PUSH    CX                              }
  50.     /$53                   {        PUSH    BX                              }
  51.     /$50                   {        PUSH    AX                              }
  52.                            {        ;                                       }
  53.     /$8B/$96/>START        {        MOV     DX,>start[BP]                   }
  54.     /$8B/$8E/>SECTORS      {        MOV     CX,>sectors[BP]                 }
  55.     /$C5/$9E/>DTA          {        LDS     BX,>dta[BP]                     }
  56.     /$8A/$86/>DRV          {        MOV     AL,>drv[BP]                     }
  57.     /$CD/$25               {        INT     $25 ; DOS - Absolute Disk Read  }
  58.     /$58                   {        POP     AX  ; Pop copy of flags left    }
  59.                            {        ;               on stack by INT $25     }
  60.     /$58                   {        POP     AX                              }
  61.     /$5B                   {        POP     BX                              }
  62.     /$59                   {        POP     CX                              }
  63.     /$5A                   {        POP     DX                              }
  64.     /$5D                   {        POP     BP                              }
  65.     /$5E                   {        POP     SI                              }
  66.     /$1F                   {        POP     DS                              }
  67.     /$07                   {        POP     ES                              }
  68.   );
  69. end;
  70.  
  71. procedure WriteSectors( drv, start, sectors : integer; DTA : Addr_T );
  72.   {
  73.   Again we must use inline code for DOS interrupt $26 for the same
  74.     reasons as above.
  75.   }
  76. begin
  77.   drv := drv - 1;
  78.   Inline(
  79.     $06                    {        PUSH    ES ; Be careful,Int $26 destroys }
  80.     /$1E                   {        PUSH    DS ;   the contents of all regs. }
  81.     /$56                   {        PUSH    SI                               }
  82.     /$55                   {        PUSH    BP                               }
  83.     /$52                   {        PUSH    DX                               }
  84.     /$51                   {        PUSH    CX                               }
  85.     /$53                   {        PUSH    BX                               }
  86.     /$50                   {        PUSH    AX                               }
  87.                            {        ;                                        }
  88.     /$8B/$96/>START        {        MOV     DX,[BP+>START]                   }
  89.     /$8B/$8E/>SECTORS      {        MOV     CX,[BP+>SECTORS]                 }
  90.     /$C5/$9E/>DTA          {        LDS     BX,[BP+>DTA]                     }
  91.     /$8A/$86/>DRV          {        MOV     AL,[BP+>DRV]                     }
  92.     /$CD/$26               {        INT     $26  ; DOS - Absolute Disk Write }
  93.     /$58                   {        POP     AX   ; Pop copy of flags left    }
  94.                            {        ;                on stack by int $26     }
  95.     /$58                   {        POP     AX                               }
  96.     /$5B                   {        POP     BX                               }
  97.     /$59                   {        POP     CX                               }
  98.     /$5A                   {        POP     DX                               }
  99.     /$5D                   {        POP     BP                               }
  100.     /$5E                   {        POP     SI                               }
  101.     /$1F                   {        POP     DS                               }
  102.     /$07                   {        POP     ES                               }
  103.   );
  104. end;
  105.  
  106. function RealToInt( r : real ) : integer;
  107. var
  108.   i : integer;
  109. begin
  110.   if r  > 32768.0 then r := r - 65536.0;
  111.   if r <> 32768.0 then i := trunc( r )
  112.   else                 i := $8000;
  113.   RealToInt := i;
  114. end;
  115.  
  116. procedure SetDTA( DTA : Addr_T );
  117.   {
  118.   When using the older DOS function requests we must first
  119.     specify the Disk Transfer Address.
  120.   }
  121. var
  122.   Regs : reg_T;
  123. begin
  124.   with Regs do
  125.   begin
  126.     AH := $1A;               { DOS function $1A - Set Disk Transfer Address }
  127.     DS := seg( DTA^ );
  128.     DX := ofs( DTA^ );
  129.     MsDos( Regs );
  130.   end;
  131. end;
  132.  
  133. procedure GetTable( drv : integer; var DiskTable : DskTblptr );
  134.   {
  135.   This DOS function returns the address of a very useful table of
  136.     information.  In many cases this is the only place I know of
  137.     to get the information reliably.  See the type declaration
  138.     DiskTable_T in the sfmVARS.inc file.
  139.   }
  140. var
  141.   Regs : reg_T;
  142. begin
  143.   with Regs do
  144.   begin
  145.     AH := $32;    { DOS function $32 - Get Address of Device Parameter Table }
  146.     DL := drv;
  147.     MsDos( Regs );
  148.     DiskTable := ptr( DS,BX );
  149.   end;
  150. end;
  151.  
  152. procedure LoadFAT( DiskTable : DskTblptr; var FAT : Addr_T );
  153.   {
  154.   Using the information in the DiskTable we can now load
  155.     in the File Allocation Table for use in the advanced functions,
  156.     or for loading a subdirectory.
  157.   }
  158. var
  159.   amt, sect : integer;
  160. begin
  161.   release( HeapStart );
  162.   with DiskTable^ do
  163.   begin
  164.     amt := FATSIZE * SECTORSIZE;
  165.     if MemoryAvail < amt then
  166.       AbortProgram( 'LoadFAT :',
  167.                     '',
  168.                     '   Insufficient memory to load FAT.',
  169.                     ''
  170.                   );
  171.     sect := ROOTSECTOR - FATSIZE * NFATS;
  172.     getmem( FAT, amt );
  173.     LoadSectors( DRIVE1+1, sect, FATSIZE, FAT );
  174.   end;
  175. end;
  176.  
  177. procedure FlushBuffers;
  178.   {
  179.   Make a DOS call to flush all info in the diskette
  180.     buffers so the disks are updated correctly.
  181.     This is done mostly to make sure the FAT and
  182.     directory sectors are written back to disk after
  183.     alterations are made and also to ensure that they
  184.     are then forced to be reloaded from disk later.
  185.   }
  186. var
  187.   Regs         : reg_T;
  188. begin
  189.   Regs.AH := $0D;                   { DOS function $0D - Reset the Disk }
  190.   MsDos( Regs );
  191. end;
  192.  
  193. procedure SaveFAT( DiskTable : DskTblptr; FAT : Addr_T );
  194.   {
  195.   Writes the FAT back to disk after changes have been made.
  196.     Only done when clearing a disk or specifically told to
  197.     by the Update disk menu option.
  198.   }
  199. var
  200.   i, sect : integer;
  201. begin
  202.   with DiskTable^ do
  203.   begin
  204.     for i := NFATS downto 1 do
  205.     begin
  206.       sect := ROOTSECTOR - FATSIZE * i;
  207.       WriteSectors( DRIVE1+1, sect, FATSIZE, FAT );
  208.     end;
  209.   end;
  210.   FlushBuffers;
  211. end;
  212.  
  213. function FATentry( Esize : real; clust : integer; FAT : Addr_T ) : integer;
  214.   {
  215.   Returns the entry in the FAT for the cluster specified.
  216.     This can be a little tricky since DOS saves space by
  217.     using only one and a half bytes for each entry whenever
  218.     a disk has fewer than 4098 clusters.
  219.     In order to make it easier for other parts of the program
  220.     we will convert any 1.5 byte entries that correspond to
  221.     special values to a 2 byte format.
  222.     (i.e. $FF0 through $FFF become $FFF0 through $FFFF )
  223.   }
  224. var
  225.   offset, contents : integer;
  226.   address          : Addr_T;
  227. begin
  228.   offset  := RealToInt( Esize * clust );
  229.   address := ptr( seg(FAT^), ofs(FAT^) + offset );
  230.   contents := address^;
  231.   if Esize = 1.5 then
  232.   begin
  233.     if clust mod 2 = 0 then
  234.       contents := contents AND $0FFF
  235.     else
  236.       contents := contents SHR 4;
  237.     if (contents >= $FF0) and (contents <= $FFF) then
  238.       contents := contents OR $F000;
  239.   end;
  240.   FATentry := contents;
  241. end;
  242.  
  243. procedure WriteFATentry( Esize:real; clust, newvalue:integer; FAT:Addr_T );
  244.   {
  245.   Writes the new value to the cluster entry specified, taking
  246.     into account the entry size for the FAT.
  247.   }
  248. var
  249.   offset  : integer;
  250.   address : Addr_T;
  251. begin
  252.   offset  := RealToInt( Esize * clust );
  253.   address := ptr( seg(FAT^), ofs(FAT^) + offset );
  254.   if Esize = 2 then
  255.     address^ := newvalue
  256.   else
  257.   begin
  258.     if clust mod 2 = 0 then
  259.       address^ := (address^ AND $F000) OR (newvalue AND $0FFF)
  260.     else
  261.       address^ := (address^ AND $000F) OR (newvalue SHL 4);
  262.   end;
  263. end;
  264.  
  265. function ClustersInChain( w, start : integer; FAT : Addr_T ) : integer;
  266.   {
  267.   Given the starting cluster we can then follow the chain untill
  268.     it terminates.  Having done this we can return the number of
  269.     clusters we found.  This is used mostly for determining how
  270.     many clusters need to be loaded for a specific subdirectory.
  271.   }
  272. var
  273.   Ncl, cl : integer;
  274. begin
  275.   Ncl := 0;
  276.   cl  := start;
  277.   repeat
  278.     Ncl := Ncl + 1;
  279.     cl  := FATentry( FATbytes[w], cl, FAT );
  280.   until (cl = $0000) or ( (cl >= $FFF0) and (cl <= $FFFF) );
  281.   if (cl >= $FFF0) and (cl <= $FFF7) then
  282.     AbortProgram( 'ClustersInChain:',
  283.                   '',
  284.                   '  Invalid cluster number in chain,',
  285.                   '  File Allocation Table may be damaged.' );
  286.   ClustersInChain := Ncl;
  287. end;
  288.  
  289. function GetCurDrive : integer;
  290.   {
  291.   Simply returns the current drive number (1 = A, 2 = B, etc.).
  292.   }
  293. var
  294.   Regs : reg_T;
  295. begin
  296.   with Regs do
  297.   begin
  298.     AH := $19;                   { DOS function $19 - Look Up Current Disk }
  299.     MsDos( Regs );
  300.     GetCurDrive := AL + 1;
  301.   end;
  302. end;
  303.  
  304. function GetCurDir( drv : integer; var path : str80 ) : integer;
  305.   {
  306.   Returns the current path name on the drive specified and
  307.     performs the trapping described at the top of this file.
  308.   }
  309. var
  310.   tstr : str80;
  311.   i    : integer;
  312. begin
  313.   {$I-}
  314.   GetDir( drv, tstr );
  315.   {$I+}
  316.   i := Int24result;
  317.   if i = 0 then path := tstr;
  318.   GetCurDir := i;
  319. end;
  320.  
  321. function ChangeCurDir( var path : str80 ) : integer;
  322.  {
  323.  Changes the current directory to that specified and also
  324.    changes the string input to the standard format used by DOS.
  325.  }
  326. var
  327.   i : integer;
  328. begin
  329.   {$I-}
  330.   chdir( path );
  331.   {$I+}
  332.   i := Int24result;
  333.   if i = 0 then
  334.     i := GetCurDir( GetCurDrive, path );
  335.   ChangeCurDir := i;
  336. end;
  337.  
  338. function StartClust( w : integer ) : integer;
  339.   {
  340.   Returns the number of the first cluster of the directory
  341.     specified by Path[w].  This is done by using the old DOS
  342.     functions to find the '.' directory entry.
  343.     Since this is an old DOS function call we must first set
  344.     up a File Control Block to perform the disk access.
  345.     Idea sparked by an article written by Ted Mirecki, contributing
  346.     editor for PC Tech Journal.
  347.   }
  348. var
  349.   FCBin  : ExtFCB_T;
  350.   Regs   : reg_T;
  351.   FCBout : Entry_T;
  352.   header : array[1..8] of byte;
  353.   err    : integer;
  354. begin
  355.   err := ChangeCurDir( Path[w] );
  356.   fillchar( FCBin.Name[1], 11, ' ' );
  357.   FCBin.Drive    := Drive[w];
  358.   FCBin.ExtFlag  := $FF;      { Tells DOS this is an extended FCB }
  359.   FCBin.Name[1]  := '.';
  360.   FCBin.FileAttr := Dbit;     { Looking for directory entry }
  361.   SetDTA( addr( header ) );
  362.   with Regs do
  363.   begin
  364.     AH := $11;              { DOS function $11 - Find First Matching File }
  365.     DS := seg( FCBin );
  366.     DX := ofs( FCBin );
  367.     MsDos( Regs );
  368.   end;
  369.   StartClust := FCBout.Cluster;
  370. end;
  371.  
  372. procedure LoadSubDir( w : integer );
  373.   {
  374.   Performs the necessary setup for loading a subdirectory from
  375.     the disk.  Once we know where it starts and how long it is
  376.     we can load the directory very quickly with a couple of
  377.     calls to LoadSectors.
  378.   }
  379. var
  380.   i, j, Ncl, sect, clust : integer;
  381.   FAT : Addr_T;
  382. begin
  383.   NoSave[w] := false;
  384.   clust := StartClust( w );
  385.   LoadFAT( DiskTable[w], FAT );
  386.   Ncl := ClustersInChain( w, clust, FAT );
  387.   with DiskTable[w]^ do
  388.     MaxEntry[w] := Ncl * (CLUSTERSIZE+1) * (SECTORSIZE div sizeof(Entry_T));
  389.   if MaxEntry[w] > MaxFiles then
  390.   begin
  391.     MaxEntry[w] := MaxFiles;
  392.     NoSave[w] := true;
  393.     MaxFileMessage;
  394.     with DiskTable[w]^ do
  395.       Ncl := (MaxEntry[w]*sizeof(Entry_T)) div ((CLUSTERSIZE+1)*SECTORSIZE);
  396.   end;
  397.   for i := 1 to Ncl do
  398.   begin
  399.     with DiskTable[w]^ do
  400.     begin
  401.       j := ( (i-1) * (SECTORSIZE div sizeof(Entry_T)) * (CLUSTERSIZE+1) ) + 1;
  402.       sect := ( (clust-2) * (CLUSTERSIZE+1) ) + DATASECTOR;
  403.       LoadSectors( Drive[w], sect, CLUSTERSIZE+1, addr(Entry[w][j] ) );
  404.     end;
  405.     if i <> Ncl then clust := FATentry( FATbytes[w], clust, FAT );
  406.   end;
  407. end;
  408.  
  409. procedure SaveSubDir( w : integer );
  410.   {
  411.   Performs the inverse of LoadSubDir.
  412.   }
  413. var
  414.   i, j, Ncl, sect, clust : integer;
  415.   FAT : Addr_T;
  416. begin
  417.   clust := StartClust( w );
  418.   LoadFAT( DiskTable[w], FAT );
  419.   Ncl := ClustersInChain( w, clust, FAT );
  420.   for i := 1 to Ncl do
  421.   begin
  422.     with DiskTable[w]^ do
  423.     begin
  424.       j := ( (i-1) * (SECTORSIZE div sizeof(Entry_T)) * (CLUSTERSIZE+1) ) + 1;
  425.       sect := ( (clust-2) * (CLUSTERSIZE+1) ) + DATASECTOR;
  426.       WriteSectors( Drive[w], sect, CLUSTERSIZE+1, addr( Entry[w][j] ) );
  427.     end;
  428.     if i <> Ncl then clust := FATentry( FATbytes[w], clust, FAT );
  429.   end;
  430.   FlushBuffers;
  431. end;
  432.  
  433. procedure LoadRoot( w : integer );
  434.   {
  435.   If it happens to be the root directory we can load even faster.
  436.     We already know where to start and how long it is and better
  437.     yet all the clusters are together.  We can load the entire
  438.     directory in one call to LoadSectors.
  439.   }
  440. var
  441.   nsects : integer;
  442. begin
  443.   with DiskTable[w]^ do
  444.   begin
  445.     if ROOTENTRIES <= MaxFiles then
  446.     begin
  447.       nsects      := DATASECTOR - ROOTSECTOR;
  448.       MaxEntry[w] := ROOTENTRIES;
  449.       NoSave[w]   := false;
  450.     end
  451.     else
  452.     begin
  453.       nsects := (MaxFiles * sizeof(Entry_T)) div SECTORSIZE;
  454.       MaxEntry[w] := MaxFiles;
  455.       NoSave[w] := true;
  456.       MaxFileMessage;
  457.     end;
  458.     LoadSectors( Drive[w], ROOTSECTOR, nsects, addr( Entry[w] ) );
  459.   end;
  460. end;
  461.  
  462. procedure SaveRoot( w : integer );
  463.   {
  464.   This procedure isn't as bad as you might have thought.
  465.   }
  466. var
  467.   nsects : integer;
  468. begin
  469.   with DiskTable[w]^ do
  470.   begin
  471.     nsects := DATASECTOR - ROOTSECTOR;
  472.     WriteSectors( Drive[w], ROOTSECTOR, nsects, addr( Entry[w] ) );
  473.   end;
  474.   FlushBuffers;
  475. end;
  476.  
  477. procedure LoadDir( w : integer );
  478.   {
  479.   Determines which of the above load routines need to be called
  480.     and updates the screen.
  481.     It also checks to see that the drive is not a substituted or
  482.     assigned drive since these are more trouble to support than
  483.     they are worth and can be accessed normally anyway.
  484.   }
  485. begin
  486.   GetTable( Drive[w], DiskTable[w] );
  487.   if Drive[w] <> DiskTable[w]^.DRIVE1 + 1 then
  488.   begin
  489.     Wind( 3 );
  490.     clrscr;
  491.     writeln;
  492.     Disp( NATTR, ' Error: ' );
  493.     Disp( HATTR, 'Assigned or substituted drives are not supported.' );
  494.     writeln;
  495.     Disp( HATTR, '        Directory was not loaded' );
  496.     writeln;
  497.     gotoxy( 9, wherey );
  498.     wait;
  499.   end
  500.   else
  501.   begin
  502.     if DiskTable[w]^.MAXCLUSTER <= 4097 then
  503.       FATbytes[w] := 1.5
  504.     else
  505.       FATbytes[w] := 2.0;
  506.     if ord( Path[w][0] ) = 3 then
  507.       LoadRoot( w )
  508.     else
  509.       LoadSubDir( w );
  510.     fillchar( Marked[w], sizeof( MarkedArr_T ), 0 );
  511.     Loaded[w] := true;
  512.     DirSize[w] := TallySizes( w );
  513.     while (Entry[w][MaxEntry[w]].Name[1] = NulChar) and (MaxEntry[w] <> 0) do
  514.       MaxEntry[w] := MaxEntry[w] - 1;
  515.     HomeKey( w );
  516.     Saved[w] := true;
  517.   end;
  518. end;
  519.  
  520. function FreeDisk( drv : integer ) : real;
  521.   {
  522.   Reads the amount of disk space on the drive.
  523.   }
  524. var
  525.   Regs : reg_T;
  526. begin
  527.   Wind( 3 );
  528.   clrscr;
  529.   writeln;
  530.   Disp( NATTR, ' Reading disk free space...' );
  531.   writeln;
  532.   with Regs do
  533.   begin
  534.     AH := $36;                     { DOS function $36 - Get Disk Free Space }
  535.     DL := drv;
  536.     {$I-}
  537.     MsDos( Regs );
  538.     {$I+}
  539.     if Int24result <> 0 then
  540.       FreeDisk := 0.0
  541.     else
  542.       FreeDisk := 1.0 * AX * BX * CX;
  543.   end;
  544. end;
  545.  
  546. procedure ReLoadDir( w, menu : integer );
  547.   {
  548.   Forces a full reload on the current path for the window.
  549.     If this can't be found it switches to the root directory and
  550.     tries again.
  551.   }
  552. var
  553.   i : integer;
  554. begin
  555.   Wind( 3 );
  556.   clrscr;
  557.   writeln;
  558.   i := ChangeCurDir( Path[w] );
  559.   if i <> 0 then
  560.     Path[w] := copy( Path[w], 1, 3 );
  561.   i := ChangeCurDir( Path[w] );
  562.   if i <> 0 then
  563.     ErrorMessage( i )
  564.   else
  565.   begin
  566.     DiskFree[w] := FreeDisk( Drive[w] );
  567.     LoadDir( w );
  568.     if menu = 2 then
  569.     begin
  570.       LoadFAT( DiskTable[w], FATptr );
  571.       FATsaved := true;
  572.     end;
  573.   end;
  574. end;
  575.  
  576. function DeleteFile( fname : str80 ) : integer;
  577.   {
  578.   Removes the specified file from disk.
  579.   }
  580. var
  581.   Regs : reg_T;
  582.   tstr : str80;
  583.   i    : integer;
  584. begin
  585.   tstr := fname + #00;
  586.   with Regs do
  587.   begin
  588.     AH := $41;                         { DOS function $41 - Delete a File }
  589.     DS := seg( tstr[1] );
  590.     DX := ofs( tstr[1] );
  591.     {$I-}
  592.     MsDos( Regs );
  593.     {$I+}
  594.     i := Int24result;
  595.     if i = 0 then
  596.       if (Flags AND $01) <> 0 then i := (AX SHL 8) OR $8000;
  597.   end;
  598.   DeleteFile := i;
  599. end;
  600.  
  601. function RenameFile( oldname, newname : str80 ) : integer;
  602.   {
  603.   Changes the files name to the new one specified.
  604.     Note that if the paths are different DOS will actually
  605.     delete the file's entry from the old directory and put it
  606.     in the new one as long as both paths are on the same disk.
  607.   }
  608. var
  609.   oldn, newn : str80;
  610.   Regs       : reg_T;
  611.   i          : integer;
  612. begin
  613.   oldn := oldname + #00;
  614.   newn := newname + #00;
  615.   with Regs do
  616.   begin
  617.     AH := $56;                          { DOS function $56 - Rename a File }
  618.     DS := seg( oldn[1] );
  619.     DX := ofs( oldn[1] );
  620.     ES := seg( newn[1] );
  621.     DI := ofs( newn[1] );
  622.     {$I-}
  623.     MsDos( Regs );
  624.     {$I+}
  625.     i := Int24result;
  626.     if i = 0 then
  627.       if (Flags AND $01) <> 0 then i := (AX SHL 8) OR $8000;
  628.   end;
  629.   RenameFile := i;
  630. end;
  631.  
  632. function ParseFileName( s : str80; address : Addr_T ) : boolean;
  633.   {
  634.   Why write our own file name parser when DOS will do it for us?
  635.     This includes expanding wildcards.
  636.     We do, however, have to save space for an archaic FCB.
  637.   }
  638. var
  639.   FCB  : FCB_T;
  640.   Regs : reg_T;
  641.   tstr : str80;
  642. begin
  643.   tstr := s + #00;
  644.   with Regs do
  645.   begin
  646.     AH := $29;                     { DOS function $29 - Parse a File Name }
  647.     AL := $01;                     { $01 - skip blanks at start. }
  648.     DS := seg( tstr[1] );
  649.     SI := ofs( tstr[1] );
  650.     ES := seg( FCB );
  651.     DI := ofs( FCB );
  652.     MsDos( Regs );
  653.     if AL = $FF then
  654.       ParseFileName := false
  655.     else
  656.     begin
  657.       move( FCB.Name[1], address^, 11 );
  658.       ParseFileName := true;
  659.     end;
  660.   end;
  661. end;
  662.  
  663. function RemDir( dname : str80 ) : integer;
  664.   {
  665.   Deletes the directory specified from disk.
  666.   }
  667. var
  668.   Regs : reg_T;
  669.   tstr : str80;
  670.   i    : integer;
  671. begin
  672.   tstr := dname + #00;
  673.   with Regs do
  674.   begin
  675.     AH := $3A;                        { DOS function $3A - Remove Directory }
  676.     DS := seg( tstr[1] );
  677.     DX := ofs( tstr[1] );
  678.     {$I-}
  679.     MsDos( Regs );
  680.     {$I+}
  681.     i := Int24result;
  682.     if i = 0 then
  683.       if (Flags AND $01) <> 0 then i := (AX SHL 8) OR $8000;
  684.   end;
  685.   RemDir := i;
  686. end;
  687.  
  688. procedure CloseFile( var handle : integer );
  689.   {
  690.   Closes the handle and then sets it to zero.
  691.   }
  692. var
  693.   Regs : reg_T;
  694. begin
  695.   Regs.AH := $3E;                  { DOS function $3E - Close a File Handle }
  696.   Regs.BX := handle;
  697.   MsDos( Regs );
  698.   handle := 0;
  699. end;
  700.  
  701. function OpenFile( fname : str80; var handle : integer ) : integer;
  702.   {
  703.   Opens a file just for reading and returns the handle assigned to it.
  704.   }
  705. var
  706.   tstr : str80;
  707.   Regs : reg_T;
  708.   i    : integer;
  709. begin
  710.   tstr := fname + #00;
  711.   with Regs do
  712.   begin
  713.     Ah := $3D;                             { DOS function $3D - Open a File }
  714.     AL := $00;                             { $00 - just for reading }
  715.     DS := seg( tstr[1] );
  716.     DX := ofs( tstr[1] );
  717.     {$I-}
  718.     MsDos( Regs );
  719.     {$I+}
  720.     i := int24result;
  721.     if i = 0 then
  722.     begin
  723.       if (Flags AND $01) <> 0 then
  724.       begin
  725.         i := (AX SHL 8) OR $8000;
  726.         handle := 0;
  727.       end
  728.       else
  729.         handle := AX;
  730.     end
  731.     else
  732.     begin            { If there was an Int24 error then we make sure }
  733.       handle := AX;  {    the file handle is closed.                 }
  734.       if ((Flags AND $01) = 0) then CloseFile (handle)
  735.       else                          handle := 0;
  736.     end;
  737.   end;
  738.   OpenFile := i;
  739. end;
  740.  
  741. function CreateFile( fname:str80; attr:integer; var handle:integer ):integer;
  742.   {
  743.   Makes the file specified no matter what, unless there is already
  744.     a file of that name with the read-only attribute set.
  745.     It also returns the new files handle.
  746.   }
  747. var
  748.   Regs : reg_T;
  749.   tstr : str80;
  750.   i    : integer;
  751. begin
  752.   tstr := fname + #00;
  753.   with Regs do
  754.   begin
  755.     AH := $3C;                            { DOS function $3C - Create a File }
  756.     DS := seg( tstr[1] );
  757.     DX := ofs( tstr[1] );
  758.     CL := attr;
  759.     {$I-}
  760.     MsDos( Regs );
  761.     {$I+}
  762.     i := Int24result;
  763.     if (i = 0) then
  764.     begin
  765.       if ((Flags AND $01) <> 0) then
  766.       begin
  767.         i := (AX SHL 8) OR $8000;
  768.         handle := 0;
  769.       end
  770.       else
  771.         handle := AX;
  772.     end
  773.     else
  774.     begin
  775.       handle := AX;
  776.       if ((Flags AND $01) = 0) then CloseFile( handle )
  777.       else                          handle := 0;
  778.     end;
  779.   end;
  780.   CreateFile := i;
  781. end;
  782.  
  783. procedure ReadFrom( handle : integer; address : Addr_T; amt : integer );
  784.   {
  785.   Read from an open file handle to memory.
  786.   }
  787. var
  788.   Regs : reg_T;
  789. begin
  790.   with Regs do
  791.   begin
  792.     AH := $3F;              { DOS function $3F - Read From a File or Device }
  793.     BX := handle;
  794.     CX := amt;
  795.     DS := seg( address^ );
  796.     DX := ofs( address^ );
  797.     MsDos( Regs );
  798.   end;
  799. end;
  800.  
  801. function WriteTo( handle:integer; address:Addr_T; amt:integer ) : boolean;
  802.   {
  803.   Write to a handle from memory.
  804.   }
  805. var
  806.   Regs : reg_T;
  807.   i    : integer;
  808. begin
  809.   with Regs do
  810.   begin
  811.     AH := $40;               { DOS function $40 - Write to a File or Device }
  812.     BX := handle;
  813.     CX := amt;
  814.     DS := seg( address^ );
  815.     DX := ofs( address^ );
  816.     {$I-}
  817.     MsDos( Regs );
  818.     {$I+}
  819.     i := Int24result;
  820.     WriteTo := (AX = amt) and (i = 0);
  821.   end;
  822. end;
  823.  
  824. function ChangeFileTime( fname : str80; newt, newd : integer ) : integer;
  825.   {
  826.   Sets the file's time to the same as specified in the original's
  827.     directory entry.  Used by the copy routine since a mere copy
  828.     does not deserve to have its time changed.
  829.   }
  830. var
  831.   Regs      : reg_T;
  832.   tstr      : str80;
  833.   i, handle : integer;
  834. begin
  835.   i := OpenFile( fname, handle );
  836.   if i = 0 then
  837.   begin
  838.     with Regs do
  839.     begin
  840.       AH := $57;          { DOS function $57 - Get or Set File's Date & Time }
  841.       AL := $01;          { $01 - Set }
  842.       BX := handle;
  843.       CX := newt;
  844.       DX := newd;
  845.       MsDos( Regs );
  846.     end;
  847.     CloseFile( handle );
  848.   end;
  849.   ChangeFileTime := i;
  850. end;
  851.  
  852. function MakDir( dname : str80 ) : integer;
  853.   {
  854.   Will create the path specified.
  855.   }
  856. var
  857.   Regs : reg_T;
  858.   tstr : str80;
  859.   i    : integer;
  860. begin
  861.   tstr := dname + #00;
  862.   with Regs do
  863.   begin
  864.     AH := $39;                   { DOS function $39 - Create Subdirectory }
  865.     DS := seg( tstr[1] );
  866.     DX := ofs( tstr[1] );
  867.     {$I-}
  868.     MsDos( Regs );
  869.     {$I+}
  870.     i := Int24result;
  871.     if i = 0 then
  872.       if (Flags AND $01) <> 0 then i := (AX SHL 8) OR $8000;
  873.   end;
  874.   MakDir := i;
  875. end;
  876.  
  877. function ChangeAttr( fname : str80; attr : byte ) : integer;
  878.   {
  879.   Changes the file's attribute byte to that specified.
  880.   }
  881. var
  882.   Regs : reg_T;
  883.   tstr : str80;
  884.   i    : integer;
  885. begin
  886.   tstr := fname + #00;
  887.   with Regs do
  888.   begin
  889.     AH := $43;               { DOS function $43 - Get or Set File Attributes }
  890.     AL := $01;               { $01 - set }
  891.     CX := attr;
  892.     DS := seg( tstr[1] );
  893.     DX := ofs( tstr[1] );
  894.     {$I-}
  895.     MsDos( Regs );
  896.     {$I+}
  897.     i := Int24result;
  898.     if i = 0 then
  899.       if (Flags AND $01) <> 0 then i := (AX SHL 8) OR $8000;
  900.   end;
  901.   ChangeAttr := i;
  902. end;
  903.  
  904. procedure GoDir( var w : integer; loadw : integer );
  905.   {
  906.   Will read the current entry in the window and then attempt to
  907.     change to that path if it is a directory.
  908.   }
  909. var
  910.   tstr    : str80;
  911.   i, tdrv : integer;
  912. begin
  913.   if CurEntry[w] <> 0 then
  914.   begin
  915.     if (Entry[w][CurEntry[w]].Attr AND Dbit) <> 0 then
  916.     begin
  917.       tstr := ConvertName( Entry[w][CurEntry[w]] );
  918.       Wind( 3 );
  919.       clrscr;
  920.       writeln;
  921.       Disp( NATTR, ' Changing to ' );
  922.       Disp( HATTR, tstr );
  923.       writeln;
  924.       i := ChangeCurDir( Path[w] );
  925.       i := ChangeCurDir( tstr );
  926.       if (i <> 0) then
  927.         ErrorMessage( i )
  928.       else if (tstr = Path[3-loadw]) then
  929.         DupPathMessage
  930.       else
  931.       begin
  932.         Path[loadw] := tstr;
  933.         HelpScreen[loadw] := false;
  934.         tdrv := GetCurDrive;
  935.         if (Drive[3-loadw] = tdrv) and Loaded[3-loadw] then
  936.           DiskFree[loadw] := DiskFree[3-loadw]
  937.         else
  938.           if (Drive[loadw] <> tdrv) or not Loaded[loadw] then
  939.             DiskFree[loadw] := FreeDisk( tdrv );
  940.         Drive[loadw] := tdrv;
  941.         LoadDir( loadw );
  942.         w := loadw;
  943.       end;
  944.     end;
  945.   end;
  946. end;
  947.  
  948. procedure ClearFAT( drv : integer; disktable : DskTblptr );
  949.   {
  950.   Will just zero out the File Allocation Table and root directory
  951.     on the disk specified.  Much quicker than deleting them all.
  952.     Since we cannot verify the disk without potential compatibility
  953.     problems we will trust that the old FAT has the diskette's
  954.     bad sectors marked appropriately.
  955.   }
  956. var
  957.   FATbytes     : real;
  958.   i, amt, sect : integer;
  959.   buffer       : Addr_T;
  960. begin
  961.   release( HeapStart );
  962.   with disktable^ do
  963.   begin
  964.     if MAXCLUSTER <= 4097 then
  965.       FATbytes := 1.5
  966.     else
  967.       FATbytes := 2.0;
  968.     if FATSIZE < DATASECTOR - ROOTSECTOR then
  969.       amt := (DATASECTOR-ROOTSECTOR)
  970.     else
  971.       amt := FATSIZE;
  972.     amt := amt * SECTORSIZE;
  973.     if MemoryAvail < amt then
  974.       AbortProgram( 'ClearFAT :',
  975.                     '',
  976.                     '   Insufficient memory for temporary buffer.',
  977.                     ''
  978.                   );
  979.     getmem( buffer, amt );
  980.     fillchar( buffer^, amt, 0 );
  981.     WriteSectors( drv, ROOTSECTOR, DATASECTOR-ROOTSECTOR, buffer );
  982.  
  983.     LoadSectors( drv, ROOTSECTOR - NFATS * FATSIZE, FATSIZE, buffer );
  984.     for i := 2 to MAXCLUSTER-1 do
  985.       if FATentry( FATbytes, i, buffer ) <> $FFF7 then
  986.         WriteFATentry( FATbytes, i, 0, buffer );
  987.  
  988.     SaveFAT( DiskTable, buffer );    { Buffers are flushed here }
  989.   end;
  990. end;
  991.  
  992. function ChangeCopyDisk( w:integer; dest:str80; var split:boolean ) : str80;
  993.   {
  994.   Changing disks in the middle of a copy is no small matter.
  995.     Note that the flag Split is set to true if the user Clears
  996.     the disk before continuing.  This happens because the ClearFAT
  997.     procedure must use the same area of memory as the copy buffer
  998.     and we must force a reload.
  999.   }
  1000. var
  1001.   tstr      : str80;
  1002.   err,
  1003.   drv       : integer;
  1004.   disktable : DskTblptr;
  1005. begin
  1006.   repeat
  1007.     tstr := dest;
  1008.     writeln;
  1009.     Disp( NATTR, ' Insert new disk in drive ' + copy(dest,1,2) + ' and ' );
  1010.     wait;
  1011.     writeln;
  1012.     if ord( tstr[0] ) <> 3 then
  1013.       if ChangeCurDir( tstr ) <> 0 then
  1014.         tstr := copy( tstr, 1, 3 );
  1015.     err := ChangeCurDir( tstr );
  1016.     if err <> 0 then
  1017.     begin
  1018.       ErrorMessage( err );
  1019.       tstr := '';
  1020.     end
  1021.     else
  1022.     begin
  1023.       if (w <> 0) then
  1024.       begin
  1025.         Path[w] := tstr;
  1026.         ReloadDir( w, 1 );
  1027.       end;
  1028.       Wind( 3 );
  1029.       clrscr;
  1030.       writeln;
  1031.       Disp( NATTR, ' Do you wish to CLEAR this disk' );
  1032.       if YorN( false ) then
  1033.       begin
  1034.         tstr  := copy( dest, 1, 3 );
  1035.         split := true;
  1036.         drv   := GetCurDrive;
  1037.         GetTable( drv, disktable );
  1038.         ClearFAT( drv, disktable );
  1039.       end;
  1040.       writeln;
  1041.       if (ord( dest[0] ) <> 3) and (ord( tstr[0] ) = 3) then
  1042.       begin
  1043.         writeln;
  1044.         Disp( NATTR, ' Attempt to create ' + dest );
  1045.         if not YorN( false ) then
  1046.           tstr := copy( dest, 1, 3 )
  1047.         else
  1048.         begin
  1049.           tstr := dest;
  1050.           err  := MakDir( tstr );
  1051.           if err <> 0 then
  1052.           begin
  1053.             ErrorMessage( err );
  1054.             tstr := '';
  1055.           end;
  1056.         end;
  1057.       end;
  1058.     end;
  1059.   until tstr <> '';
  1060.   if (w <> 0) then Path[w] := tstr;
  1061.   if ord( tstr[0] ) <> 3 then tstr := tstr + '\';
  1062.   ChangeCopyDisk := tstr;
  1063. end;
  1064.  
  1065. procedure CopyEntries( w : integer; dest : str80; wflag : boolean );
  1066.   {
  1067.   This is a very important routine.  It will read as many
  1068.     'marked' files into memory as it can fit before writing them
  1069.     back out.  This means that unless you load a bunch of resident
  1070.     programs first, you should be able to hold an entire floppy
  1071.     in memory on a 640K system with room to spare.
  1072.     It also allows you to change disks if you happen to fill one up.
  1073.   }
  1074. const
  1075.   MaxBuf  = 100;
  1076.   MaxSize = 65520.0;  { Largest buffer possible (almost one segment)  }
  1077.                       {   don't go bigger since 65536.0 as an integer }
  1078.                       {   converts to 0 (not a good thing).           }
  1079. type
  1080.   Buffer_T = record
  1081.                address : Addr_T;
  1082.                size,
  1083.                ent     : integer;  { Which entry buffer belongs to }
  1084.                more    : boolean;  { Does the file own other buffers? }
  1085.              end;
  1086. var
  1087.   Buffer                       : array[1..MaxBuf] of Buffer_T;
  1088.   tstr, tsrc, tdest            : str80;
  1089.   i, j, nb, n, tn, err,
  1090.   Rhandle, Whandle,
  1091.   cnt, Rcnt, Wcnt, Nwrit       : integer;
  1092.   MA, left, rsize              : real;
  1093.   done, split, tmore, diskfull : boolean;
  1094.  
  1095.   procedure ReadToBuffer;          { Local to CopyEntries }
  1096.     {
  1097.     Read until out of files or buffer full.
  1098.     }
  1099.   var
  1100.     tcnt : integer;
  1101.   begin
  1102.     nb   := 0;
  1103.     tcnt := 0;
  1104.     Rcnt := Rcnt + Nwrit;
  1105.     repeat
  1106.       if Rhandle = 0 then
  1107.       begin
  1108.         repeat
  1109.           i := NextEntry( w, i );
  1110.         until Marked[w][i] or (i = 0);
  1111.       end;
  1112.       if i <> 0 then
  1113.       begin
  1114.         tstr := ConvertName( Entry[w][i] );
  1115.         err  := 0;
  1116.         repeat
  1117.           clrscr;
  1118.           writeln;
  1119.           Disp( NATTR, ' Reading from file ' );
  1120.           Disp( HATTR, tsrc + tstr );
  1121.           Disp( NATTR, ' ('+Cstr(Rcnt+tcnt,0,0)+' of '+Cstr(cnt,0,0)+')' );
  1122.           writeln;
  1123.           if Rhandle = 0 then
  1124.           begin
  1125.             err := OpenFile( tsrc + tstr, Rhandle );
  1126.             if err <> 0 then
  1127.             begin
  1128.               ErrorMessage( err );
  1129.               done := not TryAgain;
  1130.             end
  1131.             else
  1132.             begin
  1133.               tcnt := tcnt + 1;
  1134.               left := EntrySize( Entry[w][i] );
  1135.             end;
  1136.           end;
  1137.         until (err = 0) or done;
  1138.         if not done then
  1139.         begin
  1140.           repeat
  1141.             MA := MemoryAvail;
  1142.             if MA > 0 then
  1143.             begin
  1144.               if MA > MaxSize then MA := MaxSize;
  1145.               rsize := MA;
  1146.               if rsize > left then rsize := left;
  1147.               left := left - rsize;
  1148.               nb := nb + 1;
  1149.               with Buffer[nb] do
  1150.               begin
  1151.                 Ent  := i;
  1152.                 More := (left > 0);
  1153.                 Size := RealToInt( rsize );
  1154.                 getmem( Address, Size );
  1155.                 ReadFrom( Rhandle, Address, Size );
  1156.               end;
  1157.             end;
  1158.           until (left = 0) or (nb = MaxBuf) or (MA <= 0) or done;
  1159.           if (left = 0) then
  1160.             CloseFile( Rhandle );
  1161.         end;
  1162.       end;
  1163.     until (i = 0) or (nb = MaxBuf) or (MA <= 0) or done;
  1164.   end;
  1165.  
  1166.   procedure WriteFromBuffer;        { Local to CopyEntries }
  1167.     {
  1168.     Take those files read and put them all back on disk.
  1169.     }
  1170.   begin
  1171.     n        := 1;
  1172.     split    := (Whandle = 0);
  1173.     diskfull := false;
  1174.     Nwrit    := 0;
  1175.     while (n <= nb) and not done and not( diskfull and split) do
  1176.     begin
  1177.       tn   := n;
  1178.       j    := Buffer[n].Ent;
  1179.       tstr := ConvertName( Entry[w][j] );
  1180.       clrscr;
  1181.       writeln;
  1182.       Disp( NATTR, ' Writing to file ' );
  1183.       Disp( HATTR, tdest + tstr );
  1184.       Disp( NATTR, ' ('+Cstr(Wcnt+Nwrit,0,0)+' of '+Cstr(cnt,0,0)+')' );
  1185.       writeln;
  1186.       err := 0;
  1187.       if Whandle = 0 then
  1188.       begin
  1189.         err   := CreateFile( tdest + tstr, Entry[w][j].Attr, Whandle );
  1190.         Nwrit := Nwrit + 1;
  1191.       end;
  1192.       if err <> 0 then
  1193.       begin
  1194.         ErrorMessage( err );
  1195.         done := not TryAgain;
  1196.         if done then
  1197.         begin
  1198.           done := not Continue;  { This series of prompts allows the user    }
  1199.           if not done then       {   to skip the current file and continue   }
  1200.           begin                  {   with the next.  A good reason for       }
  1201.             writeln;             {   allowing this is when the copy routine  }
  1202.             diskfull := true;    {   is attempting to overwrite a file that  }
  1203.             split    := true;    {   has its read-only bit set.              }
  1204.             i := j;
  1205.           end;
  1206.         end
  1207.         else Wcnt := Wcnt - 1;
  1208.       end
  1209.       else
  1210.       begin
  1211.         repeat
  1212.           with Buffer[n] do
  1213.           begin
  1214.             tmore := More;
  1215.             diskfull := not WriteTo( Whandle, Address, Size );
  1216.           end;
  1217.           if not diskfull then
  1218.             n := n + 1
  1219.           else
  1220.           begin
  1221.             CloseFile( Whandle );
  1222.             err := DeleteFile( tdest + tstr );
  1223.             Disp( NATTR, ' Disk full: ' );
  1224.  
  1225.             if not (dest[1] in ['A','B']) then
  1226.             begin
  1227.               Disp(HATTR,'Can''t change disk in drive '+copy(dest,1,2)+'.');
  1228.               done := true;
  1229.               writeln;                { These prompts allow the user to    }
  1230.               gotoxy( 12, wherey );   {   change disks if they are copying }
  1231.               wait;                   {   to one of the floppy drives.     }
  1232.             end
  1233.             else
  1234.             begin
  1235.               Disp( NATTR, 'Continue with copy' );
  1236.               done := not YorN( false );
  1237.               writeln;
  1238.             end;
  1239.             if not done then
  1240.             begin
  1241.               if (wflag) then
  1242.                 dest := ChangeCopyDisk( 3-w, dest, split )
  1243.               else
  1244.                 dest := ChangeCopyDisk( 0, dest, split );
  1245.             end;
  1246.  
  1247.             if not split then
  1248.               n := tn
  1249.             else
  1250.             begin
  1251.               if Rhandle <> 0 then CloseFile( Rhandle );
  1252.               i := LastEntry( w, j );
  1253.               Rcnt := Rcnt - 1;
  1254.               Wcnt := Wcnt - 1;
  1255.             end;
  1256.           end;
  1257.         until not tmore or (n > nb) or diskfull;
  1258.         if not tmore and (Whandle <> 0) then
  1259.         begin
  1260.           CloseFile( Whandle );
  1261.           err := ChangeFileTime(tdest+tstr,Entry[w][j].Time,Entry[w][j].Date);
  1262.           split := false;
  1263.         end;
  1264.       end;
  1265.     end;
  1266.     Wcnt := Wcnt + Nwrit;
  1267.   end;
  1268.  
  1269. begin         { Actual start of CopyEntries }
  1270.   Wind( 3 );
  1271.   clrscr;
  1272.   writeln;
  1273.   tsrc := Path[w];
  1274.   if ord( tsrc[0] ) <> 3 then tsrc := tsrc + '\';
  1275.   tdest := dest;
  1276.   if ord( tdest[0] ) <> 3 then tdest := tdest + '\';
  1277.   done    := false;
  1278.   Rhandle := 0;
  1279.   Whandle := 0;
  1280.   Nwrit   := 0;
  1281.   cnt     := 0;
  1282.   i := NextEntry( w, 0 );
  1283.   while (i <> 0) do
  1284.   begin
  1285.     if (Marked[w][i]) then cnt := cnt + 1;
  1286.     i := NextEntry( w, i );
  1287.   end;
  1288.   i := 0;
  1289.   if (cnt > 0) then
  1290.   begin
  1291.     Rcnt    := 1;
  1292.     Wcnt    := 1;
  1293.     repeat
  1294.       release( HeapStart );   { Clear up heap each time }
  1295.  
  1296.       ReadToBuffer;           { Read as much as possible }
  1297.       WriteFromBuffer;        {   then write it back out }
  1298.  
  1299.     until done or (i = 0);
  1300.     if Rhandle <> 0 then CloseFile( Rhandle );
  1301.     if Whandle <> 0 then CloseFile( Whandle );
  1302.   end;
  1303. end;
  1304.  
  1305. function SortTime( E : Entry_T ) : real;
  1306.   {
  1307.   Returns a real number that reflects the date and
  1308.     time converted to one parameter.
  1309.   }
  1310. var
  1311.   dword, tword : real;
  1312. begin
  1313.   if E.Date < 0 then dword := E.Date + 65536.0
  1314.   else dword := E.Date;
  1315.   if E.Time < 0 then tword := E.Time + 65536.0
  1316.   else tword := E.Time;
  1317.   SortTime := dword * 65536.0 + tword;
  1318. end;
  1319.  
  1320. function SortAttr( w : integer; E : Entry_T ) : integer;
  1321.   {
  1322.   Returns a very special sort key that puts files into a logical
  1323.     order.  Examples are directories before normal entries and
  1324.     deleted files go last.
  1325.   }
  1326. begin
  1327.   if E.Name[1] = DelChar then         SortAttr := 9   { Deleted }
  1328.   else if (E.Attr AND $1E) = 0 then   SortAttr := 6   { Normal }
  1329.   else if E.Name[1] = '.' then
  1330.   begin
  1331.     if E.Name[2] = '.' then           SortAttr := 2   { Parent directory }
  1332.     else                              SortAttr := 1;  { Current directory }
  1333.   end
  1334.   else if (E.Attr AND Dbit) <> 0 then SortAttr := 5   { Directory }
  1335.   else if (E.Attr AND Sbit) <> 0 then
  1336.   begin
  1337.     if ord( Path[w][0] ) = 3 then     SortAttr := 0   { System in root }
  1338.     else                              SortAttr := 7   { System elsewhere }
  1339.   end
  1340.   else if (E.Attr AND Hbit) <> 0 then SortAttr := 8   { Hidden }
  1341.   else if (E.Attr AND Vbit) <> 0 then SortAttr := 3   { Volume label }
  1342.   else                                SortAttr := 10; { just in case }
  1343. end;
  1344.  
  1345. procedure InsertSort( w, field : integer; forwrd : boolean );
  1346.   {
  1347.   Performs an insertion sort on the field specified.
  1348.     0 = attributes, 1 = name, 2 = extension, 3 = size and 4 = time.
  1349.     An insertion sort was chosen because it is a stable sort
  1350.     and not such a bad one since we generally won't be sorting
  1351.     more than about 150 - 200 files at the very most.
  1352.   }
  1353. var
  1354.   i, j, count : integer;
  1355.   tempArray   : array[1..MaxFiles] of real;
  1356.   tempR       : real;
  1357.   tEntry      : Entry_T;
  1358.   exchange    : boolean;
  1359. begin
  1360.   count := 0;
  1361.   Wind( 3 );
  1362.   clrscr;
  1363.   writeln;
  1364.   Disp( NATTR, ' Sorting' );
  1365.   textcolor( White );
  1366.   if field in [0,3,4] then
  1367.   begin
  1368.     for i := 1 to MaxEntry[w] do
  1369.     begin
  1370.       case field of
  1371.         0 : tempArray[i] := SortAttr( w, Entry[w][i] );
  1372.         3 : tempArray[i] := EntrySize(   Entry[w][i] );
  1373.         4 : tempArray[i] := SortTime(    Entry[w][i] );
  1374.       end;
  1375.     end;
  1376.   end;
  1377.   for i := 2 to MaxEntry[w] do
  1378.   begin
  1379.     tEntry := Entry[w][i];
  1380.     tempR  := tempArray[i];
  1381.     j := i - 1;
  1382.     repeat
  1383.       count := count + 1;
  1384.       case forwrd of
  1385.         true : case field of
  1386.                  1 :  exchange := ( tEntry.Name < Entry[w][j].Name );
  1387.                  2 :  exchange := ( tEntry.Ext  < Entry[w][j].Ext );
  1388.                  else exchange := ( tempR       < tempArray[j] );
  1389.                end;
  1390.         false: case field of
  1391.                  1 :  exchange := ( tEntry.Name > Entry[w][j].Name );
  1392.                  2 :  exchange := ( tEntry.Ext  > Entry[w][j].Ext );
  1393.                  else exchange := ( tempR       > tempArray[j] );
  1394.                end;
  1395.       end;
  1396.       if exchange then
  1397.       begin
  1398.         Entry[w][j+1]  := Entry[w][j];
  1399.         tempArray[j+1] := tempArray[j];
  1400.         j := j - 1;
  1401.       end;
  1402.     until (j = 0) or not exchange;
  1403.     Entry[w][j+1]  := tEntry;
  1404.     tempArray[j+1] := tempR;
  1405.     if count > 1000 then
  1406.     begin
  1407.       write( '.' );
  1408.       count := 0;
  1409.     end;
  1410.   end;
  1411. end;
  1412.  
  1413. function CheckMatch( w : integer; s : str80 ) : boolean;
  1414.   {
  1415.   Does a simple search for an entry that matches S.
  1416.   }
  1417. var
  1418.   match : boolean;
  1419.   i     : integer;
  1420. begin
  1421.   match := false;
  1422.   for i := 1 to MaxEntry[w] do
  1423.     if ConvertName( Entry[w][i] ) = s then match := true;
  1424.   if match then
  1425.   begin
  1426.     Disp( NATTR, ' Error: ' );
  1427.     Disp( HATTR, 'Name already exists, try again.' );
  1428.   end;
  1429.   CheckMatch := match;
  1430. end;
  1431.  
  1432. function UnDel( w : integer ) : boolean;
  1433.   {
  1434.   Takes the best guess approach to recovering deleted files.
  1435.     It just starts at the cluster that was specified in the
  1436.     old directory entry and searches the FAT for free clusters
  1437.     until it finds as many as it needs or runs out of free ones.
  1438.     If the first cluster has already been alocated to a file
  1439.     then undeletion of the file is not possible and we
  1440.     must pass the bad news on to the user.
  1441.     I think it is as reliable as Norton's QuickUnerase (tm or whatever).
  1442.   }
  1443. var
  1444.   amt, CLbytes, MaxCL, clust, lastclust : integer;
  1445.   tempR, tempDisk                       : real;
  1446.   tHeapptr, tFATptr                     : Addr_T;
  1447.   error                                 : boolean;
  1448. begin
  1449.   error := false;
  1450.   with DiskTable[w]^ do
  1451.   begin
  1452.     amt     := FATSIZE * SECTORSIZE;
  1453.     CLbytes := (CLUSTERSIZE+1) * SECTORSIZE;
  1454.     MaxCL   := MAXCLUSTER;
  1455.   end;
  1456.   tempR    := EntrySize( Entry[w][CurEntry[w]] );
  1457.   tempDisk := DiskFree[w];
  1458.   clust    := Entry[w][CurEntry[w]].Cluster;
  1459.   if tempR = 0 then
  1460.   begin
  1461.     if clust <> 0 then error := true
  1462.   end
  1463.   else
  1464.   begin
  1465.     if FATentry( FATbytes[w], clust, FATptr ) <> 0 then
  1466.       error := true
  1467.     else
  1468.     begin
  1469.       Mark( tHeapptr );
  1470.       getmem( tFATptr, amt );
  1471.       move( FATptr^, tFATptr^, amt );
  1472.       tempR     := tempR - CLbytes;
  1473.       tempDisk  := tempDisk - CLbytes;
  1474.       lastclust := clust;
  1475.       repeat
  1476.         clust := clust + 1
  1477.       until (FATentry(FATbytes[w],clust,FATptr) = 0) or (clust > MaxCL);
  1478.  
  1479.       while (tempR > 0) and (clust <= MaxCL) do
  1480.       begin
  1481.         WriteFATentry( FATbytes[w], lastclust, clust, FATptr );
  1482.         tempR     := tempR - CLbytes;
  1483.         tempDisk  := tempDisk - CLbytes;
  1484.         lastclust := clust;
  1485.         repeat
  1486.           clust := clust + 1
  1487.         until (FATentry(FATbytes[w],clust,FATptr) = 0) or (clust > MaxCL);
  1488.       end;
  1489.  
  1490.       if (tempR <= 0) and (tempDisk >= 0) then
  1491.       begin
  1492.         WriteFATentry( FATbytes[w], lastclust, $FFFF, FATptr );
  1493.         DiskFree[w] := tempDisk;
  1494.       end
  1495.       else
  1496.       begin
  1497.         error := true;
  1498.         move( tFATptr^, FATptr^, amt );
  1499.       end;
  1500.       Release( tHeapptr );
  1501.     end;
  1502.   end;
  1503.   UnDel := not error;
  1504. end;
  1505.  
  1506. procedure RemoveDeleted( w : integer );
  1507.   {
  1508.   Purges all deleted files from directory.  That is, it
  1509.     moves them all to the end and then zeros them out so
  1510.     they look like they have never been used.
  1511.   }
  1512. var
  1513.   tEntry : Entry_T;
  1514.   i, j   : integer;
  1515. begin
  1516.   for i := 2 to MaxEntry[w] do
  1517.   begin
  1518.     tEntry := Entry[w][i];
  1519.     j := i - 1;
  1520.     if tEntry.Name[1] <> DelChar then
  1521.     begin
  1522.       while ( Entry[w][j].Name[1] = DelChar ) and ( j > 0 ) do
  1523.       begin
  1524.         Entry[w][j+1]  := Entry[w][j];
  1525.         j := j - 1;
  1526.       end;
  1527.     end;
  1528.     Entry[w][j+1]  := tEntry;
  1529.   end;
  1530.   while Entry[w][MaxEntry[w]].Name[1] = DelChar do
  1531.   begin
  1532.     fillchar( Entry[w][MaxEntry[w]], sizeof( Entry_T ), 0 );
  1533.     MaxEntry[w] := MaxEntry[w] - 1;
  1534.   end;
  1535. end;
  1536.  
  1537. function SysTime : integer;
  1538.   {
  1539.   Returns the current system clock time in the format
  1540.     used in directory entries.
  1541.     Time is put into the following word format for a file's
  1542.     directory entry
  1543.       [hhhhhmmmmmmsssss]
  1544.   }
  1545. var
  1546.   Regs    : reg_T;
  1547. begin
  1548.   with Regs do
  1549.   begin
  1550.     AH := $2C;                             { DOS function $2C - Get the Time }
  1551.     MsDos( Regs );
  1552.     SysTime := (CH SHL 11) OR (CL SHL 5) OR (DH SHR 1);
  1553.   end;
  1554. end;
  1555.  
  1556. function SysDate : integer;
  1557.   {
  1558.   Returns the current system date in the format
  1559.     required for disk files.
  1560.     Date field is put into the following word format
  1561.     for a file's directory entry
  1562.          [yyyyyyymmmmddddd]
  1563.   }
  1564. var
  1565.   Regs : reg_T;
  1566. begin
  1567.   with Regs do
  1568.   begin
  1569.     AH := $2A;                            { DOS function $2A - Get the Date }
  1570.     MsDos( Regs );
  1571.     SysDate := ((CX - 1980) SHL 9) OR (DH SHL 5) OR DL;
  1572.   end;
  1573. end;
  1574.