Patch.c
  1  /** fMSX: portable MSX emulator ******************************/
  2  /**                                                         **/
  3  /**                        Patch.c                          **/
  4  /**                                                         **/
  5  /** This file contains implementation for the PatchZ80()    **/
  6  /** function necessary for emulating MSX disk and tape IO.  **/
  7  /**                                                         **/
  8  /** Copyright (C) Marat Fayzullin 1994-2005                 **/
  9  /**     You are not allowed to distribute this software     **/
 10  /**     commercially. Please, notify me, if you make any    **/
 11  /**     changes to this file.                               **/
 12  /*************************************************************/
 13  
 14  #include "MSX.h"
 15  #include "Bootblock.h"
 16  #include <stdio.h>
 17  #include <string.h>
 18  #include <stdlib.h>
 19  #include <fcntl.h>
 20  
 21  #include "shared.h"
 22  
 23  
 24  #ifndef SEEK_CUR
 25  #define SEEK_CUR 1
 26  #endif
 27  
 28  void SSlot(byte Value); /* Used to switch secondary slots */
 29  
 30  #ifdef FMSX
 31  extern byte *RAM[],PSL[],SSLReg;
 32  static byte RdZ80(WORD A)
 33  {
 34    if(A!=0xFFFF) return(RAM[A>>13][A&0x1FFF]);
 35    else return((PSL[3]==3)? ~SSLReg:RAM[7][0x1FFF]);
 36  }
 37  #endif
 38  
 39  /** PatchZ80() ***********************************************/
 40  /** Emulate BIOS calls. This function is called on an ED FE **/
 41  /** instruction to emulate disk/tape access, etc.           **/
 42  /*************************************************************/
 43  void PatchZ80(Z80 *R)
 44  {
 45    static const byte TapeHeader[8] = { 0x1F,0xA6,0xDE,0xBA,0xCC,0x13,0x7D,0x74 };
 46  
 47    static const struct
 48    { int Sectors;byte Heads,Names,PerTrack,PerFAT,PerCluster; }
 49    Info[8] =
 50    {
 51      {  720,1,112,9,2,2 },
 52      { 1440,2,112,9,3,2 },
 53      {  640,1,112,8,1,2 },
 54      { 1280,2,112,8,2,2 },
 55      {  360,1, 64,9,2,1 },
 56      {  720,2,112,9,2,2 },
 57      {  320,1, 64,8,1,1 },
 58      {  640,2,112,8,1,2 }
 59    };
 60  
 61    byte Buf[512],Count,PS,SS,N,*P;
 62    int J,I,Sector;
 63    WORD Addr;
 64  
 65    switch(R->PC.W-2)
 66    {
 67  
 68  case 0x4010:
 69  /** PHYDIO: Read/write sectors to disk **************************
 70  *** Input:                                                    ***
 71  *** [F] CARRY=WRITE                  [A] Drive number (0=A:)  ***
 72  *** [B] Number of sectors to write   [C] Media descriptor     ***
 73  *** [DE] Logical sector number (starts at 0)                  ***
 74  *** [HL] Transfer address                                     ***
 75  *** Output:                                                   ***
 76  *** [F] CARRY=ERROR                  [A] If error: errorcode  ***
 77  *** [B] Number of sectors remaining (not read/written)        ***
 78  *** Error codes in [A] can be:                                ***
 79  *** 0 Write protected      8  Record not found                ***
 80  *** 2 Not ready            10 Write fault                     ***
 81  *** 4 Data (CRC) error     12 Other errors                    ***
 82  *** 6 Seek error                                              ***
 83  ****************************************************************/
 84  {
 85    //if(Verbose&0x04)
 86    //  printf
 87    //  (
 88    //    "%s DISK %c: %d sectors starting from %04Xh [buffer at %04Xh]\n",
 89    //    R->AF.B.l&C_FLAG? "WRITE":"READ",R->AF.B.h+'A',R->BC.B.h,
 90    //    R->DE.W,R->HL.W
 91    //  );
 92  
 93    R->IFF|=1;
 94    Addr  = R->HL.W;
 95    Count = R->BC.B.h;
 96  
 97    if(!DiskPresent(R->AF.B.h))
 98    { R->AF.W=0x0201;return; }  /* No disk      -> "Not ready"        */
 99    if((int)(R->DE.W)+Count>Info[R->BC.B.l-0xF8].Sectors)
100    { R->AF.W=0x0801;return; }  /* Wrong sector -> "Record not found" */
101  
102    /* If data does not fit into 64kB address space, trim it */
103    if((int)(R->HL.W)+Count*512>0x10000) Count=(0x10000-R->HL.W)/512;
104  
105    /* Save slot states */
106    PS=PSLReg;SS=SSLReg;
107  
108    /* Turn on RAM in all slots */
109    OutZ80(0xA8,0xFF);
110    SSlot(0xAA);
111  
112    if(R->AF.B.l&C_FLAG)
113      for(Sector=R->DE.W;Count--;Sector++) /* WRITE */
114      {
115        for(J=0;J<512;J++) Buf[J]=RdZ80(Addr++);
116  
117        if(DiskWrite(R->AF.B.h,Buf,Sector)) R->BC.B.h--;
118        else
119        {
120          R->AF.W=0x0A01;
121          SSlot(SS);
122          OutZ80(0xA8,PS);
123          return;
124        }
125      }
126    else
127      for(Sector=R->DE.W;Count--;Sector++) /* READ */
128      {
129        if(DiskRead(R->AF.B.h,Buf,Sector)) R->BC.B.h--;
130        else
131        {
132          R->AF.W=0x0401;
133          SSlot(SS);
134          OutZ80(0xA8,PS);
135          return;
136        }
137  
138        for(J=0;J<512;J++) WrZ80(Addr++,Buf[J]);
139      }
140  
141    /* Restore slot states */
142    SSlot(SS);
143    OutZ80(0xA8,PS);
144  
145    /* Return "Success" */
146    R->AF.B.l&=~C_FLAG;
147    return;
148  }
149  
150  case 0x4013:
151  /** DSKCHG: Check if the disk was changed ***********************
152  *** Input:                                                    ***
153  *** [A] Drive number (0=A:)       [B]  Media descriptor       ***
154  *** [C] Media descriptor          [HL] Base address of DPB    ***
155  *** Output:                                                   ***
156  *** [F] CARRY=ERROR       [A] If error: errorcode (see DSKIO) ***
157  *** [B] If success: 1=Unchanged, 0=Unknown, -1=Changed        ***
158  *** Note:                                                     ***
159  *** If the disk has been changed or may have been  changed    ***
160  *** (unknown) read the boot sector or the FAT sector for disk ***
161  *** media descriptor and transfer a new DPB as with GETDPB.   ***
162  ****************************************************************/
163  {
164    //if(Verbose&0x04) printf("CHECK DISK %c\n",R->AF.B.h+'A');
165  
166    R->IFF|=1;
167  
168    /* If no disk, return "Not ready": */
169    if(!DiskPresent(R->AF.B.h)) { R->AF.W=0x0201;return; }
170  
171    /* This requires some major work to be done: */
172    R->BC.B.h=0;R->AF.B.l&=~C_FLAG;
173  
174    /* We continue with GETDPB now... */
175  }
176  
177  case 0x4016:
178  /** GETDPB: Disk format *****************************************
179  *** Input:                                                    ***
180  *** [A] Drive number   [B] 1st byte of FAT (media descriptor) ***
181  *** [C] Media descriptor  [HL] Base address of DPB            ***
182  *** Output:                                                   ***
183  *** [HL+1] .. [HL+18] = DPB for specified drive               ***
184  *** DPB consists of:                                          ***
185  *** Name   Offset Size Description                            ***
186  *** MEDIA    0     1   Media type (F8..FF)                    ***
187  *** SECSIZ   1     2   Sector size (must be 2^n)              ***
188  *** DIRMSK   3     1   (SECSIZE/32)-1                         ***
189  *** DIRSHFT  4     1   Number of one bits in DIRMSK           ***
190  *** CLUSMSK  5     1   (Sectors per cluster)-1                ***
191  *** CLUSSHFT 6     1   (Number of one bits in CLUSMSK)+1      ***
192  *** FIRFAT   7     2   Logical sector number of first FAT     ***
193  *** FATCNT   8     1   Number of FATs                         ***
194  *** MAXENT   A     1   Number of directory entries (max 254)  ***
195  *** FIRREC   B     2   Logical sector number of first data    ***
196  *** MAXCLUS  D     2   Number of clusters (not including      ***
197  ***                    reserved, FAT and directory sectors)+1 ***
198  *** FATSIZ   F     1   Number of sectors used                 ***
199  *** FIRDIR   10    2   FAT logical sector number of start of  ***
200  ***                    directory                              ***
201  ****************************************************************/
202  {
203    int BytesPerSector,SectorsPerDisk,SectorsPerFAT,ReservedSectors;
204  
205    /* If no disk, return "Not ready": */
206    if(!DiskPresent(R->AF.B.h))    { R->AF.W=0x0201;return; }
207    /* If can't read, return "Other error": */
208    if(!DiskRead(R->AF.B.h,Buf,0)) { R->AF.W=0x0C01;return; }
209  
210    BytesPerSector  = (int)Buf[0x0C]*256+Buf[0x0B];
211    SectorsPerDisk  = (int)Buf[0x14]*256+Buf[0x13];
212    SectorsPerFAT   = (int)Buf[0x17]*256+Buf[0x16];
213    ReservedSectors = (int)Buf[0x0F]*256+Buf[0x0E];
214   
215    Addr=R->HL.W+1;
216    WrZ80(Addr++,Buf[0x15]);             /* Format ID [F8h-FFh] */
217    WrZ80(Addr++,Buf[0x0B]);             /* Sector size         */
218    WrZ80(Addr++,Buf[0x0C]);
219    J=(BytesPerSector>>5)-1;
220    for(I=0;J&(1<<I);I++);
221    WrZ80(Addr++,J);                     /* Directory mask/shft */
222    WrZ80(Addr++,I);
223    J=Buf[0x0D]-1;
224    for(I=0;J&(1<<I);I++);
225    WrZ80(Addr++,J);                     /* Cluster mask/shift  */
226    WrZ80(Addr++,I+1);
227    WrZ80(Addr++,Buf[0x0E]);             /* Sector # of 1st FAT */
228    WrZ80(Addr++,Buf[0x0F]);
229    WrZ80(Addr++,Buf[0x10]);             /* Number of FATs      */
230    WrZ80(Addr++,Buf[0x11]);             /* Number of dirent-s  */
231    J=ReservedSectors+Buf[0x10]*SectorsPerFAT;
232    J+=32*Buf[0x11]/BytesPerSector;
233    WrZ80(Addr++,J&0xFF);                /* Sector # of data    */
234    WrZ80(Addr++,(J>>8)&0xFF);
235    J=(SectorsPerDisk-J)/Buf[0x0D];
236    WrZ80(Addr++,J&0xFF);                /* Number of clusters  */
237    WrZ80(Addr++,(J>>8)&0xFF);
238    WrZ80(Addr++,Buf[0x16]);             /* Sectors per FAT     */
239    J=ReservedSectors+Buf[0x10]*SectorsPerFAT;
240    WrZ80(Addr++,J&0xFF);                /* Sector # of dir.    */
241    WrZ80(Addr,(J>>8)&0xFF);
242  
243    /* Return success      */
244    R->AF.B.l&=~C_FLAG;
245    return;
246  }
247  
248  case 0x401C:
249  /** DSKFMT: Disk format *****************************************
250  *** Input:                                                    ***
251  *** [A]  Specified choice (1-9)      [D]  Drive number (0=A:) ***
252  *** [HL] Begin address of work area  [BC] Length of work area ***
253  *** Output:                                                   ***
254  *** [F] CARRY=ERROR                                           ***
255  *** Notes:                                                    ***
256  *** 1) Also writes a MSX boot sector at sector 0, clears all  ***
257  ***    FATs (media descriptor at first byte, 0FFh at second/  ***
258  ***    third byte and rest zero) and clears the directory     ***
259  ***    filling it with zeros.                                 ***
260  *** 2) Error codes are:                                       ***
261  ***    0 Write protected       10 Write fault                 ***
262  ***    2 Not ready             12 Bad parameter               ***
263  ***    4 Data (CRC) error      14 Insufficient memory         ***
264  ***    6 Seek error            16 Other errors                ***
265  ***    8 Record not found                                     ***
266  ****************************************************************/
267  {
268    R->IFF|=1;
269  
270    /* If invalid choice, return "Bad parameter": */
271    if(!R->AF.B.h||(R->AF.B.h>2)) { R->AF.W=0x0C01;return; }
272    /* If no disk, return "Not ready": */
273    if(!DiskPresent(R->DE.B.h)) { R->AF.W=0x0201;return; }
274  
275    /* Fill bootblock with data: */
276    P=BootBlock+3;
277    N=2-R->AF.B.h;
278    memcpy(P,"fMSXdisk",8);P+=10;    /* Manufacturer's ID   */
279    *P=Info[N].PerCluster;P+=4;      /* Sectors per cluster */
280    *P++=Info[N].Names;*P++=0x00;    /* Number of names     */
281    *P++=Info[N].Sectors&0xFF;       /* Number of sectors   */
282    *P++=(Info[N].Sectors>>8)&0xFF;
283    *P++=N+0xF8;                     /* Format ID [F8h-FFh] */
284    *P++=Info[N].PerFAT;*P++=0x00;   /* Sectors per FAT     */
285    *P++=Info[N].PerTrack;*P++=0x00; /* Sectors per track   */
286    *P++=Info[N].Heads;*P=0x00;      /* Number of heads     */
287  
288    /* If can't write bootblock, return "Write protected": */
289    if(!DiskWrite(R->DE.B.h,BootBlock,0)) { R->AF.W=0x0001;return; };
290  
291    /* Writing FATs: */
292    for(Sector=1,J=0;J<2;J++)
293    {
294      Buf[0]=N+0xF8;
295      Buf[1]=Buf[2]=0xFF;
296      memset(Buf+3,0x00,509);
297  
298      if(!DiskWrite(R->DE.B.h,Buf,Sector++)) { R->AF.W=0x0A01;return; }
299  
300      memset(Buf,0x00,512);
301  
302      for(I=Info[N].PerFAT;I>1;I--)
303        if(!DiskWrite(R->DE.B.h,Buf,Sector++)) { R->AF.W=0x0A01;return; }
304    }
305  
306    J=Info[N].Names/16;                     /* Directory size */
307    I=Info[N].Sectors-2*Info[N].PerFAT-J-1; /* Data size */
308  
309    for(memset(Buf,0x00,512);J;J--)
310      if(!DiskWrite(R->DE.B.h,Buf,Sector++)) { R->AF.W=0x0A01;return; }
311    for(memset(Buf,0xFF,512);I;I--)
312      if(!DiskWrite(R->DE.B.h,Buf,Sector++)) { R->AF.W=0x0A01;return; }
313  
314    /* Return success      */
315    R->AF.B.l&=~C_FLAG;
316    return;
317  }
318  
319  case 0x401F:
320  /** DRVOFF: Stop drives *****************************************
321  *** Input:  None                                              ***
322  *** Output: None                                              ***
323  ****************************************************************/
324    return;
325  
326  case 0x00E1:
327  /** TAPION: Open for read and read header ***********************
328  ****************************************************************/
329  {
330    long Pos;
331  
332    //if(Verbose&0x04) printf("TAPE: Looking for header...");
333  
334    R->AF.B.l|=C_FLAG;
335  #ifdef unused
336    if(CasStream)
337    {
338      Pos=ftell(CasStream);
339      if(Pos&7)
340        if(fseek(CasStream,8-(Pos&7),SEEK_CUR))
341        {
342          if(Verbose&0x04) puts("FAILED");
343          rewind(CasStream);return;
344        }
345  
346      while(fread(Buf,1,8,CasStream)==8)
347        if(!memcmp(Buf,TapeHeader,8))
348        {
349          if(Verbose&0x04) puts("OK");
350          R->AF.B.l&=~C_FLAG;return;
351        }
352  
353      rewind(CasStream);
354    }
355  
356    if(Verbose&0x04) puts("FAILED");
357  #endif
358    return;
359  }
360  
361  case 0x00E4:
362  /** TAPIN: Read tape ********************************************
363  ****************************************************************/
364  {
365    R->AF.B.l|=C_FLAG;
366  
367  #ifdef unused
368    if(CasStream)
369    {
370      J=fgetc(CasStream);
371      if(J<0) rewind(CasStream);
372      else { R->AF.B.h=J;R->AF.B.l&=~C_FLAG; }
373    }
374  #endif
375  
376    return;
377  }
378  
379  case 0x00E7:
380  /** TAPIOF: *****************************************************
381  ****************************************************************/
382    R->AF.B.l&=~C_FLAG;
383    return;
384  
385  case 0x00EA:
386  /** TAPOON: *****************************************************
387  ****************************************************************/
388  {
389    long Pos;
390  
391    R->AF.B.l|=C_FLAG;
392  
393  #ifdef unused
394    if(CasStream)
395    {
396      Pos=ftell(CasStream);
397      if(Pos&7)
398        if(fseek(CasStream,8-(Pos&7),SEEK_CUR))
399        { R->AF.B.l|=C_FLAG;return; }
400  
401      fwrite(TapeHeader,1,8,CasStream);
402      R->AF.B.l&=~C_FLAG;
403    }   
404  #endif
405  
406    return;
407  }
408  
409  case 0x00ED:
410  /** TAPOUT: Write tape ******************************************
411  ****************************************************************/
412    R->AF.B.l|=C_FLAG;
413  
414  #ifdef unused
415    if(CasStream)
416    {
417      fputc(R->AF.B.h,CasStream);
418      R->AF.B.l&=~C_FLAG;
419    }
420  #endif
421  
422    return;
423  
424  case 0x00F0:
425  /** TAPOOF: *****************************************************
426  ****************************************************************/
427    R->AF.B.l&=~C_FLAG;
428    return;
429  
430  case 0x00F3:
431  /** STMOTR: *****************************************************
432  ****************************************************************/
433    R->AF.B.l&=~C_FLAG;
434    return;
435  
436  default:
437    //printf("Unknown BIOS trap called at PC=%04Xh\n",R->PC.W-2);
438    break;
439    }
440  }