/*
 *  mpq.c -- functions for developers using libmpq.
 *
 *  Copyright (C) 2003 Maik Broemme <mbroemme@plusserver.de>
 *
 *  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.
 *
 *  $Id: mpq.c,v 1.6 2004/02/12 00:49:00 mbroemme Exp $
 */
#define _CRT_SECURE_NO_DEPRECATE

#include <stdlib.h>
#include <sys/stat.h>
//#include <unistd.h>
//#include <io.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include "mpq.h"
#include "common.h"

/*
 *  This function returns version information.
 *  format: MAJOR.MINOR.PATCH
 */
char *libmpq_version() {
    static char version[10];
    sprintf(version, "%i.%i.%i", LIBMPQ_MAJOR_VERSION, LIBMPQ_MINOR_VERSION, LIBMPQ_PATCH_VERSION);
    return version;
}

/*
 *  This function reads a file and verify if it is a legit MPQ archive
 *  or not. Then it fills the mpq_header structure and reads the hash
 *  table.
 */
int libmpq_archive_open(mpq_archive *mpq_a, unsigned char *mpq_filename) {
    int fd = 0;
    int rb = 0;
    int ncnt = FALSE;
    struct stat fileinfo;

    /* allocate memory */
    mpq_a->mpq_l = (mpq_list *)malloc(sizeof(mpq_list));
    memset(mpq_a->mpq_l, 0, sizeof(mpq_list));
    mpq_a->header = (mpq_header *)malloc(sizeof(mpq_header));
    memset(mpq_a->header, 0, sizeof(mpq_header));

    /* Check if file exists and is readable */
    fd = _open((char *)mpq_filename, MPQ_FILE_OPEN_FLAGS);
    if (fd == LIBMPQ_EFILE) {
        return LIBMPQ_EFILE;
    }

    /* fill the structures with informations */
    strcpy((char *)mpq_a->filename, (char *)mpq_filename);
    libmpq_init_buffer(mpq_a);
    mpq_a->fd               = fd;
    mpq_a->header->id       = 0;
    mpq_a->maxblockindex    = 0;
    mpq_a->mpq_l->mpq_files = NULL;

    mpq_a->mpqpos = 0; //k

    while (!ncnt) {
        mpq_a->header->id = 0;
        #ifdef WIN32
            _lseeki64(mpq_a->fd, mpq_a->mpqpos, SEEK_SET);
        #else
            lseek64(mpq_a->fd, mpq_a->mpqpos, SEEK_SET);
        #endif
        rb = _read(mpq_a->fd, mpq_a->header, sizeof(mpq_header));

        /* if different number of bytes read, break the loop */
        if (rb != sizeof(mpq_header)) {
            return LIBMPQ_EFILE_FORMAT;
        }

        /* special offset for protected MPQs */
        if (mpq_a->header->offset == LIBMPQ_HEADER_W3M) {
            mpq_a->flags |= LIBMPQ_FLAG_PROTECTED;
            mpq_a->header->offset = sizeof(mpq_header);
        }

        /* if valid signature has been found, break the loop */
        if (mpq_a->header->id == LIBMPQ_ID_MPQ) {
            ncnt = true;
        }
        /*if (mpq_a->header->id == LIBMPQ_ID_MPQ &&
            mpq_a->header->offset == sizeof(mpq_header) &&
            mpq_a->header->hashtablepos < mpq_a->header->archivesize &&
            mpq_a->header->blocktablepos < mpq_a->header->archivesize) {
            ncnt = TRUE;
        }*/

        /* move to the next possible offset */
        if (!ncnt) {
            mpq_a->mpqpos += 0x200;
        }
    }

    /* get the right positions of the hash table and the block table. */
    mpq_a->blocksize = (0x200 << mpq_a->header->blocksize);
    fstat(mpq_a->fd, &fileinfo);

    /* Normal MPQs must have position of */
    /*if (mpq_a->header->hashtablepos + mpq_a->mpqpos < fileinfo.st_size &&
        mpq_a->header->blocktablepos + mpq_a->mpqpos < fileinfo.st_size) {
        mpq_a->header->hashtablepos  += mpq_a->mpqpos;
        mpq_a->header->blocktablepos += mpq_a->mpqpos;
    } else {
        return LIBMPQ_EFILE_FORMAT;
    }*/

    /* Try to read and decrypt the hashtable */
    if (libmpq_read_hashtable(mpq_a) != 0) {
        return LIBMPQ_EHASHTABLE;
    }

    /* Try to read and decrypt the blocktable */
    if (libmpq_read_blocktable(mpq_a) != 0) {
        return LIBMPQ_EBLOCKTABLE;
    }

    return LIBMPQ_TOOLS_SUCCESS;
}

