1 /** EMULib Emulation Library *********************************/
2 /**                                                         **/
3 /**                        FDIDisk.c                        **/
4 /**                                                         **/
5 /** This file contains routines to load, save, and access   **/
6 /** disk images in various formats. The internal format is  **/
7 /** always .FDI. See FDIDisk.h for declarations.            **/
8 /**                                                         **/
9 /** Copyright (C) Marat Fayzullin 2007-2014                 **/
10 /**     You are not allowed to distribute this software     **/
11 /**     commercially. Please, notify me, if you make any    **/
12 /**     changes to this file.                               **/
13 /*************************************************************/
14 #include "FDIDisk.h"
15 #include <stdio.h>
16 #include <string.h>
17 #include <stdlib.h>
18 #ifdef _WIN32
19 #include <direct.h>
20 #else
21 #include <unistd.h>
22 #endif
23 #include <ctype.h>
24 
25 #define IMAGE_SIZE(Fmt) \
26   (Formats[Fmt].Sides*Formats[Fmt].Tracks*    \
27    Formats[Fmt].Sectors*Formats[Fmt].SecSize)
28 
29 #define FDI_SIDES(P)      ((P)[6]+((int)((P)[7])<<8))
30 #define FDI_TRACKS(P)     ((P)[4]+((int)((P)[5])<<8))
31 #define FDI_DIR(P)        ((P)+(P)[12]+((int)((P)[13])<<8)+14)
32 #define FDI_DATA(P)       ((P)+(P)[10]+((int)((P)[11])<<8))
33 #define FDI_INFO(P)       ((P)+(P)[8]+((int)((P)[9])<<8))
34 #define FDI_SECTORS(T)    ((T)[6])
35 #define FDI_TRACK(P,T)    (FDI_DATA(P)+(T)[0]+((int)((T)[1])<<8)+((int)((T)[2])<<16)+((int)((T)[3])<<24))
36 #define FDI_SECSIZE(S)    (SecSizes[(S)[3]<=4? (S)[3]:4])
37 #define FDI_SECTOR(P,T,S) (FDI_TRACK(P,T)+(S)[5]+((int)((S)[6])<<8))
38 
39 #ifdef _MSC_VER
40 #undef unlink
41 #define unlink _unlink
42 #endif
43 
44 static const struct { int Sides,Tracks,Sectors,SecSize; } Formats[] =
45 {
46   { 2,80,16,256 }, /* Dummy format */
47   { 2,80,10,512 }, /* FMT_IMG can be 256 */
48   { 2,80,10,512 }, /* FMT_MGT can be 256 */
49   { 2,80,16,256 }, /* FMT_TRD */
50   { 2,80,10,512 }, /* FMT_FDI */
51   { 2,80,16,256 }, /* FMT_SCL */
52   { 2,80,16,256 }, /* FMT_HOBETA */
53   { 2,80,9,512 },  /* FMT_DSK */
54   { 2,80,9,512 },  /* FMT_CPCDSK */
55   { 1,40,16,256 }  /* FMT_SF7000 */
56 };
57 
58 static const int SecSizes[] =
59 { 128,256,512,1024,4096,0 };
60 
61 static const char FDIDiskLabel[] =
62 "Disk image created by EMULib (C)Marat Fayzullin";
63 
64 static const byte TRDDiskInfo[] =
65 {
66   0x01,0x16,0x00,0xF0,0x09,0x10,0x00,0x00,
67   0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
68   0x20,0x00,0x00,0x64,0x69,0x73,0x6B,0x6E,
69   0x61,0x6D,0x65,0x00,0x00,0x00,0x46,0x55
70 };
71 
stricmpn(const char * S1,const char * S2,int Limit)72 static int stricmpn(const char *S1,const char *S2,int Limit)
73 {
74   for(;*S1&&*S2&&Limit&&(toupper(*S1)==toupper(*S2));++S1,++S2,--Limit);
75   return(Limit? toupper(*S1)-toupper(*S2):0);
76 }
77 
78 /** InitFDI() ************************************************/
79 /** Clear all data structure fields.                        **/
80 /*************************************************************/
InitFDI(FDIDisk * D)81 void InitFDI(FDIDisk *D)
82 {
83   D->Format   = 0;
84   D->Data     = 0;
85   D->DataSize = 0;
86   D->Sides    = 0;
87   D->Tracks   = 0;
88   D->Sectors  = 0;
89   D->SecSize  = 0;
90 }
91 
92 /** EjectFDI() ***********************************************/
93 /** Eject disk image. Free all allocated memory.            **/
94 /*************************************************************/
EjectFDI(FDIDisk * D)95 void EjectFDI(FDIDisk *D)
96 {
97   if(D->Data) free(D->Data);
98   InitFDI(D);
99 }
100 
101 /** NewFDI() *************************************************/
102 /** Allocate memory and create new .FDI disk image of given **/
103 /** dimensions. Returns disk data pointer on success, 0 on  **/
104 /** failure.                                                **/
105 /*************************************************************/
NewFDI(FDIDisk * D,int Sides,int Tracks,int Sectors,int SecSize)106 byte *NewFDI(FDIDisk *D,int Sides,int Tracks,int Sectors,int SecSize)
107 {
108   byte *P,*DDir;
109   int I,J,K,L,N;
110 
111   /* Find sector size code */
112   for(L=0;SecSizes[L]&&(SecSizes[L]!=SecSize);++L);
113   if(!SecSizes[L]) return(0);
114 
115   /* Allocate memory */
116   K = Sides*Tracks*Sectors*SecSize+sizeof(FDIDiskLabel);
117   I = Sides*Tracks*(Sectors+1)*7+14;
118   if(!(P=(byte *)malloc(I+K))) return(0);
119   memset(P,0x00,I+K);
120 
121   /* Eject previous disk image */
122   EjectFDI(D);
123 
124   /* Set disk dimensions according to format */
125   D->Format   = FMT_FDI;
126   D->Data     = P;
127   D->DataSize = I+K;
128   D->Sides    = Sides;
129   D->Tracks   = Tracks;
130   D->Sectors  = Sectors;
131   D->SecSize  = SecSize;
132 
133   /* .FDI magic number */
134   memcpy(P,"FDI",3);
135   /* Disk description */
136   memcpy(P+I,FDIDiskLabel,sizeof(FDIDiskLabel));
137   /* Write protection (1=ON) */
138   P[3]  = 0;
139   P[4]  = Tracks&0xFF;
140   P[5]  = Tracks>>8;
141   P[6]  = Sides&0xFF;
142   P[7]  = Sides>>8;
143   /* Disk description offset */
144   P[8]  = I&0xFF;
145   P[9]  = I>>8;
146   I    += sizeof(FDIDiskLabel);
147   /* Sector data offset */
148   P[10] = I&0xFF;
149   P[11] = I>>8;
150   /* Track directory offset */
151   P[12] = 0;
152   P[13] = 0;
153 
154   /* Create track directory */
155   for(J=K=0,DDir=P+14;J<Sides*Tracks;++J,K+=Sectors*SecSize)
156   {
157     /* Create track entry */
158     DDir[0] = K&0xFF;
159     DDir[1] = (K>>8)&0xFF;
160     DDir[2] = (K>>16)&0xFF;
161     DDir[3] = (K>>24)&0xFF;
162     /* Reserved bytes */
163     DDir[4] = 0;
164     DDir[5] = 0;
165     DDir[6] = Sectors;
166     /* For all sectors on a track... */
167     for(I=N=0,DDir+=7;I<Sectors;++I,DDir+=7,N+=SecSize)
168     {
169       /* Create sector entry */
170       DDir[0] = J/Sides;
171       DDir[1] = J%Sides;
172       DDir[2] = I+1;
173       DDir[3] = L;
174       /* CRC marks and "deleted" bit (D00CCCCC) */
175       DDir[4] = (1<<L);
176       DDir[5] = N&0xFF;
177       DDir[6] = N>>8;
178     }
179   }
180 
181   /* Done */
182   return(FDI_DATA(P));
183 }
184 
185 /** LoadFDI() ************************************************/
186 /** Load a disk image from a given file, in a given format  **/
187 /** (see FMT_* #defines). Guess format from the file name   **/
188 /** when Format=FMT_AUTO. Returns format ID on success or   **/
189 /** 0 on failure. When FileName=0, ejects the disk freeing  **/
190 /** memory and returns 0.                                   **/
191 /*************************************************************/
LoadFDI(FDIDisk * D,const char * FileName,int Format)192 int LoadFDI(FDIDisk *D,const char *FileName,int Format)
193 {
194   byte Buf[256],*P,*DDir;
195   const char *T;
196   int J,I,K,L,N;
197   FILE *F;
198 
199   /* If just ejecting a disk, drop out */
200   if(!FileName) { EjectFDI(D);return(0); }
201 
202   /* If requested automatic format recognition... */
203   if(!Format)
204   {
205     /* Recognize disk image format */
206     T = strrchr(FileName,'\\');
207     T = T? T:strrchr(FileName,'/');
208     T = T? T+1:FileName;
209     T = strchr(T,'.');
210     Format = !T? 0
211            : !stricmpn(T,".FDI",4)? FMT_FDI
212            : !stricmpn(T,".IMG",4)? FMT_IMG
213            : !stricmpn(T,".MGT",4)? FMT_MGT
214            : !stricmpn(T,".TRD",4)? FMT_TRD
215            : !stricmpn(T,".SCL",4)? FMT_SCL
216            : !stricmpn(T,".DSK",4)? FMT_DSK
217            : !stricmpn(T,".$",2)?   FMT_HOBETA
218            : 0;
219 
220     /* Try loading by extension */
221     if(Format&&(J=LoadFDI(D,FileName,Format))) return(J);
222 
223     /* Attention: FMT_DSK and FMT_CPCDSK share the same .DSK extension */
224     if((Format!=FMT_CPCDSK)&&LoadFDI(D,FileName,FMT_CPCDSK)) return(FMT_CPCDSK);
225 
226     /* Try loading by magic number */
227     if((Format!=FMT_FDI)&&LoadFDI(D,FileName,FMT_FDI)) return(FMT_FDI);
228     if((Format!=FMT_SCL)&&LoadFDI(D,FileName,FMT_SCL)) return(FMT_SCL);
229     if((Format!=FMT_DSK)&&LoadFDI(D,FileName,FMT_DSK)) return(FMT_DSK);
230 
231     /* Everything failed */
232     return(0);
233   }
234 
235   /* Open file and find its size */
236   if(!(F=fopen(FileName,"rb"))) return(0);
237   if(fseek(F,0,SEEK_END)<0) { fclose(F);return(0); }
238   if((J=ftell(F))<=0)       { fclose(F);return(0); }
239   rewind(F);
240 
241   switch(Format)
242   {
243     case FMT_FDI: /* If .FDI format... */
244       /* Allocate memory and read file */
245       if(!(P=(byte *)malloc(J))) { fclose(F);return(0); }
246       if(fread(P,1,J,F)!=J)      { free(P);fclose(F);return(0); }
247       /* Verify .FDI format tag */
248       if(memcmp(P,"FDI",3))      { free(P);fclose(F);return(0); }
249       /* Eject current disk image */
250       EjectFDI(D);
251       /* Read disk dimensions */
252       D->Sides   = FDI_SIDES(P);
253       D->Tracks  = FDI_TRACKS(P);
254       D->Sectors = 0;
255       D->SecSize = 0;
256       /* Check number of sectors and sector size */
257       for(J=FDI_SIDES(P)*FDI_TRACKS(P),DDir=FDI_DIR(P);J;--J)
258       {
259         /* Get number of sectors */
260         I=FDI_SECTORS(DDir);
261         /* Check that all tracks have the same number of sectors */
262         if(!D->Sectors) D->Sectors=I; else if(D->Sectors!=I) break;
263         /* Check that all sectors have the same size */
264         for(DDir+=7;I;--I,DDir+=7)
265           if(!D->SecSize) D->SecSize=FDI_SECSIZE(DDir);
266           else if(D->SecSize!=FDI_SECSIZE(DDir)) break;
267         /* Drop out if the sector size is not uniform */
268         if(I) break;
269       }
270       /* If no uniform sectors or sector size, set them to zeros */
271       if(J) D->Sectors=D->SecSize=0;
272       break;
273 
274     case FMT_DSK: /* If .DSK format... */
275       /* Read header */
276       if(fread(Buf,1,32,F)!=32) { fclose(F);return(0); }
277       /* Check magic number */
278       if((Buf[0]!=0xE9)&&(Buf[0]!=0xEB)) { fclose(F);return(0); }
279       /* Check media descriptor */
280       if(Buf[21]<0xF8) { fclose(F);return(0); }
281       /* Compute disk geometry */
282       K = Buf[26]+((int)Buf[27]<<8);       /* Heads   */
283       N = Buf[24]+((int)Buf[25]<<8);       /* Sectors */
284       L = Buf[11]+((int)Buf[12]<<8);       /* SecSize */
285       I = Buf[19]+((int)Buf[20]<<8);       /* Total S.*/
286       I = K&&N? I/K/N:0;                   /* Tracks  */
287       /* Number of heads CAN BE WRONG */
288       K = I&&N&&L? J/I/N/L:0;
289       /* Create a new disk image */
290       P = NewFDI(D,K,I,N,L);
291       if(!P) { fclose(F);return(0); }
292       /* Make sure we do not read too much data */
293       I = K*I*N*L;
294       J = J>I? I:J;
295       /* Read disk image file (ignore short reads!) */
296       rewind(F);
297       fread(P,1,J,F);
298       /* Done */
299       P = D->Data;
300       break;
301 
302     case FMT_CPCDSK: /* If Amstrad CPC .DSK format... */
303       /* Read header (first track is next) */
304       if(fread(Buf,1,256,F)!=256) { fclose(F);return(0); }
305       /* Check magic string */
306       if(memcmp(Buf,"MV - CPC",8)&&memcmp(Buf,"EXTENDED CPC DSK File",21))
307       { fclose(F);return(0); }
308       /* Compute disk geometry */
309       I = Buf[48];                   /* Tracks  */
310       K = Buf[49];                   /* Heads   */
311       N = Formats[Format].Sectors;   /* Maximal number of sectors */
312       L = Buf[50]+((int)Buf[51]<<8); /* Track size + 0x100 */
313       /* Extended CPC .DSK format lists sizes by track */
314       if(!L)
315         for(J=0;J<I;++J)
316           if(L<((int)Buf[J+52]<<8)) L=(int)Buf[J+52]<<8;
317       /* Maximal sector size */
318       L = (L-0x100+N-1)/N;
319       /* Check geometry */
320 //printf("Tracks=%d, Heads=%d, Sectors=%d, SectorSize=%d\n",I,K,N,L);
321       if(!K||!N||!L||!I) { fclose(F);return(0); }
322       /* Create a new disk image */
323       if(!NewFDI(D,K,I,N,L)) { fclose(F);return(0); }
324       /* Sectors-per-track and bytes-per-sector may vary */
325       D->Sectors = 0;
326       D->SecSize = 0;
327       /* We are rewriting .FDI directory and data */
328       DDir = FDI_DIR(D->Data);
329       P    = FDI_DATA(D->Data);
330       /* Skip to the first track info block */
331       fseek(F,0x100,SEEK_SET);
332       /* Read tracks */
333       for(I*=K;I;--I)
334       {
335         /* Read track header */
336         if(fread(Buf,1,0x18,F)!=0x18) break;
337         /* Check magic string */
338         if(memcmp(Buf,"Track-Info\r\n",12)) break;
339         /* Compute track geometry */
340         N = Buf[21];             /* Sectors */
341         L = Buf[20];             /* SecSize */
342         J = P-FDI_DATA(D->Data); /* Data offset */
343         /* Create .FDI track entry */
344         DDir[0] = J&0xFF;
345         DDir[1] = (J>>8)&0xFF;
346         DDir[2] = (J>>16)&0xFF;
347         DDir[3] = (J>>24)&0xFF;
348         DDir[4] = 0;
349         DDir[5] = 0;
350         DDir[6] = N;
351         /* Read sector headers */
352         for(DDir+=7,J=N,K=0;J&&(fread(Buf,8,1,F)==8);DDir+=7,--J,K+=SecSizes[L])
353         {
354           /* Create .FDI sector entry */
355           DDir[0] = Buf[0];
356           DDir[1] = Buf[1];
357           DDir[2] = Buf[2];
358           DDir[3] = Buf[3];
359           DDir[4] = (1<<L)|(~Buf[4]&0x80);
360           DDir[5] = K&0xFF;
361           DDir[6] = K>>8;
362         }
363         /* Seek to the track data */
364         if(fseek(F,0x100-0x18-8*N,SEEK_CUR)<0) break;
365         /* Read track data */
366         if(fread(P,1,K,F)!=K) break; else P+=K;
367       }
368       /* Done */
369       P = D->Data;
370       break;
371 
372     case FMT_TRD:    /* If .TRD format... */
373     case FMT_MGT:    /* If .MGT format... */
374     case FMT_SF7000: /* If .SF format...  */
375       /* Create a new disk image */
376       P = NewFDI(
377             D,
378             Formats[Format].Sides,
379             Formats[Format].Tracks,
380             Formats[Format].Sectors,
381             Formats[Format].SecSize
382           );
383       if(!P) { fclose(F);return(0); }
384       /* Make sure we do not read too much data */
385       J = J>IMAGE_SIZE(Format)? IMAGE_SIZE(Format):J;
386       /* Read disk image file (ignore short reads!) */
387       fread(P,1,J,F);
388       /* Done */
389       P = D->Data;
390       break;
391 
392     case FMT_IMG: /* If .IMG format... */
393       /* Create a new disk image */
394       P = NewFDI(
395             D,
396             Formats[Format].Sides,
397             Formats[Format].Tracks,
398             Formats[Format].Sectors,
399             Formats[Format].SecSize
400           );
401       if(!P) { fclose(F);return(0); }
402       /* Read disk image file track-by-track */
403       K = Formats[Format].Tracks;
404       L = Formats[Format].Sectors*Formats[Format].SecSize;
405       I = Formats[Format].Tracks*Formats[Format].Sides;
406       for(J=0;J<I;++J)
407         if(fread(P+L*2*(J%K)+(J>=K? L:0),1,L,F)!=L) break;
408       /* Done */
409       P = D->Data;
410       break;
411 
412     case FMT_SCL: /* If .SCL format... */
413       /* @@@ NEED TO CHECK CHECKSUM AT THE END */
414       /* Read header */
415       if(fread(Buf,1,9,F)!=9) { fclose(F);return(0); }
416       /* Verify .SCL format tag and the number of files */
417       if(memcmp(Buf,"SINCLAIR",8)||(Buf[8]>128)) { fclose(F);return(0); }
418       /* Create a new disk image */
419       P = NewFDI(
420             D,
421             Formats[Format].Sides,
422             Formats[Format].Tracks,
423             Formats[Format].Sectors,
424             Formats[Format].SecSize
425           );
426       if(!P) { fclose(F);return(0); }
427       /* Compute the number of free sectors */
428       I = D->Sides*D->Tracks*D->Sectors;
429       /* Build directory, until we run out of disk space */
430       for(J=0,K=D->Sectors,DDir=P;(J<Buf[8])&&(K<I);++J)
431       {
432         /* Read .SCL directory entry */
433         if(fread(DDir,1,14,F)!=14) break;
434         /* Compute sector and track */
435         DDir[14] = K%D->Sectors;
436         DDir[15] = K/D->Sectors;
437         /* Next entry */
438         K       += DDir[13];
439         DDir    += 16;
440       }
441       /* Skip over remaining directory entries */
442       if(J<Buf[8]) fseek(F,(Buf[8]-J)*14,SEEK_CUR);
443       /* Build disk information */
444       memset(P+J*16,0,D->Sectors*D->SecSize-J*16);
445       memcpy(P+0x08E2,TRDDiskInfo,sizeof(TRDDiskInfo));
446       strncpy((char *)P+0x08F5,"SPECCY",8);
447       P[0x8E1] = K%D->Sectors;  /* First free sector */
448       P[0x8E2] = K/D->Sectors;  /* Track it belongs to */
449       P[0x8E3] = 0x16 + (D->Tracks>40? 0:1) + (D->Sides>1? 0:2);
450       P[0x8E4] = J;             /* Number of files */
451       P[0x8E5] = (I-K)&0xFF;    /* Number of free sectors */
452       P[0x8E6] = (I-K)>>8;
453       /* Read data */
454       for(DDir=P;J;--J,DDir+=16)
455       {
456         /* Determine data offset and size */
457         I = (DDir[15]*D->Sectors+DDir[14])*D->SecSize;
458         N = DDir[13]*D->SecSize;
459         /* Read .SCL data (ignore short reads!) */
460         fread(P+I,1,N,F);
461       }
462       /* Done */
463       P = D->Data;
464       break;
465 
466     case FMT_HOBETA: /* If .$* format... */
467       /* Create a new disk image */
468       P = NewFDI(
469             D,
470             Formats[Format].Sides,
471             Formats[Format].Tracks,
472             Formats[Format].Sectors,
473             Formats[Format].SecSize
474           );
475       if(!P) { fclose(F);return(0); }
476       /* Read header */
477       if(fread(P,1,17,F)!=17) { fclose(F);return(0); }
478       /* Determine data offset and size */
479       I = D->Sectors*D->SecSize;
480       N = P[13]+((int)P[14]<<8);
481       /* Build disk information */
482       memset(P+16,0,I-16);
483       memcpy(P+0x08E2,TRDDiskInfo,sizeof(TRDDiskInfo));
484       strncpy((char*)P+0x08F5,"SPECCY",8);
485       K        = D->Sectors+N;
486       J        = D->Sectors*D->Tracks*D->Sides-K;
487       P[0x8E1] = K%D->Sectors;  /* First free sector */
488       P[0x8E2] = K/D->Sectors;  /* Track it belongs to */
489       P[0x8E3] = 0x16 + (D->Tracks>40? 0:1) + (D->Sides>1? 0:2);
490       P[0x8E4] = 1;             /* Number of files */
491       P[0x8E5] = J&0xFF;        /* Number of free sectors */
492       P[0x8E6] = J>>8;
493       N        = N*D->SecSize;  /* N is now in bytes */
494       /* Read data (ignore short reads!) */
495       fread(P+I,1,N,F);
496       /* Compute and check checksum */
497       for(L=I=0;I<15;++I) L+=P[I];
498       L = ((L*257+105)&0xFFFF)-P[15]-((int)P[16]<<8);
499       if(L) { /* @@@ DO SOMETHING BAD! */ }
500       /* Place file at track #1 sector #0, limit its size to 255 sectors */
501       P[13] = P[14]? 255:P[13];
502       P[14] = 0;
503       P[15] = 1;
504       P[16] = 0;
505       /* Done */
506       P = D->Data;
507       break;
508 
509     default:
510       /* Format not recognized */
511       return(0);
512   }
513 
514   if(D->Verbose)
515     printf(
516       "LoadFDI(): Loaded '%s', %d sides x %d tracks x %d sectors x %d bytes\n",
517       FileName,D->Sides,D->Tracks,D->Sectors,D->SecSize
518     );
519 
520   /* Done */
521   fclose(F);
522   D->Data   = P;
523   D->Format = Format;
524   return(Format);
525 }
526 
527 /** SaveFDI() ************************************************/
528 /** Save a disk image to a given file, in a given format    **/
529 /** (see FMT_* #defines). Use the original format when      **/
530 /** when Format=FMT_AUTO. Returns format ID on success or   **/
531 /** 0 on failure.                                           **/
532 /*************************************************************/
SaveFDI(FDIDisk * D,const char * FileName,int Format)533 int SaveFDI(FDIDisk *D,const char *FileName,int Format)
534 {
535   byte S[32];
536   int I,J,K,C,L;
537   FILE *F;
538   byte *P,*T;
539 
540   /* Must have a disk to save */
541   if(!D->Data) return(0);
542   /* Use original format if requested */
543   if(!Format) Format=D->Format;
544   /* Open file for writing */
545   if(!(F=fopen(FileName,"wb"))) return(0);
546 
547   /* Depending on the format... */
548   switch(Format)
549   {
550     case FMT_FDI:
551       if(fwrite(D->Data,1,D->DataSize,F)!=D->DataSize)
552       {
553          fclose(F);
554          unlink(FileName);
555          return(0);
556       }
557       break;
558 
559     case FMT_IMG:
560       /* Check the number of tracks and sides */
561       if((FDI_TRACKS(D->Data)!=Formats[Format].Tracks)||(FDI_SIDES(D->Data)!=Formats[Format].Sides))
562       {
563          fclose(F);
564          unlink(FileName);
565          return(0);
566       }
567       /* Scan through all sides,tracks,sectors */
568       L=Formats[Format].SecSize;
569       for(I=0;I<Formats[Format].Sides;++I)
570         for(J=0;J<Formats[Format].Tracks;++J)
571           for(K=0;K<Formats[Format].Sectors;++K)
572           {
573             P = SeekFDI(D,I,J,I,J,K+1);
574             C = D->SecSize<L? D->SecSize:L;
575             if(!P||(fwrite(P,1,C,F)!=C)) break;
576           }
577       /* If failed to write all sectors, clean up */
578       if(I<Formats[Format].Sides)
579       {
580          fclose(F);
581          unlink(FileName);
582          return(0);
583       }
584       break;
585 
586     case FMT_TRD:
587     case FMT_MGT:
588     case FMT_SF7000:
589       /* Check the number of tracks and sides */
590       if((FDI_TRACKS(D->Data)!=Formats[Format].Tracks)||(FDI_SIDES(D->Data)!=Formats[Format].Sides))
591       {
592          fclose(F);
593          unlink(FileName);
594          return(0);
595       }
596     case FMT_DSK:
597       /* Scan through all tracks */
598       J = FDI_SIDES(D->Data)*FDI_TRACKS(D->Data);
599       for(P=FDI_DIR(D->Data);J;--J,P=T)
600       {
601         /* Compute total track length for this format */
602         L = Formats[Format].Sectors*Formats[Format].SecSize;
603         /* For every sector on a track, if track length remains... */
604         for(I=FDI_SECTORS(P),T=P+7;I;--I,T+=7)
605           if(L)
606           {
607             /* Write out a sector */
608             K = FDI_SECSIZE(T);
609             K = K>L? L:K;
610             L-= K;
611             if(fwrite(FDI_SECTOR(D->Data,P,T),1,K,F)!=K)
612             {
613                fclose(F);
614                unlink(FileName);
615                return(0);
616             }
617           }
618         /* Fill remaining track length with zeros */
619         if(L>0) fseek(F,L,SEEK_CUR);
620       }
621       /* Done */
622       break;
623 
624     case FMT_SCL:
625       /* Get data pointer */
626       T=FDI_DATA(D->Data);
627       /* Check tracks, sides, sectors, and the TR-DOS magic number */
628       if((FDI_SIDES(D->Data)!=Formats[Format].Sides)
629        ||(FDI_TRACKS(D->Data)!=Formats[Format].Tracks)
630        ||(D->Sectors!=Formats[Format].Sectors)
631        ||(D->SecSize!=Formats[Format].SecSize)
632        ||(T[0x8E3]!=0x16)
633       )
634       {
635          fclose(F);
636          unlink(FileName);
637          return(0);
638       }
639       /* Write header */
640       strcpy((char *)S,"SINCLAIR");
641       S[8]=T[0x8E4];
642       if(fwrite(S,1,9,F)!=9)
643       {
644          fclose(F);
645          unlink(FileName);
646          return(0);
647       }
648       for(C=I=0;I<9;++I) C+=S[I];
649       /* Write directory entries */
650       for(J=0,P=T;J<T[0x8E4];++J,P+=16)
651       {
652         if(fwrite(P,1,14,F)!=14)
653         {
654            fclose(F);
655            unlink(FileName);
656            return(0);
657         }
658         for(I=0;I<14;++I) C+=P[I];
659       }
660       /* Write files */
661       for(J=0,P=T;J<T[0x8E4];++J,P+=16)
662       {
663         /* Determine data offset and size */
664         K = (P[15]*D->Sectors+P[14])*D->SecSize;
665         I = P[13]*D->SecSize;
666         /* Write data */
667         if(fwrite(T+K,1,I,F)!=I)
668         {
669            fclose(F);
670            unlink(FileName);
671            return(0);
672         }
673         /* Compute checksum */
674         for(L=K,I+=K;L<I;++L) C+=T[L];
675       }
676       /* Write checksum */
677       S[0] = C&0xFF;
678       S[1] = (C>>8)&0xFF;
679       S[2] = (C>>16)&0xFF;
680       S[3] = (C>>24)&0xFF;
681       if(fwrite(S,1,4,F)!=4)
682       {
683          fclose(F);
684          unlink(FileName);
685          return(0);
686       }
687       /* Done */
688       break;
689 
690     case FMT_HOBETA:
691       /* Get data pointer */
692       T=FDI_DATA(D->Data);
693       /* Check tracks, sides, sectors, and the TR-DOS magic number */
694       if((FDI_SIDES(D->Data)!=Formats[Format].Sides)
695        ||(FDI_TRACKS(D->Data)!=Formats[Format].Tracks)
696        ||(D->Sectors!=Formats[Format].Sectors)
697        ||(D->SecSize!=Formats[Format].SecSize)
698        ||(T[0x8E3]!=0x16)
699       )
700       {
701          fclose(F);
702          unlink(FileName);
703          return(0);
704       }
705       /* Look for the first file */
706       for(J=0,P=T;(J<T[0x8E4])&&!P[0];++J,P+=16);
707       /* If not found, drop out */
708       if(J>=T[0x8E4])
709       {
710          fclose(F);
711          unlink(FileName);
712          return(0);
713       }
714       /* Copy header */
715       memcpy(S,P,14);
716       /* Get single file address and size */
717       I = P[13]*D->SecSize;
718       P = T+(P[14]+D->Sectors*P[15])*D->SecSize;
719       /* Compute checksum and build header */
720       for(C=J=0;J<14;++J) C+=P[J];
721       S[14] = 0;
722       C     = (C+S[14])*257+105;
723       S[15] = C&0xFF;
724       S[16] = (C>>8)&0xFF;
725       /* Write header */
726       if(fwrite(S,1,17,F)!=17)
727       {
728          fclose(F);
729          unlink(FileName);
730          return(0);
731       }
732       /* Write file data */
733       if(fwrite(P,1,I,F)!=I)
734       {
735          fclose(F);
736          unlink(FileName);
737          return(0);
738       }
739       /* Done */
740       break;
741 
742     default:
743       /* Can't save this format for now */
744       fclose(F);
745       unlink(FileName);
746       return(0);
747   }
748 
749   /* Done */
750   fclose(F);
751   return(Format);
752 }
753 
754 /** SeekFDI() ************************************************/
755 /** Seek to given side/track/sector. Returns sector address **/
756 /** on success or 0 on failure.                             **/
757 /*************************************************************/
SeekFDI(FDIDisk * D,int Side,int Track,int SideID,int TrackID,int SectorID)758 byte *SeekFDI(FDIDisk *D,int Side,int Track,int SideID,int TrackID,int SectorID)
759 {
760   byte *P,*T;
761   int J;
762 
763   /* Have to have disk mounted */
764   if(!D||!D->Data) return(0);
765 
766   switch(D->Format)
767   {
768     case FMT_TRD:
769     case FMT_DSK:
770     case FMT_SCL:
771     case FMT_FDI:
772     case FMT_MGT:
773     case FMT_IMG:
774     case FMT_CPCDSK:
775     case FMT_SF7000:
776       /* Track directory */
777       P = FDI_DIR(D->Data);
778       /* Find current track entry */
779       for(J=Track*D->Sides+Side%D->Sides;J;--J) P+=(FDI_SECTORS(P)+1)*7;
780       /* Find sector entry */
781       for(J=FDI_SECTORS(P),T=P+7;J;--J,T+=7)
782         if((T[0]==TrackID)&&(T[1]==SideID)&&(T[2]==SectorID)) break;
783       /* Fall out if not found */
784       if(!J) return(0);
785       /* FDI stores a header for each sector */
786       D->Header[0] = T[0];
787       D->Header[1] = T[1];
788       D->Header[2] = T[2];
789       D->Header[3] = T[3]<=3? T[3]:3;
790       D->Header[4] = T[4];
791       D->Header[5] = 0x00;
792       /* FDI has variable sector numbers and sizes */
793       D->Sectors   = FDI_SECTORS(P);
794       D->SecSize   = FDI_SECSIZE(T);
795       return(FDI_SECTOR(D->Data,P,T));
796   }
797 
798   /* Unknown format */
799   return(0);
800 }
801 
802