#!/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: ***