/*
    Copyright 2007,2008 Luigi Auriemma

    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

    http://www.gnu.org/licenses/gpl.txt
*/

#define _LARGE_FILES        // if it's not supported the tool will work
#define __USE_LARGEFILE64   // without support for large files
#define __USE_FILE_OFFSET64
#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS   64

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <zlib.h>

#ifdef WIN32
    #include <windows.h>
    char *get_file(void);
    char *put_file(void);
#else
#endif

typedef uint8_t     u8;
typedef uint16_t    u16;
typedef uint32_t    u32;
typedef uint64_t    u64;



#define VER         "0.1.2"
#define BBIS_SIGN   0x73696262
#define BLHR_SIGN   0x72686c62
#define BSDR_SIGN   0x72647362

#define PRINTF64(x) (u32)(x >> 32), (u32)(x)    // I64x, llx? blah
#if defined(__MINGW32__) && defined(_LARGE_FILES)
    #define off_t   off64_t
    #define fopen   fopen64
    #define fseek   fseeko64
    #define ftell   ftello64
#endif

#define __OSX__
#if defined(__OSX__)
    #define off_t   off64_t
    #define fseek   fseeko
    #define ftell   ftello
#endif


#pragma pack(2)

typedef struct {
    u32     sign;       // blhr
    u32     size;       // size of the data plus ver and num
    u32     ver;        // ignored
    u32     num;        // number of blhr_data structures
} blhr_t;

typedef struct {
    u64     offset;     // input offset
    u32     zsize;      // block size
    u32     sector;     // where to place the output
    u32     size;       // size in sectors!
    u32     type;       // type
} blhr_data_t;

typedef struct {
    u32     sign;       // bbis
    u32     unknown1;   // ignored, probably the size of the structure
    u16     ver;        // version, 1
    u32     image_type; // 8
    u16     padding;    // ignored
    u32     sectors;    // number of sectors of the ISO
    u32     sectorsz;   // CD use sectors and this is the size of them (chunks)
    u32     unknown2;   // almost ignored
    u64     blhr;       // where is located the blhr header
    u32     blhrbbissz; // total size of the blhr and bbis headers
    u8      hash[16];   // hash? what algo?
    u32     unknown3;   // ignored
    u32     unknown4;   // ignored
} bbis_t;

#pragma pack()



u8 *show_hash(u8 *hash);
void myalloc(u8 **data, unsigned wantsize, unsigned *currsize);
void myfr(FILE *fd, void *data, unsigned size);
void myfw(FILE *fd, void *data, unsigned size);
int unzip(z_stream *z, u8 *in, int insz, u8 *out, int outsz);
void b2l_blhr(blhr_t *p);
void b2l_blhr_data(blhr_data_t *p);
void b2l_bbis(bbis_t *p);
void b2l_16(u16 *num);
void b2l_32(u32 *num);
void b2l_64(u64 *num);
void std_err(void);
void myexit(void);



int     endian;