/*
 *  This function closes the file descriptor opened by
 *  mpq_open_archive(); and frees the decryption buffer.
 */
int libmpq_archive_close(mpq_archive *mpq_a) {
    memset(mpq_a->buf, 0, sizeof(mpq_a->buf));

    /* free the allocated memory. */
    free(mpq_a->header);
    free(mpq_a->mpq_l);

    /* Check if file descriptor is valid. */
    if ((_close(mpq_a->fd)) == LIBMPQ_EFILE) {
        return LIBMPQ_EFILE;
    }

    return LIBMPQ_TOOLS_SUCCESS;
}

/*
 * This function returns the value for the given infotype.
 * If an error occurs something < 0 is returned.
 */
int libmpq_archive_info(mpq_archive *mpq_a, unsigned int infotype) {
    unsigned int filecount = 0;
    unsigned int fsize = 0;
    unsigned int csize = 0;
    mpq_block *mpq_b_end = mpq_a->blocktable + mpq_a->header->blocktablesize;
    mpq_block *mpq_b = NULL;

    switch (infotype) {
        case LIBMPQ_MPQ_ARCHIVE_SIZE:
            return mpq_a->header->archivesize;
        case LIBMPQ_MPQ_HASHTABLE_SIZE:
            return mpq_a->header->hashtablesize;
        case LIBMPQ_MPQ_BLOCKTABLE_SIZE:
            return mpq_a->header->blocktablesize;
        case LIBMPQ_MPQ_BLOCKSIZE:
            return mpq_a->blocksize;
        case LIBMPQ_MPQ_NUMFILES:
            for (mpq_b = mpq_a->blocktable; mpq_b < mpq_b_end; mpq_b++) {
                filecount++;
            }
            return filecount;
        case LIBMPQ_MPQ_COMPRESSED_SIZE:
            for (mpq_b = mpq_a->blocktable; mpq_b < mpq_b_end; mpq_b++) {
                csize += mpq_b->csize;
            }
            return csize;
        case LIBMPQ_MPQ_UNCOMPRESSED_SIZE:
            for (mpq_b = mpq_a->blocktable; mpq_b < mpq_b_end; mpq_b++) {
                fsize += mpq_b->fsize;
            }
            return fsize;
        default:
            return LIBMPQ_TOOLS_SUCCESS;
    }
}

/*
 * This function returns some useful file information.
 */
int libmpq_file_info(mpq_archive *mpq_a, unsigned int infotype, const unsigned int number) {
    int blockindex = number; //-1;
    int i = 0;
    mpq_block *mpq_b = NULL;
    mpq_hash *mpq_h = NULL;

    /* check if given number is not out of range */
    if (number < 1 || number > mpq_a->header->blocktablesize) {
        return LIBMPQ_EINV_RANGE;
    }

    /* search for correct hashtable */
    /*for (i = 0; i < mpq_a->header->hashtablesize; i++) {
        if ((number - 1) == (mpq_a->hashtable[i]).blockindex) {
            blockindex = (mpq_a->hashtable[i]).blockindex;
            mpq_h = &(mpq_a->hashtable[i]);
            break;
        }
    }*/

    /* check if file was found */
    /*if (blockindex == -1 || blockindex > mpq_a->header->blocktablesize) {
        return LIBMPQ_EFILE_NOT_FOUND;
    }*/

    /* check if sizes are correct */
    mpq_b = mpq_a->blocktable + blockindex;
    if (mpq_b->filepos > (mpq_a->header->archivesize + mpq_a->mpqpos) || mpq_b->csize > mpq_a->header->archivesize) {
        return LIBMPQ_EFILE_CORRUPT;
    }

    /* check if file exists */
    if ((mpq_b->flags & LIBMPQ_FILE_EXISTS) == 0) {
        return LIBMPQ_EFILE_NOT_FOUND;
    }

    switch (infotype) {
        case LIBMPQ_FILE_COMPRESSED_SIZE:
            return mpq_b->csize;
        case LIBMPQ_FILE_UNCOMPRESSED_SIZE:
            return mpq_b->fsize;
        case LIBMPQ_FILE_COMPRESSION_TYPE:
            if (mpq_b->flags & LIBMPQ_FILE_COMPRESS_PKWARE) {
                return LIBMPQ_FILE_COMPRESS_PKWARE;
            }
            if (mpq_b->flags & LIBMPQ_FILE_COMPRESS_MULTI) {
                return LIBMPQ_FILE_COMPRESS_MULTI;
            }
        default:
            return LIBMPQ_TOOLS_SUCCESS;
    }
}

