/*
* uHex is a small and fast hex editor for DOS.
* Copyright (C) 2013 Mateusz Viste
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <errno.h>
#include "io.h"
#define pVER "1.01"
#define pDATE "2013"
/* color scheme FG | BG */
#define COL_BG 0 | (1 << 4)
#define COL_OFFSET 6 | (1 << 4)
#define COL_DATA 7 | (1 << 4)
#define COL_DATACUR 15 | (6 << 4)
#define COL_DATAEMPTY 8 | (1 << 4)
#define COL_STATUSBAR 0 | (7 << 4)
#define COL_USERMSG 14 | (4 << 4)
#define COL_COLUMNS 7 | (1 << 4)
/* mono scheme */
#define MONO_BG 0
#define MONO_OFFSET 7
#define MONO_DATA 7
#define MONO_DATACUR 0 | (7 << 4)
#define MONO_DATAEMPTY 8
#define MONO_STATUSBAR 0 | (7 << 4)
#define MONO_USERMSG 0 | (7 << 4)
#define MONO_COLUMNS 7
/* The frame '|' character to use */
#define FRAMCHAR 0xB3
struct changeitem {
long offset; /* the offset of the changed byte */
unsigned char byte; /* the new content at offset's position */
unsigned char orig; /* the original content at this position */
struct changeitem *next; /* the next item of the list */
};
struct programState {
char *filename; /* the full path/filename of the file */
char *filename_base; /* the base filename (without path) */
FILE *fd; /* file descriptor for the open data file */
long filepos; /* position of the cursor in the file */
long screenpos; /* position of the screen's start in the file */
long filesize; /* the total file size */
char *usermsg; /* a user message to display */
int mode; /* 0 = cursor on hex side ; 1 = cursor on ascii side */
int termheight; /* number of rows */
int termwidth; /* number of columns */
int termcolor; /* is it a color terminal? 0=no ; 1=yes */
int cursorstart; /* cursor's shape (start scan line) */
int cursorend; /* cursor's shape (end scan line) */
int col_bg; /* background color */
int col_offset; /* color of the offset table */
int col_data; /* color of data */
int col_datacur; /* color of data when cursor on it */
int col_dataempty; /* color of data when empty fields */
int col_statusbar; /* status bar */
int col_usermsg; /* color for messages */
int col_columns; /* pseudo-graphic delimiters */
int ro; /* readonly mode (0=rw / 1=ro) */
struct changeitem *changelist; /* a linked list with changes */
};
enum inputType {
INPUT_NONE,
INPUT_LEFT,
INPUT_RIGHT,
INPUT_UP,
INPUT_DOWN,
INPUT_PAGEUP,
INPUT_PAGEDOWN,
INPUT_TAB,
INPUT_HOME,
INPUT_END,
INPUT_QUIT,
INPUT_HELP,
INPUT_JUMP,
INPUT_FIND,
INPUT_SAVE,
INPUT_UNDO,
INPUT_UNKNOWN
};
/* converts a single hex char (0..F) and returns it's integer value.
* Returns -1 if the hexchar wasn't a valid hex digit. */
int hexchar2int(char hexchar) {
switch (hexchar) {
case '0':
return(0);
case '1':
return(1);
case '2':
return(2);
case '3':
return(3);
case '4':
return(4);
case '5':
return(5);
case '6':
return(6);
case '7':
return(7);
case '8':
return(8);
case '9':
return(9);
case 'A':
case 'a':
return(10);
case 'B':
case 'b':
return(11);
case 'C':
case 'c':
return(12);
case 'D':
case 'd':
return(13);
case 'E':
case 'e':
return(14);
case 'F':
case 'f':
return(15);
default:
return(-1);
}
}
/* parses the cmdline and fills pState. returns 0 on success, non-zero on failure. */
int parsecmdline(int argc, char **argv, struct programState *pState) {
int x;
for (x = 1; x < argc; x++) {
if (strcmp(argv[x], "/mono") == 0) { /* force bw mode */
pState->termcolor = 0;
} else if (strcmp(argv[x], "/color") == 0) { /* force color mode */
pState->termcolor = 1;
} else if (strcmp(argv[x], "/ro") == 0) { /* read-only mode */
pState->ro = 1;
} else { /* anything else is supposed to be a filename, unless it starts with a '/' */
if (argv[x][0] == '/') return(-1); /* unknown parameter */
if (pState->filename != NULL) return(-1); /* a filename is already set */
pState->filename = argv[x];
}
}
if (pState->filename == NULL) return(-2); /* no filename has been provided */
return(0);
}
void setmsg(char *msg, struct programState *pState) {
if (pState->usermsg != NULL) return; /* there is already a message set, abort */
if (msg == NULL) return; /* no message provided */
pState->usermsg = (char *)malloc(strlen(msg) + 1);
if (pState->usermsg == NULL) return; /* malloc() failed, probably not enough memory */
sprintf(pState->usermsg, msg); /* copy the message into the buffer */
}
void about() {
puts("uHex v" pVER " Copyright (C) Mateusz Viste " pDATE);
puts("");
puts("uHex is a small and fast hex editor for DOS. It supports large files, and runs");
puts("on any 8086 compatible CPU.");
puts("");
puts("This program is free software: you can redistribute it and/or modify it under");
puts("the terms of the GNU General Public License as published by the Free Software");
puts("Foundation, either version 3 of the License, or (at your option) any later");
puts("version.");
puts("");
puts(" Usage: uhex file [/mono | /color] [/ro]");
puts("");
puts("where:");
puts(" /mono forces monochrome display mode");
puts(" /color forces color display mode");
puts(" /ro opens the file in read-only mode");
puts("");
}
void drawFrames(struct programState *pState) {
int x, y;
locate(0,0);
for (y = 0; y < pState->termheight - 1; y++) {
/* left edge */
locate(y, 0);
printchar(FRAMCHAR, pState->col_columns);
/* offset - hex space separator */
locate(y, 9);
printchar(FRAMCHAR, pState->col_columns);
/* hex space - ascii separator */
locate(y, 60);
printchar(FRAMCHAR, pState->col_columns);
/* right edge */
locate(y, 79);
printchar(FRAMCHAR, pState->col_columns);
}
}
/* checks the cursor's position, and scroll the screen if needed */
void adjustscreenposition(struct programState *pState) {
if ((pState->filepos >> 4) - (pState->screenpos >> 4) >= (pState->termheight - 3)) { /* cursor is going to get out of the screen (under lower edge) */
pState->screenpos = 3 + (pState->filepos >> 4) - (pState->termheight);
pState->screenpos <<= 4;
} else if ((pState->filepos >> 4) < (pState->screenpos >> 4)) { /* cursor is going to get out of the screen (above higher edge) */
pState->screenpos = (pState->filepos >> 4);
pState->screenpos <<= 4;
}
}
/* Get the next offset with modified byte in the file, since pos 'startpos'.
* Returns a pointer to the changeitem with the next modification, or NULL if no change found. */
struct changeitem *getnextmodificationoffset(struct programState *pState, long startpos) {
struct changeitem *curitem;
for (curitem = pState->changelist; curitem != NULL; curitem = curitem->next) {
if (curitem->offset >= startpos) return(curitem);
}
return(NULL);
}
void drawContent(struct programState *pState) {
int x, y, z, gotbytes, colattr;
unsigned char linebuff[16];
struct changeitem *nextmodification;
char *hexlist = "0123456789ABCDEF";
cursor_set(0x0F, 0x0E); /* hide the cursor */
if (pState->usermsg != NULL) { /* Display the user message, if any */
locate(pState->termheight - 1, 0);
printchar(' ', pState->col_statusbar);
locate(pState->termheight - 1, 1);
printchar(' ', pState->col_usermsg);
for (x = 0; pState->usermsg[x] != 0; x++) {
locate(pState->termheight - 1, x + 2);
printchar(pState->usermsg[x], pState->col_usermsg);
}
locate(pState->termheight - 1, x + 2);
printchar(' ', pState->col_usermsg);
for (;x + 3 < pState->termwidth; x++) {
locate(pState->termheight - 1, x + 3);
printchar(' ', pState->col_statusbar);
}
free(pState->usermsg);
pState->usermsg = NULL;
} else { /* pState->usermsg == NULL */
char linebuff[32];
char *linebuffptr;
/* print the filename (without path) in the status bar */
for (x = 0; (pState->filename_base[x] != 0) && (x < 28); x++) {
locate(pState->termheight - 1, x);
printchar(pState->filename_base[x], pState->col_statusbar);
}
/* print file's size */
sprintf(linebuff, " (%ld bytes)", pState->filesize);
for (linebuffptr = linebuff; *linebuffptr != 0; linebuffptr++) {
locate(pState->termheight - 1, x);
printchar(*linebuffptr, pState->col_statusbar);
x++;
}
/* print blank space until the offset position string starts */
for (; x < 48; x++) {
locate(pState->termheight - 1, x);
printchar(' ', pState->col_statusbar);
}
/* Print the help key binding */
sprintf(linebuff, "ALT+H: Help ");
for (linebuffptr = linebuff; *linebuffptr != 0; linebuffptr++) {
locate(pState->termheight - 1, x);
printchar(*linebuffptr, pState->col_statusbar);
x++;
}
/* print current offset */
sprintf(linebuff, "offset: 0x%08lX ", pState->filepos);
for (linebuffptr = linebuff; *linebuffptr != 0; linebuffptr++) {
locate(pState->termheight - 1, x);
printchar(*linebuffptr, pState->col_statusbar);
x++;
}
}
/* Display the actual file */
fseek(pState->fd, pState->screenpos, SEEK_SET);
nextmodification = getnextmodificationoffset(pState, pState->screenpos);
for (y = 0; y < pState->termheight - 1; y++) {
/* display the offsets column */
sprintf(linebuff, "%08lX", pState->screenpos + (y << 4));
for (x = 0 ; x < 8 ; x++) {
locate(y, 1 + x);
printchar(linebuff[x], pState->col_offset);
}
/* proceed to hex and ascii columns (and read a chunk of the file) */
gotbytes = read(fileno(pState->fd), linebuff, 16);
z = 0;
for (x = 0; x < 16; x++) {
if (x == 8) z = 1;
if (x < gotbytes) { /* real content */
if (nextmodification != NULL) {
if ((pState->screenpos + (y << 4) + x) == nextmodification->offset) { /* this byte was modified */
linebuff[x] = nextmodification->byte;
nextmodification = getnextmodificationoffset(pState, nextmodification->offset + 1);
}
}
if (pState->screenpos + (y << 4) + x == pState->filepos) { /* this is the currently selected byte */
colattr = pState->col_datacur;
} else {
colattr = pState->col_data;
}
/* display the ascii content */
locate(y, 62+x);
printchar(linebuff[x], colattr);
/* display the hex content */
locate(y, 11 + z + (x * 3));
printchar(hexlist[(linebuff[x] >> 4) & 0x0F], colattr);
locate(y, 11 + z + 1 + (x * 3));
printchar(hexlist[linebuff[x] & 0x0F], colattr);
} else { /* got eof -> fill the rest of the line */
locate(y, 62+x);
printchar('.', pState->col_dataempty);
locate(y, 11 + z + (x * 3));
printchar('.', pState->col_dataempty);
locate(y, 11 + z + 1 + (x * 3));
printchar('.', pState->col_dataempty);
}
}
}
/* replace the cursor at its previous place */
if (pState->mode == 0) { /* we are in the hex space */
if ((pState->filepos % 16) > 7) z = 1; else z = 0;
locate((pState->filepos - pState->screenpos) >> 4, 12 + z + ((pState->filepos % 16) * 3));
} else { /* we are in the ascii space */
locate((pState->filepos - pState->screenpos) >> 4, 62 + (pState->filepos % 16));
}
cursor_set(pState->cursorstart, pState->cursorend); /* unhide the cursor */
}
void freemodifications(struct programState *pState) {
if (pState->changelist == NULL) {
setmsg("No modifications were done.", pState);
} else {
while (pState->changelist != NULL) {
struct changeitem *victim;
victim = pState->changelist;
pState->changelist = pState->changelist->next;
free(victim);
}
setmsg("All modifications have been undone.", pState);
}
}
void findstr(struct programState *pState) {
char *label;
#define findstr_size 32
unsigned char findstr[findstr_size];
unsigned char readbuff[findstr_size];
char *hexlist = "0123456789ABCDEF";
int x, y, gotbytes, inkey, findstr_len = 0;
long searchpos;
struct changeitem *nextmodification;
if (pState->mode == 0) { /* hex mode */
label = "Find [HEX]:";
} else { /* ascii mode */
label = "Find [ASCII]:";
}
locate(pState->termheight - 1, 0);
printchar(' ', pState->col_usermsg);
for (x = 1; label[x - 1] != 0; x++) {
locate(pState->termheight - 1, x);
printchar(label[x - 1], pState->col_usermsg);
}
for (inkey = x; inkey < 60; inkey ++) { /* finish drawing the usermsg color on the status bar */
locate(pState->termheight - 1, inkey);
printchar(' ', pState->col_usermsg);
}
x++; /* skip a single space after the label */
for (;;) {
if (pState->mode == 0) { /* display routine for hex mode */
for (inkey = 0; inkey < findstr_len; inkey++) {
locate(pState->termheight - 1, x + inkey + (inkey >> 1));
printchar(findstr[inkey], pState->col_usermsg);
}
locate(pState->termheight - 1, x + findstr_len + (findstr_len >> 1)); /* locate the cursor at the end of the string */
printchar(' ', pState->col_usermsg); /* clear out the last char (in case there was something there before) */
locate(pState->termheight - 1, x + findstr_len + (findstr_len >> 1)); /* locate the cursor at the end of the string */
} else { /* display routine for ascii mode */
for (inkey = 0; inkey < findstr_len; inkey++) {
locate(pState->termheight - 1, x + inkey);
printchar(findstr[inkey], pState->col_usermsg);
}
locate(pState->termheight - 1, x + findstr_len); /* locate the cursor at the end of the string */
printchar(' ', pState->col_usermsg); /* clear out the last char (in case there was something there before) */
locate(pState->termheight - 1, x + findstr_len); /* locate the cursor at the end of the string */
}
inkey = getkey();
if (inkey == 0) { /* getkey returns 0 when an extended key has been pressed */
inkey = 0x100 | getkey();
}
/* a few rules common to hex and ascii mode */
if ((inkey < 0) || (inkey > 0xff) || (inkey == 0x1B)) return;
if (inkey == 8) {
if (findstr_len > 0) findstr_len--;
continue;
}
if (inkey == 0x0D) break; /* user pressed ENTER -> let's go find some stuff */
/* check that the input is within expected range */
if (pState->mode == 0) { /* hex mode */
inkey = hexchar2int(inkey); /* this to normalize the input */
if (inkey < 0) return;
inkey = hexlist[inkey];
if (findstr_len >= (findstr_size >> 1)) continue; /* ignore if the buffer is full already */
} else { /* ascii mode */
if (inkey < 0x20) return; /* must be actual ascii, otherwise abort search */
if (findstr_len >= findstr_size) continue; /* ignore if the buffer is full already */
}
/* add the new input to findstr, if not too long yet */
findstr[findstr_len] = inkey;
findstr_len++;
}
/* If we got here, then we have something to search for */
if (pState->mode == 0) { /* if in hex mode, change the search string to its real (binary) form */
for (x = 0; x < findstr_len; x += 2) {
if (x == findstr_len - 1) { /* we are at the last digit, and the digit is 4bits only */
findstr[x >> 1] = hexchar2int(findstr[x]);
findstr_len++; /* increase the length of the string to compute correct binary length later */
} else { /* normal 2x4bits hex byte */
findstr[x >> 1] = hexchar2int(findstr[x]);
findstr[x >> 1] <<= 4;
findstr[x >> 1] |= hexchar2int(findstr[x+1]);
}
}
findstr_len >>= 1; /* the findstr string is cut in half after packing bytes */
}
/* print out 'searching' message */
label = "Searching...";
for (x = 1; label[x - 1] != 0; x++) {
locate(pState->termheight - 1, x);
printchar(label[x - 1], pState->col_usermsg);
}
for (inkey = x; inkey < 60; inkey ++) { /* finish drawing the usermsg color on the status bar */
locate(pState->termheight - 1, inkey);
printchar(' ', pState->col_usermsg);
}
locate(pState->termheight - 1, x); /* put the blinking cursor at the end of the label */
/* Do the actual research */
for (searchpos = pState->filepos; searchpos < (pState->filesize - findstr_len); ) {
fseek(pState->fd, searchpos, SEEK_SET); /* position the cursor */
nextmodification = getnextmodificationoffset(pState, searchpos);
gotbytes = read(fileno(pState->fd), readbuff, findstr_size);
for (x = 0; x < gotbytes; x++) {
if (nextmodification != NULL) { /* apply any possible changes to the read chunk of bytes */
if ((searchpos + x) == nextmodification->offset) { /* this byte was modified */
readbuff[x] = nextmodification->byte;
nextmodification = getnextmodificationoffset(pState, nextmodification->offset + 1);
}
}
if (readbuff[x] == findstr[0]) { /* if the byte is the same than the first byte we are looking for.. */
fseek(pState->fd, searchpos + x + 1, SEEK_SET);
gotbytes = read(fileno(pState->fd), readbuff, findstr_len - 1);
if (gotbytes != findstr_len - 1) break;
for (y = 1; y < findstr_len; y++) {
if ((searchpos + x + y) == nextmodification->offset) { /* this byte was modified */
readbuff[y - 1] = nextmodification->byte;
nextmodification = getnextmodificationoffset(pState, nextmodification->offset + 1);
}
if (readbuff[y - 1] != findstr[y]) break;
}
if (y == findstr_len) { /* found! */
pState->filepos = searchpos + x;
return;
}
}
}
if (gotbytes < 1) break;
searchpos += gotbytes;
}
setmsg("No match found", pState);
}
int savefile(struct programState *pState) {
struct changeitem *victim;
if (pState->changelist == NULL) {
setmsg("No modifications were done. File not saved.", pState);
return(-1);
}
while (pState->changelist != NULL) {
/* apply the modification */
fseek(pState->fd, pState->changelist->offset, SEEK_SET);
if (write(fileno(pState->fd), &(pState->changelist->byte), 1) != 1) {
setmsg("Write error!", pState);
return(-1);
}
/* free the modification */
victim = pState->changelist;
pState->changelist = pState->changelist->next;
free(victim);
}
setmsg("File saved.", pState);
return(0);
}
void jumpto (struct programState *pState) {
char *label = " Jump to offset: 0x";
char offsetstr[16];
long newoffset = 0;
int x, inkey, inputval;
for (x = 0; label[x] != 0; x++) {
locate(pState->termheight - 1, x);
printchar(label[x], pState->col_usermsg);
}
for (inkey = x; inkey < 60; inkey ++) { /* finish drawing the usermsg color ver the status bar */
locate(pState->termheight - 1, inkey);
printchar(' ', pState->col_usermsg);
}
for (;;) {
if (newoffset > 0) {
sprintf(offsetstr, "%lX ", newoffset);
} else {
offsetstr[0] = ' ';
offsetstr[1] = 0;
}
for (inkey = 0; offsetstr[inkey] != 0; inkey++) {
locate(pState->termheight - 1, x + inkey);
printchar(offsetstr[inkey], pState->col_usermsg);
}
inkey = getkey();
if (((inkey >= 'a') && (inkey <= 'f')) ||
((inkey >= 'A') && (inkey <= 'F')) ||
((inkey >= '0') && (inkey <= '9'))) { /* valid hex input */
if (newoffset <= 0x7FFFFFFL) {
newoffset <<= 4;
newoffset |= hexchar2int(inkey);
}
} else if (inkey == 8) { /* Back space */
if (newoffset > 0) newoffset >>= 4;
} else if (inkey == 13) { /* Return */
pState->filepos = newoffset;
if (pState->filepos >= pState->filesize) pState->filepos = pState->filesize - 1;
return;
} else { /* Invalid entry */
return;
}
}
}
void processInput(enum inputType inputRequest, struct programState *pState) {
switch (inputRequest) {
case INPUT_UP:
if (pState->filepos > 15) pState->filepos -= 16;
break;
case INPUT_DOWN:
if (pState->filepos + 16 < pState->filesize) pState->filepos += 16;
break;
case INPUT_LEFT:
if (pState->filepos > 0) pState->filepos -= 1;
break;
case INPUT_RIGHT:
if (pState->filepos < pState->filesize - 1) pState->filepos += 1;
break;
case INPUT_PAGEUP:
if (pState->filepos - ((pState->termheight - 2) << 4) >= 0) {
pState->filepos -= ((pState->termheight - 2) << 4);
if (pState->screenpos - ((pState->termheight - 2) << 4) >= 0) {
pState->screenpos -= ((pState->termheight - 2) << 4);
} else {
pState->screenpos = 0;
}
} else {
pState->filepos = 0;
}
break;
case INPUT_PAGEDOWN:
if (pState->filepos + ((pState->termheight - 2) << 4) < pState->filesize) {
pState->filepos += ((pState->termheight - 2) << 4);
pState->screenpos += ((pState->termheight - 2) << 4);
} else {
pState->filepos = pState->filesize - 1;
}
break;
case INPUT_HOME:
pState->filepos = 0;
break;
case INPUT_END:
pState->filepos = pState->filesize - 1;
break;
case INPUT_TAB:
pState->mode = 1 - pState->mode;
break;
case INPUT_JUMP: /* jump to offset... */
jumpto(pState);
break;
case INPUT_HELP:
setmsg("ALT+H Help ALT+J Jump ALT+F Find ALT+S Save ALT+U Undo ESC Quit ", pState);
break;
case INPUT_SAVE:
savefile(pState);
break;
case INPUT_UNDO:
freemodifications(pState);
break;
case INPUT_FIND:
findstr(pState);
break;
case INPUT_NONE:
/* do nothing */
break;
}
/* check if the screen position needs to be adjusted */
adjustscreenposition(pState);
}
int getcurbyte(struct programState *pState) {
struct changeitem *curchange;
unsigned char bytebuff;
for (curchange = pState->changelist; curchange != NULL; curchange = curchange->next) {
if (curchange->offset > pState->filepos) break; /* we stop here, changelist entries are sorted */
if (curchange->offset == pState->filepos) return(curchange->byte);
}
/* the cur byte wasn't changed. let's read it from disk */
fseek(pState->fd, pState->filepos, SEEK_SET);
read(fileno(pState->fd), &bytebuff, 1);
return(bytebuff);
}
/* adds an entry to the changelist. returns 0 on success, nonzero on out-of-memory error. */
int addnewchangeitem(struct programState *pState, struct changeitem *newchange) {
struct changeitem *parentchange, *newentry, *curpos;
int found = 0;
curpos = pState->changelist;
parentchange = NULL;
for (;;) {
if (curpos == NULL) { /* leaf position */
found = 1;
} else if (curpos->offset > newchange->offset) {
found = 1;
}
if (found == 1) { /* insert it before this entry */
newentry = (struct changeitem *) malloc(sizeof(struct changeitem));
if (newentry == NULL) return(-1); /* out of memory */
newentry->offset = newchange->offset;
newentry->byte = newchange->byte;
newentry->orig = newchange->orig;
if (parentchange == NULL) { /* no parent */
newentry->next = pState->changelist;
pState->changelist = newentry;
} else { /* a parent exists */
newentry->next = parentchange->next;
parentchange->next = newentry;
}
return(0);
}
if (curpos->offset == newchange->offset) { /* update this entry */
curpos->byte = newchange->byte;
if (curpos->byte == curpos->orig) { /* we got back to the original state - remove this change, it's not a 'change' anymore! */
if (parentchange == NULL) {
pState->changelist = curpos->next;
} else {
parentchange->next = curpos->next;
}
free(curpos);
}
return(0);
}
parentchange = curpos;
curpos = curpos->next;
}
}
enum inputType getInput(struct programState *pState) {
int x;
int keypress = getkey();
struct changeitem bytechange;
char errmsg[32];
if (keypress == 0) { /* extended keystroke require a second call */
keypress = getkey();
keypress |= 0x100;
}
switch (keypress) {
case 0x009: /* TAB */
return(INPUT_TAB);
case 0x01B: /* ESC */
return(INPUT_QUIT);
case 0x13B: /* F1 */
case 0x123: /* ALT+h */
return(INPUT_HELP);
case 0x147: /* HOME */
return(INPUT_HOME);
case 0x148: /* UP */
return(INPUT_UP);
case 0x149: /* PAGEUP */
return(INPUT_PAGEUP);
case 0x14B: /* LEFT */
return(INPUT_LEFT);
case 0x14D: /* RIGHT */
return(INPUT_RIGHT);
case 0x14F: /* END */
return(INPUT_END);
case 0x150: /* DOWN */
return(INPUT_DOWN);
case 0x151: /* PAGEDOWN */
return(INPUT_PAGEDOWN);
case 0x124: /* JUMP (ALT+j) */
return(INPUT_JUMP);
case 0x11F: /* SAVE (ALT+s) */
return(INPUT_SAVE);
case 0x116: /* UNDO (ALT+u) */
return(INPUT_UNDO);
case 0x121: /* FIND (ALT+f) */
return(INPUT_FIND);
default:
if (pState->mode == 0) { /* uHex is in hex mode */
x = hexchar2int(keypress);
if (x >= 0) { /* valid hex char */
if (pState->ro != 0) { /* are we in read-only mode? */
setmsg("This file is opened in read-only mode.", pState);
return(INPUT_NONE);
}
bytechange.offset = pState->filepos;
bytechange.orig = getcurbyte(pState);
bytechange.byte = bytechange.orig;
bytechange.byte <<= 4;
bytechange.byte &= 0xF0;
bytechange.byte |= x;
if (addnewchangeitem(pState, &bytechange) != 0) setmsg("Out of memory! Change aborted.", pState);
return(INPUT_NONE);
}
} else { /* uHex is in ASCII mode */
if ((keypress <= 0xFF) && (keypress >= 0)) {
if (pState->ro != 0) { /* are we in read-only mode? */
setmsg("This file is opened in read-only mode.", pState);
return(INPUT_NONE);
}
bytechange.orig = getcurbyte(pState);
bytechange.offset = pState->filepos;
bytechange.byte = keypress;
if (addnewchangeitem(pState, &bytechange) != 0) setmsg("Out of memory! Change aborted.", pState);
return(INPUT_NONE);
}
}
sprintf(errmsg, "unrecognized key code: 0x%04X", keypress);
setmsg(errmsg, pState);
return(INPUT_UNKNOWN);
}
}
char *getbasefilename(char *fullfilename) {
char *lastseparator;
lastseparator = fullfilename;
while (*fullfilename != 0) {
if ((*fullfilename == '\\') || (*fullfilename == '/')) lastseparator = fullfilename + 1;
fullfilename++;
}
return(lastseparator);
}
int main(int argc, char **argv) {
int savedattr;
struct programState pState;
enum inputType inputRequest;
/* init all internal variables */
pState.filename = NULL;
pState.filepos = 0;
pState.screenpos = 0;
pState.mode = 0;
pState.usermsg = NULL;
pState.changelist = NULL;
pState.ro = 0;
/* pState.termheight = 0; -- all these don't need to be inited, because
pState.termwidth = 0; -- they will be set by getcurvideomode() or
pState.termcolor = 0; -- cursor_getprops().
pState.cursorstart = 0;
pState.cursorend = 0; */
readchar(NULL, &savedattr); /* there we save current color attributes for later restoration */
cursor_getprops(NULL, NULL, &pState.cursorstart, &pState.cursorend); /* save the current cursor's shape */
getcurvideomode(&pState.termwidth, &pState.termheight, &pState.termcolor);
if (parsecmdline(argc, argv, &pState) != 0) {
about();
return(1);
}
if (pState.termwidth < 80) {
printf("Terminal's size detected as %dx%d. This program requires a 80 columns width.", pState.termwidth, pState.termheight);
puts("");
return(6);
}
pState.filename_base = getbasefilename(pState.filename);
if (pState.termcolor == 0) { /* load the mono scheme */
pState.col_bg = MONO_BG;
pState.col_offset = MONO_OFFSET;
pState.col_data = MONO_DATA;
pState.col_datacur = MONO_DATACUR;
pState.col_dataempty = MONO_DATAEMPTY;
pState.col_statusbar = MONO_STATUSBAR;
pState.col_usermsg = MONO_USERMSG;
pState.col_columns = MONO_COLUMNS;
} else { /* load the color scheme */
pState.col_bg = COL_BG;
pState.col_offset = COL_OFFSET;
pState.col_data = COL_DATA;
pState.col_datacur = COL_DATACUR;
pState.col_dataempty = COL_DATAEMPTY;
pState.col_statusbar = COL_STATUSBAR;
pState.col_usermsg = COL_USERMSG;
pState.col_columns = COL_COLUMNS;
}
if (pState.ro == 0) { /* open the file in read/write mode */
pState.fd = fopen(pState.filename, "rb+");
} else { /* open the file in strict readonly mode */
pState.fd = fopen(pState.filename, "rb");
}
if (pState.fd == NULL) {
if ((errno == 5) && (pState.ro == 0)) { /* code 5 is 'access denied' - try to open it again in readonly mode */
pState.ro = 1;
pState.fd = fopen(pState.filename, "rb");
}
if (pState.fd == NULL) {
printf("Error: Failed to open '%s' (code %d) -> %s", pState.filename, errno, strerror(errno));
puts("");
return(2);
}
}
cls(pState.termwidth, pState.termheight, pState.col_bg); /* clear screen and fill with BG color */
fseek(pState.fd, 0, SEEK_END); /* jump to the end */
pState.filesize = ftell(pState.fd); /* learn the file's size */
if (pState.filesize < 0) {
fclose(pState.fd);
puts("Error: Failed to retrieve file's length.");
return(3);
}
drawFrames(&pState); /* draw all static elements */
for (;;) {
drawContent(&pState);
inputRequest = getInput(&pState);
if (inputRequest == INPUT_QUIT) {
if (pState.changelist != NULL) {
setmsg("There are unsaved modifications. Press Esc again if you really want to quit.", &pState);
drawContent(&pState);
inputRequest = getInput(&pState);
if (inputRequest == INPUT_QUIT) break;
inputRequest = INPUT_NONE;
} else {
break;
}
}
processInput(inputRequest, &pState);
}
fclose(pState.fd);
cls(pState.termwidth, pState.termheight, savedattr); /* clears the screen and restore the original colors of the shell */
freemodifications(&pState);
if (pState.usermsg != NULL) free(pState.usermsg); /* free the user message string, if any (you must call this AFTER freemodifications!) */
return(0);
}