1 //--------------------------------------------------------------------
4 // Copyright 1995-2008 by Christopher J. Madsen
6 // Visual display of differences in binary files
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of
11 // the License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 //--------------------------------------------------------------------
37 #include "GetOpt/GetOpt.hpp"
42 const char titleString
[] =
43 "\nVBinDiff " PACKAGE_VERSION
"\nCopyright 1995-2008 Christopher J. Madsen";
45 void exitMsg(int status
, const char* message
);
46 void usage(bool showHelp
=true, int exitStatus
=0);
48 //====================================================================
51 typedef unsigned char Byte
;
52 typedef unsigned short Word
;
56 enum LockState
{ lockNeither
= 0, lockTop
, lockBottom
};
58 //--------------------------------------------------------------------
61 typedef string String
;
63 typedef String::size_type StrIdx
;
64 typedef String::iterator StrItr
;
65 typedef String::const_iterator StrConstItr
;
67 //--------------------------------------------------------------------
70 typedef vector
<String
> StrVec
;
71 typedef StrVec::iterator SVItr
;
72 typedef StrVec::const_iterator SVConstItr
;
74 typedef StrVec::size_type VecSize
;
76 //--------------------------------------------------------------------
79 typedef map
<VecSize
, String
> StrMap
;
80 typedef StrMap::value_type SMVal
;
81 typedef StrMap::iterator SMItr
;
82 typedef StrMap::const_iterator SMConstItr
;
84 //====================================================================
87 const Command cmmMove
= 0x80;
89 const Command cmmMoveSize
= 0x03;
90 const Command cmmMoveForward
= 0x04;
91 const Command cmmMoveTop
= 0x08;
92 const Command cmmMoveBottom
= 0x10;
94 const Command cmmMoveByte
= 0x00; // Move 1 byte
95 const Command cmmMoveLine
= 0x01; // Move 1 line
96 const Command cmmMovePage
= 0x02; // Move 1 page
97 const Command cmmMoveAll
= 0x03; // Move to beginning or end
99 const Command cmmMoveBoth
= cmmMoveTop
|cmmMoveBottom
;
101 const Command cmgGoto
= 0x04; // Commands 4-7
102 const Command cmgGotoTop
= 0x01;
103 const Command cmgGotoBottom
= 0x02;
104 const Command cmgGotoBoth
= cmgGotoTop
|cmgGotoBottom
;
105 const Command cmgGotoMask
= ~cmgGotoBoth
;
107 const Command cmNothing
= 0;
108 const Command cmNextDiff
= 1;
109 const Command cmQuit
= 2;
110 const Command cmEditTop
= 8;
111 const Command cmEditBottom
= 9;
112 const Command cmUseTop
= 10;
113 const Command cmUseBottom
= 11;
114 const Command cmToggleASCII
= 12;
115 const Command cmFind
= 16; // Commands 16-19
117 const short leftMar
= 11; // Starting column of hex display
118 const short leftMar2
= 61; // Starting column of ASCII display
120 const int lineWidth
= 16; // Number of bytes displayed per line
122 const int promptHeight
= 4; // Height of prompt window
123 const int inWidth
= 10; // Width of input window (excluding border)
124 const int screenWidth
= 80;
126 const int maxPath
= 260;
128 const VecSize maxHistory
= 2000;
130 const char hexDigits
[] = "0123456789ABCDEF";
132 #include "tables.h" // ASCII and EBCDIC tables
134 //====================================================================
135 // Class Declarations:
137 void showEditPrompt();
144 Byte line
[1][lineWidth
];
145 Byte buffer
[lineWidth
];
150 friend class Difference
;
155 const Difference
* diffs
;
157 char fileName
[maxPath
];
165 void init(int y
, const Difference
* aDiff
=NULL
,
166 const char* aFileName
=NULL
);
170 bool edit(const FileDisplay
* other
);
171 const Byte
* getBuffer() const { return data
->buffer
; };
172 void move(int step
) { moveTo(offset
+ step
); };
173 void moveTo(FPos newOffset
);
174 bool moveTo(const Byte
* searchFor
, int searchLen
);
175 void moveToEnd(FileDisplay
* other
);
176 bool setFile(const char* aFileName
);
178 void setByte(short x
, short y
, Byte b
);
179 }; // end FileDisplay
183 friend void FileDisplay::display();
187 const FileDisplay
* file1
;
188 const FileDisplay
* file2
;
191 Difference(const FileDisplay
* aFile1
, const FileDisplay
* aFile2
);
194 int getNumDiffs() const { return numDiffs
; };
201 char* buf
; // The editing buffer
202 const char* restrict
; // If non-NULL, only allow these chars
203 StrVec
& history
; // The history vector to use
204 StrMap historyOverlay
; // Overlay of modified history entries
205 VecSize historyPos
; // The current offset into history[]
206 int maxLen
; // The size of buf (not including NUL)
207 int len
; // The current length of the string
208 int i
; // The current cursor position
209 bool upcase
; // Force all characters to uppercase?
210 bool splitHex
; // Entering space-separated hex bytes?
211 bool insert
; // False for overstrike mode
214 InputManager(char* aBuf
, int aMaxLen
, StrVec
& aHistory
);
216 void setCharacters(const char* aRestriction
) { restrict
= aRestriction
; };
217 void setSplitHex(bool val
) { splitHex
= val
; };
218 void setUpcase(bool val
) { upcase
= val
; };
221 bool normalize(int pos
);
222 void useHistory(int delta
);
223 }; // end InputManager
225 //====================================================================
229 StrVec hexSearchHistory
, textSearchHistory
, positionHistory
;
230 ConWindow promptWin
,inWin
;
231 FileDisplay file1
, file2
;
232 Difference
diffs(&file1
, &file2
);
233 const char* displayTable
= asciiDisplayTable
;
234 const char* program_name
; // Name under which this program was invoked
235 LockState lockState
= lockNeither
;
236 bool singleFile
= false;
238 int numLines
= 9; // Number of lines of each file to display
239 int bufSize
= numLines
* lineWidth
;
240 int linesBetween
= 1; // Number of lines of padding between files
242 // The number of bytes to move for each possible step size:
243 // See cmmMoveByte, cmmMoveLine, cmmMovePage
244 int steps
[4] = {1, lineWidth
, bufSize
-lineWidth
, 0};
247 //====================================================================
248 // Miscellaneous Functions:
249 //--------------------------------------------------------------------
252 #ifdef WIN32_CONSOLE // beep() is defined by ncurses
257 #endif // WIN32_CONSOLE
259 //--------------------------------------------------------------------
260 // Convert a character to uppercase:
262 // The standard toupper(c) isn't guaranteed for arbitrary integers.
266 return (c
>= 0 && c
<= UCHAR_MAX
) ? toupper(c
) : c
;
269 //====================================================================
274 // The FileDisplay objects being compared
276 // The number of differences between the two FileDisplay buffers
278 // An array of bools for each byte in the FileDisplay buffers
279 // True marks differences
281 //--------------------------------------------------------------------
286 // Pointers to the FileDisplay objects to compare
288 Difference::Difference(const FileDisplay
* aFile1
, const FileDisplay
* aFile2
)
293 } // end Difference::Difference
295 //--------------------------------------------------------------------
296 Difference::~Difference()
298 delete [] reinterpret_cast<Byte
*>(data
);
299 } // end Difference::~Difference
301 //--------------------------------------------------------------------
302 // Compute differences:
305 // file1, file2: The files to compare
308 // The number of differences between the buffers
309 // -1 if both buffers are empty
312 // numDiffs: The number of differences between the buffers
314 int Difference::compute()
317 // We return 1 so that cmNextDiff won't keep searching:
318 return (file1
->bufContents
? 1 : -1);
320 memset(data
->buffer
, 0, bufSize
); // Clear the difference table
324 const Byte
* buf1
= file1
->data
->buffer
;
325 const Byte
* buf2
= file2
->data
->buffer
;
327 int size
= min(file1
->bufContents
, file2
->bufContents
);
330 for (i
= 0; i
< size
; i
++)
331 if (*(buf1
++) != *(buf2
++)) {
332 data
->buffer
[i
] = true;
336 size
= max(file1
->bufContents
, file2
->bufContents
);
339 // One buffer has more data than the other:
340 different
+= size
- i
;
341 for (; i
< size
; i
++)
342 data
->buffer
[i
] = true; // These bytes are only in 1 buffer
344 return -1; // Both buffers are empty
346 numDiffs
= different
;
349 } // end Difference::compute
351 //--------------------------------------------------------------------
352 void Difference::resize()
354 if (singleFile
) return;
357 delete [] reinterpret_cast<Byte
*>(data
);
359 data
= reinterpret_cast<FileBuffer
*>(new Byte
[bufSize
]);
360 } // end Difference::resize
362 //====================================================================
363 // Class FileDisplay:
367 // The number of bytes in the file buffer
369 // A pointer to the Difference object related to this file
371 // The file being displayed
373 // The relative pathname of the file being displayed
375 // The position in the file of the first byte in the buffer
377 // The handle of the window used for display
379 // The vertical position of the display window
381 // The currently displayed portion of the file
383 //--------------------------------------------------------------------
386 FileDisplay::FileDisplay()
395 } // end FileDisplay::FileDisplay
397 //--------------------------------------------------------------------
400 // Creates the display window and opens the file.
403 // y: The vertical position of the display window
404 // aDiff: The Difference object related to this buffer
405 // aFileName: The name of the file to display
407 void FileDisplay::init(int y
, const Difference
* aDiff
,
408 const char* aFileName
)
413 win
.init(0,y
, screenWidth
, (numLines
+ 1 + ((y
==0) ? linesBetween
: 0)),
420 } // end FileDisplay::init
422 //--------------------------------------------------------------------
425 FileDisplay::~FileDisplay()
429 delete [] reinterpret_cast<Byte
*>(data
);
430 } // end FileDisplay::~FileDisplay
432 //--------------------------------------------------------------------
433 void FileDisplay::resize()
436 delete [] reinterpret_cast<Byte
*>(data
);
438 data
= reinterpret_cast<FileBuffer
*>(new Byte
[bufSize
]);
440 // FIXME resize window
441 } // end FileDisplay::resize
443 //--------------------------------------------------------------------
444 // Shut down the file display:
446 // Deletes the display window.
448 void FileDisplay::shutDown()
451 } // end FileDisplay::shutDown
453 //--------------------------------------------------------------------
454 // Display the file contents:
456 void FileDisplay::display()
458 if (!fileName
[0]) return;
460 FPos lineOffset
= offset
;
462 short i
,j
,index
,lineLength
;
463 char buf
[lineWidth
+ lineWidth
/8 + 1];
464 buf
[sizeof(buf
)-1] = '\0';
466 char buf2
[screenWidth
+1];
467 buf2
[screenWidth
] = '\0';
469 memset(buf
, ' ', sizeof(buf
)-1);
471 for (i
= 0; i
< numLines
; i
++) {
472 // cerr << i << '\n';
475 sprintf(str
, "%04X %04X:",Word(lineOffset
>>16),Word(lineOffset
&0xFFFF));
477 lineLength
= min(lineWidth
, bufContents
- i
*lineWidth
);
479 for (j
= 0, index
= -1; j
< lineLength
; j
++) {
484 str
+= sprintf(str
, "%02X ", data
->line
[i
][j
]);
486 buf
[index
++] = displayTable
[data
->line
[i
][j
]];
488 memset(buf
+ index
, ' ', sizeof(buf
) - index
- 1);
489 memset(str
, ' ', screenWidth
- (str
- buf2
));
491 win
.put(0,i
+1, buf2
);
492 win
.put(leftMar2
,i
+1, buf
);
495 for (j
= 0; j
< lineWidth
; j
++)
496 if (diffs
->data
->line
[i
][j
]) {
497 win
.putAttribs(j
*3 + leftMar
+ (j
>7),i
+1, cFileDiff
,2);
498 win
.putAttribs(j
+ leftMar2
+ (j
>7),i
+1, cFileDiff
,1);
500 lineOffset
+= lineWidth
;
501 } // end for i up to numLines
504 } // end FileDisplay::display
506 //--------------------------------------------------------------------
510 // true: File changed
511 // false: File did not change
513 bool FileDisplay::edit(const FileDisplay
* other
)
515 if (!bufContents
&& offset
)
516 return false; // You must not be completely past EOF
519 File w
= OpenFile(fileName
, true);
520 if (w
== InvalidFile
) return false;
526 if (bufContents
< bufSize
)
527 memset(data
->buffer
+ bufContents
, 0, bufSize
- bufContents
);
533 bool changed
= false;
536 const Byte
*const inputTable
= ((displayTable
== ebcdicDisplayTable
)
538 : NULL
); // No translation
541 win
.setCursor(leftMar
,1);
542 ConWindow::showCursor();
545 win
.setCursor((ascii
? leftMar2
+ x
: leftMar
+ 3*x
+ !hiNib
) + (x
/ 8),
550 case KEY_ESCAPE
: goto done
;
562 if (!ascii
) hiNib
= false;
563 if (--x
< 0) x
= lineWidth
-1;
565 if (hiNib
|| (x
< lineWidth
-1))
568 case KEY_UP
: if (--y
< 0) y
= numLines
-1; break;
572 if ((key
== KEY_RETURN
) && other
&&
573 (other
->bufContents
> x
+ y
*lineWidth
)) {
574 newByte
= other
->data
->line
[y
][x
]; // Copy from other file
575 hiNib
= ascii
; // Always advance cursor to next byte
577 if (isprint(key
)) newByte
= (inputTable
? inputTable
[key
] : key
);
581 else if (isxdigit(key
))
582 newByte
= safeUC(key
) - 'A' + 10;
585 newByte
= (newByte
* 0x10) | (0x0F & data
->line
[y
][x
]);
587 newByte
|= 0xF0 & data
->line
[y
][x
];
588 } // end if valid digit entered
592 setByte(x
,y
,newByte
);
595 } // end default and fall thru
601 if (++x
>= lineWidth
) x
= 0;
606 case KEY_DOWN
: if (++y
>= numLines
) y
= 0; break;
616 promptWin
.put(30,1,"Save changes (Y/N):");
618 promptWin
.setCursor(50,1);
619 key
= promptWin
.readKey();
620 if (safeUC(key
) != 'Y') {
622 moveTo(offset
); // Re-read buffer contents
624 SeekFile(file
, offset
);
625 WriteFile(file
, data
->buffer
, bufContents
);
629 ConWindow::hideCursor();
631 } // end FileDisplay::edit
633 //--------------------------------------------------------------------
634 void FileDisplay::setByte(short x
, short y
, Byte b
)
636 if (x
+ y
*lineWidth
>= bufContents
) {
637 if (x
+ y
*lineWidth
> bufContents
) {
638 short y1
= bufContents
/ lineWidth
;
639 short x1
= bufContents
% lineWidth
;
640 while (y1
<= numLines
) {
641 while (x1
< lineWidth
) {
642 if ((x1
== x
) && (y1
== y
)) goto done
;
649 } // end if more than 1 byte past the end
652 data
->line
[y
][x
] = b
^ 1; // Make sure it's different
653 } // end if past the end
655 if (data
->line
[y
][x
] != b
) {
656 data
->line
[y
][x
] = b
;
658 sprintf(str
, "%02X", b
);
659 win
.setAttribs(cFileEdit
);
660 win
.put(leftMar
+ 3*x
+ (x
/ 8), y
+1, str
);
661 str
[0] = displayTable
[b
];
663 win
.put(leftMar2
+ x
+ (x
/ 8), y
+1, str
);
664 win
.setAttribs(cFileWin
);
667 } // end FileDisplay::setByte
669 //--------------------------------------------------------------------
670 // Change the file position:
672 // Changes the file offset and updates the buffer.
673 // Does not update the display.
677 // The number of bytes to move
678 // A negative value means to move backward
680 // void FileDisplay::move(int step) /* Inline function */
682 //--------------------------------------------------------------------
683 // Change the file position:
685 // Changes the file offset and updates the buffer.
686 // Does not update the display.
690 // The new position of the file
692 void FileDisplay::moveTo(FPos newOffset
)
694 if (!fileName
[0]) return; // No file
701 SeekFile(file
, offset
);
702 bufContents
= ReadFile(file
, data
->buffer
, bufSize
);
703 } // end FileDisplay::moveTo
705 //--------------------------------------------------------------------
706 // Change the file position by searching:
708 // Changes the file offset and updates the buffer.
709 // Does not update the display.
712 // searchFor: The bytes to search for
713 // searchLen: The number of bytes in searchFor
716 // true: The search was successful
717 // false: Search unsuccessful, file not moved
719 bool FileDisplay::moveTo(const Byte
* searchFor
, int searchLen
)
721 if (!fileName
[0]) return true; // No file, pretend success
723 // Using algorithm based on QuickSearch:
724 // http://www-igm.univ-mlv.fr/~lecroq/string/node19.htm
726 // Compute offset table:
730 for (i
= 0; i
< 256; ++i
)
731 moveOver
[i
] = searchLen
+ 1;
732 for (i
= 0; i
< searchLen
; ++i
)
733 moveOver
[searchFor
[i
]] = searchLen
- i
;
735 // Prepare the search buffer:
738 blockSize
= 8 * 1024,
739 moveLength
= searchLen
,
740 restartAt
= blockSize
- moveLength
,
741 fullStop
= blockSize
* 2 - moveLength
;
743 Byte
*const searchBuf
= new Byte
[2 * blockSize
];
745 Byte
*const copyTo
= searchBuf
+ restartAt
;
746 const Byte
*const copyFrom
= searchBuf
+ fullStop
;
748 char *const readAt
= reinterpret_cast<char*>(searchBuf
) + blockSize
;
750 FPos newPos
= offset
+ 1;
752 SeekFile(file
, newPos
);
753 Size bytesRead
= ReadFile(file
, searchBuf
, blockSize
* 2);
754 int stopAt
= bytesRead
- moveLength
;
759 if (stopAt
< fullStop
) ++stopAt
;
762 if (memcmp(searchFor
, searchBuf
+ i
, searchLen
) == 0)
765 i
+= moveOver
[searchBuf
[i
+ searchLen
]]; // shift
766 } // end while more buffer to search
768 if (stopAt
!= fullStop
) {
771 } // Nothing more to read
775 memcpy(copyTo
, copyFrom
, moveLength
);
776 bytesRead
= ReadFile(file
, readAt
, blockSize
);
777 stopAt
= bytesRead
+ blockSize
- moveLength
;
783 if (i
< 0) return false; // No match
788 } // end FileDisplay::moveTo
790 //--------------------------------------------------------------------
791 // Move to the end of the file:
794 // other: If non NULL, move both files to the end of the shorter file
796 void FileDisplay::moveToEnd(FileDisplay
* other
)
798 if (!fileName
[0]) return; // No file
800 FPos end
= SeekFile(file
, 0, SeekEnd
);
804 // If the files aren't currently at the same position,
805 // we want to keep them offset by the same amount:
806 diff
= other
->offset
- offset
;
808 end
= min(end
, SeekFile(other
->file
, 0, SeekEnd
) - diff
);
809 } // end if moving other file too
811 end
-= steps
[cmmMovePage
];
815 if (other
) other
->moveTo(end
+ diff
);
816 } // end FileDisplay::moveToEnd
818 //--------------------------------------------------------------------
819 // Open a file for display:
821 // Opens the file, updates the filename display, and reads the start
822 // of the file into the buffer.
825 // aFileName: The name of the file to open
828 // True: Operation successful
829 // False: Unable to open file (call ErrorMsg for error message)
831 bool FileDisplay::setFile(const char* aFileName
)
833 strncpy(fileName
, aFileName
, maxPath
);
834 fileName
[maxPath
-1] = '\0';
836 win
.put(0,0, fileName
);
837 win
.putAttribs(0,0, cFileName
, screenWidth
);
838 win
.update(); // FIXME
841 file
= OpenFile(fileName
);
844 if (file
== InvalidFile
)
848 bufContents
= ReadFile(file
, data
->buffer
, bufSize
);
851 } // end FileDisplay::setFile
853 //====================================================================
855 //--------------------------------------------------------------------
856 void calcScreenLayout(bool resize
= true)
858 int screenX
, screenY
;
860 ConWindow::getScreenSize(screenX
, screenY
);
862 if (screenX
< screenWidth
) {
864 err
<< "The screen must be at least "
865 << screenWidth
<< " characters wide.";
866 exitMsg(2, err
.str().c_str());
869 if (screenY
< promptHeight
+ 4) {
871 err
<< "The screen must be at least "
872 << (promptHeight
+ 4) << " lines high.";
873 exitMsg(2, err
.str().c_str());
876 numLines
= screenY
- promptHeight
- (singleFile
? 1 : 2);
881 linesBetween
= numLines
% 2;
882 numLines
= (numLines
- linesBetween
) / 2;
885 bufSize
= numLines
* lineWidth
;
887 steps
[cmmMovePage
] = bufSize
-lineWidth
;
889 // FIXME resize existing windows
890 } // end calcScreenLayout
892 //--------------------------------------------------------------------
893 void displayCharacterSet()
895 const bool isASCII
= (displayTable
== asciiDisplayTable
);
897 promptWin
.putAttribs(3,2, (isASCII
? cCurrentMode
: cBackground
), 5);
898 promptWin
.putAttribs(9,2, (isASCII
? cBackground
: cCurrentMode
), 6);
901 } // end displayCharacterSet
903 //--------------------------------------------------------------------
904 void displayLockState()
906 #ifndef WIN32_CONSOLE // The Win32 version uses Ctrl & Alt instead
907 if (singleFile
) return;
909 promptWin
.putAttribs(63,1,
910 ((lockState
== lockBottom
) ? cCurrentMode
: cBackground
),
912 promptWin
.putAttribs(63,2,
913 ((lockState
== lockTop
) ? cCurrentMode
: cBackground
),
916 } // end displayLockState
918 //--------------------------------------------------------------------
919 // Print a message to stderr and exit:
922 // status: The exit status to use
923 // message: The message to print
925 void exitMsg(int status
, const char* message
)
927 ConWindow::shutdown();
929 cerr
<< endl
<< message
<< endl
;
933 //--------------------------------------------------------------------
934 // Normalize the string in the input window:
936 // Does nothing unless splitHex mode is active.
939 // pos: The position of the cursor in buf
942 // true: The input buffer was changed
943 // false: No changes were necessary
945 bool InputManager::normalize(int pos
)
947 if (!splitHex
) return false;
950 if (pos
&& buf
[pos
] == ' ' && buf
[pos
-1] != ' ') {
951 buf
[pos
] = buf
[pos
-1];
953 if (pos
== len
) len
+= 2;
958 if (pos
< len
&& buf
[pos
] == ' ' && buf
[pos
+1] != ' ') {
963 return false; // No changes necessary
964 } // end InputManager::normalize
966 //--------------------------------------------------------------------
967 // Get a string using inWin:
970 // buf: The buffer where the string will be stored
971 // maxLen: The maximum number of chars to accept (not including NUL byte)
972 // history: The history vector to use
973 // restrict: If not NULL, accept only chars in this string
974 // upcase: If true, convert all chars with safeUC
976 void getString(char* buf
, int maxLen
, StrVec
& history
,
977 const char* restrict
=NULL
,
978 bool upcase
=false, bool splitHex
=false)
980 InputManager
manager(buf
, maxLen
, history
);
982 manager
.setCharacters(restrict
);
983 manager
.setSplitHex(splitHex
);
984 manager
.setUpcase(upcase
);
989 //--------------------------------------------------------------------
990 // Construct the InputManager object:
993 // aBuf: The buffer where the string will be stored
994 // aMaxLen: The maximum number of chars to accept (not including NUL byte)
995 // aHistory: The history vector to use
997 InputManager::InputManager(char* aBuf
, int aMaxLen
, StrVec
& aHistory
)
1001 historyPos(aHistory
.size()),
1009 } // end InputManager
1011 //--------------------------------------------------------------------
1012 // Run the main loop to get an input string:
1015 // true: Enter was pressed
1016 // false: Escape was pressed
1018 bool InputManager::run()
1020 inWin
.setCursor(2,1);
1022 bool inWinShown
= false;
1024 bool aborted
= true;
1026 ConWindow::showCursor(insert
);
1028 memset(buf
, ' ', maxLen
);
1031 // We need to be able to display complete bytes:
1032 if (splitHex
&& (maxLen
% 3 == 1)) --maxLen
;
1037 if (inWinShown
) inWin
.update(1); // Only update inside the box
1038 else { inWin
.update(); inWinShown
= true; } // Show the input window
1039 inWin
.setCursor(2+i
,1);
1040 int key
= inWin
.readKey();
1041 if (upcase
) key
= safeUC(key
);
1044 case KEY_ESCAPE
: buf
[0] = '\0'; done
= true; break; // ESC
1046 case KEY_RETURN
: // Enter
1054 case KEY_DELETE
: // Backspace on most Unix terminals
1055 case 0x08: // Backspace (Ctrl-H)
1056 if (!i
) continue; // Can't back up if we're at the beginning already
1059 // At the beginning of a byte; erase last digit of previous byte:
1060 if (i
== len
) len
-= 2;
1063 } else if (i
< len
&& buf
[i
] != ' ') {
1064 // On the second digit; erase the first digit:
1067 // On a blank second digit; delete the entire byte:
1069 memmove(buf
+ i
, buf
+ i
+ 3, maxLen
- i
- 3);
1071 if (len
< i
) len
= i
;
1073 } else { // not splitHex mode
1074 memmove(buf
+ i
- 1, buf
+ i
, maxLen
- i
);
1075 buf
[maxLen
-1] = ' ';
1077 } // end else not splitHex mode
1080 case 0x04: // Ctrl-D
1082 if (i
>= len
) continue;
1085 memmove(buf
+ i
, buf
+ i
+ 3, maxLen
- i
- 3);
1087 if (len
< i
) len
= i
;
1089 memmove(buf
+ i
, buf
+ i
+ 1, maxLen
- i
- 1);
1090 buf
[maxLen
-1] = ' ';
1092 } // end else not splitHex mode
1097 ConWindow::showCursor(insert
);
1100 case 0x02: // Ctrl-B
1106 if (i
% 3 == 2) --i
;
1111 case 0x06: // Ctrl-F
1117 if ((i
< maxLen
) && (i
% 3 == 2)) ++i
;
1122 case 0x0B: // Ctrl-K
1124 memset(buf
+ i
, ' ', len
- i
);
1129 case 0x01: // Ctrl-A
1135 case 0x05: // Ctrl-E
1137 if (splitHex
&& (i
< len
))
1142 case 0x10: // Ctrl-P
1144 if (historyPos
== 0) beep();
1145 else useHistory(-1);
1148 case 0x0E: // Ctrl-N
1150 if (historyPos
== history
.size()) beep();
1151 else useHistory(+1);
1155 if (isprint(key
) && (!restrict
|| strchr(restrict
, key
))) {
1158 if (buf
[i
] == ' ') {
1159 if (i
>= maxLen
) continue;
1161 if (len
>= maxLen
) continue;
1163 memmove(buf
+ i
+ 3, buf
+ i
, maxLen
- i
- 3);
1167 } // end if splitHex mode
1169 if (len
>= maxLen
) continue;
1170 memmove(buf
+ i
+ 1, buf
+ i
, maxLen
- i
- 1);
1172 } // end else not splitHex mode
1173 } else { // overstrike mode
1174 if (i
>= maxLen
) continue;
1175 } // end else overstrike mode
1177 if (splitHex
&& (i
< maxLen
) && (i
% 3 == 2))
1179 if (i
> len
) len
= i
;
1180 } // end if is acceptable character to insert
1182 } // end while not done
1184 // Hide the input window & cursor:
1185 ConWindow::hideCursor();
1188 // Record the result in the history:
1189 if (!aborted
&& len
) {
1190 String
newValue(buf
);
1192 SVItr exists
= find(history
.begin(), history
.end(), newValue
);
1193 if (exists
!= history
.end())
1194 // Already in history. Move it to the end:
1195 rotate(exists
, exists
+ 1, history
.end());
1196 else if (history
.size() >= maxHistory
) {
1197 // History is full. Replace the first entry & move it to the end:
1198 history
.front().swap(newValue
);
1199 rotate(history
.begin(), history
.begin() + 1, history
.end());
1201 // Just append to history:
1202 history
.push_back(newValue
);
1203 } // end if we have a value to store in the history
1208 //--------------------------------------------------------------------
1209 // Switch the current input line with one from the history:
1212 // delta: The number to add to historyPos (-1 previous, +1 next)
1214 void InputManager::useHistory(int delta
)
1216 // Clean up the current string if necessary:
1219 // Update the history overlay if necessary:
1220 // We always store the initial value, because it doesn't
1221 // correspond to a valid entry in history.
1222 if (len
|| historyPos
== history
.size())
1223 historyOverlay
[historyPos
].assign(buf
, len
);
1225 // Look for an entry in the overlay:
1226 SMItr itr
= historyOverlay
.find(historyPos
+= delta
);
1228 String
& s
= ((itr
== historyOverlay
.end())
1229 ? history
[historyPos
] : itr
->second
);
1231 // Store the new string in the buffer:
1232 memset(buf
, ' ', maxLen
);
1233 i
= len
= min(static_cast<VecSize
>(maxLen
), s
.length());
1234 memcpy(buf
, s
.c_str(), len
);
1237 //--------------------------------------------------------------------
1238 // Convert hex string to bytes:
1241 // buf: Must contain a well-formed string of hex characters
1242 // (each byte must be separated by spaces)
1245 // buf: Contains the translated bytes
1248 // The number of bytes in buf
1250 int packHex(Byte
* buf
)
1254 char* in
= reinterpret_cast<char*>(buf
);
1261 val
= strtoul(in
, &in
, 16);
1262 *(out
++) = Byte(val
);
1269 //--------------------------------------------------------------------
1270 // Position the input window:
1273 // cmd: Indicates where the window should be positioned
1274 // width: The width of the window
1275 // title: The title for the window
1277 void positionInWin(Command cmd
, short width
, const char* title
)
1279 inWin
.resize(width
, 3);
1280 inWin
.move((screenWidth
-width
)/2,
1281 ((!singleFile
&& (cmd
& cmgGotoBottom
))
1282 ? ((cmd
& cmgGotoTop
)
1283 ? numLines
+ linesBetween
// Moving both
1284 : numLines
+ numLines
/2 + 1 + linesBetween
) // Moving bottom
1285 : numLines
/2)); // Moving top
1288 inWin
.put((width
-strlen(title
))/2,0, title
);
1289 } // end positionInWin
1291 //--------------------------------------------------------------------
1292 // Display prompt window for editing:
1294 void showEditPrompt()
1298 promptWin
.put(3,1, "Arrow keys move cursor TAB hex\x3C\x3E"
1300 if (displayTable
== ebcdicDisplayTable
)
1301 promptWin
.put(42,1, "EBCDIC");
1303 promptWin
.putAttribs( 3,1, cPromptKey
, 10);
1304 promptWin
.putAttribs(33,1, cPromptKey
, 3);
1305 promptWin
.putAttribs(54,1, cPromptKey
, 3);
1308 promptWin
.put(25,2, "RET copy byte from other file");
1309 promptWin
.putAttribs(25,2, cPromptKey
, 3);
1312 } // end showEditPrompt
1314 //--------------------------------------------------------------------
1315 // Display prompt window:
1322 #ifdef WIN32_CONSOLE
1323 promptWin
.put(1,1, "Arrow keys move F find "
1324 "RET next difference ESC quit ALT freeze top");
1325 promptWin
.put(1,2, "C ASCII/EBCDIC E edit file "
1326 "G goto position Q quit CTRL freeze bottom");
1331 promptWin
.put(1,1, "Arrow keys move F find "
1332 "RET next difference ESC quit T move top");
1333 promptWin
.put(1,2, "C ASCII/EBCDIC E edit file "
1334 "G goto position Q quit B move bottom");
1340 promptWin
.putAttribs( 1,1, cPromptKey
, 10);
1341 promptWin
.putAttribs(18,1, cPromptKey
, 1);
1342 promptWin
.putAttribs(30,1, cPromptKey
, 3);
1343 promptWin
.putAttribs(51,1, cPromptKey
, 3);
1344 promptWin
.putAttribs( 1,2, cPromptKey
, 1);
1345 promptWin
.putAttribs(18,2, cPromptKey
, 1);
1346 promptWin
.putAttribs(32,2, cPromptKey
, 1);
1347 promptWin
.putAttribs(53,2, cPromptKey
, 1);
1349 // Erase "move top" & "move bottom":
1350 promptWin
.putChar(61,1, ' ', topLength
);
1351 promptWin
.putChar(61,2, ' ', topLength
+ 3);
1353 promptWin
.putAttribs(61,1, cPromptKey
, topBotLength
);
1354 promptWin
.putAttribs(61,2, cPromptKey
, topBotLength
);
1357 displayCharacterSet(); // Calls promptWin.update()
1360 //--------------------------------------------------------------------
1361 // Initialize program:
1364 // True: Initialization complete
1369 if (!ConWindow::startup())
1372 ConWindow::hideCursor();
1374 calcScreenLayout(false);
1376 inWin
.init(0,0, inWidth
+2,3, cPromptBdr
);
1378 inWin
.put((inWidth
-4)/2,0, " Goto ");
1379 inWin
.setAttribs(cPromptWin
);
1383 if (singleFile
) y
= numLines
+ 1;
1384 else y
= numLines
* 2 + linesBetween
+ 2;
1386 promptWin
.init(0,y
, screenWidth
,promptHeight
, cBackground
);
1389 if (!singleFile
) diffs
.resize();
1391 file1
.init(0, (singleFile
? NULL
: &diffs
));
1393 if (!singleFile
) file2
.init(numLines
+ linesBetween
+ 1, &diffs
);
1398 //--------------------------------------------------------------------
1399 // Get a command from the keyboard:
1404 #ifdef WIN32_CONSOLE
1405 Command
getCommand()
1408 Command cmd
= cmNothing
;
1410 while (cmd
== cmNothing
) {
1411 ConWindow::readKey(e
);
1413 switch (safeUC(e
.uChar
.AsciiChar
)) {
1414 case KEY_RETURN
: // Enter
1419 case 0x05: // Ctrl+E
1421 if (e
.dwControlKeyState
& (LEFT_ALT_PRESSED
|RIGHT_ALT_PRESSED
))
1428 if (e
.dwControlKeyState
& (LEFT_ALT_PRESSED
|RIGHT_ALT_PRESSED
))
1429 cmd
= cmFind
|cmgGotoBottom
;
1431 cmd
= cmFind
|cmgGotoBoth
;
1434 case 0x06: // Ctrl+F
1435 cmd
= cmFind
|cmgGotoTop
;
1439 if (e
.dwControlKeyState
& (LEFT_ALT_PRESSED
|RIGHT_ALT_PRESSED
))
1440 cmd
= cmgGoto
|cmgGotoBottom
;
1442 cmd
= cmgGoto
|cmgGotoBoth
;
1445 case 0x07: // Ctrl+G
1446 cmd
= cmgGoto
|cmgGotoTop
;
1449 case KEY_ESCAPE
: // Esc
1450 case 0x03: // Ctrl+C
1455 case 'C': cmd
= cmToggleASCII
; break;
1457 default: // Try extended codes
1458 switch (e
.wVirtualKeyCode
) {
1459 case VK_DOWN
: cmd
= cmmMove
|cmmMoveLine
|cmmMoveForward
; break;
1460 case VK_RIGHT
: cmd
= cmmMove
|cmmMoveByte
|cmmMoveForward
; break;
1461 case VK_NEXT
: cmd
= cmmMove
|cmmMovePage
|cmmMoveForward
; break;
1462 case VK_END
: cmd
= cmmMove
|cmmMoveAll
|cmmMoveForward
; break;
1463 case VK_LEFT
: cmd
= cmmMove
|cmmMoveByte
; break;
1464 case VK_UP
: cmd
= cmmMove
|cmmMoveLine
; break;
1465 case VK_PRIOR
: cmd
= cmmMove
|cmmMovePage
; break;
1466 case VK_HOME
: cmd
= cmmMove
|cmmMoveAll
; break;
1467 } // end switch virtual key code
1469 } // end switch ASCII code
1470 } // end while no command
1472 if (cmd
& cmmMove
) {
1473 if ((e
.dwControlKeyState
& (LEFT_ALT_PRESSED
|RIGHT_ALT_PRESSED
)) == 0)
1475 if ((e
.dwControlKeyState
& (LEFT_CTRL_PRESSED
|RIGHT_CTRL_PRESSED
)) == 0)
1476 cmd
|= cmmMoveBottom
;
1477 } // end if move command
1482 #else // using curses interface
1483 Command
getCommand()
1485 Command cmd
= cmNothing
;
1487 while (cmd
== cmNothing
) {
1488 int e
= promptWin
.readKey();
1490 switch (safeUC(e
)) {
1491 case KEY_RETURN
: // Enter
1497 if (lockState
== lockTop
)
1505 if (lockState
!= lockTop
) cmd
|= cmgGotoTop
;
1506 if (lockState
!= lockBottom
) cmd
|= cmgGotoBottom
;
1511 if (lockState
!= lockTop
) cmd
|= cmgGotoTop
;
1512 if (lockState
!= lockBottom
) cmd
|= cmgGotoBottom
;
1516 case 0x03: // Ctrl+C
1521 case 'C': cmd
= cmToggleASCII
; break;
1523 case 'B': if (!singleFile
) cmd
= cmUseBottom
; break;
1524 case 'T': if (!singleFile
) cmd
= cmUseTop
; break;
1526 case KEY_DOWN
: cmd
= cmmMove
|cmmMoveLine
|cmmMoveForward
; break;
1527 case KEY_RIGHT
: cmd
= cmmMove
|cmmMoveByte
|cmmMoveForward
; break;
1528 case KEY_NPAGE
: cmd
= cmmMove
|cmmMovePage
|cmmMoveForward
; break;
1529 case KEY_END
: cmd
= cmmMove
|cmmMoveAll
|cmmMoveForward
; break;
1530 case KEY_LEFT
: cmd
= cmmMove
|cmmMoveByte
; break;
1531 case KEY_UP
: cmd
= cmmMove
|cmmMoveLine
; break;
1532 case KEY_PPAGE
: cmd
= cmmMove
|cmmMovePage
; break;
1533 case KEY_HOME
: cmd
= cmmMove
|cmmMoveAll
; break;
1534 } // end switch ASCII code
1535 } // end while no command
1537 if (cmd
& cmmMove
) {
1538 if (lockState
!= lockTop
) cmd
|= cmmMoveTop
;
1539 if (lockState
!= lockBottom
) cmd
|= cmmMoveBottom
;
1540 } // end if move command
1544 #endif // end else curses interface
1546 //--------------------------------------------------------------------
1547 // Get a file position and move there:
1549 void gotoPosition(Command cmd
)
1551 positionInWin(cmd
, inWidth
+2, " Goto ");
1553 const int maxLen
= inWidth
-2;
1556 getString(buf
, maxLen
, positionHistory
, hexDigits
, true);
1561 FPos pos
= strtoul(buf
, NULL
, 16);
1563 if (cmd
& cmgGotoTop
)
1565 if (cmd
& cmgGotoBottom
)
1567 } // end gotoPosition
1569 //--------------------------------------------------------------------
1570 // Search for text or bytes in the files:
1572 void searchFiles(Command cmd
)
1574 const bool havePrev
= !lastSearch
.empty();
1576 positionInWin(cmd
, (havePrev
? 47 : 32), " Find ");
1578 inWin
.put(2, 1,"H Hex search T Text search");
1579 inWin
.putAttribs( 2,1, cPromptKey
, 1);
1580 inWin
.putAttribs(17,1, cPromptKey
, 1);
1582 inWin
.put(33, 1,"N Next match");
1583 inWin
.putAttribs(33,1, cPromptKey
, 1);
1586 int key
= safeUC(inWin
.readKey());
1590 if (key
== KEY_ESCAPE
) {
1593 } else if (key
== 'H')
1596 if (key
== 'N' && havePrev
) {
1599 positionInWin(cmd
, screenWidth
, (hex
? " Find Hex Bytes" : " Find Text "));
1601 const int maxLen
= screenWidth
-4;
1606 getString(reinterpret_cast<char*>(buf
), maxLen
, hexSearchHistory
, hexDigits
, true, true);
1607 searchLen
= packHex(buf
);
1609 getString(reinterpret_cast<char*>(buf
), maxLen
, textSearchHistory
);
1611 searchLen
= strlen(reinterpret_cast<char*>(buf
));
1612 if (displayTable
== ebcdicDisplayTable
) {
1613 for (int i
= 0; i
< searchLen
; ++i
)
1614 buf
[i
] = ascii2ebcdicTable
[buf
[i
]];
1615 } // end if in EBCDIC mode
1616 } // end else text search
1618 if (!searchLen
) return;
1620 lastSearch
.assign(reinterpret_cast<char*>(buf
), searchLen
);
1621 } // end else need to read search string
1623 bool problem
= false;
1624 const Byte
*const searchPattern
=
1625 reinterpret_cast<const Byte
*>(lastSearch
.c_str());
1627 if ((cmd
& cmgGotoTop
) &&
1628 !file1
.moveTo(searchPattern
, lastSearch
.length()))
1630 if ((cmd
& cmgGotoBottom
) &&
1631 !file2
.moveTo(searchPattern
, lastSearch
.length()))
1634 if (problem
) beep();
1635 } // end searchFiles
1637 //--------------------------------------------------------------------
1638 // Handle a command:
1641 // cmd: The command to be handled
1643 void handleCmd(Command cmd
)
1645 if (cmd
& cmmMove
) {
1646 int step
= steps
[cmd
& cmmMoveSize
];
1648 if ((cmd
& cmmMoveForward
) == 0)
1649 step
*= -1; // We're moving backward
1651 if ((cmd
& cmmMoveForward
) && !step
) {
1652 if (cmd
& cmmMoveTop
)
1653 file1
.moveToEnd((!singleFile
&& (cmd
& cmmMoveBottom
)) ? &file2
: NULL
);
1655 file2
.moveToEnd(NULL
);
1657 if (cmd
& cmmMoveTop
) {
1662 } // end if moving top file
1664 if (cmd
& cmmMoveBottom
) {
1669 } // end if moving bottom file
1670 } // end else not moving to end
1672 else if ((cmd
& cmgGotoMask
) == cmgGoto
)
1674 else if ((cmd
& cmgGotoMask
) == cmFind
)
1676 else if (cmd
== cmNextDiff
) {
1678 lockState
= lockNeither
;
1682 file1
.move(bufSize
);
1683 file2
.move(bufSize
);
1684 } while (!diffs
.compute());
1685 } // end else if cmNextDiff
1686 else if (cmd
== cmUseTop
) {
1687 if (lockState
== lockBottom
)
1688 lockState
= lockNeither
;
1690 lockState
= lockBottom
;
1693 else if (cmd
== cmUseBottom
) {
1694 if (lockState
== lockTop
)
1695 lockState
= lockNeither
;
1697 lockState
= lockTop
;
1700 else if (cmd
== cmToggleASCII
) {
1701 displayTable
= ((displayTable
== asciiDisplayTable
)
1702 ? ebcdicDisplayTable
1703 : asciiDisplayTable
);
1704 displayCharacterSet();
1706 else if (cmd
== cmEditTop
)
1707 file1
.edit(singleFile
? NULL
: &file2
);
1708 else if (cmd
== cmEditBottom
)
1711 // Make sure we haven't gone past the end of both files:
1712 while (diffs
.compute() < 0) {
1713 file1
.move(-steps
[cmmMovePage
]);
1714 file2
.move(-steps
[cmmMovePage
]);
1721 //====================================================================
1722 // Initialization and option processing:
1723 //====================================================================
1724 // Display license information and exit:
1726 bool license(GetOpt
*, const GetOpt::Option
*, const char*,
1727 GetOpt::Connection
, const char*, int*)
1731 "This program is free software; you can redistribute it and/or\n"
1732 "modify it under the terms of the GNU General Public License as\n"
1733 "published by the Free Software Foundation; either version 2 of\n"
1734 "the License, or (at your option) any later version.\n"
1736 "This program is distributed in the hope that it will be useful,\n"
1737 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
1738 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
1739 "GNU General Public License for more details.\n"
1741 "You should have received a copy of the GNU General Public License\n"
1742 "along with this program; if not, see <http://www.gnu.org/licenses/>."
1746 return false; // Never happens
1749 //--------------------------------------------------------------------
1750 // Display version & usage information and exit:
1753 // showHelp: True means display usage information
1754 // exitStatus: Status code to pass to exit()
1756 void usage(bool showHelp
, int exitStatus
)
1759 cerr
<< "Try `" << program_name
<< " --help' for more information.\n";
1761 cout
<< titleString
<< endl
;
1764 cout
<< "Usage: " << program_name
<< " FILE1 [FILE2]\n\
1765 Compare FILE1 and FILE2 byte by byte.\n\
1766 If FILE2 is omitted, just display FILE1.\n\
1769 --help display this help information and exit\n\
1770 -L, --license display license & warranty information and exit\n\
1771 -V, --version display version information and exit\n";
1777 bool usage(GetOpt
* getopt
, const GetOpt::Option
* option
,
1778 const char*, GetOpt::Connection
, const char*, int*)
1780 usage(option
->shortName
== '?');
1781 return false; // Never happens
1784 //--------------------------------------------------------------------
1788 // argc, argv: The parameters passed to main
1792 // Modified to list only the non-option arguments
1793 // Note: argv[0] may not be the executable name
1795 void processOptions(int& argc
, char**& argv
)
1797 static const GetOpt::Option options
[] =
1799 { '?', "help", NULL
, 0, &usage
},
1800 { 'L', "license", NULL
, 0, &license
},
1801 { 'V', "version", NULL
, 0, &usage
},
1805 GetOpt
getopt(options
);
1806 int argi
= getopt
.process(argc
, const_cast<const char**>(argv
));
1811 argc
= 1; // No arguments
1813 argc
-= --argi
; // Reduce argc by number of arguments used
1814 argv
+= argi
; // And adjust argv[1] to the next argument
1816 } // end processOptions
1818 //====================================================================
1820 //====================================================================
1821 int main(int argc
, char* argv
[])
1823 if ((program_name
= strrchr(argv
[0], '\\')))
1824 // Isolate the filename:
1827 program_name
= argv
[0];
1829 processOptions(argc
, argv
);
1831 if (argc
< 2 || argc
> 3)
1835 VBinDiff " PACKAGE_VERSION
", Copyright 1995-2008 Christopher J. Madsen\n\
1836 VBinDiff comes with ABSOLUTELY NO WARRANTY; for details type `vbindiff -L'.\n";
1838 singleFile
= (argc
== 2);
1839 if (!initialize()) {
1840 cerr
<< '\n' << program_name
<< ": Unable to initialize windows\n";
1845 ostringstream errMsg
;
1847 if (!file1
.setFile(argv
[1])) {
1848 const char* errStr
= ErrorMsg();
1849 errMsg
<< "Unable to open " << argv
[1] << ": " << errStr
;
1851 else if (!singleFile
&& !file2
.setFile(argv
[2])) {
1852 const char* errStr
= ErrorMsg();
1853 errMsg
<< "Unable to open " << argv
[2] << ": " << errStr
;
1855 string
error(errMsg
.str());
1857 exitMsg(1, error
.c_str());
1858 } // end block around errMsg
1866 while ((cmd
= getCommand()) != cmQuit
)
1874 ConWindow::shutdown();
1879 //--------------------------------------------------------------------
1881 // c-file-style: "cjm"