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