/*
  biounzip - A program to extract BioZip V1.0 files (specifically disk2.bzf on
  Neverwinter Nights install disk 2 :) ).
  Copyright (C) 2003, Colin Walsh <c_walsh@roadrunner.nf.net>
  See the file COPYING for more details

  Author: Colin Walsh <c_walsh@roadrunner.nf.net>
  Created on: March 12, 2003
  Requires: zlib
  Usage: biounzip <filename> <base directory>

  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 2
  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, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

/*
  biounzip v1.1 changelog:
  *fix directory creation in certain situations
  *add missing headers
  *remove unused variables
  *fix signed / unsigned integer comparisons
  *remove excessive debug output during normal use
  *minor code cleanup
  *add function prototypes
  *error conditions should go to stderr, not stdout

  The source should now compile cleanly with GCC.
  Fixes by Michael Mohr <m.mohr@laposte.net>
*/

#include <stdlib.h>
#include <zlib.h>
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include "parse.h"

typedef struct{
  int zero;
  int fname_length;
  int dirname_length;
  int numchunks;
  int unknown1;
  int unknown2;
  int offset;
}bzfile;

void createdirs(char *);
void writefile(bzfile *, FILE *, char *);


void createdirs(char *dirpath)
{
  struct stat status;
  int returnval,n;
  char *tmppath;
  char **dirs;
  /*This is in case there are doubled slashes in the path given*/
  n=0;
  while(dirpath[n]!=0)
    {
      if(dirpath[n]=='/'&&dirpath[n+1]=='/')
	{
	  memmove(&dirpath[n+1],&dirpath[n+2],strlen(dirpath)-(n+1));
	  n--;
	}
      n++;
    }

  /*Pad this, just to be on the safe side*/
  tmppath=(char *)malloc(strlen(dirpath)+128);
  dirs=split(dirpath,"/");
  tmppath[0]=0;
  for(n=0;n<argcount(dirs);n++)
    {
      if(strlen(dirs[n])>0)
	{
	  if(strlen(tmppath)>0)strcat(tmppath,"/");
	  strcat(tmppath,dirs[n]);
	  /* add a leading slash if the given path is absolute */
	  if(n == 1 && dirpath[0] == '/') {
	    memmove(&tmppath[1],&tmppath[0],strlen(tmppath)+1);
	    tmppath[0] = '/';
	  }
	  returnval=mkdir(tmppath,(mode_t)0755);
	  if(returnval!=0)
	    {
	      if(errno!=EEXIST)
		{
		  perror("mkdir");
		  fprintf(stderr,"Can't continue!\n");
		  exit(-1);
		}else{
		  stat(tmppath,&status);
		  if(!S_ISDIR(status.st_mode))
		    {
		      perror("mkdir");
		      fprintf(stderr,"A file exists with the same name as a directory I'm trying to create!\n");
		      fprintf(stderr,"Trying to create: %s\n",tmppath);
		      exit(-1);
						
		    }
		}
	    }
	}
    }
  freeargs(dirs);
  free(tmppath);
}

