Imported Upstream version 2.5
[debian/cpmtools] / fsed.cpm.c
1 /* #includes */ /*{{{C}}}*//*{{{*/
2 #include "config.h"
3
4 #include <assert.h>
5 #include <ctype.h>
6 #include <curses.h>
7 #include <errno.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <string.h>
11
12 #include "cpmfs.h"
13 /*}}}*/
14
15 extern char **environ;
16
17 static char *mapbuf;
18
19 static struct tm *cpmtime(char lday, char hday, char hour, char min) /*{{{*/
20 {
21   static struct tm tm;
22   unsigned long days=(lday&0xff)|((hday&0xff)<<8);
23   int d;
24   int md[12]={31,0,31,30,31,30,31,31,30,31,30,31};
25
26   tm.tm_sec=0;
27   tm.tm_min=((min>>4)&0xf)*10+(min&0xf);
28   tm.tm_hour=((hour>>4)&0xf)*10+(hour&0xf);
29   tm.tm_mon=0;
30   tm.tm_year=1978;
31   tm.tm_isdst=-1;
32   if (days) --days;
33   while (days>=(d=(((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 366 : 365)))
34   {
35     days-=d;
36     ++tm.tm_year;
37   }
38   md[1]=((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 29 : 28;
39   while (days>=md[tm.tm_mon])
40   {
41     days-=md[tm.tm_mon];
42     ++tm.tm_mon;
43   }
44   tm.tm_mday=days+1;
45   tm.tm_year-=1900;
46   return &tm;
47 }
48 /*}}}*/
49 static void info(struct cpmSuperBlock *sb, const char *format, const char *image) /*{{{*/
50 {
51   const char *msg;
52
53   clear();
54   msg="File system characteristics";
55   move(0,(COLS-strlen(msg))/2); printw(msg);
56   move(2,0); printw("                      Image: %s",image);
57   move(3,0); printw("                     Format: %s",format);
58   move(4,0); printw("                File system: ");
59   switch (sb->type)
60   {
61     case CPMFS_DR22: printw("CP/M 2.2"); break;
62     case CPMFS_P2DOS: printw("P2DOS 2.3"); break;
63     case CPMFS_DR3: printw("CP/M Plus"); break;
64   }
65
66   move(6,0); printw("              Sector length: %d",sb->secLength);
67   move(7,0); printw("           Number of tracks: %d",sb->tracks);
68   move(8,0); printw("          Sectors per track: %d",sb->sectrk);
69
70   move(10,0);printw("                 Block size: %d",sb->blksiz);
71   move(11,0);printw("Number of directory entries: %d",sb->maxdir);
72   move(12,0);printw("        Logical sector skew: %d",sb->skew);
73   move(13,0);printw("    Number of system tracks: %d",sb->boottrk);
74   move(14,0);printw(" Logical extents per extent: %d",sb->extents);
75   move(15,0);printw("    Allocatable data blocks: %d",sb->size-(sb->maxdir*32+sb->blksiz-1)/sb->blksiz);
76
77   msg="Any key to continue";
78   move(23,(COLS-strlen(msg))/2); printw(msg);
79   getch();
80 }
81 /*}}}*/
82 static void map(struct cpmSuperBlock *sb) /*{{{*/
83 {
84   const char *msg;
85   char bmap[18*80];
86   int secmap,pos,sys,directory;
87
88   clear();
89   msg="Data map";
90   move(0,(COLS-strlen(msg))/2); printw(msg);
91
92   secmap=(sb->tracks*sb->sectrk+80*18-1)/(80*18);
93   memset(bmap,' ',sizeof(bmap));
94   sys=sb->boottrk*sb->sectrk;
95   memset(bmap,'S',sys/secmap);
96   directory=(sb->maxdir*32+sb->secLength-1)/sb->secLength;
97   memset(bmap+sys/secmap,'D',directory/secmap);
98   memset(bmap+(sys+directory)/secmap,'.',sb->sectrk*sb->tracks/secmap);
99
100   for (pos=0; pos<(sb->maxdir*32+sb->secLength-1)/sb->secLength; ++pos)
101   {
102     int entry;
103
104     Device_readSector(&sb->dev,sb->boottrk+pos/(sb->sectrk*sb->secLength),pos/sb->secLength,mapbuf);
105     for (entry=0; entry<sb->secLength/32 && (pos*sb->secLength/32)+entry<sb->maxdir; ++entry)
106     {
107       int i;
108
109       if (mapbuf[entry*32]>=0 && mapbuf[entry*32]<=(sb->type==CPMFS_P2DOS ? 31 : 15))
110       {
111         for (i=0; i<16; ++i)
112         {
113           unsigned int sector;
114
115           sector=mapbuf[entry*32+16+i]&0xff;
116           if (sb->size>=256) sector|=(((mapbuf[entry*32+16+ ++i]&0xff)<<8));
117           if (sector>0 && sector<=sb->size)
118           {
119             /* not entirely correct without the last extent record count */
120             sector=sector*(sb->blksiz/sb->secLength)+sb->sectrk*sb->boottrk;
121             memset(bmap+sector/secmap,'#',sb->blksiz/(sb->secLength*secmap));
122           }
123         }
124       }
125     }
126   }
127
128   for (pos=0; pos<sizeof(bmap); ++pos)
129   {
130     move(2+pos%18,pos/18);
131     addch(bmap[pos]);
132   }
133   move(21,0); printw("S=System area   D=Directory area   #=File data   .=Free");
134   msg="Any key to continue";
135   move(23,(COLS-strlen(msg))/2); printw(msg);
136   getch();
137 }
138 /*}}}*/
139 static void data(struct cpmSuperBlock *sb, const char *buf, unsigned long int pos) /*{{{*/
140 {
141   int offset=(pos%sb->secLength)&~0x7f;
142   int i;
143
144   for (i=0; i<128; ++i)
145   {
146     move(4+(i>>4),(i&0x0f)*3+!!(i&0x8)); printw("%02x",buf[i+offset]&0xff);
147     if (pos%sb->secLength==i+offset) attron(A_REVERSE);
148     move(4+(i>>4),50+(i&0x0f)); printw("%c",isprint(buf[i+offset]) ? buf[i+offset] : '.');
149     attroff(A_REVERSE);
150   }
151   move(4+((pos&0x7f)>>4),((pos&0x7f)&0x0f)*3+!!((pos&0x7f)&0x8)+1);
152 }
153 /*}}}*/
154
155 const char cmd[]="fsed.cpm";
156
157 int main(int argc, char *argv[]) /*{{{*/
158 {
159   /* variables */ /*{{{*/
160   const char *devopts=(const char*)0;
161   char *image;
162   const char *err;
163   struct cpmSuperBlock drive;
164   struct cpmInode root;
165   const char *format=FORMAT;
166   int c,usage=0;
167   unsigned long pos;
168   chtype ch;
169   int reload;
170   char *buf;
171   /*}}}*/
172
173   /* parse options */ /*{{{*/
174   while ((c=getopt(argc,argv,"f:h?"))!=EOF) switch(c)
175   {
176     case 'f': format=optarg; break;
177     case 'T': devopts=optarg; break;
178     case 'h':
179     case '?': usage=1; break;
180   }
181
182   if (optind!=(argc-1)) usage=1;
183   else image=argv[optind];
184
185   if (usage)
186   {
187     fprintf(stderr,"Usage: fsed.cpm [-f format] image\n");
188     exit(1);
189   }
190   /*}}}*/
191   /* open image */ /*{{{*/
192   if ((err=Device_open(&drive.dev,image,O_RDONLY,devopts))) 
193   {
194     fprintf(stderr,"%s: can not open %s (%s)\n",cmd,image,err);
195     exit(1);
196   }
197   cpmReadSuper(&drive,&root,format);
198   /*}}}*/
199   /* alloc sector buffers */ /*{{{*/
200   if ((buf=malloc(drive.secLength))==(char*)0 || (mapbuf=malloc(drive.secLength))==(char*)0)
201   {
202     fprintf(stderr,"fsed.cpm: can not allocate sector buffer (%s).\n",strerror(errno));
203     exit(1);
204   }
205   /*}}}*/
206   /* init curses */ /*{{{*/
207   initscr();
208   noecho();
209   raw();
210   nonl();
211   idlok(stdscr,TRUE);
212   idcok(stdscr,TRUE);
213   keypad(stdscr,TRUE);
214   clear();
215   /*}}}*/
216
217   pos=0;
218   reload=1;
219   do
220   {
221     /* display position and load data */ /*{{{*/
222     clear();
223     move(2,0); printw("Byte %8d (0x%08x)  ",pos,pos);
224     if (pos<(drive.boottrk*drive.sectrk*drive.secLength))
225     {
226       printw("Physical sector %3d  ",((pos/drive.secLength)%drive.sectrk)+1);
227     }
228     else
229     {
230       printw("Sector %3d ",((pos/drive.secLength)%drive.sectrk)+1);
231       printw("(physical %3d)  ",drive.skewtab[(pos/drive.secLength)%drive.sectrk]+1);
232     }
233     printw("Offset %5d  ",pos%drive.secLength);
234     printw("Track %5d",pos/(drive.secLength*drive.sectrk));
235     move(LINES-3,0); printw("N)ext track    P)revious track");
236     move(LINES-2,0); printw("n)ext record   p)revious record     f)orward byte      b)ackward byte");
237     move(LINES-1,0); printw("i)nfo          q)uit");
238     if (reload)
239     {
240       if (pos<(drive.boottrk*drive.sectrk*drive.secLength))
241       {
242         err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),(pos/drive.secLength)%drive.sectrk,buf);
243       }
244       else
245       {
246         err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),drive.skewtab[(pos/drive.secLength)%drive.sectrk],buf);
247       }
248       if (err)
249       {
250         move(4,0); printw("Data can not be read: %s",err);
251       }
252       else reload=0;
253     }
254     /*}}}*/
255
256     if /* position before end of system area */ /*{{{*/
257     (pos<(drive.boottrk*drive.sectrk*drive.secLength))
258     {
259       const char *msg;
260
261       msg="System area"; move(0,(COLS-strlen(msg))/2); printw(msg);
262       move(LINES-3,36); printw("F)orward 16 byte   B)ackward 16 byte");
263       if (!reload) data(&drive,buf,pos);
264       switch (ch=getch())
265       {
266         case 'F': /* next 16 byte */ /*{{{*/
267         {
268           if (pos+16<(drive.sectrk*drive.tracks*(unsigned long)drive.secLength))
269           {
270             if (pos/drive.secLength!=(pos+16)/drive.secLength) reload=1;
271             pos+=16;
272           }
273           break;
274         }
275         /*}}}*/
276         case 'B': /* previous 16 byte */ /*{{{*/
277         {
278           if (pos>=16)
279           {
280             if (pos/drive.secLength!=(pos-16)/drive.secLength) reload=1;
281             pos-=16;
282           }
283           break;
284         }
285         /*}}}*/
286       }
287     }
288     /*}}}*/
289     else if /* position before end of directory area */ /*{{{*/
290     (pos<(drive.boottrk*drive.sectrk*drive.secLength+drive.maxdir*32))
291     {
292       const char *msg;
293       unsigned long entrystart=(pos&~0x1f)%drive.secLength;
294       int entry=(pos-(drive.boottrk*drive.sectrk*drive.secLength))>>5;
295       int offset=pos&0x1f;
296
297       msg="Directory area"; move(0,(COLS-strlen(msg))/2); printw(msg);
298       move(LINES-3,36); printw("F)orward entry     B)ackward entry");
299
300       move(13,0); printw("Entry %3d: ",entry);      
301       if /* free or used directory entry */ /*{{{*/
302       ((buf[entrystart]>=0 && buf[entrystart]<=(drive.type==CPMFS_P2DOS ? 31 : 15)) || buf[entrystart]==(char)0xe5)
303       {
304         int i;
305
306         if (buf[entrystart]==(char)0xe5)
307         {
308           if (offset==0) attron(A_REVERSE);
309           printw("Free");
310           attroff(A_REVERSE);
311         }
312         else printw("Directory entry");
313         move(15,0);
314         if (buf[entrystart]!=(char)0xe5)
315         {
316           printw("User: ");
317           if (offset==0) attron(A_REVERSE);
318           printw("%2d",buf[entrystart]);
319           attroff(A_REVERSE);
320           printw(" ");
321         }
322         printw("Name: ");
323         for (i=0; i<8; ++i)
324         {
325           if (offset==1+i) attron(A_REVERSE);
326           printw("%c",buf[entrystart+1+i]&0x7f);
327           attroff(A_REVERSE);
328         }
329         printw(" Extension: ");
330         for (i=0; i<3; ++i)
331         {
332           if (offset==9+i) attron(A_REVERSE);
333           printw("%c",buf[entrystart+9+i]&0x7f);
334           attroff(A_REVERSE);
335         }
336         move(16,0); printw("Extent: %3d",((buf[entrystart+12]&0xff)+((buf[entrystart+14]&0xff)<<5))/drive.extents);
337         printw(" (low: ");
338         if (offset==12) attron(A_REVERSE);
339         printw("%2d",buf[entrystart+12]&0xff);
340         attroff(A_REVERSE);
341         printw(", high: ");
342         if (offset==14) attron(A_REVERSE);
343         printw("%2d",buf[entrystart+14]&0xff);
344         attroff(A_REVERSE);
345         printw(")");
346         move(17,0); printw("Last extent record count: ");
347         if (offset==15) attron(A_REVERSE);
348         printw("%3d",buf[entrystart+15]&0xff);
349         attroff(A_REVERSE);
350         move(18,0); printw("Last record byte count: ");
351         if (offset==13) attron(A_REVERSE);
352         printw("%3d",buf[entrystart+13]&0xff);
353         attroff(A_REVERSE);
354         move(19,0); printw("Data blocks:");
355         for (i=0; i<16; ++i)
356         {
357           unsigned int block=buf[entrystart+16+i]&0xff;
358           if (drive.size>=256)
359           {
360             printw(" ");
361             if (offset==16+i || offset==16+i+1) attron(A_REVERSE);
362             printw("%5d",block|(((buf[entrystart+16+ ++i]&0xff)<<8)));
363             attroff(A_REVERSE);
364           }
365           else
366           {
367             printw(" ");
368             if (offset==16+i) attron(A_REVERSE);
369             printw("%3d",block);
370             attroff(A_REVERSE);
371           }
372         }
373       }
374       /*}}}*/
375       else if /* disc label */ /*{{{*/
376       (buf[entrystart]==0x20 && drive.type==CPMFS_DR3)
377       {
378         int i;
379         const struct tm *tm;
380         char s[30];
381
382         if (offset==0) attron(A_REVERSE);
383         printw("Disc label");
384         attroff(A_REVERSE);
385         move(15,0);
386         printw("Label: ");
387         for (i=0; i<11; ++i)
388         {
389           if (i+1==offset) attron(A_REVERSE);
390           printw("%c",buf[entrystart+1+i]&0x7f);
391           attroff(A_REVERSE);
392         }
393         move(16,0);
394         printw("Bit 0,7: ");
395         if (offset==12) attron(A_REVERSE);
396         printw("Label %s",buf[entrystart+12]&1 ? "set" : "not set");
397         printw(", password protection %s",buf[entrystart+12]&0x80 ? "set" : "not set");
398         attroff(A_REVERSE);
399         move(17,0);
400         printw("Bit 4,5,6: ");
401         if (offset==12) attron(A_REVERSE);
402         printw("Time stamp ");
403         if (buf[entrystart+12]&0x10) printw("on create, ");
404         else printw("not on create, ");
405         if (buf[entrystart+12]&0x20) printw("on modification, ");
406         else printw("not on modifiction, ");
407         if (buf[entrystart+12]&0x40) printw("on access");
408         else printw("not on access");
409         attroff(A_REVERSE); 
410         move(18,0);
411         printw("Password: ");
412         for (i=0; i<8; ++i)
413         {
414           char printable;
415
416           if (offset==16+(7-i)) attron(A_REVERSE);
417           printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f;
418           printw("%c",isprint(printable) ? printable : ' ');
419           attroff(A_REVERSE);
420         }
421         printw(" XOR value: ");
422         if (offset==13) attron(A_REVERSE);
423         printw("0x%02x",buf[entrystart+13]&0xff);
424         attroff(A_REVERSE);
425         move(19,0);
426         printw("Created: ");
427         tm=cpmtime(buf[entrystart+24],buf[entrystart+25],buf[entrystart+26],buf[entrystart+27]);
428         if (offset==24 || offset==25) attron(A_REVERSE);
429         strftime(s,sizeof(s),"%x",tm);
430         printw("%s",s);
431         attroff(A_REVERSE);
432         printw(" ");
433         if (offset==26) attron(A_REVERSE);
434         printw("%2d",tm->tm_hour);
435         attroff(A_REVERSE);
436         printw(":");
437         if (offset==27) attron(A_REVERSE);
438         printw("%02d",tm->tm_min);
439         attroff(A_REVERSE);
440         printw(" Updated: ");
441         tm=cpmtime(buf[entrystart+28],buf[entrystart+29],buf[entrystart+30],buf[entrystart+31]);
442         if (offset==28 || offset==29) attron(A_REVERSE);
443         strftime(s,sizeof(s),"%x",tm);
444         printw("%s",s);
445         attroff(A_REVERSE);
446         printw(" ");
447         if (offset==30) attron(A_REVERSE);
448         printw("%2d",tm->tm_hour);
449         attroff(A_REVERSE);
450         printw(":");
451         if (offset==31) attron(A_REVERSE);
452         printw("%02d",tm->tm_min);
453         attroff(A_REVERSE);
454       }
455       /*}}}*/
456       else if /* time stamp */ /*{{{*/
457       (buf[entrystart]==0x21 && (drive.type==CPMFS_P2DOS || drive.type==CPMFS_DR3))
458       {
459         const struct tm *tm;
460         char s[30];
461
462         if (offset==0) attron(A_REVERSE);
463         printw("Time stamps");
464         attroff(A_REVERSE);
465         move(15,0);
466         printw("3rd last extent: Created/Accessed ");
467         tm=cpmtime(buf[entrystart+1],buf[entrystart+2],buf[entrystart+3],buf[entrystart+4]);
468         if (offset==1 || offset==2) attron(A_REVERSE);
469         strftime(s,sizeof(s),"%x",tm);
470         printw("%s",s);
471         attroff(A_REVERSE);
472         printw(" ");
473         if (offset==3) attron(A_REVERSE);
474         printw("%2d",tm->tm_hour);
475         attroff(A_REVERSE);
476         printw(":");
477         if (offset==4) attron(A_REVERSE);
478         printw("%02d",tm->tm_min);
479         attroff(A_REVERSE);
480         printw(" Modified ");
481         tm=cpmtime(buf[entrystart+5],buf[entrystart+6],buf[entrystart+7],buf[entrystart+8]);
482         if (offset==5 || offset==6) attron(A_REVERSE);
483         strftime(s,sizeof(s),"%x",tm);
484         printw("%s",s);
485         attroff(A_REVERSE);
486         printw(" ");
487         if (offset==7) attron(A_REVERSE);
488         printw("%2d",tm->tm_hour);
489         attroff(A_REVERSE);
490         printw(":");
491         if (offset==8) attron(A_REVERSE);
492         printw("%02d",tm->tm_min);
493         attroff(A_REVERSE);
494
495         move(16,0);
496         printw("2nd last extent: Created/Accessed ");
497         tm=cpmtime(buf[entrystart+11],buf[entrystart+12],buf[entrystart+13],buf[entrystart+14]);
498         if (offset==11 || offset==12) attron(A_REVERSE);
499         strftime(s,sizeof(s),"%x",tm);
500         printw("%s",s);
501         attroff(A_REVERSE);
502         printw(" ");
503         if (offset==13) attron(A_REVERSE);
504         printw("%2d",tm->tm_hour);
505         attroff(A_REVERSE);
506         printw(":");
507         if (offset==14) attron(A_REVERSE);
508         printw("%02d",tm->tm_min);
509         attroff(A_REVERSE);
510         printw(" Modified ");
511         tm=cpmtime(buf[entrystart+15],buf[entrystart+16],buf[entrystart+17],buf[entrystart+18]);
512         if (offset==15 || offset==16) attron(A_REVERSE);
513         strftime(s,sizeof(s),"%x",tm);
514         printw("%s",s);
515         attroff(A_REVERSE);
516         printw(" ");
517         if (offset==17) attron(A_REVERSE);
518         printw("%2d",tm->tm_hour);
519         attroff(A_REVERSE);
520         printw(":");
521         if (offset==18) attron(A_REVERSE);
522         printw("%02d",tm->tm_min);
523         attroff(A_REVERSE);
524
525         move(17,0);
526         printw("    Last extent: Created/Accessed ");
527         tm=cpmtime(buf[entrystart+21],buf[entrystart+22],buf[entrystart+23],buf[entrystart+24]);
528         if (offset==21 || offset==22) attron(A_REVERSE);
529         strftime(s,sizeof(s),"%x",tm);
530         printw("%s",s);
531         attroff(A_REVERSE);
532         printw(" ");
533         if (offset==23) attron(A_REVERSE);
534         printw("%2d",tm->tm_hour);
535         attroff(A_REVERSE);
536         printw(":");
537         if (offset==24) attron(A_REVERSE);
538         printw("%02d",tm->tm_min);
539         attroff(A_REVERSE);
540         printw(" Modified ");
541         tm=cpmtime(buf[entrystart+25],buf[entrystart+26],buf[entrystart+27],buf[entrystart+28]);
542         if (offset==25 || offset==26) attron(A_REVERSE);
543         strftime(s,sizeof(s),"%x",tm);
544         printw("%s",s);
545         attroff(A_REVERSE);
546         printw(" ");
547         if (offset==27) attron(A_REVERSE);
548         printw("%2d",tm->tm_hour);
549         attroff(A_REVERSE);
550         printw(":");
551         if (offset==28) attron(A_REVERSE);
552         printw("%02d",tm->tm_min);
553         attroff(A_REVERSE);
554       }
555       /*}}}*/
556       else if /* password */ /*{{{*/
557       (buf[entrystart]>=16 && buf[entrystart]<=31 && drive.type==CPMFS_DR3)
558       {
559         int i;
560
561         if (offset==0) attron(A_REVERSE);
562         printw("Password");
563         attroff(A_REVERSE);
564
565         move(15,0);
566         printw("Name: ");
567         for (i=0; i<8; ++i)
568         {
569           if (offset==1+i) attron(A_REVERSE);
570           printw("%c",buf[entrystart+1+i]&0x7f);
571           attroff(A_REVERSE);
572         }
573         printw(" Extension: ");
574         for (i=0; i<3; ++i)
575         {
576           if (offset==9+i) attron(A_REVERSE);
577           printw("%c",buf[entrystart+9+i]&0x7f);
578           attroff(A_REVERSE);
579         }
580
581         move(16,0);
582         printw("Password required for: ");
583         if (offset==12) attron(A_REVERSE);
584         if (buf[entrystart+12]&0x80) printw("Reading ");
585         if (buf[entrystart+12]&0x40) printw("Writing ");
586         if (buf[entrystart+12]&0x20) printw("Deleting ");
587         attroff(A_REVERSE);
588
589         move(17,0);
590         printw("Password: ");
591         for (i=0; i<8; ++i)
592         {
593           char printable;
594
595           if (offset==16+(7-i)) attron(A_REVERSE);
596           printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f;
597           printw("%c",isprint(printable) ? printable : ' ');
598           attroff(A_REVERSE);
599         }
600         printw(" XOR value: ");
601         if (offset==13) attron(A_REVERSE);
602         printw("0x%02x",buf[entrystart+13]&0xff);
603         attroff(A_REVERSE);
604       }
605       /*}}}*/
606       else /* bad status */ /*{{{*/
607       {
608         printw("Bad status ");
609         if (offset==0) attron(A_REVERSE);
610         printw("0x%02x",buf[entrystart]);
611         attroff(A_REVERSE);
612       }
613       /*}}}*/
614       if (!reload) data(&drive,buf,pos);
615       switch (ch=getch())
616       {
617         case 'F': /* next entry */ /*{{{*/
618         {
619           if (pos+32<(drive.sectrk*drive.tracks*(unsigned long)drive.secLength))
620           {
621             if (pos/drive.secLength!=(pos+32)/drive.secLength) reload=1;
622             pos+=32;
623           }
624           break;
625         }
626         /*}}}*/
627         case 'B': /* previous entry */ /*{{{*/
628         {
629           if (pos>=32)
630           {
631             if (pos/drive.secLength!=(pos-32)/drive.secLength) reload=1;
632             pos-=32;
633           }
634           break;
635         }
636         /*}}}*/
637       }
638     }
639     /*}}}*/
640     else /* data area */ /*{{{*/
641     {
642       const char *msg;
643
644       msg="Data area"; move(0,(COLS-strlen(msg))/2); printw(msg);
645       if (!reload) data(&drive,buf,pos);
646       ch=getch();
647     }
648     /*}}}*/
649
650     /* process common commands */ /*{{{*/
651     switch (ch)
652     {
653       case 'n': /* next record */ /*{{{*/
654       {
655         if (pos+128<(drive.sectrk*drive.tracks*(unsigned long)drive.secLength))
656         {
657           if (pos/drive.secLength!=(pos+128)/drive.secLength) reload=1;
658           pos+=128;
659         }
660         break;
661       }
662       /*}}}*/
663       case 'p': /* previous record */ /*{{{*/
664       {
665         if (pos>=128)
666         {
667           if (pos/drive.secLength!=(pos-128)/drive.secLength) reload=1;
668           pos-=128;
669         }
670         break;
671       }
672       /*}}}*/
673       case 'N': /* next track */ /*{{{*/
674       {
675         if ((pos+drive.sectrk*drive.secLength)<(drive.sectrk*drive.tracks*drive.secLength))
676         {
677           pos+=drive.sectrk*drive.secLength;
678           reload=1;
679         }
680         break;
681       }
682       /*}}}*/
683       case 'P': /* previous track */ /*{{{*/
684       {
685         if (pos>=drive.sectrk*drive.secLength)
686         {
687           pos-=drive.sectrk*drive.secLength;
688           reload=1;
689         }
690         break;
691       }
692       /*}}}*/
693       case 'b': /* byte back */ /*{{{*/
694       {
695         if (pos)
696         {
697           if (pos/drive.secLength!=(pos-1)/drive.secLength) reload=1;
698           --pos;
699         }
700         break;
701       }
702       /*}}}*/
703       case 'f': /* byte forward */ /*{{{*/
704       {
705         if (pos+1<drive.tracks*drive.sectrk*drive.secLength)
706         {
707           if (pos/drive.secLength!=(pos+1)/drive.secLength) reload=1;
708           ++pos;
709         }
710         break;
711       }
712       /*}}}*/
713       case 'i': info(&drive,format,image); break;
714       case 'm': map(&drive); break;
715     }
716     /*}}}*/
717   } while (ch!='q');
718
719   /* exit curses */ /*{{{*/
720   move(LINES-1,0);
721   refresh();
722   echo();
723   noraw();
724   endwin();
725   /*}}}*/
726   exit(0);
727 }
728 /*}}}*/