int main(int argc, char *argv[]) {
    z_stream    z;
    blhr_data_t *blhr_data;
    blhr_t      blhr;
    bbis_t      bbis;
    FILE    *fdi,
            *fdo;
    u64     tot,
            file_size;
	int     i;
	unsigned insz,
		 	 outsz;
    u8      *in,
            *out;
	char ans[8],
		 *filei,
		 *fileo;

    setbuf(stdout, NULL);

    fputs("\n"
        "UIF2ISO "VER"\n"
        "by Luigi Auriemma\n"
        "e-mail: aluigi@autistici.org\n"
        "web:    aluigi.org\n"
        "\n", stdout);

    endian = 1;
    if(*(char *)&endian) endian = 0;

#ifdef WIN32
    if(GetWindowLong(GetForegroundWindow(), GWL_WNDPROC)) {
        argv = malloc(sizeof(char *) * 3);
        argv[1] = get_file();
        argv[2] = put_file();
        argc = 3;
    }
#endif

    if(argc < 3) {
        printf("\n"
            "Usage: %s <input.UIF> <output.ISO>\n"
            "\n", argv[0]);
        myexit();
    }

    filei = argv[1];
    fileo = argv[2];

    printf("- open %s\n", filei);
    fdi = fopen(filei, "rb");
    if(!fdi) std_err();

    printf("- create %s\n", fileo);
    fdo = fopen(fileo, "rb");
    if(fdo) {
        fclose(fdo);
        printf("- the output file already exists, do you want to overwrite it (y/N)? ");
        if(!fgets(ans, sizeof(ans), stdin)) myexit();
        if((ans[0] != 'y') && (ans[0] != 'Y')) myexit();
    }
    fdo = fopen(fileo, "wb");
    if(!fdo) std_err();

    z.zalloc = (alloc_func)0;
    z.zfree  = (free_func)0;
    z.opaque = (voidpf)0;
    if(inflateInit2(&z, 15)) {
        printf("\nError: zlib initialization error\n");
        myexit();
    }

    fseek(fdi, 0, SEEK_END);
    file_size = ftell(fdi);
    if(fseek(fdi, file_size - sizeof(bbis), SEEK_SET)) {
        if(((file_size - sizeof(bbis)) > 0x7fffffff) && ((file_size - sizeof(bbis)) < file_size)) printf("  an error here means that your exe has no full LARGE_FILES 64 bit support!\n");
        std_err();
    }
    myfr(fdi, &bbis, sizeof(bbis));
    b2l_bbis(&bbis);
    if(bbis.sign != BBIS_SIGN) {
        printf("\nError: wrong bbis signature (%08x)\n", bbis.sign);
        myexit();
    }

    printf("\n"
        "  file size    %08x%08x\n"
        "  version      %hu\n"
        "  image type   %u\n"
        "  padding      %hu\n"
        "  sectors      %u\n"
        "  sectors size %u\n"
        "  blhr offset  %08x%08x\n"
        "  blhr size    %u\n"
        "  hash         %s\n",
        PRINTF64(file_size),
        bbis.ver,
        bbis.image_type,
        bbis.padding,
        bbis.sectors,
        bbis.sectorsz,
        PRINTF64(bbis.blhr),
        bbis.blhrbbissz,
        show_hash(bbis.hash));
        //"  unknowns     %08x %08x %08x %08x\n",
        //bbis.unknown1, bbis.unknown2, bbis.unknown3, bbis.unknown4);

    if(fseek(fdi, bbis.blhr, SEEK_SET)) std_err();
    myfr(fdi, &blhr, sizeof(blhr));
    b2l_blhr(&blhr);
    if(blhr.sign != BLHR_SIGN) {
        if(blhr.sign == BSDR_SIGN) {
            printf("\nError: the input UIF is password protected (not yet supported)\n");
        } else {
            printf("\nError: wrong blhr signature (%08x)\n", blhr.sign);
        }
        myexit();
    }

    in   = out   = NULL;
    insz = outsz = 0;
    tot  = 0;

    myalloc(&in, blhr.size - 8, &insz);
    myfr(fdi, in, insz);

    blhr_data = (void *)malloc(sizeof(blhr_data_t) * blhr.num);
    if(!blhr_data) std_err();

    unzip(&z, in, insz, (void *)blhr_data, sizeof(blhr_data_t) * blhr.num);

    printf("- start unpacking:\n");
    for(i = 0; i < blhr.num; i++) {
        b2l_blhr_data(&blhr_data[i]);

        printf("  %03d%%\r", (i * 100) / blhr.num);

        #ifdef VERBOSE
        printf("\n"
            "offset        %08x%08x\n"
            "input size    %08x\n"
            "output sector %08x\n"
            "sectors       %08x\n",
            PRINTF64(blhr_data[i].offset),
            blhr_data[i].zsize,
            blhr_data[i].sector,
            blhr_data[i].size);
        #endif

        myalloc(&in, blhr_data[i].zsize, &insz);

        if(blhr_data[i].zsize) {
            if(fseek(fdi, blhr_data[i].offset, SEEK_SET)) std_err();
            myfr(fdi, in, blhr_data[i].zsize);
        }

        blhr_data[i].size *= bbis.sectorsz;
        myalloc(&out, blhr_data[i].size, &outsz);

        switch(blhr_data[i].type) {
            case 1: {   // non compressed
                if(blhr_data[i].zsize > blhr_data[i].size) {
                    printf("\nError: input size is bigger than output\n");
                    myexit();
                }
                memcpy(out, in, blhr_data[i].zsize);
                memset(out + blhr_data[i].zsize, 0, blhr_data[i].size - blhr_data[i].zsize); // needed?
                break;
            }
            case 3: {   // multi byte
                memset(out, 0, blhr_data[i].size);
                break;
            }
            case 5: {   // compressed
                unzip(&z, in, blhr_data[i].zsize, out, blhr_data[i].size);
                break;
            }
            default: {
                printf("\nError: unknown type (%d)\n", blhr_data[i].type);
                myexit();
            }
        }

        if(fseek(fdo, blhr_data[i].sector * bbis.sectorsz, SEEK_SET)) std_err();
        myfw(fdo, out, blhr_data[i].size);
        tot += blhr_data[i].size;
    }

    printf("  100%%\n"
        "- 0x%08x%08x bytes written\n",
        PRINTF64(tot));

    inflateEnd(&z);
    fclose(fdi);
    fclose(fdo);
    myexit();
    return(0);
}



#ifdef WIN32
char *get_file(void) {
    OPENFILENAME    ofn;
    static char     filename[1024];
    static const char   filter[] =
                    "UIF file\0"    "*.uif\0"
                    "(*.*)\0"       "*.*\0"
                    "\0"            "\0";

    filename[0] = 0;
    memset(&ofn, 0, sizeof(ofn));
    ofn.lStructSize     = sizeof(ofn);
    ofn.lpstrFilter     = filter;
    ofn.nFilterIndex    = 1;
    ofn.lpstrFile       = filename;
    ofn.nMaxFile        = sizeof(filename);
    ofn.lpstrTitle      = "Select the input UIF file to convert";
    ofn.Flags           = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_LONGNAMES | OFN_EXPLORER | OFN_HIDEREADONLY;

    if(!GetOpenFileName(&ofn)) exit(1);
    return(filename);
}

