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