#!/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] pakfile
Creates/Expands quake .pak files to/from directories.
Example, to add a file to pak0.pak: extract in a temp dir /tmp/pak with 
    qpak -X /tmp/pak id1/pak0.pak
then change the contents of /tmp/pak at will, and then recreate the pak:
    qpak -c -A /tmp/pak id1/pak0.pak
Warning! this program will fail is there is not enough disk space!
If no option is given, just checks file validity"
    ("-v" () verbose "verbose operation")
    ("-l" () do-list "list contents. With -v, lists more info (file sizes)")
    ("-h" () do-headers "list headers")
    ("-c" () do-check "computes and sets checksum. return error if dont check")
    ("-n" () do-nothing "do not modify .pak file. Useful with -c")
    ("-X" dir extract-dir "extracts the whole .pak contents in dir")
    ("-x" file extract-files "extracts only this file (into dir if -X dir)"
      :multiple t)
    ("-A" dir add-dir "creates/overwrites the pakfile with the dir contents.
Does not validate checksum, use -c also for that.")
    ("-Ac" dir add-dir-c "same as -c -A dir.")
    ("-u" file update-file "only updates one file in the pack with a new
version. file must be the complete relative path, in the pak and
in current dir or the dir specified by -A. Compresses pak.
New version can also be given as -A argument")
    ("-C" () do-compress "copmpress holes in .pak")
    ("-i" () ignore-errors "ignore .pak errors, try to work anyways")
    ("-checksum" N checksum-value "checksum value, normally 32981")
))

(if (not args) (getopts :usage))
(if update-file
  (if add-dir (setqn update-dir add-dir add-dir ())
    (setq update-dir ".")
))

;; constants
(defvar PAK0_CRC 32981)			;the good checksum
(defvar PAK0_COUNT 339)			;good number of files
(defvar PAK0_LEN 18689235)		;orig .pak file size

(defvar Q2PAK0_CRC 45390)		;the good checksum
(defvar Q2PAK0_COUNT 1106)		;good number of files
(defvar Q2PAK0_LEN 49951322)		;orig .pak file size

(defstruct pfe				;pak file entries
  name
  start
  len
)
(setqn
  pak:pfe-len 64			;length of a pfe
  pak:pfe-namelen (- pak:pfe-len 8)	;length of a pfe name
)

;; infos on current pack
(defun pakinits () (setqn
    pak:name ()				;name of file
    pak:dir-start ()			;offset of toc
    pak:dir-len ()			;length of toc
    pak:len ()				;length of pak file
    pak:files (list)			;list of pak files entries (pfes)
    pak:checksum 0			;TOC checksum
    pak:has-holes? ()			;has holes in it
))

(if checksum-value (setq PAK0_CRC (logand 0xffff (Int checksum-value))))
(if (and do-list verbose) (setq do-headers t))
(if (and extract-files (not extract-dir)) (setq extract-dir "."))
(if add-dir-c (setqn add-dir add-dir-c do-check t))

(defun main (&aux (name (0 args)) (check-status 0)
  )
  (if add-dir (add-from-dir name add-dir))
  (pakdir name)
  (if do-compress (compress-pak name))
  (if update-file (update-one-file name update-dir update-file))
  (if do-check (pakcheck name))
  (if do-list (paklist name))
  (if do-headers (paklistheaders name))
  (if extract-dir (extract-to-dir name extract-dir extract-files))
  (exit check-status)
)

;;=============================================================================
;;                    PAK file routines
;;=============================================================================
;; structure of the file: (ints are 4 bytes, little endian).
;; "PACK" (4 bytes, magic number)
;; start of TOC (int)
;; length of TOC (int)
;; ... start of data: concatenation of file contents, no padding nor separation
;; ... end of data
;; TOC: an array of File Entries, being:
;;      name: null-terminated name of file (full relative path), 56 bytes
;;      start: offset (from file start) where starts the file in data (int)
;;      length: length of it in data (int)
;; rest of data..
;;
;; The full TOC is checksumed. we adjust it by changing the last 4 bytes of 
;; name of last file