/*
 * This function searches the listfile for the filename.
 * On success it returns the filename, otherwiese a name
 * like file000001.xxx and if number is out of range it
 * returns NULL.
 */
char *libmpq_file_name(mpq_archive *mpq_a, const int number) {
    static char tempfile[PATH_MAX];

    /* check if we are in the range of available files. */
    if (number > libmpq_archive_info(mpq_a, LIBMPQ_MPQ_NUMFILES) || number < 1) {
        return NULL;
    }

    /* this is safe because we built a fallback filelist, if something was wrong. */
    sprintf(tempfile, (char *)mpq_a->mpq_l->mpq_files[number - 1], number);

    return tempfile;
}

/*
 *  This function returns the number to the given
 *  filename.
 */
int libmpq_file_number(mpq_archive *mpq_a, const char *name) {
    int i;
    char tempfile[PATH_MAX];

    for (i = 0; mpq_a->mpq_l->mpq_files[i]; i++) {
        sprintf(tempfile, (char *)mpq_a->mpq_l->mpq_files[i], i + 1);
        if (strncmp(tempfile, name, strlen(name)) == 0) {

            /* if file found return the number */
            return i + 1;
        }
    }

    /* if no matching entry found return LIBMPQ_EFILE_NOT_FOUND */
    return LIBMPQ_EFILE_NOT_FOUND;
}

/*
 *  This function verifies if a given file (by number
 *  or name) is in the opened mpq archive. On success
 *  it returns 0, otherwise LIBMPQ_EFILE_NOT_FOUND.
 */
int libmpq_file_check(mpq_archive *mpq_a, void *file, int type) {
    int found = 0;
    int i;
    char tempfile[PATH_MAX];

    switch (type) {
        case LIBMPQ_FILE_TYPE_INT:

            /* check if we are in the range of available files. */
            if (*(int *)file > libmpq_archive_info(mpq_a, LIBMPQ_MPQ_NUMFILES) || *(int *)file < 1) {
                return LIBMPQ_EFILE_NOT_FOUND;
            } else {
                return LIBMPQ_TOOLS_SUCCESS;
            }
        case LIBMPQ_FILE_TYPE_CHAR:
            for (i = 0; mpq_a->mpq_l->mpq_files[i]; i++) {
                sprintf(tempfile, (char *)mpq_a->mpq_l->mpq_files[i], i);
                if (strncmp(tempfile, (char *)file, strlen((char *)file)) == 0) {

                    /* if file found break */
                    found = 1;
                    break;
                }
            }

            /* if a file was found return 0 */
            if (found == 1) {
                return LIBMPQ_TOOLS_SUCCESS;
            } else {
                return LIBMPQ_EFILE_NOT_FOUND;
            }
        default:
            return LIBMPQ_TOOLS_SUCCESS;
    }
}

/*
 *  This function extracts a file from a MPQ archive
 *  by the given number.
 */
