/* * @file unice68.c * @brief program to pack and depack ICE! packed files * @author http://sourceforge.net/users/benjihan * * Copyright (c) 1998-2016 Benjamin Gerard * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. * * If not, see . * */ #ifdef HAVE_CONFIG_H # include #endif #ifndef PACKAGE_NAME # define PACKAGE_NAME "unice68" #endif #ifndef PACKAGE_VERSION # define PACKAGE_VERSION __DATE__ #endif #include "unice68.h" #include #include #include #include #include #include #ifdef HAVE_LIBGEN_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_IO_H #include #endif /* define _O_BINARY */ #ifndef _O_BINARY # ifdef O_BINARY # define _O_BINARY O_BINARY # else # define _O_BINARY 0 # endif #endif #ifdef _MSC_VER # ifndef __cplusplus # define inline __inline # endif # define UNICE68_CDECL __cdecl #else # define UNICE68_CDECL #endif static char * prg; static int verbose; static FILE * msgout; static unsigned memmax = 1<<24; #ifndef HAVE_BASENAME # ifndef PATHSEP # ifdef UNICE68_WIN32 # define PATHSEP '\\' # else # define PATHSEP '/' # endif # endif static char *basename(char *path) { char * s = strrchr(path, PATHSEP); return !s ? path : s+1; } #endif /* Message levels */ enum { D = 2, /* Message level Debug */ V = 1, /* Message level verbose */ N = 0, /* Message level normal */ E = -1 /* Message level error */ }; static inline int myabs(const int v) { return v < 0 ? -v : v; } static void message_va(const int level, const char *fmt, va_list list) { if (verbose >= level) { int save_errno = errno; FILE * out = (level == E) ? stderr : msgout; vfprintf(out,fmt,list); fflush(out); errno = save_errno; } } static void message(const int level, const char *fmt, ...) { va_list list; va_start(list, fmt); message_va(level,fmt,list); va_end(list); } static void error(const char * fmt, ...) { va_list list; va_start(list, fmt); message(E,"%s: ", prg); message_va(E,fmt,list); va_end(list); } static void syserror(const char * obj) { message(E,"%s: %s -- %s\n", prg, obj, strerror(errno)); } static int print_usage(void) { int ice_d_version = unice68_unice_version(); int ice_p_version = unice68_ice_version(); printf ( "Usage: %s [MODE] [OPTION...] [--] [[] ]\n" "\n" "ICE! depacker %x.%02x\n" " packer %x.%02x\n" "\n" " `-' as input/output uses respectively stdin/stdout.\n" " If output is stdout all messages are diverted to stderr.\n" "\n" "Modes:\n" "\n" " -h --help Print this message and exit\n" " -V --version Print version and copyright and exit\n" /* " -c --cat Output input file as is if not ICE! packed\n" */ " -t --test Test if input is an ICE! packed file\n" " -T --deep-test Test if input is a valid ICE! packed file\n" " -d --depack Depack mode\n" " -p --pack Pack mode\n" " -P --pack-old Force pack with deprecated 'Ice!' identifier\n" " -s --stress Pack and unpack for testing\n" "\n" " If no mode is given the default is to pack an unpacked file\n" " and to unpack a packed one.\n" "\n" "Options:\n" "\n" " -n --no-limit Ignore memory sanity check\n" " -v --verbose Be more verbose (multiple use possible)\n" " -q --quiet Be less verbose (multiple use possible)\n" "\n" "Copyright (c) 1998-2016 Benjamin Gerard\n" "\n" "Visit <" PACKAGE_URL ">\n" "Report bugs to <" PACKAGE_BUGREPORT ">\n", prg, (unsigned int)(ice_d_version>>8), (unsigned int)(ice_d_version&255), (unsigned int)(ice_p_version>>8), (unsigned int)(ice_p_version&255) ); return 0; } static int print_version(void) { puts(unice68_versionstr()); puts ( "\n" "Copyright (c) 1998-2016 Benjamin Gerard.\n" "License GPLv3+ or later \n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n" "\n" "Written by Benjamin Gerard" ); return 0; } static unsigned hash_buffer(const char * buf, unsigned int len) { const unsigned char * k = (unsigned char *) buf; unsigned int h = 0; if (len > 0) do { h += *k++; h += h << 10; h ^= h >> 6; } while (--len); return h; } #if _O_BINARY /* Fallback file descriptors if all other methods failed */ #ifndef STDIN_FILENO # define STDIN_FILENO 0 #endif #ifndef STDOUT_FILENO # define STDOUT_FILENO 1 #endif #ifndef STDERR_FILENO # define STDERR_FILENO 2 #endif /* If _fileno is a macro just pretends we have the function. */ #if defined(_fileno) && !defined(HAVE__FILENO) # define HAVE__FILENO 1 #endif /* If fileno is a macro just pretends we have the function. */ #if defined(fileno) && !defined(HAVE_FILENO) # define HAVE_FILENO 1 #endif /* If _setmode is a macro just pretend we have the function. */ #if defined(_setmode) && !defined(HAVE__SETMODE) # define HAVE__SETMODE 1 #endif /* If setmode is a macro just pretend we have the function. */ #if defined(setmode) && !defined(HAVE_SETMODE) # define HAVE_SETMODE 1 #endif static int myfileno(FILE * file) { #if defined(HAVE_FILENO) return fileno(file); #elif defined(HAVE__FILENO) return _fileno(file); #else if (file == stdin) return STDIN_FILENO; else if (file == stdout) return STDOUT_FILENO; else if (file == stderr) return STDERR_FILENO; return -1; #endif } static int set_binary_mode(FILE *file, const char * path) { int fd = myfileno(file); if (fd != -1) { int res = -1; #if defined(HAVE__SETMODE) res = _setmode(fd,_O_BINARY); #elif defined(HAVE_SETMODE) res = setmode(fd,O_BINARY); #endif if (res != -1) { message(D,"%s (%d) set to binary mode\n", path, fd); return 0; } } return -1; } #else static int set_binary_mode(FILE *file, const char * path) { ((void)file); ((void)path); return 0; } #endif static void * myalloc(void * buf, int len, char * name) { void * newbuf = 0; const unsigned int al = (1<<10) - 1; message(D, "Allocating %d bytes for the %s buffer\n", len, name); if (memmax && (unsigned int)len >= memmax) { error("cowardly refuse to allocate %u KiB of memory (try `-n')\n", (len+al) >> 10); free(buf); } else { newbuf = realloc(buf,len); if (!newbuf) { syserror(name); free(buf); } } return newbuf; } static int myread(void * buf, int len, FILE * inp, const char * name) { // rom-properties FIXME: fread() returns size_t, so it can't possibly return a negative value. int n = (int)fread(buf, 1, len, inp); if (n == -1) { syserror(name); return -1; } return n; } static int exactread(void * buf, int len, FILE * inp, const char * name) { int n = myread(buf, len, inp, name); if (n != -1 && n != len) { error("%s -- truncated at %d; expected %d\n", name, n, len); n = -1; } return n; } /* return codes */ enum { ERR_OK = 0, /* no error */ ERR_UNDEFINED, /* undefined */ ERR_CLI, /* command line parsing */ ERR_INPUT, /* input file op */ ERR_OUTPUT, /* output file op */ ERR_UNPACK_NOT_ICE=10, /* no ICE! header */ ERR_UNPACK_TOO_SMALL, /* too snall for being ICE! */ ERR_UNPACK_SIZE_MISMATCH, /* file size and header size mismatch */ ERR_PACKER = 20, ERR_PACKER_SIZE_MISMATCH, ERR_PACKER_OVERFLOW, ERR_PACKER_STRESS, }; int UNICE68_CDECL main(int argc, char *argv[]) { int err = ERR_UNDEFINED; int mode = 0, oneop = 0, sens = 0, oldid = 0; char * finp = 0, *fout = 0; char * ibuffer = 0, * obuffer = 0; FILE * inp = 0, * out = 0; int ilen = -1, olen = -1; int csize = 0, dsize = -1; int iextra = 0; int i, n, hread; char header[12]; unsigned hash1 = 0; int verified = 0; msgout = stdout; prg = basename(argv[0]); if (!prg) prg = PACKAGE_NAME; argv[0] = prg; for (i=n=1; i 2 + !oneop) { error("too many argument -- `%s'\n", argv[2 + !oneop]); return ERR_CLI; } if (argc > 1) { finp = (argv[1][0] == '-' && !argv[1][1]) ? 0 : argv[1]; } if (argc > 2) { fout = (argv[2][0] == '-' && !argv[2][1]) ? 0 : argv[2]; } /* Divert all messages to stderr if neccessary */ if (!oneop && !fout) { msgout = stderr; message(D,"All messages diverted to stderr\n"); } /*********************************************************************** * Input **********************************************************************/ err = ERR_INPUT; ilen = -1; if (!finp) { set_binary_mode(inp=stdin, finp=""); } else { inp = fopen(finp,"rb"); if (inp) { if (fseek(inp, 0, SEEK_END) != -1) { long pos = ftell(inp); if (fseek(inp, 0, SEEK_SET) != -1) { ilen = pos; } } if (ilen == -1) { syserror(finp); goto error; } } } message(D,"input: %s (%d)\n", finp, ilen); if (!inp) { syserror(finp); goto error; } err = (int) fread(header, 1, sizeof(header), inp); if (err == -1) { syserror(finp); goto error; } hread = err; if (hread < (int)sizeof(header)) { if (sens == 'd') { err = ERR_UNPACK_TOO_SMALL; error("input is too small, not ice packed.\n"); goto error; } mode = sens = 'p'; message(D,"Assume mode `%c'\n", mode); } if (sens != 'p') { csize = 0; dsize = unice68_depacked_size(header, &csize); if (dsize == -1) { message(D,"Not ice\n"); if (sens == 'd') { err = ERR_UNPACK_NOT_ICE; error("input is not ice packed.\n"); goto error; } assert(!sens); sens = mode = 'p'; message(D,"Assume mode `%c'\n", mode); } else if (!sens) { sens = mode = 'd'; message(D,"Assume mode `%c'\n", mode); } if (sens == 'd') { const int margin = 16; if (ilen == -1 || ilen == csize) ilen = csize; else if ( myabs(ilen-csize) > margin ) { err = ERR_UNPACK_SIZE_MISMATCH; error("file size (%d) and packed size (%d) do not match.\n", ilen, csize); goto error; } else if (csize > ilen) { iextra = csize - ilen; } if (mode == 't') { err = ( dsize == -1 ) ? ERR_UNPACK_NOT_ICE : ERR_OK; goto error; } } } err = ERR_INPUT; if (ilen != -1) { if (ibuffer = myalloc(0,ilen+iextra,"input"), !ibuffer) goto error; memcpy(ibuffer, header, hread); if (-1 == exactread(ibuffer+hread, ilen-hread, inp, finp)) goto error; } else { int max = 1<<16, m, n; ibuffer = myalloc(0, max, "input"); if (!ibuffer) goto error; /* copy pre-read header */ memcpy(ibuffer, header, hread); ilen = hread; do { m = max - ilen; if (!m) { max <<= 1; ibuffer = myalloc(ibuffer, max, "input"); if (!ibuffer) goto error; m = max - ilen; } n = myread(ibuffer+ilen, m, inp, finp); message(D,"got %d out of %d\n", n, m); if (n == -1) goto error; ilen += n; } while (n > 0 /* && n == m */); } message(D,"Have read all %d input bytes from `%s' ...\n", ilen, finp); err = ERR_OUTPUT; olen = (sens == 'd') ? dsize : (ilen + (ilen>>1) + 1000); if (obuffer = myalloc(0, olen, "output"), !obuffer) goto error; /*********************************************************************** * Process **********************************************************************/ switch (sens) { case 'p': message(V, "ice packing \"%s\" (%d bytes) ...\n", finp, ilen); err = unice68_packer(obuffer, olen, ibuffer, ilen); message(D, "packing returns with %d\n", err); if (err == -1) { err = ERR_PACKER; break; } if (err > olen) { error("CRITICAL ! ice packer buffer overflow (%d > %d)\n", err , olen); exit(ERR_PACKER_OVERFLOW); } csize = err; dsize = unice68_depacked_size(obuffer, &csize); if (dsize == -1 || dsize != ilen) { if (dsize != -1) dsize = ~dsize; error("size inconsistency (%d != %d)\n", dsize, ilen); err = ERR_PACKER_SIZE_MISMATCH; goto error; } olen = csize; /* Patch ICE magic to fit requested */ memcpy(obuffer+1,oldid ? "ce" : "CE", 2); err = 0; if (mode != 's') { break; } else { int tlen; char * tbuffer; hash1 = hash_buffer(ibuffer,ilen); message(D,"input hash: %x\n", hash1); memset(ibuffer,0,ilen); tbuffer = ibuffer; tlen = ilen; ibuffer = obuffer; ilen = olen; obuffer = tbuffer; olen = tlen; } /* fall-through */ case 'd': message(V, "ice depacking \"%s\" (%d bytes) ...\n", finp, ilen); err = unice68_depacker(obuffer, olen, ibuffer, ilen); message(D, "depacking returns with %d\n", err); if (!err && mode =='s') { unsigned int hash2 = hash_buffer(obuffer, olen); message(D,"depack hash: %x\n", hash2); err = - (hash1 != hash2); verified = !err; } if (err) { err = ERR_PACKER_STRESS; error("stress has failed\n"); } break; default: assert(!"!!!! internal error !!!! invalid sens"); } /*********************************************************************** * Output **********************************************************************/ if (!oneop && !err) { err = ERR_OUTPUT; if (!fout) { set_binary_mode(out=stdout, fout=""); } else { out = fopen(fout,"wb"); } message(D,"output: %s (%d)\n", fout, olen); if (!out) { syserror(fout); goto error; } // rom-properties FIXME: fread() returns size_t, so it can't possibly return a negative value. n = (int)fwrite(obuffer,1,olen,out); message(D,"Have written %d bytes to %s\n", n, fout); if (n != olen) { syserror(fout); goto error; } err = 0; } error: if (inp && inp != stdin) { fclose(inp); } if (out) { fflush(out); if (out != stdout) { fclose(out); } } free(ibuffer); free(obuffer); if (!err) { message(N,"ICE! compressed:%d uncompressed:%d ratio:%d%%%s\n", csize, dsize, dsize?(csize+50)*100/dsize:-1, verified ? " (verified)" : ""); } err &= 127; message(D,"exit with code %d\n", err); return err; }