Skip to content

Instantly share code, notes, and snippets.

@su8
Last active December 9, 2025 16:04
Show Gist options
  • Select an option

  • Save su8/d1cc3fde06ba35d5dc09646984590fcb to your computer and use it in GitHub Desktop.

Select an option

Save su8/d1cc3fde06ba35d5dc09646984590fcb to your computer and use it in GitHub Desktop.
hex.cpp
/*
* Copyright 12/05/2025 https://github.com/su8/0verhex
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <iostream>
#include <fstream>
#include <vector>
#include <iomanip>
#include <sstream>
#include <cctype>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <stack>
#include <limits>
#include <cstdint>
#include <ncurses.h>
// Structure to store an edit operation
struct Edit {
size_t offset;
unsigned char oldValue;
unsigned char newValue;
};
// File I/O
bool readFile(const std::string &filename, std::vector<unsigned char> &buffer);
bool writeFile(const std::string &filename, const std::vector<unsigned char> &buffer);
// Draw functions
void drawHexView(WINDOW *win, const std::vector<unsigned char> &buffer, size_t start, size_t cursor, size_t bytesPerLine);
void drawStatus(WINDOW *status, const std::vector<unsigned char> &buffer, const std::string &filename, size_t cursor, size_t filesize, bool modified);
std::string prompt(WINDOW *status, const std::string &msg);
// Searching functions
size_t searchText(const std::vector<unsigned char> &buffer, const std::string &text, size_t start);
size_t searchHex(const std::vector<unsigned char> &buffer, const std::vector<unsigned char> &pattern, size_t start);
// Function to compute an 8-bit checksum (sum of bytes modulo 256)
uint8_t checkSum(const std::vector<uint8_t> &data);
// Edit a byte and push to undo stack
void editByte(std::vector<unsigned char> &buffer, size_t offset, unsigned int newValue);
// Undo last edit
void undo(std::vector<unsigned char> &buffer);
// Redo last undone edit
void redo(std::vector<unsigned char> &buffer);
// Undo/Redo actions
std::stack<Edit> undoStack;
std::stack<Edit> redoStack;
// The hex and status line windows
WINDOW *hexWin;
WINDOW *statusWin;
int main(int argc, char *argv[]) {
if (argc < 2) { std::cerr << "You must provide some file to work on." << std::endl; return EXIT_FAILURE;}
std::string filename = argv[1];
std::vector<unsigned char> buffer;
if (!readFile(filename, buffer)) { std::cerr << "Error: Cannot open file " << filename << std::endl; return EXIT_FAILURE; }
std::vector<bool> modifiedFlags(buffer.size(), false);
initscr();
// Enable colors
if (has_colors()) {
start_color();
init_pair(1, COLOR_CYAN, COLOR_BLACK); // Offset
init_pair(2, COLOR_YELLOW, COLOR_BLACK); // Hex
init_pair(3, COLOR_GREEN, COLOR_BLACK); // ASCII printable
init_pair(4, COLOR_WHITE, COLOR_BLACK); // ASCII non-printable
init_pair(5, COLOR_WHITE, COLOR_BLUE); // Status bar
}
noecho();
cbreak();
keypad(stdscr, TRUE);
curs_set(0);
int rows, cols;
getmaxyx(stdscr, rows, cols);
size_t cursor = 0;
size_t start = 0;
const int bytesPerLine = 16;
bool running = true;
bool modified = false;
hexWin = newwin(rows - 1, cols, 0, 0);
statusWin = newwin(1, cols, rows - 1, 0);
werase(stdscr);
wrefresh(stdscr);
while (running) {
drawHexView(hexWin, buffer, start, cursor, bytesPerLine);
drawStatus(statusWin, buffer, filename, cursor, buffer.size(), modified);
int ch = getch();
switch (ch) {
case KEY_UP: {
if (cursor >= bytesPerLine) cursor -= bytesPerLine;
if (cursor < start) start -= bytesPerLine;
}
break;
case KEY_DOWN: {
if (cursor + bytesPerLine < buffer.size()) cursor += bytesPerLine;
if (cursor >= start + (rows - 2) * bytesPerLine) start += bytesPerLine;
}
break;
case KEY_LEFT: {
if (cursor > 0) cursor--;
if (cursor < start) start -= bytesPerLine;
}
break;
case KEY_RIGHT: {
if (cursor + 1 < buffer.size()) cursor++;
if (cursor >= start + (rows - 2) * bytesPerLine) start += bytesPerLine;
}
break;
case KEY_NPAGE: { // Page Down
if (cursor + (rows - 2) * bytesPerLine < buffer.size())
cursor += (rows - 2) * bytesPerLine;
start = std::min(start + (rows - 2) * bytesPerLine, buffer.size() - 1);
}
break;
case KEY_PPAGE: { // Page Up
if (cursor >= (rows - 2) * bytesPerLine)
cursor -= (rows - 2) * bytesPerLine;
else
cursor = 0;
if (start >= (rows - 2) * bytesPerLine)
start -= (rows - 2) * bytesPerLine;
else
start = 0;
}
break;
case 'u': { // undo change
undo(buffer);
}
break;
case 'r': { // redo change
redo(buffer);
}
break;
case 'e': { // Edit byte
std::string valStr = prompt(statusWin, "Enter new hex value (00-FF): ");
unsigned int val;
std::stringstream ss;
ss << std::hex << valStr;
if (ss >> val && val <= 0xFF) {
buffer[cursor] = static_cast<unsigned char>(val);
modifiedFlags[cursor] = true;
modified = true;
editByte(buffer, val, val);
}
}
break;
case 'i': { // Insert bytes
std::string hexStr = prompt(statusWin, "Enter hex bytes to insert (e.g., 41 42 43): ");
std::istringstream iss(hexStr);
unsigned int byte;
std::vector<unsigned char> data;
while (iss >> std::hex >> byte)
data.push_back(static_cast<unsigned char>(byte));
buffer.insert(buffer.begin() + cursor, data.begin(), data.end());
modifiedFlags.insert(modifiedFlags.begin() + cursor, data.size(), true);
modified = true;
}
break;
case 'd': { // Delete byte
if (!buffer.empty()) {
buffer.erase(buffer.begin() + cursor);
modifiedFlags.erase(modifiedFlags.begin() + cursor);
if (cursor >= buffer.size() && cursor > 0) cursor--;
modified = true;
}
}
break;
case '/': { // Search ASCII
std::string text = prompt(statusWin, "Enter ASCII text to search: ");
size_t pos = searchText(buffer, text, cursor + 1);
if (pos != SIZE_MAX) {
cursor = pos;
if (cursor < start || cursor >= start + (rows - 2) * bytesPerLine)
start = (cursor / bytesPerLine) * bytesPerLine;
} else {
prompt(statusWin, "Not found. Press Enter to continue.");
}
}
break;
case 'h': { // Search hex sequence
std::string hexStr = prompt(statusWin, "Enter hex sequence (e.g., 48 65 6C): ");
std::istringstream iss(hexStr);
unsigned int byte;
std::vector<unsigned char> pattern;
while (iss >> std::hex >> byte)
pattern.push_back(static_cast<unsigned char>(byte));
size_t pos = searchHex(buffer, pattern, cursor + 1);
if (pos != SIZE_MAX) {
cursor = pos;
if (cursor < start || cursor >= start + (rows - 2) * bytesPerLine)
start = (cursor / bytesPerLine) * bytesPerLine;
} else {
prompt(statusWin, "Not found. Press Enter to continue.");
}
}
break;
case 's': // Save
if (writeFile(filename, buffer)) {
modified = false;
std::fill(modifiedFlags.begin(), modifiedFlags.end(), false);
prompt(statusWin, "File saved. Press Enter to continue.");
} else {
prompt(statusWin, "Error saving file! Press Enter to continue.");
}
break;
case 'q': // Quit
if (modified) {
std::string confirm = prompt(statusWin, "Unsaved changes! Type 'yes' to quit: ");
if (confirm == "yes") running = false;
} else {
running = false;
}
break;
default:
break;
}
}
delwin(hexWin);
delwin(statusWin);
endwin();
return EXIT_SUCCESS;
}
// Function to compute an 8-bit checksum (sum of bytes modulo 256)
uint8_t checkSum(const std::vector<uint8_t> &data) {
uint32_t sum = 0;
for (uint8_t byte : data) { sum += byte; }
return static_cast<uint8_t>(sum & 0xFF); // modulo 256
}
// Edit a byte and push to undo stack
void editByte(std::vector<unsigned char> &buffer, size_t offset, unsigned int newValue) {
if (offset >= buffer.size()) { prompt(statusWin, "Error: Offset out of range. Press enter to continue."); return; }
if (newValue > 0xFF) { prompt(statusWin, "Error: Value must be between 0x00 and 0xFF. Press enter to continue."); return; }
Edit e{offset, buffer[offset], static_cast<unsigned char>(newValue)};
buffer[offset] = e.newValue;
undoStack.push(e);
while (!redoStack.empty()) redoStack.pop(); // Clear redo stack after new edit
}
// Undo last edit
void undo(std::vector<unsigned char> &buffer) {
if (undoStack.empty()) { prompt(statusWin, "Nothing to undo. Press enter to continue."); return; }
Edit e = undoStack.top();
undoStack.pop();
buffer[e.offset] = e.oldValue;
redoStack.push(e);
}
// Redo last undone edit
void redo(std::vector<unsigned char> &buffer) {
if (redoStack.empty()) { prompt(statusWin, "Nothing to redo. Press enter to continue."); return; }
Edit e = redoStack.top();
redoStack.pop();
buffer[e.offset] = e.newValue;
undoStack.push(e);
}
// Load file into buffer
bool readFile(const std::string &filename, std::vector<unsigned char> &buffer) {
std::ifstream file(filename, std::ios::binary);
if (!file) return false;
buffer.assign(std::istreambuf_iterator<char>(file), {});
return true;
}
// Save buffer to file
bool writeFile(const std::string &filename, const std::vector<unsigned char> &buffer) {
std::ofstream file(filename, std::ios::binary);
if (!file) return false;
file.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
return true;
}
// Draw hex + ASCII view with colors
void drawHexView(WINDOW *win, const std::vector<unsigned char> &buffer, size_t start, size_t cursor, size_t bytesPerLine) {
werase(win);
int rows, cols;
getmaxyx(win, rows, cols);
for (int row = 0; row < rows - 1; ++row) {
size_t offset = start + row * bytesPerLine;
if (offset >= buffer.size()) break;
// Offset column (cyan)
wattron(win, COLOR_PAIR(1) | A_BOLD);
mvwprintw(win, row, 0, "%08zx", offset);
wattroff(win, COLOR_PAIR(1) | A_BOLD);
wprintw(win, " ");
// Hex bytes (yellow, highlight cursor)
for (size_t i = 0; i < bytesPerLine; ++i) {
if (offset + i < buffer.size()) {
if (offset + i == cursor) {
wattron(win, A_REVERSE | COLOR_PAIR(2) | A_BOLD);
mvwprintw(win, row, 10 + i * 3, "%02X", buffer[offset + i]);
wattroff(win, A_REVERSE | COLOR_PAIR(2) | A_BOLD);
} else {
wattron(win, COLOR_PAIR(2) | A_BOLD);
mvwprintw(win, row, 10 + i * 3, "%02X", buffer[offset + i]);
wattroff(win, COLOR_PAIR(2) | A_BOLD);
}
} else {
mvwprintw(win, row, 10 + i * 3, " ");
}
}
// ASCII representation (green for printable, dim gray for non-printable)
for (size_t i = 0; i < bytesPerLine; ++i) {
if (offset + i < buffer.size()) {
unsigned char c = buffer[offset + i];
if (std::isprint(c)) {
wattron(win, COLOR_PAIR(3) | A_BOLD);
mvwaddch(win, row, 10 + bytesPerLine * 3 + 2 + i, c);
wattroff(win, COLOR_PAIR(3) | A_BOLD);
} else {
wattron(win, COLOR_PAIR(4) | A_BOLD);
mvwaddch(win, row, 10 + bytesPerLine * 3 + 2 + i, '.');
wattroff(win, COLOR_PAIR(4) | A_BOLD);
}
}
}
}
wrefresh(win);
}
// Draw status bar (white on blue)
void drawStatus(WINDOW *status, const std::vector<unsigned char> &buffer, const std::string &filename, size_t cursor, size_t filesize, bool modified) {
werase(status);
wattron(status, COLOR_PAIR(5) | A_BOLD);
mvwprintw(status, 0, 0, "File: %s | Size: %zu bytes | 8bitSum: %s | Cursor: 0x%zx%s | q=Quit s=Save e=Edit i=Insert d=Delete / SearchASCII h=SearchHex u=Undo r=Redo", filename.c_str(), filesize, std::to_string(checkSum(buffer)).c_str(), cursor, modified ? " [MODIFIED]" : "");
wattroff(status, COLOR_PAIR(5) | A_BOLD);
wrefresh(status);
}
// Prompt user for input
std::string prompt(WINDOW *status, const std::string &msg) {
char buf[256] = {'\0'};
werase(status);
mvwprintw(status, 0, 0, "%s", msg.c_str());
wclrtoeol(status);
wrefresh(status);
echo();
curs_set(1);
wgetnstr(status, buf, 255);
noecho();
curs_set(0);
return std::string(buf);
}
// Search ASCII text
size_t searchText(const std::vector<unsigned char> &buffer, const std::string &text, size_t start) {
auto it = std::search(buffer.begin() + start, buffer.end(), text.begin(), text.end());
if (it != buffer.end()) return std::distance(buffer.begin(), it);
return SIZE_MAX;
}
// Search hex sequence
size_t searchHex(const std::vector<unsigned char> &buffer, const std::vector<unsigned char> &pattern, size_t start) {
auto it = std::search(buffer.begin() + start, buffer.end(), pattern.begin(), pattern.end());
if (it != buffer.end()) return std::distance(buffer.begin(), it);
return SIZE_MAX;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment