/**
 * TrueReality - file_loader.c
 * Copyright (C) 1998, 1999 Niki W. Waibel
 *
 * This program is free software; you can redistribute it and/
 * or modify it under the terms of the GNU General Public Li-
 * cence as published by the Free Software Foundation; either
 * version 2 of the Licence, or any later version.
 *
 * This program is distributed in the hope that it will be use-
 * ful, but WITHOUT ANY WARRANTY; without even the implied war-
 * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public Licence for more details.
 *
 * You should have received a copy of the GNU General Public
 * Licence along with this program; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 * USA.
 *
 * Information about me (the author):
 *   Niki W. Waibel, Reichenau 20, 6890 Lustenau, Austria - EUROPE
 *   niki.waibel@gmx.net
**/





#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <config.h>
#include <memory_extern.h>
#include <file_loader_extern.h>
#include <parser_extern.h>

#include "endian.h"     /* needs the #defines */
#include "file_loader.h"



#ifdef WITH_ZLIB
        #include <zlib.h>
#endif



#ifdef DEBUG
        #include <debug.h>
#endif





struct  tr_rominfo      rominfo;
struct  tr_rom          rom;





static  void    read_symbol_table();
static  int     read_n64_image();
static  int     read_n64_dmem_imem();
static  void    change_rom_endian(int rom_endian);

static  int     copy_bootcode_of_n64_image();
static  int     copy_n64_dmem_imem();
static  void    change_endian(int file_endian, char *addr, int length);





static  char    *manufacturerid[] = 
{
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "Nintendo", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
};

static  char    *countrycode[] = 
{
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "Germany", "USA", "?", "?", "?", "?", "Japan", "?", "?", "?", "?", "?",
        "Europe", "?", "?", "?", "?", "Australia", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
        "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?", "?",
};










int load_files()
{
        if(prefs.symbols == USE_SYMBOLS)
                read_symbol_table();   /* switches prefs.symbols to NO_SYMBOLS if fail */

        switch(prefs.load_what)
        {
            case N64_IMAGE:
                return(read_n64_image());

            case N64_DMEM:
            case N64_IMEM:
            case N64_DMEM | N64_IMEM:
                return(read_n64_dmem_imem());

        } /* switch(prefs.load_what) */

        return(0);   /* To make the compiler happy :-) */

} /* int load_files() */





static void read_symbol_table()
{
        FILE    *tab;
        int     e;   /* error var */
        int     i;
        int     n;   /* counts the number of symbols */
        char    name[256];
        int     c;   /* for a character */



        puts("Creating symbol table ...\n"
             "        Address   <=> 'Symbol'");
        fflush(stdout);



        tab = fopen(prefs.symbol_file, "r");



        n = 0;

        for(;;)
        {
                if(n == 0)
                        prefs.symtab = (tr_stab *)malloc(sizeof(tr_stab));
                else
                        prefs.symtab = (tr_stab *)realloc(prefs.symtab, (n+1) * sizeof(tr_stab));

                e = fscanf(tab, "%lx", &(((prefs.symtab)[n]).addr));
                if(e != 1)
                        break;

                e = fscanf(tab, "%s", name);
                if(e != 1)
                        break;

                ((prefs.symtab)[n]).name = name;

            /**
            *  Get the symbolic name with ALPHA-NUM characters plus '_' & '/'
            *  Otherwise break.
            **/
                c = 's';   /* c should not be '\0' at the beginning :) */
                for(i=0; c!='\0'; i++)
                {
                        c = ((prefs.symtab)[n]).name[i];
                        if(isalnum(c) || c == '_' || c == '/')
                        {
                                e = 0;   /* no error */
                                break;
                        }
                        else
                        {
                                e = 1;   /* error */
                                (((prefs.symtab)[n]).name)++;
                        }
                }
                if(e == 1)
                        break;

            /**
            *  Read up to the next line to make comments possible in symbolfile
            **/
                do
                {
                        c = fgetc(tab);
                }
                while((c != EOF) && (c != '\n'));


                ((prefs.symtab)[n]).name = strdup(((prefs.symtab)[n]).name);

                printf("        $%08lx <=> '%s'\n", ((prefs.symtab)[n]).addr, ((prefs.symtab)[n]).name);
                fflush(stdout);
                
                n++;

        } /* for(;;) */



        if(n != 0)
                prefs.n_symbols = n;
        else
                prefs.symbols = NO_SYMBOLS;



        fclose(tab);

        printf("Loaded %u symbols.\n", prefs.n_symbols);
        puts("-------------------------------------------------------------------------------"); fflush(stdout);
        
} /* static void read_symbol_table() */