char *put_file(void) {
    OPENFILENAME    ofn;
    static char     filename[1024];
    static const char   filter[] =
                    "ISO file\0"    "*.iso\0"
                    "(*.*)\0"       "*.*\0"
                    "\0"            "\0";

    filename[0] = 0;
    memset(&ofn, 0, sizeof(ofn));
    ofn.lStructSize     = sizeof(ofn);
    ofn.lpstrFilter     = filter;
    ofn.nFilterIndex    = 1;
    ofn.lpstrFile       = filename;
    ofn.nMaxFile        = sizeof(filename);
    ofn.lpstrTitle      = "Choose the name of the output ISO file to create";
    ofn.Flags           = OFN_PATHMUSTEXIST | OFN_LONGNAMES | OFN_EXPLORER | OFN_HIDEREADONLY;

    if(!GetSaveFileName(&ofn)) exit(1);
    return(filename);
}
#endif



u8 *show_hash(u8 *hash) {
    int     i;
    static u8   vis[33];
    static const char hex[16] = "0123456789abcdef";
    u8      *p;

    p = vis;
    for(i = 0; i < 16; i++) {
        *p++ = hex[hash[i] >> 4];
        *p++ = hex[hash[i] & 15];
    }
    *p = 0;

    return(vis);
}



void myalloc(u8 **data, unsigned wantsize, unsigned *currsize) {
    if(wantsize <= *currsize) return;
    *data = realloc(*data, wantsize);
    if(!*data) std_err();
    *currsize = wantsize;
}



void myfr(FILE *fd, void *data, unsigned size) {
    if(fread(data, 1, size, fd) == size) return;
    printf("\nError: incomplete input file, can't read %u bytes\n", size);
    myexit();
}



void myfw(FILE *fd, void *data, unsigned size) {
    if(fwrite(data, 1, size, fd) == size) return;
    printf("\nError: problems during the writing of the output file\n");
    myexit();
}



int unzip(z_stream *z, u8 *in, int insz, u8 *out, int outsz) {
    inflateReset(z);

    z->next_in   = in;
    z->avail_in  = insz;
    z->next_out  = out;
    z->avail_out = outsz;
    if(inflate(z, Z_SYNC_FLUSH) != Z_STREAM_END) {
        printf("\nError: the compressed input is wrong or incomplete\n");
        myexit();
    }
    return(z->total_out);
}



void b2l_blhr(blhr_t *p) {
    if(!endian) return;
    b2l_32(&p->sign);
    b2l_32(&p->size);
    b2l_32(&p->ver);
    b2l_32(&p->num);
}



void b2l_blhr_data(blhr_data_t *p) {
    if(!endian) return;
    b2l_64(&p->offset);
    b2l_32(&p->zsize);
    b2l_32(&p->sector);
    b2l_32(&p->size);
    b2l_32(&p->type);
}



void b2l_bbis(bbis_t *p) {
    if(!endian) return;
    b2l_32(&p->sign);
    b2l_32(&p->unknown1);
    b2l_16(&p->ver);
    b2l_32(&p->image_type);
    b2l_16(&p->padding);
    b2l_32(&p->sectors);
    b2l_32(&p->sectorsz);
    b2l_32(&p->unknown2);
    b2l_64(&p->blhr);
    b2l_32(&p->blhrbbissz);
    b2l_32(&p->unknown3);
    b2l_32(&p->unknown4);
}



void b2l_16(u16 *num) {
    u16     tmp;

    if(!endian) return;

    tmp = *num;
    *num = ((tmp & 0xff00) >> 8) |
           ((tmp & 0x00ff) << 8);
}



void b2l_32(u32 *num) {
    u32     tmp;

    if(!endian) return;

    tmp = *num;
    *num = ((tmp & 0xff000000) >> 24) |
           ((tmp & 0x00ff0000) >>  8) |
           ((tmp & 0x0000ff00) <<  8) |
           ((tmp & 0x000000ff) << 24);
}



void b2l_64(u64 *num) {
    u64     tmp;

    if(!endian) return;

    tmp = *num;
    *num = ((u64)(tmp & (u64)0xff00000000000000ULL) >> 56) |
           ((u64)(tmp & (u64)0x00ff000000000000ULL) >> 40) |
           ((u64)(tmp & (u64)0x0000ff0000000000ULL) >> 24) |
           ((u64)(tmp & (u64)0x000000ff00000000ULL) >>  8) |
           ((u64)(tmp & (u64)0x00000000ff000000ULL) <<  8) |
           ((u64)(tmp & (u64)0x0000000000ff0000ULL) << 24) |
           ((u64)(tmp & (u64)0x000000000000ff00ULL) << 40) |
           ((u64)(tmp & (u64)0x00000000000000ffULL) << 56);
}



void std_err(void) {
    perror("\nError");
    myexit();
}



void myexit(void) {
#ifdef WIN32
    if(GetWindowLong(GetForegroundWindow(), GWL_WNDPROC)) {
        printf("\nPress RETURN to quit");
        while(fgetc(stdin) != '\n');
    }
#endif
    exit(1);
}