(defun pakdir (name &aux		;reads directory
    (fd (open name))
    (id (catch 'EOF (read-chars 4 fd)))
    file
    pos
    header
  )
  (pakinits)
  (setq pak:name name)
  (if (/= "PACK" id)
    (print-exit 1 "Error: %0 is not a quake file!\n" name)
  )
  (setq pak:len (get (file-stats name) 'size))
  (setq pak:dir-start (read-int (read-chars 4 fd)))
  (setq pak:dir-len (read-int (read-chars 4 fd)))
  (setq pos pak:dir-start)
  (while (< pos (+ pak:dir-start pak:dir-len))
    (file-position fd pos)
    (lappend pak:files (setq file (make-pfe
	:name (strip-nulls (read-chars pak:pfe-namelen fd))
	:start (read-int (read-chars 4 fd))
	:len (read-int (read-chars 4 fd))
    )))
    (incf pos pak:pfe-len)
  )
  (file-position fd pak:dir-start)
  (setq header (read-chars pak:dir-len fd))
  (setq pak:checksum (CRC_ProcessBuffer (CRC_Init) header))
  (verify-pak-validity)
)

(defun paklist (name)
  (if verbose (progn
      (PF "  NAME                                                             POS      LEN\n")
      (dolist (file pak:files)
	(PF "  %0%1%2%3\n"
	  (pfe-name file) 
	  (make-string (- pak:pfe-namelen (length (pfe-name file))))
	  (expand-num (pfe-start file) 9 :filler #\ )
	  (expand-num (pfe-len file) 9 :filler #\ )
      ))
    )
    ;; not verbose? list only file names
    (dolist (file pak:files)
      (PF "%0\n" (pfe-name file))
    )
))

(defun paklistheaders (name)
  (PF "PAK name: %0
length: %1 (orig %5)
header at offset: %2, length: %3 %4\n"
    (0 args) pak:len pak:dir-start pak:dir-len 
    (if (= pak:len (+ pak:dir-start pak:dir-len)) "(No trailing data)"
      "(trailing data)"
    )
    PAK0_LEN
  )
  (PF "Number of files in PAK: %0  (should be %1, %2)
Header CRC checksum: %3 (should be %4, %5)\n"
    (length pak:files) PAK0_COUNT
    (if (= PAK0_COUNT (length pak:files)) "OK" 
      "********** BAD **********")
    pak:checksum PAK0_CRC
    (if (= PAK0_CRC pak:checksum) "OK" "********** BAD **********")
  )
)

;; checksum is modified by changing last 4 bytes of last name
(defun pakcheck (name &aux
    (fd (open name))
    (crc-base (CRC_ProcessBuffer (CRC_Init) 
	(read-chars (progn (file-position fd pak:dir-start) (- pak:dir-len 12))
	  fd
    )))
    lastname
    (crc pak:checksum)
    s
  )
  (file-position fd (+ pak:dir-start (- pak:dir-len 8)))
  (setq s (+ '(0 0 0 0) (List (read-chars 8 fd))))
  (file-position fd (- (+ pak:dir-start pak:dir-len) pak:pfe-len))
  (setq lastname (strip-nulls (read-chars pak:pfe-namelen fd)))
  (if (> (length lastname) (- pak:pfe-namelen 5))
    (print-exit 1 
      "Error: last filename in PAK too long, cannot insert checksum!\N")
  )
  ;; loop until we find the good crc
  (if (= pak:checksum PAK0_CRC)
    (verbose? "PAK checksum already valid, file untouched")
    (progn
      (verbose? "Computing checksum, now: %0, must be: %1" crc PAK0_CRC)
      (setq crc (CRC_ProcessBuffer crc-base s))
      (while (/= crc PAK0_CRC)
    ;; increment s. unroll loop to be faster
	(if (= 255 (3 s)) (progn (3 s 0) 
	    (if (= 255 (2 s)) (progn (2 s 0)
		(if (= 255 (1 s)) (progn (1 s 0)
		    (if (= 255 (0 s))
		      (print-exit 1 "Error! could not compute checksum!!!\n")
		      (0 s (+ (0 s) 1))
		  ))
		  (1 s (+ (1 s) 1))
		  (verbose? "  ... %0 iterations..." (+ (3 s) (* 256 (2 s))
		      (* (* 256 256) (1 s)) (* (* (* 256 256) 256) (0 s)))
	      )))
	      (2 s (+ (2 s) 1))
	  ))
	  (3 s (+ (3 s) 1))
	)
	(setq crc (CRC_ProcessBuffer crc-base s))
      )
      (verbose? "Checksum reached for values: %0 (%1 iterations)" 
	(subseq s 0 4)
	(+ 1 (3 s) (* 256 (2 s)) (* (* 256 256) (1 s)) 
	  (* (* (* 256 256) 256) (0 s)))
  )))
  (if (/= PAK0_COUNT (length pak:files)) (progn
    (print-format *standard-error* 
      "WARNING: bad number of files: %0 (should be %1)\n"
      (length pak:files) PAK0_COUNT
    ))
    (setq check-status 1)
  )
  (if (and verbose (pakfile-exists "progs.dat"))
    (check-missing-files)
  )
  (if (and (not do-nothing) (/= pak:checksum PAK0_CRC)) (progn
      (close fd)
      (setq fd (open name :direction :io :if-exists :overwrite))
      (file-position fd (- (+ pak:dir-start pak:dir-len) 12))
      (dolist (c s) (write-char c fd))
      (close fd)
      (verbose? "PAK checksum corrected in file")
      (pakdir name)
  ))
)

;; Some coherence checks in verbose mode: try to see files in pak referenced
;; in quakeC but that are not in .pak
(defun check-missing-files (&aux
    (dir (+ "/tmp/qpak." (String *current-process-id*)))
    (missing-files (list))
  )
  (extract-to-dir name dir "progs.dat")
  (shell (+ "cd " dir "; strings progs.dat|egrep -i '^(.*[/])?[^/. ]+[/][^ /.]+[.][a-z][a-z][a-z]$'>FILES"))
  (with (fdl (open (+ dir "/FILES")))
    (while (setq line (read-line fdl ()))
      (if (and (not (pakfile-exists line t))
	  (/= "gfx/pop.lmp" line)	;belongs to pak1
	)
	(lappend missing-files line)
  )))
  (sh rm -rf ,dir)
  (if missing-files
    (with (print-margin-item:prompts '("  " "  " "" "" " " "" ""))
      (print-format *standard-error* "WARNING: progs.dat refers to %0 files missing from pak:\n" (length missing-files))
      (print-margin-item :begin)
      (apply print-margin-item missing-files)
      (print-margin-item :end)
      (print-format *standard-error* 
	"You can probably safely ignore them.\n"
      )
      (if (has-mdl missing-files) 
	(print-format *standard-error* 
	  "***Except*** for the .mdl files: \n%0\n" 
	  (has-mdl missing-files)
)))))

;; returns the pfe struct
(defun pakfile-exists (onlyfile &optional in-subdir? &aux res)
  (setq onlyfile (tolower onlyfile))
  (catch 'Done
    (dolist (file pak:files)
      (if (or (= onlyfile (pfe-name file))
	  (and in-subdir?
	    (or (= onlyfile (pfe-name file))
	      (= (+ "/" onlyfile) (subseq (pfe-name file) 
		  (- (length (pfe-name file)) (+ 1 (length onlyfile)))
	)))))
	(throw 'Done (setq res file))
  )))
  res
)

(setq has-mdl-re (regcomp "[.]mdl$"))
(defun has-mdl (files &aux (res (list)))
  (dolist (file files) 
    (if (regexec has-mdl-re file)
      (lappend res file)
)))

(defun shell (s) (wait (system s)))

;; onlyfiles can be () = all, string = this file only, list of files
(defvar re-file (regcomp "^((.*)[/])?([^/]*)$"))
(defun extract-to-dir (name dir &optional onlyfiles &aux
    (fd (open name))
    fo
    buffer
    subdir
    (existing-dirs (list))
    (must-extract-file? (if		
	(not onlyfiles) (lambda (f) t)
	(typep onlyfiles String) (lambda (f) (= onlyfiles f))
	(lambda (f) (seek onlyfiles f))
    ))
  )
  (shell (+ "mkdir -p " dir " 2>/dev/null"))
  (verbose? "Extracting %0 into %1" name dir)
  (with (*current-directory* dir)
    (dolist (file pak:files)
      (if (not (regexec re-file (pfe-name file)))
	(print-exit 1 "Error: bad PAK file entry: %0" (pfe-name file))
      )
      (if (must-extract-file? (pfe-name file)) (progn
	  (setq subdir (regsub re-file 2))
	  (if (and (/= "" subdir)
	      (not (seek existing-dirs subdir))
	      (/= 'directory (file-type subdir))
	    ) (progn
	      (verbose? "  Creating dir %0" subdir)
	      (shell (+ "rm -f " subdir))
	      (shell (+ "mkdir -p " subdir " 2>/dev/null"))
	      (insert  existing-dirs 0 subdir)
	  ))
	  (verbose? "    extracting file %0 (length %1)" (pfe-name file)
	    (pfe-len file)
	  )
	  (setq fo (open (pfe-name file) 
	      :direction :output :if-exists :supersede))
	  (file-position fd (pfe-start file))
	  (setq buffer (read-chars (pfe-len file) fd))
	  (write-chars buffer () fo)
	  (close fo)
  ))))
)

(defun add-from-dir (name dir &aux
    (fd (open name :direction :output :if-exists :supersede))
    (pak:files (list))
  )
  (verbose? "Creating %0 from contents of %1" name dir)
  (write-chars "PACK....____" 12 fd)	;ID + room for header info
  (with (*current-directory* dir)	;recursively add files
    (add-files-from-dir fd (copy ""))
  )
  (verbose? "%0 files added, creating PAK header" (length pak:files))
  (setq pak:dir-start (file-position fd))
  (setq pak:dir-len (* (length pak:files) pak:pfe-len))
  (file-position fd 4)
  (write-chars (write-int pak:dir-start) 4 fd)
  (write-chars (write-int pak:dir-len) 4 fd)
  (file-position fd pak:dir-start)
  (dolist (file pak:files)
    (if (< (length (pfe-name file)) pak:pfe-namelen) (progn
	(write-chars (pfe-name file) () fd)
	(write-chars 
	  (make-string (- pak:pfe-namelen (length (pfe-name file))) 0)
	  () fd
      ))
      (print-exit 1 "Error: file name too long: %0\n" (pfe-name file))
    )
    (write-chars (write-int (pfe-start file)) 4 fd)
    (write-chars (write-int (pfe-len file)) 4 fd)
  )
  (close fd)
)

;;updates the global variable pak:files
(defun add-files-from-dir (fdout subdir &aux
    (dirlist (list))
    (filelist (list))
    buffer
    pfefile
  )
  (dolist (file (directory))
    (if (= 'directory (file-type file))
      (lappend dirlist file)
      (lappend filelist file)
  ))
  (sort dirlist compare)
  (sort filelist compare)
  ;; we put dirs first to put shortest names at end to be sure to have room 
  ;; for checksum
  (dolist (dir dirlist)
    (verbose? "  Adding contents of directory %0"
      (if (/= "" subdir) (+ subdir "/" dir) dir)
    )
    (with (*current-directory* dir)
      (add-files-from-dir fdout (if (/= "" subdir) (+ subdir "/" dir) dir))
  ))
  (dolist (file filelist)
    (lappend pak:files (setq pfefile (make-pfe
	  :name (tolower (if (/= "" subdir) (+ subdir "/" file) file))
	  :start (file-position fdout)
	  :len (get (file-stats file) 'size)
    )))
    (verbose? "  Adding file %0" (pfe-name pfefile))
    (setq buffer (String (open file)))
    (write-chars buffer () fdout)
  )
)

;;replaces just one file in pak. leaves one hole.
(defun update-one-file (name update-dir update-file &aux
    file start end must-truncate?
    (source-file update-file)
    (fd (open name :direction :io :if-exists :overwrite))
  )
  (with (*current-directory* *current-directory*) ;save current dir
    (if (= 'directory (file-type update-dir))
      (setq *current-directory* update-dir)
      (= 'file (file-type update-dir))
      (setq source-file update-dir)
      (fatal-error 1 "Source dir/file %0 not found\n" update-dir)
    )
    (if (not (setq file (pakfile-exists update-file)))
      (fatal-error 1 "File %0 not found in %1, aborting\n" update-file name)
    )
    (verbose? "Updating file %0" update-file)
      ;; append new file to end of pak
    (if (= (+ (pfe-start file) (pfe-len file)) pak:len)
      (progn
	(file-position fd (pfe-start file) 0) ; at end already, overwrite
	(setq must-truncate? t)
      )
      (file-position fd 0 2)		;otherwise, append
    )
    (setq start (file-position fd))
    (write-chars (String (open source-file :error
	  '(fatal-error 1 "Replacement file %0 not found\n" source-file)
      )) () fd
    )
    (flush fd)
    (setq end (file-position fd))
      ;; updates TOC
    (pfe-start file start)
    (pfe-len file (- end start))
    (file-position fd pak:dir-start)
    (catch 'Done
      (dolist (f pak:files)
	(if (eq f file) (progn
	    (file-position fd pak:pfe-namelen 1) ;skip name
	    (write-chars (write-int (pfe-start file)) 4 fd);update pos & len
	    (write-chars (write-int (pfe-len file)) 4 fd)
	    (throw 'Done)
	  ) 
	  (file-position fd pak:pfe-len 1) ; skip entry
    )))
    (close fd)
  )					;ok, get back to normal dir
  (setq pak:len (get (file-stats name) 'size))
  (if (and must-truncate? (> pak:len (+ (pfe-start file) (pfe-len file))))
    (file-truncate name (+ (pfe-start file) (pfe-len file)))
  )
  (pakdir name);; recomputes checksum
  (compress-pak name) 
)

;; compress a pak file after a hole has been made in it
(defun compress-pak (name &aux 
    (fd (open name :direction :io :if-exists :overwrite))
    (files (copy pak:files))
    (curpos 12)
    file-contents
  )
  (if pak:has-holes? (progn
      (verbose? "filling hole(s) in %0" name)
      ;; sort files by position
      (sort files (lambda (x y) (compare (pfe-start x) (pfe-start y))))
      ;; then fill holes by moving files contents down
      (dolist (file files)
	(if (> (pfe-start file) curpos) (progn ;hole found
	    (verbose? "Moving file %0 from %1 to %2 (%3 down)"
	      (pfe-name file) (pfe-start file) curpos 
	      (- (pfe-start file) curpos)
	    )
	    (file-position fd (pfe-start file))
	    (setq file-contents (read-chars (pfe-len file) fd))
	    (file-position fd curpos)
	    (write-chars file-contents () fd)
	    (pfe-start file curpos)
	))
	(setq curpos (+ (pfe-start file) (pfe-len file)))
      )
      ;; then, write header at end
      (file-position fd (setq pak:dir-start curpos))
      (dolist (file pak:files)
	(if (< (length (pfe-name file)) pak:pfe-namelen) (progn
	    (write-chars (pfe-name file) () fd)
	    (write-chars 
	      (make-string (- pak:pfe-namelen (length (pfe-name file))) 0)
	      () fd
	  ))
	  (print-exit 1 "Error: file name too long: %0\n" (pfe-name file))
	)
	(write-chars (write-int (pfe-start file)) 4 fd)
	(write-chars (write-int (pfe-len file)) 4 fd)
      )
      ;; updates structures
      (setq pak:len (file-position fd))
      (setq pak:dir-len (- pak:len pak:dir-start))
      (file-position fd 4)
      (write-chars (write-int pak:dir-start) 4 fd)
      (write-chars (write-int pak:dir-len) 4 fd)
      (close fd)
      (if (< pak:len (get (file-stats name) 'size 0))
	(file-truncate name pak:len)
      )
      (PF "pakfile holes removed.\n")
      (pakdir name)				;recompute checksum
    )
    (verbose? "This pak file has no holes in it, no need to compress")
  )
)

;; verify validity of pak files, by checking for olverlays or holes in it
(defun verify-pak-validity (&aux
    (prevpos 12)
    (prevname "PAK header")
    (files (copy pak:files))
    (is-toc? (lambda (f r1 r2) 
	(if (or (and (typep f String) (= "/*TOC*/" f))
	    (= "/*TOC*/" (pfe-name f))) r1 r2)))
  )
  ;;we append a pseudo-file of name "/*TOC*/" to represent the TOC region
  (lappend files (make-pfe 
      :name "/*TOC*/" :start pak:dir-start :len pak:dir-len)
  )
  ;; sort files by position
  (sort files (lambda (x y) (compare (pfe-start x) (pfe-start y))))
  ;; first check for fatal errors
  (setq pak:has-holes? ())
  (dolist (file files)
    (if (< (pfe-len file) 0)
      (pak-invalid (PF String "file %0 has negative length: %1\n"
	  (pfe-name file) (pfe-len file)
    )))
    (if (< (pfe-start file) prevpos)
      (pak-invalid (PF String "file %0 starts inside another one (%1)!\n"
	  (pfe-name file) prevname
      ))
      (> (pfe-start file) prevpos) (progn
	(PF "Warning: empty hole of %0 bytes before %3%1 and after %2\n"
	  (- (pfe-start file) prevpos) 
	  (is-toc? file "" (pfe-name file))
	  (is-toc? prevname "TOC" prevname)
	  (is-toc? file "TOC" "file ")
	)
	(setq pak:has-holes? t)
    ))
    (if (> (+ (pfe-start file) (pfe-len file)) pak:len)
      (pak-invalid (PF String "file %0 too long (%1), ends after EOF by %2\n"
	  (pfe-name file) (pfe-len file)
	  (- (+ (pfe-start file) (pfe-len file)) pak:len)
    )))
    (setq prevpos (+ (pfe-start file) (pfe-len file)))
    (setq prevname (pfe-name file))
  )
  (if (< prevpos pak:len) (progn
      (PF "Warning: empty hole of %0 bytes at end of pak after %1\n"
	(- pak:len prevpos) (is-toc? prevname "TOC" prevname)
      )
      (setq pak:has-holes? t)
    ) 
  )
)

(defun pak-invalid (s)
  (PF "PAK invalid: %0\n" s)
  (if (not ignore-errors) (exit 1))
)

;;=============================================================================
;;                    checksum
;;=============================================================================
;; This code is a direct translation of C code
;; this is a 16 bit, non-reflected CRC using the polynomial 0x1021
;; and the initial and final xor values shown below...  in other words, the
; CCITT standard CRC used by XMODEM

(setq CRC_INIT_VALUE	0xffff)
(setq CRC_XOR_VALUE	0x0000)
(setq CRC_MASK 0xffff)

(setq crctable [
  	0x0000	0x1021	0x2042	0x3063	0x4084	0x50a5	0x60c6	0x70e7
	0x8108	0x9129	0xa14a	0xb16b	0xc18c	0xd1ad	0xe1ce	0xf1ef
	0x1231	0x0210	0x3273	0x2252	0x52b5	0x4294	0x72f7	0x62d6
	0x9339	0x8318	0xb37b	0xa35a	0xd3bd	0xc39c	0xf3ff	0xe3de
	0x2462	0x3443	0x0420	0x1401	0x64e6	0x74c7	0x44a4	0x5485
	0xa56a	0xb54b	0x8528	0x9509	0xe5ee	0xf5cf	0xc5ac	0xd58d
	0x3653	0x2672	0x1611	0x0630	0x76d7	0x66f6	0x5695	0x46b4
	0xb75b	0xa77a	0x9719	0x8738	0xf7df	0xe7fe	0xd79d	0xc7bc
	0x48c4	0x58e5	0x6886	0x78a7	0x0840	0x1861	0x2802	0x3823
	0xc9cc	0xd9ed	0xe98e	0xf9af	0x8948	0x9969	0xa90a	0xb92b
	0x5af5	0x4ad4	0x7ab7	0x6a96	0x1a71	0x0a50	0x3a33	0x2a12
	0xdbfd	0xcbdc	0xfbbf	0xeb9e	0x9b79	0x8b58	0xbb3b	0xab1a
	0x6ca6	0x7c87	0x4ce4	0x5cc5	0x2c22	0x3c03	0x0c60	0x1c41
	0xedae	0xfd8f	0xcdec	0xddcd	0xad2a	0xbd0b	0x8d68	0x9d49
	0x7e97	0x6eb6	0x5ed5	0x4ef4	0x3e13	0x2e32	0x1e51	0x0e70
	0xff9f	0xefbe	0xdfdd	0xcffc	0xbf1b	0xaf3a	0x9f59	0x8f78
	0x9188	0x81a9	0xb1ca	0xa1eb	0xd10c	0xc12d	0xf14e	0xe16f
	0x1080	0x00a1	0x30c2	0x20e3	0x5004	0x4025	0x7046	0x6067
	0x83b9	0x9398	0xa3fb	0xb3da	0xc33d	0xd31c	0xe37f	0xf35e
	0x02b1	0x1290	0x22f3	0x32d2	0x4235	0x5214	0x6277	0x7256
	0xb5ea	0xa5cb	0x95a8	0x8589	0xf56e	0xe54f	0xd52c	0xc50d
	0x34e2	0x24c3	0x14a0	0x0481	0x7466	0x6447	0x5424	0x4405
	0xa7db	0xb7fa	0x8799	0x97b8	0xe75f	0xf77e	0xc71d	0xd73c
	0x26d3	0x36f2	0x0691	0x16b0	0x6657	0x7676	0x4615	0x5634
	0xd94c	0xc96d	0xf90e	0xe92f	0x99c8	0x89e9	0xb98a	0xa9ab
	0x5844	0x4865	0x7806	0x6827	0x18c0	0x08e1	0x3882	0x28a3
	0xcb7d	0xdb5c	0xeb3f	0xfb1e	0x8bf9	0x9bd8	0xabbb	0xbb9a
	0x4a75	0x5a54	0x6a37	0x7a16	0x0af1	0x1ad0	0x2ab3	0x3a92
	0xfd2e	0xed0f	0xdd6c	0xcd4d	0xbdaa	0xad8b	0x9de8	0x8dc9
	0x7c26	0x6c07	0x5c64	0x4c45	0x3ca2	0x2c83	0x1ce0	0x0cc1
	0xef1f	0xff3e	0xcf5d	0xdf7c	0xaf9b	0xbfba	0x8fd9	0x9ff8
	0x6e17	0x7e36	0x4e55	0x5e74	0x2e93	0x3eb2	0x0ed1	0x1ef0
  ]
)

;; Warning: we should check for negative numbers and overflow on these 
;; 16-bit unsigneds
(defun CRC_Init () CRC_INIT_VALUE)
(defun CRC_ProcessByte (value data)
  (logand 0xffff
    (logxor (logshift value 8) ((logxor (logshift value -8) data) crctable))
))
(defun CRC_ProcessBuffer (value buffer)
  (dolist (data buffer)
    (setq value (logand 0xffff
      (logxor (logshift value 8) ((logxor (logshift value -8) data) crctable))
  )))
  value
)
(defun CRC_Value (value) (logand 0xffff (logxor value CRC_XOR_VALUE)))
      

;;=============================================================================
;;                    utils
;;=============================================================================
(defun read-int (s &aux (res 0) (mul 1))
  (dolist (c s)
    (incf res (* mul c))
    (setq mul (* mul 256))
  )
  res
)
(defun write-int (n &aux (res (copy "")))
  (dotimes 4
    (put res -1 (logand 0xff n))
    (setq n (logshift n -8))
  )
  res
)

(defun strip-nulls (s &aux 
    (nullpos (seek s 0))
  )
  (if nullpos (subseq s 0 nullpos) s)
)

(main)

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