static int read_n64_dmem_imem()
{
        return(0);

} /* static int read_n64_dmem_imem() */





/*
 * read rom image ('rominfo.real_name' must be set) and corrects its endian if necessary
 * sets 'struct rominfo' and 'struct rom'
 */
   
static int read_n64_image()
{
        FILE    *in;
        int     returnValue;
        int     i;
        char    endian_1;
        char    endian_2;
   




#ifdef WITH_ZLIB
#       define  fopen           gzopen
#       define  fclose          gzclose
#       define  fseek           gzseek
#       define  fgetc           gzgetc
#       define  ftell           gztell
#       define  rewind          gzrewind
#       define  fread(b,n,l,f)  gzread(f,b,(l)*(n))
#endif
   

   
        in = fopen(rominfo.real_name, "rb");
        if(in == NULL)
        {
                perror("read_n64_image");
                fprintf(stderr, "could not open file '%s'\n\n", rominfo.real_name);
                return(-1);
        } /* if(in == NULL) */
   

         
        rewind(in);
#ifndef WITH_ZLIB   /* SEEK_END is not supported by ZLIB */
        fseek(in, 0, SEEK_END);
#else
        fputs("Determine length of ROM image ...", stdout); fflush(stdout);
        while(fgetc(in) != -1);
        puts(" OK"); fflush(stdout);
#endif

        rom.length = ftell(in);
/*
        if((rom.length % 4) != 0)
        {
                fclose(in);
                perror("read_n64_image");
                fprintf(stderr, "file '%s' must have a number of bytes mod 4!\n\n", rominfo.real_name);
                return(-2);
        }
*/
        rom.prgcode_length = rom.length - 0x1000;



        rom.image = (char *) malloc((size_t) rom.length);
        if(rom.image == NULL)
        {
                fclose(in);
                perror("read_n64_image");
                fprintf(stderr, "couldn't allocate mem for rom image from file '%s'\n\n", rominfo.real_name);
                return(-3);
        }


 
        rom.header   = rom.image;
        rom.bootcode = rom.image + 0x40;
        rom.prgcode  = (WORD *)(rom.image + 0x1000);



        /* check file endian */
        rewind(in);
        if(fread(&endian_1, (size_t) 1, (size_t) 1, in) != 1)
        {
                free(rom.image);
                fclose(in);
                perror("read_n64_image");
                fprintf(stderr, "couldn't read file '%s'\n\n", rominfo.real_name);
                return(-6);
        }
        if(fread(&endian_2, (size_t) 1, (size_t) 1, in) != 1)
        {
                free(rom.image);
                fclose(in);
                perror("read_n64_image");
                fprintf(stderr, "couldn't read file '%s'\n\n", rominfo.real_name);
                return(-6);
        }

        if( ! ( (endian_1 == (char)0x37 && endian_2 == (char)0x80)
              ||
              (endian_1 == (char)0x80 && endian_2 == (char)0x37) ) )
        {
                free(rom.image);
                fclose(in);
                fputs("read_n64_image:\n", stderr);
                fprintf(stderr, "file '%s' is not a valid N64 file\n\n", rominfo.real_name);
                return(-6);
        }



        rewind(in);
        if(fread(rom.image, (size_t) rom.length, (size_t) 1, in) != 1)
        {
                free(rom.image);
                fclose(in);
                perror("read_n64_image");
                fprintf(stderr, "couldn't read rom image from file '%s'\n\n", rominfo.real_name);
                return(-7);
        }



        fclose(in);
   

#ifdef WITH_ZLIB
#       undef fopen
#       undef fclose
#       undef fseek
#       undef frewind
#       undef fread
#endif
   
   
        /* check file endian */
   
        printf("ROM             : ");
   
        if(endian_1 == (char)0x37 && endian_2 == (char)0x80)
        {
#ifdef ENDIAN_IS_LITTLE
                printf("Little-endian          ... byteswapping (1234 -> 3412) ..."); fflush(stdout);
                change_rom_endian(LITTLE_ENDIAN);
                puts(" OK"); fflush(stdout);
                returnValue = 4;
#endif
#ifdef ENDIAN_IS_BIG
                printf("Little-endian          ... byteswapping (1234 -> 2143) ..."); fflush(stdout);
                change_rom_endian(LITTLE_ENDIAN);
                puts(" OK"); fflush(stdout);
                returnValue = 2;
#endif
        }
        else
        {
#ifdef ENDIAN_IS_LITTLE
                printf("Big-endian             ... byteswapping (1234 -> 4321) ..."); fflush(stdout);
                change_rom_endian(BIG_ENDIAN);
                puts(" OK"); fflush(stdout);
                returnValue = 3;
#endif
#ifdef ENDIAN_IS_BIG
                puts("Big-endian                                ... no byteswapping"); fflush(stdout);
                returnValue = 1;
#endif
        }



        /* maybe the 2 crc's should be checked ... has anyone out there a c routine? */


#ifdef ENDIAN_IS_BIG
        rominfo.validation           = *(HWORD *)(rom.header + 0x00);
        rominfo.compression          = *(BYTE *) (rom.header + 0x02);
        rominfo.unknown1             = *(BYTE *) (rom.header + 0x03);
        rominfo.clockrate            = *(WORD *) (rom.header + 0x04);
        rominfo.programcounter       = *(WORD *) (rom.header + 0x08);
        rominfo.release              = *(WORD *) (rom.header + 0x0c);
        rominfo.crc1                 = *(WORD *) (rom.header + 0x10);
        rominfo.crc2                 = *(WORD *) (rom.header + 0x14);
        rominfo.unknown2             = *(DWORD *)(rom.header + 0x18);
        memcpy(rominfo.name, rom.header + 0x20, (size_t) 20);
        rominfo.unknown3             = *(BYTE *) (rom.header + 0x34);
        rominfo.unknown4             = *(BYTE *) (rom.header + 0x35);
        rominfo.unknown5             = *(BYTE *) (rom.header + 0x36);
        rominfo.unknown6             = *(BYTE *) (rom.header + 0x37);
        rominfo.unknown7             = *(BYTE *) (rom.header + 0x38);
        rominfo.unknown8             = *(BYTE *) (rom.header + 0x39);
        rominfo.unknown9             = *(BYTE *) (rom.header + 0x3a);
        rominfo.manufacturerid       = *(BYTE *) (rom.header + 0x3b);
        rominfo.cartridgeid          = *(HWORD *)(rom.header + 0x3c);
        rominfo.countrycode          = *(BYTE *) (rom.header + 0x3e);
        rominfo.unknown10            = *(BYTE *) (rom.header + 0x3f);
#endif
#ifdef ENDIAN_IS_LITTLE
        rominfo.validation           = *(HWORD *)(rom.header + (0x00 ^ 0x02));
        rominfo.compression          = *(BYTE *) (rom.header + (0x02 ^ 0x03));
        rominfo.unknown1             = *(BYTE *) (rom.header + (0x03 ^ 0x03));
        rominfo.clockrate            = *(WORD *) (rom.header +  0x04);
        rominfo.programcounter       = *(WORD *) (rom.header +  0x08);
        rominfo.release              = *(WORD *) (rom.header +  0x0c);
        rominfo.crc1                 = *(WORD *) (rom.header +  0x10);
        rominfo.crc2                 = *(WORD *) (rom.header +  0x14);
        rominfo.unknown2             = ((DWORD)*(WORD *)(rom.header + 0x18) << 32) + *(WORD *)(rom.header +  0x1c);
        for(i=0; i<20; i++)
                rominfo.name[i^0x03] = rom.header[i+0x20];
        rominfo.unknown3             = *(BYTE *) (rom.header + (0x34 ^ 0x03));
        rominfo.unknown4             = *(BYTE *) (rom.header + (0x35 ^ 0x03));
        rominfo.unknown5             = *(BYTE *) (rom.header + (0x36 ^ 0x03));
        rominfo.unknown6             = *(BYTE *) (rom.header + (0x37 ^ 0x03));
        rominfo.unknown7             = *(BYTE *) (rom.header + (0x38 ^ 0x03));
        rominfo.unknown8             = *(BYTE *) (rom.header + (0x39 ^ 0x03));
        rominfo.unknown9             = *(BYTE *) (rom.header + (0x3a ^ 0x03));
        rominfo.manufacturerid       = *(BYTE *) (rom.header + (0x3b ^ 0x03));
        rominfo.cartridgeid          = *(HWORD *)(rom.header + (0x3c ^ 0x02));
        rominfo.countrycode          = *(BYTE *) (rom.header + (0x3e ^ 0x03));
        rominfo.unknown10            = *(BYTE *) (rom.header + (0x3f ^ 0x03));
#endif
   
        rom.prgcode_base_orig = rominfo.programcounter;
        rom.prgcode_base = rom.prgcode_base_orig & 0x1fffffff;

        printf("ROM length      : 0x%08lx (%lu) bytes\n", rom.length, rom.length);
        printf("ROM valid. hword: %04hx\n",     rominfo.validation);
        printf("ROM comp. byte  : %01x (%s)\n", rominfo.compression,
               rominfo.compression == 0x12 ? "uncompressed" : rominfo.compression == 0x13 ? "compressed" : "?");
        printf("ROM ????????????: %01x\n",      rominfo.unknown1);
        printf("ROM clock rate  : %08lx\n",     rominfo.clockrate);
        printf("ROM pc          : %08lx\n",     rominfo.programcounter);
        printf("ROM release     : %08lx\n",     rominfo.release);
        printf("ROM crc 1       : %08lx\n",     rominfo.crc1);
        printf("ROM crc 2       : %08lx\n",     rominfo.crc2);
        printf("ROM name        : \"%s\"\n",      rominfo.name);
        printf("ROM manufac. id : %02x (%s)\n", rominfo.manufacturerid, manufacturerid[rominfo.manufacturerid]);
        printf("ROM cartridgeid : %04hx\n",     rominfo.cartridgeid);
        printf("ROM country code: %02x (%s)\n", rominfo.countrycode, countrycode[rominfo.countrycode]);
        puts("-------------------------------------------------------------------------------"); fflush(stdout);


        return(returnValue);

} /* static int read_n64_image() */





