uniquename 2013aug18For those who do not have the time/facilities/whatever to setup and run the code below, here is an image to show what this binary editor GUI looks like.In the process of getting this image, I tried moving the scrollbar down and then back to the top. At the top, I get (consistently) the following message:Eroor: can't use empty string as operand of "&"The grid of white and black squares disappears, and I have to close down the GUI to get started again.Apparently, there is a small glitch in the code.2014-02-21: I intended to take a look at this, but can't reproduce the problem. Did someone fix the code without making a note of it? The & operator is used in the DoRow and CellClick commands, with the arguments $datum and $S(pow,$i) / $S(pow,$col). I think we can eliminate the second argument as a source for the error, since it provides a useful value for every valid $i / $col and a different error message for invalid indexes. The value of $datum could conceivably be set to the empty string if the lindex command is given an out-of-bounds index: in DoRow the upper bound is checked but not the lower bound (but it is hard to see how the index value should ever be less than 0); in CellClick neither of the bounds are checked, but it is only ever called as a callback with a row index that is guaranteed (?) to be valid. I'm leaving it at that.
#!/bin/sh # Restart with tcl: -*- mode: tcl; tab-width: 8; -*- \ exec wish $0 ${1+"$@"} ##+########################################################################## # # Hack-O-Matic -- visual binary editor # by Keith Vetter, September 2003 # # Rob Kudla on http://wiki.tcl.tk/9505 complained about a utility for # editing Atari graphic files called Hack-O-Matic, that he wrote in # tcl was 10 times slower than a windows BASIC version and unstable. # # Taking that as a challenge, here's a version I wrote in a few hours # that is quite fast. # The speed up was not in nit-picking optimizations like bracing # expressions but in a new algorithm. I don't care what language # you're using but if you have a 32k file, drawing 32k * 8 boxes is # going to take a long time. Instead, just draw as many boxes as will # fit on the screen. The scrollbar command, instead of scrolling # pixels, just changes which rows get displayed. package require Tk set S(title) "Hack-O-Matic" set S(box) 20 ;# Size of each cell set S(tm) 10 ;# Top margin set S(lm) 10 ;# Left margin set S(lm2) 45 ;# Left margin for the grid set S(rows) 16 ;# How many rows to show set S(max) 1048576 ;# Largest file we can handle set S(top) 0 ;# First row of data to display proc DoDisplay {} { global S wm title . $S(title) menu .menu . configure -menu .menu ;# Attach menu to main window .menu add cascade -menu .menu.file -label "File" -underline 0 .menu add cascade -menu .menu.help -label "Help" -underline 0 menu .menu.file menu .menu.help .menu.file add command -label "Open" -command LoadFile .menu.file add command -label "Save As..." -command SaveFile .menu.file add command -label "Exit" -command exit .menu.help add command -label "About..." -command About label .title -textvariable S(fname) -bd 2 -relief sunken scrollbar .sb -orient vertical -command ScrollProc canvas .c -width 245 -highlightthickness 0 .c config -height [expr {$S(tm) + $S(rows)*$S(box)}] pack .title -side top -fill x pack .sb -fill y -expand 0 -side right pack .c -fill both -expand 1 -side left for {set i 0; set n 1} {$i < 8} {incr i} { set ::S(pow,$i) $n set n [expr {2*$n}] } update bind .c <Configure> {Resize %W %h %w} ;# Handle resizing } # # ScrollProc -- called by the scrollbar. We need to determine what # the new top of the page is. # proc ScrollProc {args} { foreach {cmd perc} $args break if {$cmd != "moveto"} return set top [expr {round($perc * $::DATA(len))}] if {$top == $::S(top)} return DoPage $top } # # DoPage -- display a screenful of data rows starting at TOP # proc DoPage {{top ""}} { global S DATA if {$top == ""} {set top $S(top)} set S(top) $top .c delete all for {set i 0} {$i < $S(rows)} {incr i} { DoRow [expr {$S(top) + $i}] $i } # Adjust the scrollbar set sb1 [expr {double($S(top)) / $DATA(len)}] set sb2 [expr {double($S(top)+$S(rows)) / $DATA(len)}] .sb set $sb1 $sb2 } # # DoRow -- shows one row of data at a given screen row # proc DoRow {row srow} { global S DATA if {$row >= $DATA(len)} return set datum [lindex $DATA(bytes) $row] set x1 $S(lm2) set x2 [expr {$x1 + $S(box)}] set y1 [expr {$S(tm) + $S(box) * $srow}] set y2 [expr {$y1 + $S(box)}] set ym [expr {$y1 + $S(box)/2}] set num [format %04x $row] .c create text $S(lm) $ym -tag d$srow -text $num -anchor w for {set i 7} {$i >= 0} {incr i -1} { set tag b$srow,$i set xy [list $x1 $y1 $x2 $y1 $x2 $y2 $x1 $y2] .c create poly $xy -tag [list b$srow $tag] \ -fill white -outline black .c bind $tag <Button-1> [list CellClick $row $i $srow] if {$datum & $S(pow,$i)} {.c itemconfig $tag -fill black} set x1 $x2 incr x2 $S(box) } # Show the hex value and the character set ch [format %c $datum] if {! [string is ascii $ch] || [string is control $ch]} {set ch "?"} set extra [format " %02x %s" [expr {($datum + 0x100) % 0x100}] $ch] .c create text $x1 $ym -tag e$srow -text $extra -anchor w } # # CellClick -- handles clicking in a cell which toggles the bit # proc CellClick {row col srow} { global S DATA set datum [lindex $DATA(bytes) $row] if {$datum & $S(pow,$col)} { ;# Bit is already set .c itemconfig b$srow,$col -fill white incr datum -$S(pow,$col) } else { ;# Bit is off .c itemconfig b$srow,$col -fill black incr datum $S(pow,$col) } lset DATA(bytes) $row $datum set ch [format %c $datum] if {! [string is ascii $ch] || [string is control $ch]} {set ch "?"} set extra [format " %02x %s" [expr {($datum + 0x100) % 0x100}] $ch] .c itemconfig e$srow -text $extra } # # LoadFile -- reads a file and converts to an integer list # proc LoadFile {{fname ""}} { global S DATA # Read in the data if {$fname == ""} { set fname [tk_getOpenFile] if {$fname == ""} return } if {[file size $fname] >= $S(max)} { tk_messageBox -message "File $fname is too big" -icon error return } set S(fname) [file tail $fname] set FIN [open $fname r] fconfigure $FIN -translation binary set bytes [read $FIN [file size $fname]] close $FIN binary scan $bytes c* DATA(bytes) set DATA(len) [llength $DATA(bytes)] DoPage 0 } # # SaveFile -- saves our binary data # proc SaveFile {} { global DATA set fname [tk_getSaveFile] if {$fname == ""} return set FOUT [open $fname w] fconfigure $FOUT -translation binary puts -nonewline $FOUT [binary format c* $DATA(bytes)] close $FOUT } proc About {} { tk_messageBox -message "$::S(title)\nby Keith Vetter, September 2003" \ -title "About $::S(title)" } proc Resize {W h w} { global S if {$W != ".c"} return set rows [expr {1 + int(([winfo height .c] - $S(tm)) / $S(box))}] if {$rows == $S(rows)} return set S(rows) $rows DoPage } DoDisplay set script [info script] if {[file readable $script]} { ;# Use this script as a demo LoadFile $script } else { set txt "$S(title)\nby Keith Vetter" ;# Sample text to display binary scan $txt c* DATA(bytes) set DATA(len) [llength $DATA(bytes)] ;# Rows of data to display DoPage 0 }