bindiff

An example Tk application using Tk and BWidgets that shows a comparison between two binary files and shows the differences between them.


 #! /bin/sh
 # bindiff.tcl - Copyright (C) 2003 Pat Thoyts <[email protected]>
 #
 # Show a side by side comparison of two binary files with the differences
 # highlighted
 #
 # This could probably use more work, but it does the job I required it for.
 # Differences are highlighted in red.
 #
 # usage:
 #  bindiff fileA fileB
 #
 # 16 Jan 2004: Patch provided by Paul Kienzle for different length files.
 #
 # $Id: 8545,v 1.3 2005-11-23 07:01:17 jcw Exp $
 # \
 exec wish "$0" ${1+"$@"}
 
 package require Tk
 package require BWidget
 
 set filename0 {}
 set filename1 {}
 set txt {}
 set progress 0
 set top {}
 
 set menu {
     "&File" {} {} 0 {
         { command    "&Left..."  {}  "Select the left hand side file" {} -command Menu::left }
         { command    "&Right..." {}  "Select the right hand side file" {} -command Menu::right }
         { separator }
         { command    "&Compare"  {}  "Show the differences in the view window" {} -command Menu::compare}
         { command     "E&xit"    {}  "Exit the application" {} -command Menu::exitApplication }
     }
     "&Options" {} {} 0 {
         { command     "&Font..." {}  "Select a new font"    {} -command Menu::fontSelect }
         { checkbutton "&Console" {}  "Show console"  {}  -variable Menu::console -command Menu::toggleConsole}
     }
     "&Help" {} {} 0 {
     }
 }
 
 namespace eval Menu {
     variable console 0
     proc toggleConsole {} {
        variable console
        if {$console} { console hide } else {console show }
     }
     proc exitApplication {} {
         destroy $::top
     }
     proc fontSelect {} {
         global txt top
         set font [SelectFont .sel]
         font configure fdiff -family [lindex $font 0] -size [lindex $font 1] -weight [lindex $font 2]
     }
     proc left {} {
         global txt top
         set name [tk_getOpenFile -parent $top -title "Select left hand file"]
         if {$name != {}} {
             set ::filename0 $name
             $txt delete 0.0 end
         }
     }
     proc right {} {
         global txt top
         set name [tk_getOpenFile -parent $top -title "Select right hand file"]
         if {$name != {}} {
              set ::filename1 $name
              $txt delete 0.0 end
         }
     }
     proc compare {} {
         if {[file exists $::filename0] && [file exists $::filename1]} {
             cmp $::filename0 $::filename1
         } else {
             tk_messageBox -icon error -type ok -title "Cannot compare" -message "Select two files for comparison."
         }
     }
 }
 
 proc gui {dlg} {
     global txt top
 
     set top $dlg
 
     wm title $dlg "Binary diff"
     if {$dlg == "."} { set dlg ""}
 
     font create fdiff -family {Courier New} -size 8 -weight normal
 
     set mw [MainFrame $dlg.main -menu $::menu -progressvar ::progress -progressmax 100]
     $mw showstatusbar progression
 
     set f1 [frame $mw.f1]
     set l0 [label $f1.l0 -font fdiff -width 10 -text "Filename:"]
     set l1 [label $f1.l1 -font fdiff -width 49 -textvariable ::filename0]
     set l2 [label $f1.l2 -font fdiff -width 49 -textvariable ::filename1]
     set l3 [label $f1.l3 -font fdiff -width 34 -textvariable ::filename0]
     pack $l0 $l1 $l2 $l3 -side left
     pack $f1 -side top -fill x
 
     set txt [text $dlg.txt -font fdiff]
     $txt tag configure diff -background red
 
     set sw [ScrolledWindow $mw.view]
     $sw setwidget $txt
     pack $sw -side top -fill both -expand 1 -anchor n
     pack $mw -side top -fill both -expand 1 -anchor n
 
     return $dlg
 }
 
 proc cmp {file0 file1} {
     global txt top
     set taglist {}
 
     set dlg $top
     if {$dlg == "."} {set dlg ""}
     set cursors [list $txt [$txt cget -cursor] $top [$top cget -cursor]]
     #$txt configure -cursor wait
     #$top configure -cursor wait
 
     ${dlg}.main configure -progressmax [file size $file0]
     set ::progress 0
 
     set f0 [open $file0 r]
     set f1 [open $file1 r]
 
     set ::filename0 $file0
     set ::filename1 $file1
 
     fconfigure $f0 -translation binary
     fconfigure $f1 -translation binary
 
     set off 0
     set chunk 16
     set linecount 1
     while {![eof $f0] || ![eof $f1]} {
         set d0 [read $f0 $chunk]
         set d1 [read $f1 $chunk]
 
         binary scan $d0 c* c0
         binary scan $d1 c* c1
 
         set line "[format 0x%06x $off] "
         foreach v $c0 { append line [format { %02x} [expr {$v & 0xFF}]] }
         append line [string repeat "   " [expr {$chunk-[string length $d0]}]]
         append line "  "
         foreach v $c1 { append line [format { %02x} [expr {$v & 0xFF}]] }
         append line [string repeat "   " [expr {$chunk-[string length $d1]}]]
         append line "  "
         foreach v $c0 {
             set cv [expr {$v & 0xFF}]
             if {$cv < 32} { set cv 46 }
             append line [format { %c} $cv]
         }
         append line "\n"
         #set lineno [lindex [split [$txt index current] .] 0]
         #if {$lineno != $linecount} {puts ">> $lineno != $linecount"}
         $txt insert end $line
 
         set n 9
         set diff 0
         foreach v0 $c0 v1 $c1 {
             if {$diff == 0 && $v0 != $v1} {
                 lappend taglist "$linecount.[expr {$n + 1}]"
                 set diff 1
             } elseif {$diff == 1 && $v0 == $v1} {
                 lappend taglist "$linecount.$n"
                 set diff 0
             }
             incr n 3
         }
         if {$diff == 1} {
             lappend taglist "$linecount.$n"
         }
         incr off $chunk        
         if {$linecount % 50 == 0} {
             set ::progress $off
             if {[llength $taglist] > 0} {
                 eval [list $txt tag add diff] $taglist
                 set taglist {}
             }
         }
         incr linecount
     }
 
     close $f0
     close $f1
 
     if {[llength $taglist] > 0} {
         eval [list $txt tag add diff] $taglist
         set taglist {}
     }
     #foreach {w c} $cursors { $w configure -cursor $c }
     return $taglist
 }
 
 if {!$::tcl_interactive} {
     gui .
     if {[llength $argv] == 2} {
         eval [list cmp] $argv
     }
     tkwait window .
 }