/*
 * this routine is called from 'int read_n64_image()'
 * it changes the endianness of header, bootcode & prgcode in memory
 * 
 *
 */
   
static void change_rom_endian(int file_endian)
{
        int     i;
        char    change_endian[4];



        for(i=0; i<rom.length; i+=4)
        {
                change_endian[0] = rom.image[i+0];
                change_endian[1] = rom.image[i+1];
                change_endian[2] = rom.image[i+2];
                change_endian[3] = rom.image[i+3];
#ifdef ENDIAN_IS_BIG
                if(file_endian == LITTLE_ENDIAN)
                {
                        rom.image[i+0] = change_endian[1];
                        rom.image[i+1] = change_endian[0];
                        rom.image[i+2] = change_endian[3];
                        rom.image[i+3] = change_endian[2];
                }
#endif
#ifdef ENDIAN_IS_LITTLE
                if(file_endian == BIG_ENDIAN)
                {
                        rom.image[i+0] = change_endian[3];
                        rom.image[i+1] = change_endian[2];
                        rom.image[i+2] = change_endian[1];
                        rom.image[i+3] = change_endian[0];
                }
                else
                        if(file_endian == LITTLE_ENDIAN)
                        {
                                rom.image[i+0] = change_endian[2];
                                rom.image[i+1] = change_endian[3];
                                rom.image[i+2] = change_endian[0];
                                rom.image[i+3] = change_endian[1];
                        }
#endif
        } /* for(i=0; i<rom.length; i+=4) */

} /* static void change_rom_endian(int file_endian) */