int libmpq_file_extract(mpq_archive *mpq_a, const int number, const char *filename) {
    int blockindex = number; //-1;
    int fd = 0;
    int i = 0;
    char buffer[0x1000];
    //char tempfile[PATH_MAX];
    unsigned int transferred = 1;
    mpq_file *mpq_f = NULL;
    mpq_block *mpq_b = NULL;
    mpq_hash *mpq_h = NULL;

/*  if (number < 1 || number > mpq_a->header->blocktablesize) {
        return LIBMPQ_EINV_RANGE;
    }*/
/*
    sprintf(tempfile, libmpq_file_name(mpq_a, number));
*/
    /* check if mpq_f->filename could be written here. */
    fd = _open(filename, O_RDWR|O_CREAT|O_TRUNC, 0644);
    if (fd == LIBMPQ_EFILE) {
        return LIBMPQ_EFILE;
    }

    /* search for correct hashtable */
    /*for (i = 0; i < mpq_a->header->hashtablesize; i++) {
        if ((number - 1) == (mpq_a->hashtable[i]).blockindex) {
            blockindex = (mpq_a->hashtable[i]).blockindex;
            mpq_h = &(mpq_a->hashtable[i]);
            break;
        }
    }*/

    /* check if file was found */
    if (blockindex == -1 || blockindex > mpq_a->header->blocktablesize) {
        return LIBMPQ_EFILE_NOT_FOUND;
    }

    /* check if sizes are correct */
    mpq_b = mpq_a->blocktable + blockindex;
    if (mpq_b->filepos > (mpq_a->header->archivesize + mpq_a->mpqpos) || mpq_b->csize > mpq_a->header->archivesize) {
        return LIBMPQ_EFILE_CORRUPT;
    }

    /* check if file exists */
    if ((mpq_b->flags & LIBMPQ_FILE_EXISTS) == 0) {
        return LIBMPQ_EFILE_NOT_FOUND;
    }

    /* allocate memory for file structure */
    mpq_f = (mpq_file *)malloc(sizeof(mpq_file));
    if (!mpq_f) {
        return LIBMPQ_EALLOCMEM;
    }

    /* initialize file structure */
    memset(mpq_f, 0, sizeof(mpq_file));
    mpq_f->fd             = fd;
    mpq_f->mpq_b          = mpq_b;
    mpq_f->nblocks        = (mpq_f->mpq_b->fsize + mpq_a->blocksize - 1) / mpq_a->blocksize;
    mpq_f->mpq_h          = mpq_h;
    mpq_f->accessed       = FALSE;
    mpq_f->blockposloaded = FALSE;
    sprintf((char *)mpq_f->filename, filename);

    /* allocate buffers for decompression. */
    if (mpq_f->mpq_b->flags & LIBMPQ_FILE_COMPRESSED) {

        /*
         *  Allocate buffer for block positions. At the begin of file are stored
         *  unsigned ints holding positions of each block relative from begin of
         *  file in the archive.
         */
        if ((mpq_f->blockpos = (unsigned int *)malloc(sizeof(int) * mpq_f->nblocks + 1)) == NULL) {
            return LIBMPQ_EALLOCMEM;
        }
    }

    while (transferred > 0) {
        transferred = libmpq_file_read_file(mpq_a, mpq_f, mpq_f->filepos, buffer, sizeof(buffer));
        if (transferred == 0) {
            break;
        } else {
            mpq_f->accessed  = TRUE;
            mpq_f->filepos  += transferred;
        }

        transferred = _write(mpq_f->fd, buffer, transferred);
        if (transferred == 0) {
            break;
        }
    }

    _close(fd);

    /* freeing the file structure */
    free(mpq_f);
    return LIBMPQ_TOOLS_SUCCESS;
}

/*
 *  This function tries to get the filenames for the hashes. It uses
 *  an internal listfile database and gets the correct listfile from
 *  some specific archive informations.
 */

int libmpq_listfile_open(mpq_archive *mpq_a, char file[PATH_MAX]) {
    FILE *fp;
    //char **filelist;
    int i = 0;
    //int fl_count;
    //int fl_size;
    int fl_count_fb;
    int fl_size_fb;
    int result = LIBMPQ_TOOLS_SUCCESS;
    struct stat statbuf;

    /* get file status */
    if (stat(file, &statbuf) < 0) {
        result = LIBMPQ_CONF_EFILE_NOT_FOUND;
    }

    /* check if file is a filename or directory */
    /*if (S_ISDIR(statbuf.st_mode)) {

        // allocate memory for the file list
        filelist = (char **)malloc(LIBMPQ_CONF_FL_INCREMENT * sizeof(char *));
        fl_count = 0;
        fl_size = LIBMPQ_CONF_FL_INCREMENT;

        // check if it is a valid listfile
        if (libmpq_detect_listfile_rec(file, &filelist, &fl_count, &fl_size)) {
            filelist == NULL;
        }

        filelist[fl_count] = NULL;

        // return if no listfile was found
        if (filelist == NULL) {
            result = LIBMPQ_CONF_EFILE_NOT_FOUND;
        }

        for (i = 0; filelist[i]; i++) {
            if ((fp = fopen(filelist[i], "r")) != NULL ) {
                result = libmpq_read_listfile(mpq_a, fp);
                fclose(fp);
            }
        }

        // freeing the listfile struct
        libmpq_free_listfile(filelist);
    }*/

    /* if file is a regular file use it */
    //if (S_ISREG(statbuf.st_mode)) {

        /* if specific listfile was forced. */
        if ((fp = fopen(file, "r")) != NULL ) {
            result = libmpq_read_listfile(mpq_a, fp);
            fclose(fp);
        } else {
            result = LIBMPQ_CONF_EFILE_OPEN;
        }
    //}

    /* if error occured we need to create a fallback filelist. */
    if (mpq_a->mpq_l->mpq_files == NULL) {

        /* allocate memory for the file list */
        mpq_a->mpq_l->mpq_files = (unsigned char **)malloc(LIBMPQ_CONF_FL_INCREMENT * sizeof(char *));
        fl_count_fb = 0;
        fl_size_fb = LIBMPQ_CONF_FL_INCREMENT;

        for (i = 0; i < libmpq_archive_info(mpq_a, LIBMPQ_MPQ_NUMFILES); i++) {

            /* set the next filelist entry to a copy of the file */
            mpq_a->mpq_l->mpq_files[fl_count_fb++] = (unsigned char *)_strdup("file%06lu.xxx");

            /* increase the array size */
            if (fl_count_fb == fl_size_fb) {
                mpq_a->mpq_l->mpq_files = (unsigned char **)realloc(mpq_a->mpq_l->mpq_files, (fl_size_fb + LIBMPQ_CONF_FL_INCREMENT) * sizeof(char *));
                fl_size_fb += LIBMPQ_CONF_FL_INCREMENT;
            }
        }
        mpq_a->mpq_l->mpq_files[fl_count_fb] = NULL;

        /* if no error occurs and no listfile was assigned, we think there was no matching listfile. */
        if (result == 0) {
            result = LIBMPQ_CONF_EFILE_NOT_FOUND;
        }
    }

    return result;
}

