├── LICENSE ├── Makefile ├── README.md ├── context.h ├── draw.c ├── draw.h ├── io.c ├── io.h ├── lcs.c ├── lcs.h ├── main.c ├── util.c └── util.h /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License (ISC) 2 | 3 | Copyright (c) 2016-2019, Derek Buitenhuis 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX=/usr/local 2 | 3 | CFLAGS = -D_FILE_OFFSET_BITS=64 -O3 -std=c99 -Wall -Wextra -g 4 | 5 | OBJECTS = lcs.o main.o draw.o io.o util.o 6 | 7 | bxd: $(OBJECTS) 8 | $(CC) -o $@ $^ -ltermbox $(LIBS) 9 | 10 | distclean: clean 11 | 12 | clean: 13 | @rm -f $(OBJECTS) bxd 14 | 15 | install: bxd 16 | @install -v bxd $(PREFIX)/bin/ 17 | 18 | uninstall: 19 | @rm -fv $(PREFIX)/bin/bxd 20 | 21 | all: bxd 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BXD: Binary Hex Diff 2 | ==================== 3 | 4 | Copyright (c) 2016-2019 Derek Buitenhuis 5 | 6 | ![BXD](http://chromashift.org/bxd.png) 7 | 8 | BXD is a small TUI tool to compare two arbitrarily sized files. It supports 9 | diffing byte insetions, deletions, and changes. It uses block-based diff, so 10 | comparing large files should be very fast. 11 | 12 | It was written so that some thing similar to Beyond Compare could be used 13 | via SSH, to fit the author's use case and needs. As such, the code may 14 | be a bit messy, but should mostly be OK. 15 | 16 | Features 17 | -------- 18 | 19 | * Diff arbitrarily differently sized files. 20 | * Block-based diff and display for diffing large files with relatively low 21 | memory and CPU usage. 22 | * Memory mapped input. 23 | * Seek-to-next-difference. 24 | 25 | Usage 26 | ----- 27 | 28 | Currently, the command like arguments are very simple: 29 | 30 | Binary Hex Diff 31 | Copyright (c) 2016-2019 Derek Buitenhuis. 32 | 33 | A tool to compare two arbitrarily sized binary files. 34 | 35 | Usage: bxd file1 file2 36 | 37 | Keys are: 38 | 39 | * `q` to quit. 40 | * `space` / `backspace` to seek to next or previous diff. 41 | * `up`/`down` to scroll up and down. 42 | * `pgup` / `pgdn` to scroll up and down by one full screen. 43 | 44 | Current Limitations 45 | ------------------- 46 | 47 | This tool started life out of need for a quick tool for a hobby project, 48 | and as such, lacks a few features that would be very nice to have. 49 | 50 | This list will be updated as limitations are removed. 51 | 52 | * There is no seek-to-offset featrue or dialogue yet. 53 | * There may be UI text overlap when using very small terminal sizes. 54 | 55 | Building 56 | -------- 57 | 58 | To build BXD, you need a C99 compiler, a POSIX system, and a copy of 59 | [termbox](https://github.com/nsf/termbox/) in your compiler's include 60 | and library path. Then simply type `make`. 61 | 62 | If you wish to build on Windows, you will need MinGW-w64, and a copy 63 | of the [termbox-go C shim](https://github.com/dwbuiten/termbox-go-c) 64 | in the compiler's include and library path. Then type 65 | `make LIBS="-lntdll -lws2_32 -lwinmm"`. 66 | 67 | Bugs & Patches 68 | -------------- 69 | 70 | Please file any bugs or problems on the GitHub Issues page, and send 71 | any new code as a Pull Request. Please try to follow a similar coding 72 | style to the exisiting codebase. 73 | -------------------------------------------------------------------------------- /context.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONTEXT_H 2 | #define _CONTEXT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct Context { 10 | uint8_t *obuf; 11 | uint8_t *nbuf; 12 | char *oname; 13 | char *nname; 14 | size_t osize; 15 | size_t nsize; 16 | 17 | off_t of_offset; 18 | off_t nf_offset; 19 | 20 | uint8_t *odiff; 21 | uint8_t *ndiff; 22 | size_t odsize; 23 | size_t ndsize; 24 | size_t blocksize; 25 | 26 | size_t nshift; 27 | size_t oshift; 28 | off_t offset; 29 | 30 | uint16_t *prev_offset; 31 | uint16_t *prev_oshift; 32 | uint16_t *prev_nshift; 33 | size_t prev_offset_size; 34 | off_t offset_pos; 35 | 36 | bool is_cleared; 37 | bool has_change; 38 | bool done; 39 | } Context; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /draw.c: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #define __USE_MINGW_ANSI_STDIO 1 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "context.h" 12 | #include "lcs.h" 13 | 14 | unsigned int get_char_per_line(void) 15 | { 16 | int w = tb_width(); 17 | 18 | return (w / 2 - 4) / 4 - 1; 19 | } 20 | 21 | unsigned int get_line_per_side(void) 22 | { 23 | int h = tb_height(); 24 | unsigned int ret = 0; 25 | 26 | for (int i = 3; i < h - 1; i += 2) 27 | ret++; 28 | 29 | return ret; 30 | } 31 | 32 | static inline char get_char(char c) 33 | { 34 | if (c > 31 && c < 127) 35 | return c; 36 | else 37 | return '.'; 38 | } 39 | 40 | #define FBUFOK (np + (size_t) ctx->nf_offset < ctx->nsize && op + (size_t) ctx->of_offset < ctx->osize) 41 | #define BUFOK (FBUFOK && odp < ctx->odsize && ndp < ctx->ndsize) 42 | 43 | void draw_ui_dummy(Context *ctx) 44 | { 45 | int w; 46 | int h; 47 | int ratio; 48 | unsigned int op = 0; 49 | unsigned int np = 0; 50 | unsigned int odp = 0; 51 | unsigned int ndp = 0; 52 | 53 | w = tb_width(); 54 | h = tb_height(); 55 | ratio = ((w / 2 - 4) / 4) * 3; 56 | 57 | ctx->nshift = 0; 58 | ctx->oshift = 0; 59 | 60 | ctx->has_change = false; 61 | 62 | for (int j = 3; j < h - 1 + ctx->offset; j += 2) { 63 | int should_draw = (j - 3 >= ctx->offset); 64 | 65 | j -= ctx->offset; 66 | 67 | for (int i = 3; i < ratio && BUFOK; i += 3) { 68 | switch (ctx->ndiff[ndp]) { 69 | case CHANGED: 70 | ctx->has_change = should_draw; 71 | case SAME: 72 | np++; 73 | 74 | if (!should_draw) 75 | ctx->nshift = np; 76 | 77 | break; 78 | case MISSING: 79 | ctx->has_change = should_draw; 80 | break; 81 | } 82 | ndp++; 83 | 84 | switch (ctx->odiff[odp]) { 85 | case CHANGED: 86 | ctx->has_change = should_draw; 87 | case SAME: 88 | op++; 89 | 90 | if (!should_draw) 91 | ctx->oshift = op; 92 | 93 | break; 94 | case MISSING: 95 | ctx->has_change = should_draw; 96 | break; 97 | } 98 | odp++; 99 | } 100 | 101 | j += ctx->offset; 102 | } 103 | 104 | ctx->done = !FBUFOK; 105 | } 106 | 107 | void draw_ui(Context *ctx) 108 | { 109 | struct tb_cell *buf; 110 | int w; 111 | int h; 112 | int ratio; 113 | unsigned int op = 0; 114 | unsigned int np = 0; 115 | unsigned int odp = 0; 116 | unsigned int ndp = 0; 117 | off_t ooff = ctx->of_offset; 118 | off_t noff = ctx->nf_offset; 119 | 120 | tb_clear(); 121 | 122 | buf = tb_cell_buffer(); 123 | w = tb_width(); 124 | h = tb_height(); 125 | ratio = ((w / 2 - 4) / 4) * 3; 126 | 127 | /* Corners. */ 128 | buf[0 ].ch = 0x2554; 129 | buf[w - 1].ch = 0x2557; 130 | buf[w * (h - 1) ].ch = 0x255A; 131 | buf[w * (h - 1) + w - 1].ch = 0x255D; 132 | 133 | /* Sides. */ 134 | for (int i = 1; i < w - 1; i++) { 135 | buf[i].ch = 0x2550; 136 | buf[w * (h - 1) + i].ch = 0x2550; 137 | } 138 | 139 | /* Top / Bottom. */ 140 | for (int i = 1; i < h - 1; i++) { 141 | buf[w * i ].ch = 0x2551; 142 | buf[w * i + w - 1].ch = 0x2551; 143 | } 144 | 145 | /* Middle. */ 146 | buf[w / 2 ].ch = 0x2564; 147 | buf[w * (h - 1) + w / 2].ch = 0x2567; 148 | for (int i = 1; i < h - 1; i++) 149 | buf[w * i + w / 2].ch = 0x2502; 150 | 151 | /* Labels. */ 152 | { 153 | size_t olen = strlen(ctx->oname); 154 | size_t nlen = strlen(ctx->nname); 155 | int p1 = (w / 4); 156 | int p2 = (w / 4) * 3; 157 | 158 | if (p1 - (olen / 2) > 1) { 159 | p1 -= olen / 2; 160 | } else { 161 | p1 = 1; 162 | olen = w / 2 - 2; 163 | } 164 | if (p2 - (nlen / 2) > ((unsigned int) w / 2) + 1) { 165 | p2 -= nlen / 2; 166 | } else { 167 | p2 = (w / 2 ) + 1; 168 | nlen = w / 2 - 2; 169 | } 170 | 171 | for (size_t i = 0; i < olen; i++) { 172 | buf[w + p1 + i].fg |= TB_UNDERLINE; 173 | buf[w + p1 + i].ch = ctx->oname[i]; 174 | } 175 | 176 | for (size_t i = 0; i < nlen; i++) { 177 | buf[w + p2 + i].fg |= TB_UNDERLINE; 178 | buf[w + p2 + i].ch = ctx->nname[i]; 179 | } 180 | } 181 | 182 | buf[w + 3].ch = 'O'; 183 | buf[w + 4].ch = 'f'; 184 | buf[w + 5].ch = 'f'; 185 | buf[w + 6].ch = 's'; 186 | buf[w + 7].ch = 'e'; 187 | buf[w + 8].ch = 't'; 188 | buf[w + 9].ch = ':'; 189 | 190 | buf[w + w / 2 + 3].ch = 'O'; 191 | buf[w + w / 2 + 4].ch = 'f'; 192 | buf[w + w / 2 + 5].ch = 'f'; 193 | buf[w + w / 2 + 6].ch = 's'; 194 | buf[w + w / 2 + 7].ch = 'e'; 195 | buf[w + w / 2 + 8].ch = 't'; 196 | buf[w + w / 2 + 9].ch = ':'; 197 | 198 | for (int i = 3; i <= 9; i++) { 199 | buf[w + i].fg |= TB_BOLD; 200 | buf[w + w / 2 + i].fg |= TB_BOLD; 201 | } 202 | 203 | ctx->oshift = 0; 204 | ctx->nshift = 0; 205 | 206 | ctx->has_change = false; 207 | 208 | for (int j = 3; j < h - 1 + ctx->offset; j += 2) { 209 | int nprev = 0; 210 | int oprev = 0; 211 | int cnt = 3; 212 | int should_draw = (j - 3 >= ctx->offset); 213 | 214 | j -= ctx->offset; 215 | 216 | for (int i = 3; i < ratio && BUFOK; i += 3) { 217 | char sbuf[3]; 218 | 219 | switch (ctx->ndiff[ndp]) { 220 | case CHANGED: 221 | ctx->has_change = should_draw; 222 | if (should_draw) { 223 | if (nprev) 224 | buf[w * j + w / 2 + i - 1].bg = TB_WHITE; 225 | 226 | buf[w * j + w / 2 + i ].bg = TB_WHITE; 227 | buf[w * j + w / 2 + i + 1].bg = TB_WHITE; 228 | buf[w * j + w / 2 + i ].fg = TB_BLACK | TB_BOLD; 229 | buf[w * j + w / 2 + i + 1].fg = TB_BLACK | TB_BOLD; 230 | 231 | buf[w * j + w / 2 + ratio + cnt].bg = TB_WHITE; 232 | buf[w * j + w / 2 + ratio + cnt].fg = TB_BLACK | TB_BOLD; 233 | } 234 | case SAME: 235 | if (should_draw) { 236 | buf[w * j + w / 2 + ratio + cnt].ch = get_char(ctx->nbuf[np + noff]); 237 | 238 | sprintf(sbuf, "%02X", ctx->nbuf[np + noff]); 239 | buf[w * j + w / 2 + i ].ch = sbuf[0]; 240 | buf[w * j + w / 2 + i + 1].ch = sbuf[1]; 241 | } 242 | np++; 243 | 244 | if (!should_draw) 245 | ctx->nshift = np; 246 | 247 | if (should_draw) 248 | nprev = (ctx->ndiff[ndp] == CHANGED); 249 | 250 | break; 251 | case MISSING: 252 | ctx->has_change = should_draw; 253 | if (should_draw) { 254 | if (nprev) 255 | buf[w * j + w / 2 + i - 1].bg = TB_WHITE; 256 | 257 | buf[w * j + w / 2 + i ].bg = TB_WHITE; 258 | buf[w * j + w / 2 + i + 1].bg = TB_WHITE; 259 | nprev = 1; 260 | 261 | buf[w * j + w / 2 + ratio + cnt].bg = TB_WHITE; 262 | } 263 | break; 264 | } 265 | ndp++; 266 | 267 | switch (ctx->odiff[odp]) { 268 | case CHANGED: 269 | ctx->has_change = should_draw; 270 | if (should_draw) { 271 | if (oprev) 272 | buf[w * j + i - 1].bg = TB_WHITE; 273 | 274 | buf[w * j + i ].bg = TB_WHITE; 275 | buf[w * j + i + 1].bg = TB_WHITE; 276 | buf[w * j + i ].fg = TB_BLACK | TB_BOLD; 277 | buf[w * j + i + 1].fg = TB_BLACK | TB_BOLD; 278 | 279 | buf[w * j + ratio + cnt].bg = TB_WHITE; 280 | buf[w * j + ratio + cnt].fg = TB_BLACK | TB_BOLD; 281 | } 282 | case SAME: 283 | if (should_draw) { 284 | buf[w * j + ratio + cnt].ch = get_char(ctx->obuf[op + ooff]); 285 | 286 | sprintf(sbuf, "%02X", ctx->obuf[op + ooff]); 287 | buf[w * j + i ].ch = sbuf[0]; 288 | buf[w * j + i + 1].ch = sbuf[1]; 289 | } 290 | op++; 291 | 292 | if (!should_draw) 293 | ctx->oshift = op; 294 | 295 | if(should_draw) 296 | oprev = (ctx->odiff[odp] == CHANGED); 297 | 298 | break; 299 | case MISSING: 300 | ctx->has_change = should_draw; 301 | if(should_draw) { 302 | if (oprev) 303 | buf[w * j + i - 1].bg = TB_WHITE; 304 | 305 | buf[w * j + i ].bg = TB_WHITE; 306 | buf[w * j + i + 1].bg = TB_WHITE; 307 | oprev = 1; 308 | 309 | buf[w * j + ratio + cnt].bg = TB_WHITE; 310 | } 311 | break; 312 | } 313 | odp++; 314 | 315 | cnt++; 316 | } 317 | 318 | j += ctx->offset; 319 | } 320 | 321 | ctx->done = !FBUFOK; 322 | 323 | { 324 | off_t ooff = ctx->of_offset + ctx->oshift; 325 | off_t noff = ctx->nf_offset + ctx->nshift; 326 | char osbuf[11]; 327 | char nsbuf[11]; 328 | 329 | sprintf(osbuf, "0x%08jX", (intmax_t) ooff); 330 | sprintf(nsbuf, "0x%08jX", (intmax_t) noff); 331 | for (int i = 0; i < 11; i++) { 332 | buf[w + 11 + i].ch = osbuf[i]; 333 | buf[w + w / 2 + 11 + i].ch = nsbuf[i]; 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /draw.h: -------------------------------------------------------------------------------- 1 | #ifndef _DRAW_H 2 | #define _DRAW_H 3 | 4 | #include "context.h" 5 | 6 | unsigned int get_char_per_line(void); 7 | unsigned int get_line_per_side(void); 8 | void draw_ui_dummy(Context *ctx); 9 | void draw_ui(Context *ctx); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /io.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef _WIN32 10 | #include 11 | #else 12 | #include 13 | #endif 14 | 15 | #include "context.h" 16 | #include "io.h" 17 | 18 | static int stash_names(Context *ctx, const char *orig, const char *new, char err[1024]) 19 | { 20 | /* Stash filenames. */ 21 | ctx->oname = malloc(strlen(orig) + 1); 22 | if (ctx->oname == NULL) { 23 | strcpy(err, "Could not allocate oname buffer.\n"); 24 | return 1; 25 | } 26 | ctx->nname = malloc(strlen(new) + 1); 27 | if (ctx->nname == NULL) { 28 | strcpy(err, "Could not allocate oname buffer.\n"); 29 | return 1; 30 | } 31 | 32 | strcpy(ctx->oname, orig); 33 | strcpy(ctx->nname, new); 34 | 35 | return 0; 36 | } 37 | 38 | static int fill_filesize(Context *ctx, char err[1024]) 39 | { 40 | int ret; 41 | struct stat st; 42 | 43 | /* Fill in filesize. */ 44 | ret = stat(ctx->oname, &st); 45 | if (ret != 0) { 46 | sprintf(err, "Could not stat %s.\n", ctx->oname); 47 | return 1; 48 | } 49 | ctx->osize = st.st_size; 50 | 51 | memset(&st, 0, sizeof(st)); 52 | 53 | ret = stat(ctx->nname, &st); 54 | if (ret != 0) { 55 | sprintf(err, "Could not stat %s.\n", ctx->nname); 56 | return 1; 57 | } 58 | ctx->nsize = st.st_size; 59 | 60 | return 0; 61 | } 62 | 63 | #ifndef _WIN32 64 | 65 | /* Standard POSIX memory mapping. */ 66 | int open_files(Context *ctx, const char *orig, const char *new, char err[1024]) 67 | { 68 | int nfd = -1; 69 | int ofd = -1; 70 | int ret = 0; 71 | 72 | ret = stash_names(ctx, orig, new, err); 73 | if (ret < 0) 74 | goto fail; 75 | 76 | ret = fill_filesize(ctx, err); 77 | if (ret < 0) 78 | goto fail; 79 | 80 | ofd = open(ctx->oname, O_RDONLY, 0); 81 | if (ofd < 0) { 82 | sprintf(err, "Could not open %s.\n", ctx->oname); 83 | ret = 1; 84 | goto fail; 85 | } 86 | ctx->obuf = mmap(NULL, ctx->osize, PROT_READ, MAP_PRIVATE, ofd, 0); 87 | if (ctx->obuf == MAP_FAILED) { 88 | sprintf(err, "Could not mmap %s.\n", ctx->oname); 89 | ret = 1; 90 | goto fail; 91 | } 92 | close(ofd); 93 | ofd = -1; 94 | 95 | nfd = open(ctx->nname, O_RDONLY, 0); 96 | if (nfd < 0) { 97 | sprintf(err, "Could not open %s.\n", ctx->nname); 98 | ret = 1; 99 | goto fail; 100 | } 101 | ctx->nbuf = mmap(NULL, ctx->nsize, PROT_READ, MAP_PRIVATE, nfd, 0); 102 | if (ctx->nbuf == MAP_FAILED) { 103 | sprintf(err, "Could not mmap %s.\n", ctx->nname); 104 | ret = 1; 105 | goto fail; 106 | } 107 | close(nfd); 108 | nfd = -1; 109 | 110 | ret = 0; 111 | 112 | fail: 113 | if (ofd > 0) 114 | close(ofd); 115 | 116 | if (nfd > 0) 117 | close(nfd); 118 | 119 | return ret; 120 | } 121 | 122 | void close_files(Context *ctx) 123 | { 124 | if (ctx->nname != NULL) 125 | free(ctx->nname); 126 | 127 | if (ctx->oname != NULL) 128 | free(ctx->oname); 129 | 130 | if (ctx->obuf != MAP_FAILED && ctx->obuf != NULL) { 131 | int mun = munmap(ctx->obuf, ctx->osize); 132 | assert(mun == 0); 133 | } 134 | 135 | if (ctx->nbuf != MAP_FAILED && ctx->nbuf != NULL) { 136 | int mun = munmap(ctx->nbuf, ctx->nsize); 137 | assert(mun == 0); 138 | } 139 | } 140 | 141 | #else 142 | 143 | /* Windows memory mapping. */ 144 | int open_files(Context *ctx, const char *orig, const char *new, char err[1024]) 145 | { 146 | HANDLE nfd = INVALID_HANDLE_VALUE; 147 | HANDLE ofd = INVALID_HANDLE_VALUE; 148 | HANDLE mnfd = INVALID_HANDLE_VALUE; 149 | HANDLE mofd = INVALID_HANDLE_VALUE; 150 | int ret = 0; 151 | 152 | ret = stash_names(ctx, orig, new, err); 153 | if (ret < 0) 154 | goto fail; 155 | 156 | ret = fill_filesize(ctx, err); 157 | if (ret < 0) 158 | goto fail; 159 | 160 | ofd = CreateFile(ctx->oname, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL); 161 | if (ofd == INVALID_HANDLE_VALUE) { 162 | sprintf(err, "Could not open %s.\n", ctx->oname); 163 | ret = 1; 164 | goto fail; 165 | } 166 | 167 | mofd = CreateFileMapping(ofd, NULL, PAGE_READONLY, 0, 0, 0); 168 | if (mofd == INVALID_HANDLE_VALUE) { 169 | sprintf(err, "Could not create mapping for %s.\n", ctx->oname); 170 | ret = 1; 171 | goto fail; 172 | } 173 | 174 | ctx->obuf = MapViewOfFile(mofd, FILE_MAP_READ, 0, 0, 0); 175 | if (ctx->obuf == NULL) { 176 | sprintf(err, "Could not mmap %s.\n", ctx->oname); 177 | ret = 1; 178 | goto fail; 179 | } 180 | 181 | CloseHandle(ofd); 182 | CloseHandle(mofd); 183 | ofd = INVALID_HANDLE_VALUE; 184 | mofd = INVALID_HANDLE_VALUE; 185 | 186 | nfd = CreateFile(ctx->nname, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL); 187 | if (nfd == INVALID_HANDLE_VALUE) { 188 | sprintf(err, "Could not open %s.\n", ctx->nname); 189 | ret = 1; 190 | goto fail; 191 | } 192 | 193 | mnfd = CreateFileMapping(nfd, NULL, PAGE_READONLY, 0, 0, 0); 194 | if (mnfd == INVALID_HANDLE_VALUE) { 195 | sprintf(err, "Could not create mapping for %s.\n", ctx->nname); 196 | ret = 1; 197 | goto fail; 198 | } 199 | 200 | ctx->nbuf = MapViewOfFile(mnfd, FILE_MAP_READ, 0, 0, 0); 201 | if (ctx->obuf == NULL) { 202 | sprintf(err, "Could not mmap %s.\n", ctx->nname); 203 | ret = 1; 204 | goto fail; 205 | } 206 | 207 | CloseHandle(nfd); 208 | CloseHandle(mnfd); 209 | nfd = INVALID_HANDLE_VALUE; 210 | mnfd = INVALID_HANDLE_VALUE; 211 | 212 | ret = 0; 213 | 214 | fail: 215 | if (nfd != INVALID_HANDLE_VALUE) 216 | CloseHandle(nfd); 217 | 218 | if (ofd != INVALID_HANDLE_VALUE) 219 | CloseHandle(ofd); 220 | 221 | if (mnfd != INVALID_HANDLE_VALUE) 222 | CloseHandle(mnfd); 223 | 224 | if (mofd != INVALID_HANDLE_VALUE) 225 | CloseHandle(mofd); 226 | 227 | return ret; 228 | } 229 | 230 | void close_files(Context *ctx) 231 | { 232 | if (ctx->nname != NULL) 233 | free(ctx->nname); 234 | 235 | if (ctx->oname != NULL) 236 | free(ctx->oname); 237 | 238 | if (ctx->obuf != NULL) 239 | UnmapViewOfFile(ctx->obuf); 240 | 241 | if (ctx->nbuf != NULL) 242 | UnmapViewOfFile(ctx->nbuf); 243 | } 244 | 245 | #endif 246 | -------------------------------------------------------------------------------- /io.h: -------------------------------------------------------------------------------- 1 | #ifndef _IO_H 2 | #define _IO_H 3 | 4 | #include "context.h" 5 | 6 | int open_files(Context *ctx, const char *orig, const char *new, char err[1024]); 7 | void close_files(Context *ctx); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /lcs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "context.h" 8 | #include "lcs.h" 9 | 10 | #define MAX(x,y) (x > y ? x : y) 11 | 12 | static size_t lcs (uint8_t *obuf, size_t osize, uint8_t *nbuf, size_t nsize, 13 | uint8_t *lbuf, size_t *scratch) 14 | { 15 | size_t lsize; 16 | 17 | for (size_t i = 1; i <= osize; i++) { 18 | for (size_t j = 1; j <= nsize; j++) { 19 | size_t lpos = i * (nsize + 1) + j; 20 | size_t rpos = (i - 1) * (nsize + 1) + j; 21 | 22 | if (obuf[i - 1] == nbuf[j - 1]) 23 | scratch[lpos] = scratch[rpos - 1] + 1; 24 | else 25 | scratch[lpos] = MAX(scratch[rpos], scratch[rpos + nsize]); 26 | } 27 | } 28 | 29 | lsize = scratch[osize * (nsize + 1) + nsize]; 30 | 31 | { 32 | size_t op = osize; 33 | size_t np = nsize; 34 | 35 | for (int i = (int) lsize - 1; i >= 0; i--) { 36 | size_t lpos = op * (nsize + 1) + np - 1; 37 | size_t rpos = (op - 1) * (nsize + 1) + np; 38 | 39 | if (obuf[op - 1] == nbuf[np - 1]) { 40 | lbuf[i] = obuf[op - 1]; 41 | op--; 42 | np--; 43 | } else if (scratch[lpos] > scratch[rpos]) { 44 | np--; 45 | i++; 46 | } else { 47 | op--; 48 | i++; 49 | } 50 | } 51 | } 52 | 53 | return lsize; 54 | } 55 | 56 | #undef MAX 57 | 58 | static void update_masks(Context *ctx, size_t *podp, size_t *pndp, int subrun, int addrun) 59 | { 60 | uint8_t *odiff = ctx->odiff; 61 | uint8_t *ndiff = ctx->ndiff; 62 | size_t odp = *podp; 63 | size_t ndp = *pndp; 64 | 65 | /* for nbuf */ 66 | for (int j = 0; j < subrun; j++) 67 | ndiff[ndp + j] = MISSING; 68 | for (int j = 0; j < addrun; j++) { 69 | if (ndiff[ndp + j] == MISSING) 70 | ndiff[ndp + j] = CHANGED; 71 | else if (ndiff[ndp + j] == SAME) 72 | ndiff[ndp + j] = CHANGED; 73 | } 74 | /* For obuf. */ 75 | for (int j = 0; j < addrun; j++) 76 | odiff[odp + j] = MISSING; 77 | for (int j = 0; j < subrun; j++) { 78 | if (odiff[odp + j] == MISSING) 79 | odiff[odp + j] = CHANGED; 80 | else if (odiff[odp + j] == SAME) 81 | odiff[odp + j] = CHANGED; 82 | } 83 | 84 | if (subrun < addrun) { 85 | *pndp += addrun + 1; 86 | *podp += addrun + 1; 87 | } else { 88 | *pndp += subrun + 1; 89 | *podp += subrun + 1; 90 | } 91 | } 92 | 93 | bool calc_lcs_mask(Context *ctx, uint8_t *lbuf, size_t *scratch) 94 | { 95 | uint8_t *obuf = ctx->obuf; 96 | uint8_t *nbuf = ctx->nbuf; 97 | size_t blocksize = ctx->blocksize; 98 | 99 | size_t odp = 0; 100 | size_t ndp = 0; 101 | size_t op = 0; 102 | size_t np = 0; 103 | int subrun = 0; 104 | int addrun = 0; 105 | 106 | off_t ooff = ctx->of_offset; 107 | off_t noff = ctx->nf_offset; 108 | int ret; 109 | 110 | size_t lsz = lcs(&obuf[ooff], blocksize, &nbuf[noff], blocksize, lbuf, scratch); 111 | 112 | ret = (lsz == blocksize); 113 | 114 | for (size_t i = 0; i < lsz; i++) { 115 | if (obuf[ooff + op] == lbuf[i] && nbuf[noff + np] == lbuf[i]) { 116 | if (subrun || addrun) { 117 | update_masks(ctx, &odp, &ndp, subrun, addrun); 118 | subrun = 0; 119 | addrun = 0; 120 | } else { 121 | odp++; 122 | ndp++; 123 | } 124 | op++; 125 | np++; 126 | continue; 127 | } else if (obuf[ooff + op] != lbuf[i]) { 128 | subrun++; 129 | op++; 130 | i--; 131 | } else if (nbuf[noff + np] != lbuf[i]) { 132 | addrun++; 133 | np++; 134 | i--; 135 | } 136 | } 137 | subrun = blocksize - op; 138 | addrun = blocksize - np; 139 | update_masks(ctx, &odp, &ndp, subrun, addrun); 140 | 141 | ctx->odsize = odp - 1; 142 | ctx->ndsize = ndp - 1; 143 | 144 | ctx->is_cleared = false; 145 | 146 | return ret; 147 | } 148 | -------------------------------------------------------------------------------- /lcs.h: -------------------------------------------------------------------------------- 1 | #ifndef _LCS_H 2 | #define _LCS_H 3 | 4 | #include 5 | #include 6 | 7 | #include "context.h" 8 | 9 | /* Change types. */ 10 | #define SAME 0 11 | #define CHANGED 1 12 | #define MISSING 2 13 | 14 | bool calc_lcs_mask(Context *ctx, uint8_t *lbuf, size_t *scratch); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "context.h" 10 | #include "draw.h" 11 | #include "io.h" 12 | #include "lcs.h" 13 | #include "util.h" 14 | 15 | #define BLOCKSIZE (1024) 16 | 17 | int main(int argc, char *argv[]) 18 | { 19 | uint8_t lbuf[BLOCKSIZE] = { 0 }; 20 | size_t *scratch = NULL; 21 | int ret = 0; 22 | int initted = false; 23 | Context ctx; 24 | char err[1024]; 25 | 26 | if (argc < 3) { 27 | fprintf(stderr, "Binary Hex Diff\n" 28 | "Copyright (c) 2016-2019 Derek Buitenhuis.\n\n" 29 | "A tool to compare two arbitrarily sized binary files.\n\n" 30 | "Usage: %s file1 file2\n\n", argv[0]); 31 | return 0; 32 | } 33 | 34 | memset(&ctx, 0, sizeof(ctx)); 35 | 36 | /* Allocations. */ 37 | ctx.odiff = calloc(BLOCKSIZE * BLOCKSIZE, 1); 38 | if (ctx.odiff == NULL) { 39 | fprintf(stderr, "Could not allocate odiff buffer.\n"); 40 | ret = 1; 41 | goto end; 42 | } 43 | ctx.ndiff = calloc(BLOCKSIZE * BLOCKSIZE, 1); 44 | if (ctx.ndiff == NULL) { 45 | fprintf(stderr, "Could not allocate ndiff buffer.\n"); 46 | ret = 1; 47 | goto end; 48 | } 49 | scratch = calloc((BLOCKSIZE + 1) * (BLOCKSIZE + 1), sizeof(*scratch)); 50 | if (scratch == NULL) { 51 | fprintf(stderr, "Could not allocate LCS scratch buffer.\n"); 52 | ret = 1; 53 | goto end; 54 | } 55 | 56 | ctx.prev_offset = malloc(1024 * 1024 * sizeof(*(ctx.prev_offset))); 57 | ctx.prev_nshift = malloc(1024 * 1024 * sizeof(*(ctx.prev_nshift))); 58 | ctx.prev_oshift = malloc(1024 * 1024 * sizeof(*(ctx.prev_oshift))); 59 | if (ctx.prev_offset == NULL || ctx.prev_oshift == NULL || ctx.prev_nshift == NULL) { 60 | fprintf(stderr, "Could not allocate offset tracking buffers.\n"); 61 | ret = 1; 62 | goto end; 63 | } 64 | ctx.prev_offset_size = 1024 * 1024; 65 | 66 | ctx.blocksize = BLOCKSIZE; 67 | ctx.is_cleared = true; 68 | 69 | ret = open_files(&ctx, argv[1], argv[2], err); 70 | if (ret != 0) { 71 | fputs(err, stderr); 72 | ret = 1; 73 | goto end; 74 | } 75 | 76 | calc_lcs_mask(&ctx, &lbuf[0], scratch); 77 | 78 | ret = tb_init(); 79 | if (ret != 0) { 80 | fprintf(stderr, "Init failed: %d.\n", ret); 81 | ret = 1; 82 | goto end; 83 | } 84 | initted = true; 85 | 86 | draw_ui(&ctx); 87 | tb_present(); 88 | 89 | { 90 | struct tb_event ev; 91 | 92 | while (tb_poll_event(&ev)) { 93 | switch (ev.type) { 94 | case TB_EVENT_RESIZE: 95 | draw_ui(&ctx); 96 | break; 97 | case TB_EVENT_KEY: 98 | switch (ev.key) { 99 | case TB_KEY_ARROW_UP: 100 | if (ctx.offset == 0) { 101 | if (ctx.nf_offset != 0 && ctx.of_offset != 0) 102 | load_previous(&ctx, &lbuf[0], scratch); 103 | } else { 104 | ctx.offset -= 2; 105 | } 106 | break; 107 | case TB_KEY_ARROW_DOWN: { 108 | unsigned int cpl = get_char_per_line(); 109 | unsigned int lpu = get_line_per_side(); 110 | 111 | if (ctx.done) 112 | continue; 113 | 114 | ctx.offset += 2; 115 | 116 | if (((size_t) ctx.offset / 2) * cpl + cpl * lpu >= ctx.blocksize) { 117 | bool err; 118 | 119 | calc_next_mask(&ctx, &lbuf[0], scratch, &err); 120 | 121 | if (err) 122 | goto end; 123 | } 124 | 125 | break; 126 | } 127 | case TB_KEY_PGUP: { 128 | unsigned int lpu = get_line_per_side(); 129 | 130 | for (unsigned int i = 0; i < lpu; i++) { 131 | if (ctx.offset == 0) { 132 | if (ctx.nf_offset != 0 && ctx.of_offset != 0) 133 | load_previous(&ctx, &lbuf[0], scratch); 134 | } else { 135 | ctx.offset -= 2; 136 | } 137 | } 138 | break; 139 | } 140 | case TB_KEY_PGDN: { 141 | unsigned int cpl = get_char_per_line(); 142 | unsigned int lpu = get_line_per_side(); 143 | 144 | if (ctx.done) 145 | continue; 146 | 147 | for (unsigned int i = 0; i < lpu && !ctx.done; i++) { 148 | ctx.offset += 2; 149 | 150 | if (((size_t) ctx.offset / 2) * cpl + cpl * lpu >= ctx.blocksize) { 151 | bool err; 152 | 153 | calc_next_mask(&ctx, &lbuf[0], scratch, &err); 154 | 155 | if (err) 156 | goto end; 157 | } 158 | 159 | draw_ui_dummy(&ctx); 160 | } 161 | break; 162 | } 163 | case TB_KEY_BACKSPACE: 164 | case TB_KEY_BACKSPACE2: { 165 | bool change_found = false; 166 | 167 | while (!change_found && ctx.nf_offset != 0 && ctx.of_offset != 0) { 168 | ctx.offset = 0; 169 | change_found = !load_previous(&ctx, &lbuf[0], scratch); 170 | draw_ui_dummy(&ctx); 171 | } 172 | 173 | while (!ctx.has_change && ctx.offset != 0) { 174 | ctx.offset -= 2; 175 | draw_ui_dummy(&ctx); 176 | } 177 | 178 | break; 179 | } 180 | case TB_KEY_SPACE: { 181 | unsigned int cpl = get_char_per_line(); 182 | unsigned int lpu = get_line_per_side(); 183 | bool err = false; 184 | 185 | if (ctx.done) 186 | break; 187 | 188 | do { 189 | if (err) 190 | goto end; 191 | 192 | while (((size_t) ctx.offset / 2) * cpl + cpl * lpu < ctx.blocksize) 193 | ctx.offset += 2; 194 | } while(calc_next_mask(&ctx, &lbuf[0], scratch, &err) && !ctx.done); 195 | 196 | if (err) 197 | goto end; 198 | 199 | while (!ctx.has_change && !ctx.done) { 200 | ctx.offset += 2; 201 | draw_ui_dummy(&ctx); 202 | } 203 | 204 | break; 205 | } 206 | case 0: 207 | if (ev.ch == 'q') 208 | goto end; 209 | continue; 210 | default: 211 | continue; 212 | } 213 | draw_ui(&ctx); 214 | } 215 | tb_present(); 216 | } 217 | } 218 | 219 | end: 220 | if (ctx.ndiff != NULL) 221 | free(ctx.ndiff); 222 | 223 | if (ctx.odiff != NULL) 224 | free(ctx.odiff); 225 | 226 | if (scratch != NULL) 227 | free(scratch); 228 | 229 | if (ctx.prev_offset != NULL) 230 | free(ctx.prev_offset); 231 | 232 | if (ctx.prev_oshift != NULL) 233 | free(ctx.prev_oshift); 234 | 235 | if (ctx.prev_nshift != NULL) 236 | free(ctx.prev_nshift); 237 | 238 | close_files(&ctx); 239 | 240 | if (initted) 241 | tb_shutdown(); 242 | 243 | return ret; 244 | } 245 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "context.h" 8 | #include "lcs.h" 9 | #include "draw.h" 10 | 11 | #define MIN(x,y) ((x) < (y) ? (x) : (y)) 12 | 13 | bool load_previous(Context *ctx, uint8_t *lbuf, size_t *scratch) 14 | { 15 | off_t old_pos = ctx->offset_pos - 1; 16 | 17 | ctx->offset = ctx->prev_offset[old_pos]; 18 | 19 | ctx->nf_offset -= ctx->prev_nshift[old_pos]; 20 | ctx->of_offset -= ctx->prev_oshift[old_pos]; 21 | 22 | ctx->offset_pos--; 23 | 24 | if (!ctx->is_cleared) { 25 | memset(scratch, 0, (ctx->blocksize + 1) * (ctx->blocksize + 1) * sizeof(*scratch)); 26 | 27 | memset(lbuf, 0, ctx->blocksize); 28 | 29 | memset(&ctx->odiff[0], 0, ctx->odsize); 30 | memset(&ctx->ndiff[0], 0, ctx->ndsize); 31 | } 32 | ctx->ndsize = 0; 33 | ctx->odsize = 0; 34 | 35 | return calc_lcs_mask(ctx, lbuf, scratch); 36 | } 37 | 38 | bool calc_next_mask(Context *ctx, uint8_t *lbuf, size_t *scratch, bool *err) 39 | { 40 | off_t old_pos = ctx->offset_pos; 41 | size_t cmp_size; 42 | 43 | *err = false; 44 | 45 | /* Dummy call. */ 46 | draw_ui_dummy(ctx); 47 | 48 | if ((size_t) old_pos >= ctx->prev_offset_size) { 49 | uint16_t *new_prev_offset; 50 | uint16_t *new_prev_oshift; 51 | uint16_t *new_prev_nshift; 52 | 53 | ctx->prev_offset_size *= 2; 54 | 55 | new_prev_offset = realloc(ctx->prev_offset, ctx->prev_offset_size * sizeof(*new_prev_offset)); 56 | if (new_prev_offset == NULL) { 57 | *err = true; 58 | return false; 59 | } 60 | ctx->prev_offset = new_prev_offset; 61 | 62 | new_prev_oshift = realloc(ctx->prev_oshift, ctx->prev_offset_size * sizeof(*new_prev_oshift)); 63 | if (new_prev_oshift == NULL) { 64 | *err = true; 65 | return false; 66 | } 67 | ctx->prev_oshift = new_prev_oshift; 68 | 69 | new_prev_nshift = realloc(ctx->prev_nshift, ctx->prev_offset_size * sizeof(*new_prev_nshift)); 70 | if (new_prev_nshift == NULL) { 71 | *err = true; 72 | return false; 73 | } 74 | ctx->prev_nshift = new_prev_nshift; 75 | } 76 | 77 | ctx->prev_offset[old_pos] = ctx->offset - 2; 78 | ctx->prev_oshift[old_pos] = ctx->oshift; 79 | ctx->prev_nshift[old_pos] = ctx->nshift; 80 | ctx->offset_pos++; 81 | 82 | ctx->nf_offset += ctx->nshift; 83 | ctx->of_offset += ctx->oshift; 84 | 85 | if (!ctx->is_cleared) { 86 | memset(lbuf, 0, ctx->blocksize); 87 | 88 | memset(scratch, 0, (ctx->blocksize + 1) * (ctx->blocksize + 1) * sizeof(*scratch)); 89 | memset(&ctx->odiff[0], 0, ctx->odsize); 90 | memset(&ctx->ndiff[0], 0, ctx->ndsize); 91 | 92 | ctx->is_cleared = true; 93 | } 94 | ctx->ndsize = 0; 95 | ctx->odsize = 0; 96 | 97 | ctx->offset = 0; 98 | 99 | cmp_size = MIN(MIN(ctx->osize - ctx->of_offset, ctx->blocksize), ctx->nsize - ctx->nf_offset); 100 | 101 | if (!memcmp(&ctx->obuf[ctx->of_offset], &ctx->nbuf[ctx->nf_offset], cmp_size)) { 102 | ctx->ndsize = cmp_size; 103 | ctx->odsize = cmp_size; 104 | if (cmp_size < ctx->blocksize) 105 | ctx->done = true; 106 | return true; 107 | } 108 | 109 | return calc_lcs_mask(ctx, lbuf, scratch); 110 | } 111 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #ifndef _UTIL_H 2 | #define _UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "context.h" 9 | 10 | bool load_previous(Context *ctx, uint8_t *lbuf, size_t *scratch); 11 | bool calc_next_mask(Context *ctx, uint8_t *lbuf, size_t *scratch, bool *err); 12 | 13 | #endif 14 | --------------------------------------------------------------------------------