int copy_code()
{
        switch(prefs.load_what)
        {
            case N64_IMAGE:
                return(copy_bootcode_of_n64_image());

            case N64_DMEM:
            case N64_IMEM:
            case N64_DMEM | N64_IMEM:
                return(copy_n64_dmem_imem());

        } /* switch(prefs.load_what) */

        return(0);   /* To make the compiler happy :-) */

} /* int copy_code() */





static int copy_bootcode_of_n64_image()
{
        int     i;



        fputs("Copying bootcode to 0xa4000000 (0x1000 bytes)                            ...", stdout); fflush(stdout);

        for(i=0; i<0x1000; i++)
        {
                mem.sp_dmem[i] = rom.image[i];
        
        } /* for(i=0; i<0x1000; i++) */

        puts(" OK\n"
             "-------------------------------------------------------------------------------"); fflush(stdout);

        return(0);

} /* static int copy_bootcode_of_n64_image() */





static int copy_n64_dmem_imem()
{
        char    *n_d, *n_i;
        FILE    *f_d, *f_i;



        fputs("Opening dmem/imem files ...", stdout); fflush(stdout);
        
        switch(prefs.load_what)
        {
            case N64_DMEM:
                n_d = (char *)malloc(strlen(rominfo.dmem_name) + 6);
                n_i = (char *)malloc(strlen(rominfo.dmem_name) + 6);
                strcpy(n_d, rominfo.dmem_name);
                strcpy(n_i, rominfo.dmem_name);
                strcat(n_d, ".dmem");
                strcat(n_i, ".imem");
                break;

            case N64_IMEM:
                n_d = (char *)malloc(strlen(rominfo.imem_name) + 6);
                n_i = (char *)malloc(strlen(rominfo.imem_name) + 6);
                strcpy(n_d, rominfo.imem_name);
                strcpy(n_i, rominfo.imem_name);
                strcat(n_d, ".dmem");
                strcat(n_i, ".imem");
                break;

            case N64_IMEM | N64_DMEM:
                n_d = (char *)malloc(strlen(rominfo.dmem_name) + 1);
                n_i = (char *)malloc(strlen(rominfo.imem_name) + 1);
                strcpy(n_d, rominfo.dmem_name);
                strcpy(n_i, rominfo.imem_name);
                break;

        } /* switch(prefs.load_what) */

        f_d = fopen(n_d, "rb");
        if(f_d == NULL)
        {
                puts(" FAIL"); fflush(stdout);
                perror("copy_n64_dmem_imem");
                fprintf(stderr, "could not open file '%s'\n\n", n_d);
                free(n_d);
                free(n_i);
                return(-1);

        } /* if(f_d == NULL) */

        f_i = fopen(n_i, "rb");
        if(f_i == NULL)
        {
                puts(" FAIL"); fflush(stdout);
                perror("copy_n64_dmem_imem");
                fprintf(stderr, "could not open file '%s'\n\n", n_i);
                free(n_d);
                free(n_i);
                fclose(f_d);
                return(-1);

        } /* if(f_i == NULL) */



        fputs(" OK\n"
              "Copying dmem/imem to 0xa4000000 (0x2000 bytes) ...", stdout); fflush(stdout);

        if(fread(mem.sp_dmem, 0x1000, 1, f_d) != 1)
        {
                if(feof(f_d) == 0)
                {
                        puts(" FAIL"); fflush(stdout);
                        perror("copy_n64_dmem_imem");
                        fprintf(stderr, "could not read file '%s'\n\n", n_d);
                        free(n_d);
                        free(n_i);
                        fclose(f_d);
                        fclose(f_i);
                        return(-1);
                }
        }

        if(fread(mem.sp_imem, 0x1000, 1, f_i) != 1)
        {
                if(feof(f_i) == 0)
                {
                        puts(" FAIL"); fflush(stdout);
                        perror("copy_n64_dmem_imem");
                        fprintf(stderr, "could not read file '%s'\n\n", n_i);
                        free(n_d);
                        free(n_i);
                        fclose(f_d);
                        fclose(f_i);
                        return(-1);
                }
        }




        fputs(" OK\n"
              "Changing endianness at 0xa4000000 (0x2000 bytes) ...", stdout); fflush(stdout);

        change_endian(BIG_ENDIAN, mem.sp_dmem, 0x1000);
        change_endian(BIG_ENDIAN, mem.sp_imem, 0x1000);

        fputs(" OK\n"
              "Closing dmem/imem files ...", stdout); fflush(stdout);

        free(n_d);
        free(n_i);
        fclose(f_d);
        fclose(f_i);



        puts(" OK\n"
             "-------------------------------------------------------------------------------"); fflush(stdout);

        return(0);

} /* static int copy_n64_dmem_imem() */