void writefile(bzfile *ptr, FILE *fp, char *destdir)
{
  int startpos,returnval,n,debug=0;
  unsigned long size_uncompressed,size_compressed;
  char *fname,*dirname,*fullname,*fullpath;
  unsigned char *srcbuffer,*dstbuffer;
  FILE *outfile;

  startpos=ftell(fp);
  fname=(char *)malloc(ptr->fname_length+1);
  dirname=(char *)malloc(ptr->dirname_length+1);
  fname[ptr->fname_length]=0;
  dirname[ptr->dirname_length]=0;
  fseek(fp,ptr->offset,SEEK_SET);
  fread(fname,1,ptr->fname_length,fp);
  fread(dirname,1,ptr->dirname_length,fp);
  /*convert back slashes into forward slashes*/
  for(n=0;n<(int)strlen(dirname);n++)
    if(dirname[n]=='\\') dirname[n]='/';
  fullpath=(char *)malloc(strlen(destdir)+1+strlen(dirname)+1);
  strcpy(fullpath,destdir);
  strcat(fullpath,"/");
  strcat(fullpath,dirname);
  if(debug) printf("fullpath: %s\n",fullpath);
  else printf("Writing out %s%s\n",fullpath,fname);

  createdirs(fullpath);
  if(debug) printf("file: %s directory: %s\n",fname,dirname);
  fullname=(char *)malloc(strlen(fullpath)+strlen(fname)+1);
  strcpy(fullname,fullpath);
  strcat(fullname,fname);
  outfile=fopen(fullname,"wb");
  srcbuffer=(unsigned char *)malloc(512000);
  dstbuffer=(unsigned char *)malloc(512000);
  for(n=0;n<ptr->numchunks;n++)
    {
      fread((char *)&size_uncompressed,1,4,fp);
      fread((char *)&size_compressed,1,4,fp);
      fread(srcbuffer,1,size_compressed,fp);
      returnval=uncompress(dstbuffer,&size_uncompressed,srcbuffer,size_compressed);
      if(returnval==Z_OK)
	{
	  fwrite(dstbuffer,1,size_uncompressed,outfile);
	}else if(returnval==Z_BUF_ERROR){
	  fprintf(stderr,"Buffer wasn't large enough!\n");
	  exit(-1);
	}else{
	  fprintf(stderr,"Decompression error!\n");
	  exit(-1);
	}
    }
  fclose(outfile);
  free(srcbuffer);
  free(dstbuffer);
  free(fname);
  free(dirname);
  free(fullname);
  free(fullpath);
  /*return to where we started*/
  fseek(fp,startpos,SEEK_SET);
}

int main(int argc, char **argv)
{
  int n,numfiles,debug=0;
  bzfile bztemp;
  char *hdr;
  FILE *infile;
  printf("biounzip V1.1 - Copyright (C) 2003, Colin Walsh\n"
	 "This software is released under the GPL version 2.\n"
	 "See the file COPYING for more details\n\n");

  if(argc!=3)
    {
      fprintf(stderr,"Error: missing one or more command line arguments.\n"
	     "Usage: biounzip <filename> <base directory>\n");
      exit(-1);
    }
  createdirs(argv[2]);
  hdr=(char *)malloc(9);
  hdr[8]=0;
  infile=fopen(argv[1],"rb");
  fread(hdr,1,8,infile);
  if(strcmp(hdr,"BZF V1.0")==0)
    {
      printf("This file appears to be a BioZip archive!\n");
    }else{
      fprintf(stderr,"This doesn't appear to be a BioZip archive!!\n");
      exit(-1);
    }
  fseek(infile,4,SEEK_CUR);/*Seek over unknown 4 bytes*/
  fread((char *)&numfiles,1,4,infile);
  printf("Extracting %i files...\n\n",numfiles);
  fseek(infile,4,SEEK_CUR);/*Seek over unknown 4 bytes*/
  for(n=0;n<numfiles;n++)
    {
      fread((char *)&(bztemp.zero),1,4,infile);
      fread((char *)&(bztemp.fname_length),1,4,infile);
      fread((char *)&(bztemp.dirname_length),1,4,infile);
      fread((char *)&(bztemp.numchunks),1,4,infile);
      fread((char *)&(bztemp.unknown1),1,4,infile);
      fread((char *)&(bztemp.unknown2),1,4,infile);
      fread((char *)&(bztemp.offset),1,4,infile);
      if(debug) {
	printf("fname_length: %i\n",bztemp.fname_length);
	printf("dirname_length: %i\n",bztemp.dirname_length);
	printf("offset: %i\n",bztemp.offset);
	printf("unknown1: %i; unknown2: %i\n",bztemp.unknown1,bztemp.unknown2);
	printf("numchunks: %i\n",bztemp.numchunks);
      }
      writefile(&bztemp,infile,argv[2]);
    }
  fclose(infile);
  return 0;
}