/*
 *  This function frees the allocated memory for the listfile.
 */
int libmpq_listfile_close(mpq_archive *mpq_a) {
    int i = 0;

    /* safety check if we really have a filelist. */
    if (mpq_a->mpq_l->mpq_files != NULL) {
        /* freeing the filelist */
        while (mpq_a->mpq_l->mpq_files[i]) {
            free(mpq_a->mpq_l->mpq_files[i++]);
        }
        free(mpq_a->mpq_l->mpq_files);
    }
    return 0;
}

int libmpq_file_getdata(mpq_archive *mpq_a, mpq_hash mpq_h, const int number, unsigned char *dest) {
    int blockindex = number; //-1;
    int i = 0;
    mpq_file *mpq_f = NULL;
    mpq_block *mpq_b = NULL;
    int success = 0;

    /*if (number < 1 || number > mpq_a->header->blocktablesize) {
        return LIBMPQ_EINV_RANGE;
    }*/

    /* search for correct hashtable */
    /*for (i = 0; i < mpq_a->header->hashtablesize; i++) {
        if ((number - 1) == (mpq_a->hashtable[i]).blockindex) {
            blockindex = (mpq_a->hashtable[i]).blockindex;
            mpq_h = &(mpq_a->hashtable[i]);
            break;
        }
    }*/

    /* check if file was found */
    if (blockindex == -1 || blockindex > mpq_a->header->blocktablesize) {
        return LIBMPQ_EFILE_NOT_FOUND;
    }

    /* check if sizes are correct */
    mpq_b = mpq_a->blocktable + blockindex;
    if (mpq_b->filepos > (mpq_a->header->archivesize + mpq_a->mpqpos) || mpq_b->csize > mpq_a->header->archivesize) {
        return LIBMPQ_EFILE_CORRUPT;
    }

    /* check if file exists */
    if ((mpq_b->flags & LIBMPQ_FILE_EXISTS) == 0) {
        return LIBMPQ_EFILE_NOT_FOUND;
    }

    /* allocate memory for file structure */
    mpq_f = (mpq_file*)malloc(sizeof(mpq_file));
    if (!mpq_f) {
        return LIBMPQ_EALLOCMEM;
    }

    /* initialize file structure */
    memset(mpq_f, 0, sizeof(mpq_file));
    mpq_f->mpq_b          = mpq_b;
    mpq_f->nblocks        = (mpq_f->mpq_b->fsize + mpq_a->blocksize - 1) / mpq_a->blocksize;
    mpq_f->mpq_h          = &mpq_h;
    mpq_f->accessed       = FALSE;
    mpq_f->blockposloaded = FALSE;

    /* allocate buffers for decompression. */
    if (mpq_f->mpq_b->flags & LIBMPQ_FILE_COMPRESSED) {

        /*
         *  Allocate buffer for block positions. At the begin of file are stored
         *  unsigned ints holding positions of each block relative from begin of
         *  file in the archive.
         */
        if ((mpq_f->blockpos = (unsigned int*)malloc(sizeof(int) * (mpq_f->nblocks + 1))) == NULL) {
            return LIBMPQ_EALLOCMEM;
        }
    }

    if(libmpq_file_read_file(mpq_a, mpq_f, 0, (char*)dest, mpq_b->fsize) == mpq_b->fsize)
        success = 1;

    if (mpq_f->mpq_b->flags & LIBMPQ_FILE_COMPRESSED) {
        // Free buffer for block positions

           free(mpq_f->blockpos);
    }
    /* freeing the file structure */
    free(mpq_f);
    return success?LIBMPQ_TOOLS_SUCCESS:LIBMPQ_EFILE_CORRUPT;
}