static void change_endian(int file_endian, char *addr, int length)
{
        int     i;
        char    change_endian[4];



        for(i=0; i<length; i+=4)
        {
                change_endian[0] = addr[i+0];
                change_endian[1] = addr[i+1];
                change_endian[2] = addr[i+2];
                change_endian[3] = addr[i+3];
#ifdef ENDIAN_IS_BIG
                if(file_endian == LITTLE_ENDIAN)
                {
                        addr[i+0] = change_endian[1];
                        addr[i+1] = change_endian[0];
                        addr[i+2] = change_endian[3];
                        addr[i+3] = change_endian[2];
                }
#endif
#ifdef ENDIAN_IS_LITTLE
                if(file_endian == BIG_ENDIAN)
                {
                        addr[i+0] = change_endian[3];
                        addr[i+1] = change_endian[2];
                        addr[i+2] = change_endian[1];
                        addr[i+3] = change_endian[0];
                }
                else
                        if(file_endian == LITTLE_ENDIAN)
                        {
                                addr[i+0] = change_endian[2];
                                addr[i+1] = change_endian[3];
                                addr[i+2] = change_endian[0];
                                addr[i+3] = change_endian[1];
                        }
#endif
        } /* for(i=0; i<length; i+=4) */

} /* static void change_endian(int file_endian) */










