#!/bin/sh
: ; exec klone $0 "$@"
; The above line allows not to embed the exact path of the klone executable

;;Skeleton of a typical klone script

(setq args (getopts "USAGE: %0 [options] hexstring files...
grep-like program, but searches for string (in hxa) in files
Shows somes bytes of context around matches, ^ meaning start of file 
and $ end of file.
Can also patch files by setting strings at offsets, and be used as a minimal 
hex editor
Output is:
filename: hex_offset / dec_offset: left_context string right_context
"
    ("-li" () LI "string must be a decimal number that will be searched for in
files as a little endian (intel order) 4-byte number")
    ("-bi" () BI "string must be a decimal number that will be searched for in
files as a big endian (motorola order) 4-byte number")
    ("-a" () ASC "string is taken as ascii text")
    ("-e" () EVAL "ascii string is evaluated to expand klone string escapes: \\xXX
for char of ascii code XX in hexa, \\n, \\e, \\r, etc...")
    ("-c" N context "number of context bytes shown around matches. 
default to as much as fit in 80 columns")
    ("-p" () printables "shows also, underneath matches, the printable characters
Unprintable chars are show as undeline \"_\" characters")
    ("-s" N replace-at "do not search for string, instead sets the string at 
offset N in files, overwriting contents there.
N can be in decimal or hexa (prefixed by 0x)")
    ("-r" repstring replace-all "replace all occurences of string by repstring\n(hexadecimal string)") 
    ("-R" repstring replace-all-eval "replace all occurences of string by repstring\n(klone-evaluated ascii string)")
    ("-re" () REGEXP "hexstring is a regexp. Actually, not a true regexp, but a regexp\non the ascii representation: to match a number in brackets,\nsay 3c3[0-9]3e, <[0-9]> wont work\nThis work only to search, not to replace")
    ("-l" () LIST "string must be a number (decimal or 0x-prefixed hexa, only
lists memory at the location given by string")
    ("-m" N modulo "string must start at a multiple of N (4, 8...)")
    ("-q" () quiet "no output. Only return value can be used")
    ("-v" () verbose "verbose")
))

(if (not args) (getopts :usage))
(defvar found (list))
(setq context (if context (Int context) ()))
(if replace-all-eval (setq replace-all 
    (read (open (+ "\"" replace-all-eval "\"") :type :string)))
)
(if modulo (setq modulo (Int modulo)))

(defun main (&aux
  )
  (if LI (setq s (itole (Int (get args 0))))
    BI (setq s (itobe (Int (get args 0))))
    ASC (setq s (get args 0))
    EVAL (setq s (read (open (+ "\"" (get args 0) "\"") :type :string)))
    LIST (setq s (dec-or-hex (get args 0)))
    REGEXP (setq s (get args 0))
    t (setq s (decode-hex (get args 0)))
  )
  (if LIST (dolist (file (subseq args 1))
      (list-places s file))
  )
  (if replace-at (doreplace s replace-at (subseq args 1) t))
  (if replace-all (doreplace-all s (decode-hex replace-all) (subseq args 1)))
  (verbose? "Searching for %0" (encode-hex s))
  (dolist (file (subseq args 1))
    (if REGEXP
      (hexgrep-re s file (open file :error ()) (> (length args) 2) ())
      (hexgrep s file (open file :error ()) (> (length args) 2) ())
    )
  )
  (if (= 1 (length args))
    (if REGEXP
      (hexgrep-re s "stdin" *standard-input* () ())
      (hexgrep s "stdin" *standard-input* () ())
    )
  )
  (exit (if found 0 1))
)

(defun doreplace (s replace files must-exit? &aux offset fd)
  (setq offset (dec-or-hex replace))
  (verbose? "Setting %0 in files at offset %1" (encode-hex s) offset)
  (dolist (file files)
    (setq fd (open file :if-exists :overwrite :direction :io))
    (if (or (< offset 0) 
	(> (+ offset (length s)) (get (file-stats file) 'size)))
      (print-exit 1 "offset %1 is not inside file %0! aborting.\n" file offset)
    )
    (verbose? "Putting string %2 at offset %1 in file %0" file offset
      (encode-hex s)
    )
    (file-position fd offset)
    (write-chars s () fd)
    (close fd)
  )
  (if must-exit? (exit 0))
)

(defun doreplace-all (s replace files &aux (found found))
  (if (> (length replace) (length s))
    (print-exit 1 "Error: trying to replace a string %r0 by a longer one: %r1 !\n" s replace)
  )
  (verbose? "Replacing %0 by %1" (encode-hex s) (encode-hex replace))
  (dolist (file files)
    (setq found (list))
    (with (verbose () quiet t)
      (hexgrep s file (open file :error ()) () ())
    )
    (if found
      (with (fd (open file :if-exists :overwrite :direction :io))
	(dolist (pos found)
	  (verbose? "%0: %1 / %2: replacing %3 by %4"
	    file (itox pos) pos (encode-hex s) (encode-hex replace)
	  )
	  (file-position fd pos)
	  (write-chars replace () fd)
	)
	(close fd)
      )
    )
  )
  (exit 0)
)

(defun list-places (pos file &aux
    (s (make-string 24))
    (fd (open file :error ()))
    (context 0)
  )
  (hexgrep s file fd (> (length args) 2) pos) 
  (exit 0)
)

(defun hexgrep (s file fd show-filename? &optional one-pos &aux 
    buffer 
    (pos 0)
    hexpad decpad
    room-for-context
    (context context)
    (loop-cond (if one-pos
	'(if (= t one-pos) () (progn (setq pos one-pos) (setq one-pos t) pos))
	'(setq pos (seek buffer s pos))
    ))
  )
  (if (not fd) (progn
      (print-format *standard-error* "Error: cannot open file %0\n" file)
      (exit 1)
  ))
  (setq buffer (String fd))
  (setq hexpad (length (itox (length buffer))))
  (setq decpad (length (String (length buffer))))
  (setq room-for-context (/ (- 79 (+ (if show-filename? (+ (length file) 2) 0)
	  hexpad 3 decpad 2 2 (* 2 (length s)))) 4)
  )
  (setq context (if context context (max 0 room-for-context)))
  
  (while (eval loop-cond)
    (if (not (and modulo (/= 0 (mod pos modulo)))) (progn
	(lappend found pos)
	(if (not quiet) (progn
	    (if show-filename?
	      (print-format "%0: " file)
	    )
	    (print-format "%4%1 / %5%2: %3\n"
	      file (itox pos) pos (hexrep buffer pos s)
	      (make-string (- hexpad (length (itox pos))))
	      (make-string (- decpad (length (String pos))))
	    )
	    (if printables 
	      (print-format "%0%1\n"
		(make-string (+ 
		    (if show-filename? (+ (length file) 2) 0)
		    hexpad 3 decpad 2))
		(printable-rep (hexrep buffer pos s))
	    ))
	))
	(incf pos (length s))
      )
      (incf pos)
    )
  )
)

;; Same, but search on ascii encoding of file
(defun hexgrep-re (s file fd show-filename? &optional one-pos &aux 
    buffer 
    (pos 0)
    (ascpos 0)
    hexpad decpad
    room-for-context
    (context context)
    (re (regcomp s))
    len
  )
  (if (not fd) (progn
      (print-format *standard-error* "Error: cannot open file %0\n" file)
      (exit 1)
  ))
  (setq buffer (String fd))
  (setq ascbuffer (encode-hex buffer))
  (setq hexpad (length (itox (length buffer))))
  (setq decpad (length (String (length buffer))))
  (setq room-for-context (/ (- 79 (+ (if show-filename? (+ (length file) 2) 0)
	  hexpad 3 decpad 2 2 (length s))) 4)
  )
  (setq context (if context context (max 0 room-for-context)))
  
  (while (regexec re ascbuffer ascpos)
    (setq ascpos (0 (get re 0)))
    (if (/= 0 (mod ascpos 2)) (progn	;not on a byte start, ignore
	(incf ascpos)
      ) 
      (and modulo (/= 0 (mod (setq pos (/ascpos 2)) modulo)))
      (incf ascpos 2)

      (progn
	(setq pos (/ ascpos 2))
	(setq len (/ (- (1 (get re 0)) (0 (get re 0))) 2))
	(if (not quiet) (progn
	    (if show-filename?
	      (print-format "%0: " file)
	    )
	    (print-format "%4%1 / %5%2: %3\n"
	      file (itox pos) pos (hexrep buffer pos len)
	      (make-string (- hexpad (length (itox pos))))
	      (make-string (- decpad (length (String pos))))
	    )
	    (if printables 
	      (print-format "%0%1\n"
		(make-string (+ 
		    (if show-filename? (+ (length file) 2) 0)
		    hexpad 3 decpad 2))
		(printable-rep (hexrep buffer pos len))
	    ))
	))
	(setq ascpos (* 2 (/ (1 (get re 0)) 2)))	
      )
  ))
)

;; shows string and its context
(defun hexrep (buffer pos s &aux 
    (res (copy ""))
    (len (if (typep s Number) s (length s)))
  )
  (if (<= pos context)
    (nconc res "^")
  )
  (nconc res (encode-hex (subseq buffer (max 0 (- pos context)) pos)))
  (nconc res " ")
  (nconc res (encode-hex (subseq buffer pos (+ pos len))))
  (nconc res " ")
  (nconc res (encode-hex 
      (subseq buffer (+ pos len)
	(min (length buffer) (+ pos context len))
  )))
  (if (>= (+ pos context len) (length buffer))
    (nconc res "$")
  )
  res  
)

(defun printable-rep (s &aux 
    (res (copy "")) 
    upper-half-byte
    out
  )
  (dolist (c s)
    (setq out #\ )
    (if (seek encode-hex-values c)
      (if upper-half-byte
	(progn
	  (setq out (printable-rep-char 
	      (+ upper-half-byte (seek encode-hex-values c))))
	  (setq upper-half-byte ())
	)
	(setq upper-half-byte (* 16 (seek encode-hex-values c)))
    ))
    (put res -1 out)
  )
  res
)

(defun printable-rep-char (c)
  (if (and (>= c #\ ) (< c 127))
    c
    #\_
  )
)

(defun itole (n &aux (res (copy "")) (mul 1))
  (dotimes 4
    (put res -1 (logand 0xff n))
    (setq n (logshift n -8))
  )
  res
)

(defun itobe (n &aux (res (copy "")) (shift -24))
  (dotimes 4
    (put res -1 (logand 0xff (logshift n shift)))
    (incf shift 8)
  )
  res
)

;; decodes a number decimal or 0x_hex
(defun dec-or-hex (s)
  (if (match "^0x" s)
    (xtoi s)
    (Int s)
))

(defun decode-hex-error (s)
  (print-format "Error: string %0 is not hexadecimal!\n" s)
  (exit 1)
)

(setq decode-hex-values (Hashtable '(
      #\0 0 #\1 1 #\2 2 #\3 3 #\4 4 #\5 5 #\6 6 #\7 7 #\8 8 #\9 9
      #\a 10 #\b 11 #\c 12 #\d 13 #\e 14 #\f 15
      #\A 10 #\B 11 #\C 12 #\D 13 #\E 14 #\F 15
)))

(defun decode-hex (s &aux (i 0) (res (copy "")))
  (while (< i (length s))
    (if (= i (- (length s) 1))
      (decode-hex-error s)
    )
    (put res -1
      (+ (* 16 (get decode-hex-values (i s) '(decode-hex-error s)))
	(get decode-hex-values ((+ i 1) s) '(decode-hex-error s))
    ))
    (incf i 2)
  )
  res
)

(setq encode-hex-values '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 
    #\a #\b #\c #\d #\e #\f))

(defun encode-hex (s &aux (res (copy "")))
  (dolist (i s)
    (put res -1 ((/ i 16) encode-hex-values))
    (put res -1 ((mod i 16) encode-hex-values))
  )
  res
)
    
(main)

;;; EMACS MODES
;;; Local Variables: ***
;;; mode:lisp ***
;;; End: ***

