// bitmap editor for files // // $Id: bitedit.c,v 1.8 2003/05/27 12:03:20 andrewm Exp $ // // Copyright (c) 2001 Andrew McGill and Leading Edge Business Solutions (South // Africa). This software may be redistributed and/or modified only under the // terms of the GNU General Public Licence, version 2, as published by the Free // Software Foundation, and contained in the file COPYING. #include #include #include #include #include #include #include #include #include #include #include #include #ifndef VERSION #define VERSION "" #endif char *setfont_program = "setfont"; extern char **environ; int mirror = 0; // Return 0 or non-zero for the bit specified char getbit(int bit, char ch) { if (mirror) { return ch & ( 1 << bit); } else { return ch & ( 0x80 >> bit); } } // Set a bit, with attention to mirroring char setbit(int bit, int value, char ch) { if (mirror) { return (ch & ! ( 1 << bit)) | ( ch << bit ) ; } else { return (ch & ! ( 0x80 >> bit)) | (ch << (7-bit)); } } // Set a bit, with attention to mirroring char xorbit(int bit, char ch) { if (mirror) { return ch ^ ( 1 << bit); } else { return ch ^ ( 0x80 >> bit); } } /* * Display idea; * Hex Binary Chars * 00 00 ................ ?? * 7F C0 .XXXXXXXXX...... ?? * 60 00 .XX............. a? * 60 00 .XX............. a? * 78 00 .XXXXX.......... [? * 60 00 .XX............. a? * 60 00 .XX............. a? * 7F C0 .XXXXXXXXX...... ?? * 00 00 ................ * * We will need a few functions to mirror the binary display (LSB/MSB) * A function to save and run a command would be nice (e.g. save font, then run * setfont {} when you press ! ) * */ typedef struct FILE_INFO_T { char *filename; char *data; int filesize; int viewpos; int linechars; int editpos, editbit; int lasteditpos; } FILE_INFO; FILE_INFO CHARS; #define grprintf attrset(COLOR_PAIR(6)); move(0,0); clrtoeol(); printw void grtestit(FILE_INFO *chars) { int status; char *command; #define CMDBUFFER 1000 command = malloc(CMDBUFFER); if (!command) { grprintf("Memory allocate failed (oops)"); return; } snprintf(command,CMDBUFFER-1,"%s %s &",setfont_program,chars->filename); status=system(command); return; if (status) { grprintf("Test program returned status %d",status); } } #if 0 // Show something on the top line void grprintf(char *fmt, ...) { va_list ap; char buffer[160]; int width; width=sizeof(buffer)-1; if (width > COLS) width=COLS; va_start(ap, fmt); vsnprintf (buffer, width, fmt, ap); va_end(ap); attrset(COLOR_PAIR(3)); mvaddstr(0,0,buffer); clrtoeol(); refresh(); } #endif static void grcleanup(int sig) { /* Bye bye curses */ endwin(); exit(0); } // Get a byte avoiding overflow char grgetbyte(FILE_INFO *chars) { if (chars->editpos >= chars->filesize || chars->editpos <0 ) { return 0; } return chars->data[chars->editpos]; } // Set a byte avoiding overflow void grsetbyte(FILE_INFO *chars, char value) { if (chars->editpos >= chars->filesize || chars->editpos <0 ) { return; } chars->data[chars->editpos] = value; } // Redraw the display. An incredibly inefficent thing to do every time. Saved // by ncurses only. void grnewdisplay(FILE_INFO *chars) { #define LINESPERPAGE (LINES - 2) #define BYTESPERPAGE (LINESPERPAGE * chars->linechars ) #define BYTESPERHALFPAGE ((LINESPERPAGE/2) * chars->linechars ) int offset; int x,y,xx, xcursor, ycursor; char ch; int a; const char hex[17] = "0123456789abcdef"; char buffer[12]; // ugly, easy hack while (chars->editpos < chars->viewpos) { chars->viewpos -= BYTESPERHALFPAGE; if (chars->viewpos < 0 ) { chars->viewpos = 0; break; } } while (chars->editpos >= chars->viewpos+BYTESPERPAGE) { chars->viewpos += BYTESPERHALFPAGE; } // Reference block and help text xx = 6 + chars->linechars * 11 + 3; y = 1; attrset(COLOR_PAIR(5)); mvprintw(y,xx,"bitedit " VERSION " - http://ledge.co.za/software/"); y+=2; if (xx=COLS-1) { y++; x=xx; } } } y+=2; } attrset(COLOR_PAIR(5)); mvprintw(y++,xx,"Movement hjkl - left, down, up, right"); mvprintw(y++,xx,"Page n - next, b - back"); mvprintw(y++,xx,"Change bit HJKL, space, BackSpace, Enter"); mvprintw(y++,xx,"Display 1234 - columns, m - mirror bytes, <> - shift"); mvprintw(y++,xx,"Repeat 099x - repeat x 99 times"); mvprintw(y++,xx,"Save w - write %d bytes to %s",chars->filesize,chars->filename); mvprintw(y++,xx,"Test t - run test program"); xcursor = 0; ycursor = 0; offset = chars->viewpos; for (y=1; ylinechars; x++) { if (offset < chars->filesize) { ch = chars->data[offset]; // ascii attrset(COLOR_PAIR(1)); if (isprint(ch)) { mvaddch(y,x+6,ch); } else { mvaddch(y,x+6, '.'); } // binary for (a=0; a<8; a++) { xx = 6 + chars->linechars + 1 + x*8 + a; // not optimised :) if (getbit(a,ch) ) { attrset(A_REVERSE); mvaddch(y, xx, 'X'); } else { attrset(A_NORMAL); mvaddch(y, xx, (mirror ? a<4 : a>=4) ?'.':','); } if (chars->editpos == offset && a==chars->editbit) { xcursor = xx; ycursor = y; } } // hex attrset(COLOR_PAIR(2)); xx = 6+ chars->linechars * 9 + 2 + x *2; mvaddch(y,xx++,hex[(ch >> 4) & 0x0F]); mvaddch(y,xx++,hex[ch & 0x0F]); } else { // Out of range attrset(A_NORMAL); mvaddch(y,x,' '); // follow the vim tradition for eof clrtoeol(); } offset++; } } chars->lasteditpos = chars->editpos; move(ycursor,xcursor); refresh(); } // Save file in memory // Does not croak, but may write a complaint on stderr char *writefile(char *filename, char *data, int filesize) { FILE *stream; size_t writeresult ; stream=fopen(filename,"w"); if (!stream) { perror("fopen"); return NULL; } writeresult = fwrite(data, 1, filesize, stream); if (writeresult != filesize) { perror("fwrite"); fclose(stream); return NULL; } if (fclose(stream)==-1) { perror("fwrite"); return NULL; } return data; } // Load a file into memory (entirely) // Returns a pointer to the file, and its size (filesize) char *loadfile(char *filename, int *filesize) { FILE *stream; size_t readresult ; struct stat filestat; char *filebuffer; int imagesize; stream=fopen(filename,"r"); if (!stream) { perror("fopen"); return NULL; } if (fstat(fileno(stream),&filestat)==-1) { perror("fstat"); fclose(stream); return NULL; } if (filestat.st_size >= INT_MAX) { fprintf(stderr,"File too big\n"); fclose(stream); return NULL; } imagesize = (int)filestat.st_size; filebuffer=malloc(imagesize+1); if (!filebuffer) { fprintf(stderr,"Out of memory\n"); fclose(stream); return NULL; } filebuffer[imagesize]=0; readresult = fread(filebuffer, 1, imagesize, stream); if (readresult != (size_t)imagesize ) { perror("fread"); free(filebuffer); fclose(stream); return NULL; } fclose(stream); *filesize = imagesize; return filebuffer; } // Dumb function to print the standard help thingy void grprinthelp(void) { grprintf("h-left j-down k-up l-right HJKL-change 1-column 2-column w-write" " t-test font" ); } /* Decide how much to do it */ char getrepeat(int *repeat) { char c; *repeat = 0; for (;;) { grprintf("0 - Repeat next command %d times",*repeat); c = getch(); /* refresh, accept single keystroke of input */ if (c>='0' && c<='9') { *repeat = *repeat*10+(c-'0'); } else { if (*repeat > 10000) { *repeat = 10000; }; if (*repeat <= 0) { *repeat = 1; }; return c; } } } // Edit a file void gredit(FILE_INFO *chars) { int c; int lasteditbit, lastoffset; int donewdisplay = 0; int r,repeat = 1; c = getch(); /* refresh, accept single keystroke of input */ lasteditbit = chars->editbit; lastoffset = chars->editpos; if (c == '0') { c = getrepeat(&repeat); } /* Implement repeat */ for (r=0;reditbit,grgetbyte(chars)) ); c += 'a'-'A'; break; } switch (c) { case '<': if (chars->viewpos) chars->viewpos--; donewdisplay = 1; break; case '>': if (chars->viewpos < chars->filesize-1) chars->viewpos++; donewdisplay = 1; break; case ' ': grsetbyte(chars,xorbit(chars->editbit,grgetbyte(chars)) ); donewdisplay = 1; break; case KEY_ENTER: grsetbyte(chars,setbit(chars->editbit,1,grgetbyte(chars)) ); donewdisplay = 1; break; case KEY_BACKSPACE: grsetbyte(chars,setbit(chars->editbit,0,grgetbyte(chars)) ); donewdisplay = 1; break; case KEY_LEFT: case 'h': // left chars->editbit=(chars->editbit+7)%8; if ( chars->editbit == 7) chars->editpos--; break; case KEY_UP: case 'k': // up chars->editpos-=chars->linechars; break; case KEY_RIGHT: case 'l': // right chars->editbit=(chars->editbit+1)%8; if ( chars->editbit == 0) chars->editpos++; break; case KEY_DOWN: case 'j': // down chars->editpos+=chars->linechars; break; case '?': case 'G' - ('A'-1) : // Ctrl+G - like in vim (vi?) case 'L' - ('A'-1) : // Ctrl+L - screen refresh clear(); grprinthelp(); donewdisplay = 1; break; case 'q': grprintf("Press Ctrl+C to exit (without saving, mind you)"); break; case 'w': grprintf("Writing %d bytes to %s",chars->filesize, chars->filename); writefile(chars->filename, chars->data, chars->filesize); break; case 't': grprintf("Testing with %s %s",setfont_program, chars->filename); writefile(chars->filename, chars->data, chars->filesize); grtestit(chars); break; case 'm': // mirror mirror = ! mirror; donewdisplay = 1; break; case 'b': case KEY_PPAGE: chars->editpos-=BYTESPERHALFPAGE*2; break; case 'n': case 'D' - ('A'-1) : case KEY_NPAGE: chars->editpos+=BYTESPERHALFPAGE*2; break; case '1': case '2': case '3': case '4': // set width chars->linechars = c-'0'; clear(); donewdisplay = 1; break; } } // end for // Update ? if (lasteditbit != chars->editbit || lastoffset != chars->editpos) { if (chars->editpos<0) chars->editpos = 0; if (chars->editpos>=chars->filesize) chars->editpos = chars->filesize; donewdisplay = 1; } if (donewdisplay) { grnewdisplay(chars); } } /* Print usage message and die */ void usage(char **argv) { fprintf(stdout, "bitedit - a bit editor - for editing bits\n" "\n" "Usage: %s [-m1234] [-t 'cmd'] filename\n" "\n" "\t-t 'cmd' Run 'cmd filename' to test font\n" "\t-m Mirror\n" "\t-1234 Bytes to display per line\n" , argv[0]); exit(1); } int main(int argc, char *argv[]) { char c; setlocale(LC_ALL, ""); /* Process command line options */ CHARS.linechars=1; while ((c = getopt(argc, argv, "mt:1234")) != -1) { switch (c) { case 't': if (optarg == NULL || *optarg == '\0') { fprintf(stderr, "%s: -t requires an argument\n", argv[0]); exit(1); } setfont_program = optarg; break; case 'm': mirror = !mirror; break; case '1': case '2': case '3': case '4': CHARS.linechars = c-'0'; break; case '?': usage(argv); break; } } if (argc - optind != 1) { usage(argv); return 1; } CHARS.filename = argv[optind++]; /* Load our file */ CHARS.data = loadfile(CHARS.filename, &CHARS.filesize); if (!CHARS.data) { return 1; // sorry. hope you got a message from loadfile } /* initialize your non-curses data structures here */ (void) signal(SIGINT, grcleanup); /* arrange interrupts to terminate */ (void) initscr(); /* initialize the curses library */ keypad(stdscr, TRUE); /* enable keyboard mapping */ (void) nonl(); /* tell curses not to do NL->CR/NL on output */ (void) cbreak(); /* take input chars one at a time, no wait for \n */ (void) noecho(); /* worth a shot */ if (has_colors()) { start_color(); /* * Simple color assignment, often all we need. Color pair 0 cannot * be redefined. This example uses the same value for the color * pair as for the foreground color, though of course that is not * necessary: */ init_pair(1, COLOR_RED, COLOR_BLACK); init_pair(2, COLOR_BLUE, COLOR_BLACK); init_pair(3, COLOR_YELLOW, COLOR_BLACK); init_pair(4, COLOR_GREEN, COLOR_BLACK); init_pair(5, COLOR_CYAN, COLOR_BLACK); init_pair(6, COLOR_MAGENTA, COLOR_BLACK); init_pair(7, COLOR_WHITE, COLOR_BLACK); } grnewdisplay(&CHARS); for (;;) { gredit(&CHARS); } return 0; }