/******************************************************************************
*
* Copyright (C) 2007-2017  Brian Berlin
* 
* 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 <http://www.gnu.org/licenses/>
*
******************************************************************************
*
* FILENAME: media.c
*
* DESCRIPTION:  This contains media handling
*
* AUTHORS:  bberlin
*
* CHANGE LOG
*
*  VER     DATE     CHANGED BY   DESCRIPTION OF CHANGE
* ------  --------  ----------   ------------------------------------
* 0.5.0   02/01/07  bberlin      Creation, break out from config
* 0.9.0   01/24/19  bberlin      Handle zip files, no db anymore
******************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <zlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include "media_if.h"
#include "sdl_if.h"
#include "logger.h"
#include "util.h"
#include "katconfig.h"
#include "md5.h"
#include "sha1.h"
#include "../minizip/unzip.h"

int media_if_open_file ( char *filename, unsigned char **buffer );

static t_media g_media = {0};

static int carts_found;
static int total_carts;

//static int scan_progress;

//extern t_cart cart;

typedef struct {
    const char *name;
    int mapping;
} t_mapping_name;

static const t_mapping_name g_mapping_names[] = {
    { "5200.32k", CART_32K },
    { "5200.16k_two_chip", CART_16K_TWO_CHIP },
    { "5200.16k_one_chip", CART_16K_ONE_CHIP },
    { "5200.16k_be", CART_16K_BE },
    { "5200.32k_be", CART_32K_BE },
    { "5200.40k", CART_40K },
    { "5200.64k", CART_64K },
    { "5200.8k", CART_8K },
    { "5200.4k", CART_4K },
    { "5200.512k_hybrid", CART_512K_HYBRID },
    { "8bit.8k", CART_PC_8K },
    { "8bit.16k", CART_PC_16K },
    { "8bit.32k", CART_PC_32K },
    { "8bit.40k", CART_PC_40K },
    { "8bit.right", CART_PC_RIGHT },
    { "8bit.xegs", CART_XEGS },
    { "8bit.swxegs", CART_SWXEGS },
    { "8bit.mega", CART_MEGA },
    { "8bit.oss", CART_OSS },
    { "8bit.oss2", CART_OSS2 },
    { "8bit.atmax", CART_ATMAX },
    { "8bit.atrax", CART_ATRAX },
    { "8bit.will", CART_WILL },
    { "8bit.sdx", CART_SDX },
    { "8bit.exp", CART_EXP },
    { "8bit.diamond", CART_DIAMOND },
    { "8bit.phoenix", CART_PHOENIX },
    { "8bit.blizzard", CART_BLIZZARD },
    { NULL, CART_MIN }
};

/* 
 * Blantantly copied from atari++
 */
const unsigned char binload_bootstrap[] = {
  0x00,  // bootflag
  0x03,  // # of sectors to boot
  0x00,  // lo boot address
  0x07,  // hi boot address
  0x99,  // lo run address
  0x07,  // hi run address ***
  // bootstrap code runs here
  0x20,0x07,0x08,                     // disk init
  0xa9,0x00,
  0x8d,0xe0,0x02,0x8d,0xe1,0x02,      // clear run vector
  0x8d,0xe2,0x02,0x8d,0xe3,0x02,      // clear init vector
  0x8d,0x7d,0x09,                     // set sector, hi
  0x8d,0x7f,0x09,0x85,0x49,           // init sector buffer to point to sector #4
  0xa9,0x04,0x8d,0x7e,0x09,           // set sector, lo
  //
  0x20,0xe8,0x07,                     // get byte
  0xc9,0xff,                          // must be 0xff for binary load
  0xd0,0x05,                          // branch on error
  0x20,0xe8,0x07,                     // get byte
  0xc9,0xff,                          // another 0xff must follow here
  0xd0,0x62,                          // branch on error to SEC:RTS
  //
  0x20,0xe8,0x07,                     // get start address, lo
  0x85,0x43,0x8d,0xe0,0x02,
  0x20,0xe8,0x07,                     // get start address, hi
  0x85,0x44,0x8d,0xe1,0x02,
  0x20,0xe8,0x07,                     // get end address, lo
  0x85,0x45,
  0x20,0xe8,0x07,                     // get end address, hi
  0x85,0x46,
  // loading loop starts here.
  0x20,0xe8,0x07,                     // get next byte
  0xa0,0x00,                          // reset Y
  0x91,0x43,                          // store result
  0xe6,0x43,                          // increment low
  0xd0,0x02,                          // carry over into high?
  0xe6,0x44,                          // if so, do it now.
  0xa5,0x45,0xc5,0x43,                // compare low-addresses
  0xa5,0x46,0xe5,0x44,                // compare high-addresses.
  0xb0,0xe9,                          // as LONG as this is greater or equal, repeat
  0xad,0xe2,0x02,0x0d,0xe3,0x02,      // check whether the init address has been set
  0xf0,0x0f,                          // if not, avoid the init method
  // run thru init vector
  0xa5,0x49,0x48,                     // keep sector offset
  0x20,0xfb,0x07,                     // jump into the init vector
  // for compatibility, wait for a vblank
  0x20,0x1f,0x08,0x20,0x1f,0x08,
  0x68,0x85,0x49,                     // restore sector offset
  // init vector done
  0x20,0xd4,0x07,                     // get next byte
  0xf0,0x0f,                          // on EOF, exit from boot process
  0x85,0x43,                          // next start, lo
  0x20,0xe8,0x07,                     // next start, hi
  0x85,0x44,
  0x25,0x43,0xc9,0xff,                // is this 0xff,0xff?
  0xf0,0xee,                          // if so, repeat
  0xd0,0xb4,                          // jump back to the loading part
  0xf0,0x09,                          // boot process end
  // here: exit on error: BOOT ERROR
  0x68,0x68,0x68,0x68,                // drop off return address
  0x38,0x60,        
  // here: run/init jumps
  0x6c,0xe2,0x02,                     // jump on init
  0x6c,0xe0,0x02,                     // jump on start
  // here: load new sector.
  0xad,0x7e,0x09,
  0x8d,0x0a,0x03,                     // set sector lo
  0xad,0x7d,0x09,                     // get sector hi (no need to mask, only one file)
  0x8d,0x0b,0x03,                     // install it
  0x0d,0x0a,0x03,0xf0,0x26,           // return with Z flag set on EOF.
  0xa9,0x31,0x8d,0x00,0x03,           // set disk drive
  0xa9,0x01,0x8d,0x01,0x03,           // set unit
  0xa9,0x52,0x8d,0x02,0x03,           // set CMD=read
  0xa9,0x00,0x8d,0x04,0x03,0x85,0x47, // set buffer address, lo
  0x85,0x49,                          // clear offset within sector
  0xa9,0x09,0x8d,0x05,0x03,0x85,0x48, // set buffer address, hi: page 9
  0x20,0x53,0xe4,                     // call SIO
  0x30,0xbf,                          // jump to boot error generation on error.
  0xa0,0x01,                          // clear Z flag
  0x60,                               //
  // here: get byte, return eq on EOF.
  0xa4,0x49,0xcc,0x7f,0x09,           // compare against the length of the sector
  0x90,0x08,                          // skip over if not end.
  0x20,0x9c,0x07,                     // reload sector
  0xd0,0x01,0x60,                     // skip return on not EOF
  0xa0,0x00,                          // reset Y
  0xb1,0x47,                          // read value
  0xe6,0x49,                          // read next value: increment, clear Z flag
  0x60,
  // here: get byte, error on EOF
  0xa4,0x49,0xcc,0x7f,0x09,           // compare against the length of the sector
  0x90,0x07,                          // skip over if not end.
  0x20,0x9c,0x07,                     // reload sector
  0xf0,0x9e,                          // branch to error on EOF.
  0xa0,0x00,                          // reset Y
  0xb1,0x47,                          // read value
  0xe6,0x49,                          // read next value: increment, clear Z flag
  0x60,
  //
  0x20,0x96,0x07,                     // call the init vector
  0xa9,0x00,                          // clear
  0x8d,0xe2,0x02,0x8d,0xe3,0x02,      // the init vector
  0x60,
  //
  0xa9,0x01,0x85,0x09,0xa9,0x00,
  0x8d,0x44,0x02,
  0xa9,0x77,0x85,0x0a,0x85,0x0c,
  0xa9,0xe4,0x85,0x0b,0x85,0x0d,
  0x4c,0x50,0xe4,                     // disk init
  // 
  // VBI waiter
  0xad,0x0b,0xd4,0xc9,0x70,0x90,0xf9,
  0xad,0x0b,0xd4,0xc9,0x20,0xb0,0xf9,
  0x60
};

static int media_if_extension_is ( const char *filename, const char *extension ) {

    const char *dot;

    dot = strrchr ( filename, '.' );
    if ( !dot )
        return 0;

    while ( *dot && *extension ) {
        if ( tolower((unsigned char)*dot) != tolower((unsigned char)*extension) )
            return 0;
        dot++;
        extension++;
    }

    return *dot == '\0' && *extension == '\0';
}

static int media_if_replace_extension ( const char *filename, const char *extension,
        char *result, size_t result_size ) {

    const char *dot;
    const char *slash;
    const char *backslash;
    const char *base;
    size_t prefix_len;
    size_t extension_len;

    dot = strrchr ( filename, '.' );
    slash = strrchr ( filename, '/' );
    backslash = strrchr ( filename, '\\' );
    if ( slash && backslash )
        base = slash > backslash ? slash : backslash;
    else if ( slash )
        base = slash;
    else
        base = backslash;
    if ( base )
        base++;
    else
        base = filename;

    if ( !dot || dot < base )
        return 0;

    prefix_len = (size_t)(dot - filename);
    extension_len = strlen ( extension );
    if ( prefix_len + extension_len + 1 > result_size )
        return 0;

    memcpy ( result, filename, prefix_len );
    strcpy ( result + prefix_len, extension );

    return 1;
}

static int media_if_file_exists ( const char *filename ) {

    struct stat file_status;

    return stat ( filename, &file_status ) == 0;
}

static int media_if_open_hybrid_pair ( char *filename, unsigned char **buffer,
        int *bytes, int selected_dat, int launch ) {

    char hyb_file[1100];
    char dat_file[1100];
    char msg[1200];
    unsigned char *hyb_buffer = 0;
    unsigned char *dat_buffer = 0;
    int hyb_bytes;
    int dat_bytes = -1;
    int status;
    unsigned long hyb_crc;
    t_config *p_config = config_get_global_cfg();

    dat_file[0] = '\0';
    memory_clear_sd ();

    if ( selected_dat ) {
        if ( !media_if_replace_extension ( filename, ".hyb", hyb_file, sizeof(hyb_file) ) )
            return -1;
        if ( !media_if_file_exists ( hyb_file ) ) {
            if ( !media_if_replace_extension ( filename, ".HYB", hyb_file, sizeof(hyb_file) ) ||
                 !media_if_file_exists ( hyb_file ) )
                return -1;
        }

        dat_buffer = *buffer;
        dat_bytes = *bytes;
        hyb_bytes = media_if_open_file ( hyb_file, &hyb_buffer );
        if ( hyb_bytes <= 0 )
            return -1;
    }
    else {
        strncpy ( hyb_file, filename, sizeof(hyb_file) - 1 );
        hyb_file[sizeof(hyb_file) - 1] = '\0';
        hyb_buffer = *buffer;
        hyb_bytes = *bytes;
    }

    if ( hyb_bytes <= 512000 || hyb_bytes >= 540000 ) {
        if ( selected_dat )
            free ( hyb_buffer );
        return -1;
    }

    if ( !selected_dat &&
         media_if_replace_extension ( filename, ".dat", dat_file, sizeof(dat_file) ) &&
         !media_if_file_exists ( dat_file ) ) {
        media_if_replace_extension ( filename, ".DAT", dat_file, sizeof(dat_file) );
    }

    if ( !selected_dat && media_if_file_exists ( dat_file ) ) {
        dat_bytes = media_if_open_file ( dat_file, &dat_buffer );
        if ( dat_bytes <= 0 ) {
            free ( dat_buffer );
            dat_buffer = 0;
            dat_bytes = -1;
        }
    }
    else if ( selected_dat ) {
        strncpy ( dat_file, filename, sizeof(dat_file) - 1 );
        dat_file[sizeof(dat_file) - 1] = '\0';
    }
    else {
        dat_file[0] = '\0';
    }

    hyb_crc = crc32 ( 0L, hyb_buffer, hyb_bytes );
    status = config_load_media_settings ( hyb_crc, &g_media );
    if ( status )
        util_alloc_string ( &g_media.title, hyb_file );
    util_alloc_string ( &g_media.file, hyb_file );
    g_media.crc = hyb_crc;
    g_media.image_size = hyb_bytes;
    g_media.mapping = CART_512K_HYBRID;
    g_media.media_type = CART_CART;
    g_media.machine_type = MACHINE_TYPE_5200;
    g_media.write_protect = 1;

    p_config->general.machine = MACHINE_TYPE_5200;
    config_save_general(config_get_global_table(), KCFG_GENERAL_MACHINE |
            KCFG_GENERAL_ROM);
    console_set_machine_type ( MACHINE_TYPE_5200 );
    console_get_ptr()->cart->loaded = 1;
    console_get_ptr()->basic_enable = 1;
    console_get_ptr()->cart->media_info.image_size = hyb_bytes;

    if ( launch )
        sio_unmount_all ( );

    if ( console_open_media ( &g_media, hyb_buffer, 0, launch ) ) {
        if ( selected_dat )
            free ( hyb_buffer );
        if ( dat_buffer && dat_buffer != *buffer )
            free ( dat_buffer );
        return -1;
    }
    console_get_ptr()->cart->loaded = 1;

    if ( dat_buffer && dat_bytes > 0 ) {
        memory_init_sd ( dat_bytes );
        memory_write_sd_bytes ( 0, dat_buffer, dat_bytes );
        if ( dat_buffer != *buffer )
            free ( dat_buffer );
    }
    else {
        sprintf ( msg, "No hybrid SD data file found for \"%s\"", hyb_file );
        logger_log_message ( LOG_INFO, msg, "" );
    }

    if ( selected_dat ) {
        free ( *buffer );
        *buffer = hyb_buffer;
        *bytes = hyb_bytes;
    }

    return 0;
}

/******************************************************************************
**  Function   :  media_if_get_cart_header_mapping
**
**  Objective  :  Translate a CART header ID into an internal mapper only when
**                it directly matches an implementation we already have.
**
**  Parameters :  cart_type - CART header ID
**
**  return     :  internal mapper or CART_MIN when no direct match exists
******************************************************************************/
static e_cart_type media_if_get_cart_header_mapping ( unsigned int cart_type ) {

    switch ( cart_type ) {
        case 1:  return CART_PC_8K;
        case 2:  return CART_PC_16K;
        case 3:  return CART_OSS;
        case 4:  return CART_32K;
        case 5:  return CART_PC_32K;
        case 6:  return CART_16K_TWO_CHIP;
        case 7:  return CART_40K;
        case 8:  return CART_WILL;
        case 9:  return CART_EXP;
        case 10: return CART_DIAMOND;
        case 11: return CART_SDX;
        case 12:
        case 13:
        case 14: return CART_XEGS;
        case 15: return CART_OSS2;
        case 16: return CART_16K_ONE_CHIP;
        case 17: return CART_ATRAX;
        case 18: return CART_PC_40K;
        case 19: return CART_8K;
        case 20: return CART_4K;
        case 21: return CART_PC_RIGHT;
        case 22: return CART_WILL;
        case 23:
        case 24:
        case 25: return CART_XEGS;
        case 26:
        case 27:
        case 28:
        case 29:
        case 30:
        case 31:
        case 32: return CART_MEGA;
        case 33:
        case 34:
        case 35:
        case 36:
        case 37:
        case 38: return CART_SWXEGS;
        case 39: return CART_PHOENIX;
        case 40: return CART_BLIZZARD;
        case 41:
        case 42: return CART_ATMAX;
        case 71:
        case 72:
        case 73:
        case 74: return CART_32K_BE;
        case 159: return CART_40K;
        default: return CART_MIN;
    }

} /* end media_if_get_cart_header_mapping */

/******************************************************************************
**  Function   :  media_if_get_ptr
**                            
**  Objective  :  This function returns the address of the media struct
**
**  Parameters :  NONE
**                                                
**  return     :  address of g_media
**      
******************************************************************************/
t_media *media_if_get_ptr ( void ) {

    return &g_media;

} /* end media_if_get_ptr */

/******************************************************************************
**  Function   :  media_if_get_profile_from_crc
**                            
**  Objective  :  This function returns the filename of profile for this crc
**
**  Parameters :  crc - checksum of filename to grab
**                                                
**  return     :  profile filename or NULL if not found
**      
******************************************************************************/
char * media_if_get_profile_from_crc_with_machine ( unsigned long crc,
        e_machine_type *origin_machine ) {

    struct stat file_status;
    int i;
    char *temp = 0, *file = 0, *profiles_dir = 0;
    char crc_file[24];

    if ( origin_machine )
        *origin_machine = MACHINE_TYPE_MIN;

    sprintf ( crc_file, "%08x.conf", crc );

    for ( i = MACHINE_TYPE_MIN+1; i <= MACHINE_TYPE_XL; ++i ) {
        profiles_dir = config_get_profiles_dir ( i );
        temp = util_make_path_alloc ( profiles_dir, crc_file );
        file = util_create_program_path_alloc( temp );

        if ( !stat ( file, &file_status ) ) {
            if ( origin_machine )
                *origin_machine = ( i == MACHINE_TYPE_5200 ) ?
                    MACHINE_TYPE_5200 : MACHINE_TYPE_XL;
            free(temp);
            free(profiles_dir);
            return file;
        }

        free(file);
        free(temp);
        free(profiles_dir);
    }

    return NULL;

} /* end media_if_get_profile_from_crc_with_machine */

char * media_if_get_profile_from_crc ( unsigned long crc ) {

    return media_if_get_profile_from_crc_with_machine ( crc, NULL );

} /* end media_if_get_profile_from_crc */

/******************************************************************************
**  Function   :  media_if_create_profile_from_crc
**                            
**  Objective  :  This function create a profile file for this crc
**
**  Parameters :  crc - checksum of file to grab
**                                                
**  return     :  0 on success, otherwise error
**      
******************************************************************************/
int media_if_create_profile_from_crc ( unsigned long crc, t_media *p_media ) {

    struct stat file_status;
    int i;
    char *temp = 0, *file = 0, *profiles_dir = 0;
    char crc_file[24];
    FILE *fp;

    sprintf ( crc_file, "%x.conf", crc );

    profiles_dir = config_get_profiles_dir ( p_media->machine_type );
    temp = util_make_path_alloc ( profiles_dir, crc_file );
    file = util_create_program_path_alloc( temp );
 
    if ((fp = fopen(file,"w")) == NULL) {
        free(file);
        free(temp);
        free(profiles_dir);
        return -1;
    }

    fputs("#\n", fp);
    fprintf(fp,"# %s\n",crc_file);
    fputs("#\n", fp);

    fclose ( fp );

    free(file);
    free(temp);
    free(profiles_dir);

    return 0;

} /* end media_if_create_profile_from_crc */

/******************************************************************************
**  Function   :  media_if_get_mapping_from_string
**                                                                    
**  Objective  :  This function returns a mapping value from config file string
**                                                   
**  Parameters :  mapping
**                 
**  return     :  type
**
******************************************************************************/ 
int media_if_get_mapping_from_string ( const char *mapping ) {

    int i;
    char tmp_mapping[KAT_PATH_MAX];

    if ( mapping == NULL )
        return CART_MIN;

    if ( strlen(mapping) >= sizeof(tmp_mapping) )
        return CART_MIN;

    util_trim_whitespace ( tmp_mapping, sizeof(tmp_mapping), mapping );
    util_strlwr ( tmp_mapping );

    if ( !strcmp(tmp_mapping, "") || !strcmp(tmp_mapping, "auto") )
        return CART_MIN;

    for ( i = 0; g_mapping_names[i].name; ++i ) {
        if ( !strcmp(tmp_mapping, g_mapping_names[i].name) )
            return g_mapping_names[i].mapping;
    }

    return CART_MIN;

} /* end media_if_get_mapping_from_string */

/******************************************************************************
**  Function   :  media_if_get_string_from_mapping
**                                                                    
**  Objective  :  This function returns a mapping value from config file string
**                                                   
**  Parameters :  mapping
**                 
**  return     :  type
**
******************************************************************************/ 
char * media_if_get_string_from_mapping ( int mapping ) {

    int i;

    for ( i = 0; g_mapping_names[i].name; ++i ) {
        if ( mapping == g_mapping_names[i].mapping )
            return (char *)g_mapping_names[i].name;
    }

    return "";

} /* end media_if_get_string_from_mapping */

/******************************************************************************
**  Function   :  media_if_get_media_type_from_string
**                                                                    
**  Objective  :  This function returns a media type value from config file 
**                string
**                                                   
**  Parameters :  media_type
**                 
**  return     :  type value
**
******************************************************************************/ 
int media_if_get_media_type_from_string ( char *media_type ) {

    if ( media_type == NULL )
        return CART_MIN;

    if ( !strcmp(media_type, "cartridge")) return CART_CART;
    if ( !strcmp(media_type, "floppy")) return CART_FLOPPY;
    if ( !strcmp(media_type, "cassette")) return CART_CASSETTE;
    if ( !strcmp(media_type, "exe")) return CART_EXE;
    if ( !strcmp(media_type, "basic")) return CART_BASIC;

    return CART_MIN;

} /* end media_if_get_media_type_from_string */

/******************************************************************************
**  Function   :  media_if_get_string_from_media_type
**                                                                    
**  Objective  :  This function returns a string from media type value 
**                                                   
**  Parameters :  media_type
**                 
**  return     :  config file string
**
******************************************************************************/ 
char * media_if_get_string_from_media_type ( int media_type ) {

    if ( media_type == CART_CART ) return "cartridge";
    if ( media_type == CART_FLOPPY ) return "floppy";
    if ( media_type == CART_CASSETTE ) return "cassette";
    if ( media_type == CART_EXE ) return "exe";
    if ( media_type == CART_BASIC ) return "basic";

    return "";

} /* end media_if_get_string_from_media_type */

/******************************************************************************
**  Function   :  media_if_create_cart_from_file
**                                                                    
**  Objective  :  This function sets up the cart struct from a file
**                                                   
**  Parameters :  file  - file to create from
**                media - media to output
**                 
**  return     :  0 if success, otherwise failure
**
******************************************************************************/ 
int media_if_create_cart_from_file ( char *file, t_media *media ) {

    unsigned char tmp_ary[21];
    unsigned char tmp_arys[21];
    char temp_title[1024];
    char *tmp_string = 0;
    t_config *p_config = config_get_global_cfg();
    struct stat file_status;
    int i;

    stat ( file, &file_status );

    if ( util_get_all_hash ( file, &media->crc, tmp_ary, tmp_arys ) )
        return -1;

    util_alloc_string ( &media->md5, "                                " );
    for ( i = 0; i < 16; ++i )
        sprintf ( media->md5+(i*2), "%02x", tmp_ary[i] );

    util_alloc_string ( &media->sha1, "                                        " );
    for ( i = 0; i < 20; ++i )
        sprintf ( media->sha1+(i*2), "%02x", tmp_arys[i] );

    util_alloc_string ( &media->file, file );
    util_set_file_from_current_dir (media->file); 
    strcpy ( temp_title, media->file );
    util_strip_to_file ( temp_title );
    util_alloc_string ( &media->title, temp_title );
    tmp_string = strrchr ( media->title, '.' );
    if ( tmp_string && (tmp_string > (media->title + strlen(media->title)-5)) ) 
        *tmp_string = '\0';

    media->mapping = CART_MIN;
    media->media_type = CART_MIN;
    media->image_size = file_status.st_size;
    media->collections = 0;
    media->write_protect = 1;
    media->machine_type = p_config->general.machine;

    return 0;

} /* media_if_create_cart_from_file */

/******************************************************************************
**  Function   :  media_if_delete_crc                                            
**                                                                    
**  Objective  :  This function deletes the profile based on CRC
**                                                   
**  Parameters :  crc  - crc of the cart entry to be deleted
**                 
**  return     :  0 if successful, otherwise failure 
**
******************************************************************************/ 
int media_if_delete_crc ( unsigned long crc ) {

    int status = 0;
    char *file = media_if_get_profile_from_crc ( crc );

    if ( file ) {
        status = remove(file);
        free(file);
        return status;
    }

    return -1;

} /* end media_if_delete_crc */

/******************************************************************************
**  Function   :  media_if_clear_filenames       
**                            
**  Objective  :  This function deletes the filenames for each entry in the db
**
**  Parameters :  machine_type - which machine to delete filenames for
**                                                
**  return     :  0 for success, otherwise failure
**      
******************************************************************************/
int media_if_clear_filenames ( e_machine_type machine_type ) {

    int status = 0;
    int tag_found;
    long size;
    char *folder, *new_line;
    char *dir;
    char *new_file;
    char *temp_ptr, *eol;
    char *buffer;
    char *buf_ptr;
    char line[257];
    struct dirent *entry;
    struct stat file_status;
    DIR *pdir;
    FILE *fp;

    /*
     * Grab profile directory for input machine
     */
    folder = config_get_profiles_dir ( machine_type );
    if ( !folder ) {
        logger_log_message ( LOG_ERROR, 
                "media_if_delete_filenames: Could not get profiles dir", "" );
        return -1;
    }
    
    /*
     * Set it to program dir if relative
     */
    dir = util_create_program_path_alloc ( folder );
    free(folder);

    /*
     * First check start to make sure its not a file
     *       If it is, strip to directory           
     */
    if ( !stat ( dir, &file_status ) ) {
        if ( !(S_ISDIR(file_status.st_mode)) ) {
            if ( (temp_ptr=strrchr(dir,'\\')) )
                *temp_ptr = '\0';
            if ( (temp_ptr=strrchr(dir,'/')) )
                *temp_ptr = '\0';
        }
    }

    /*
     * Open directory and start searching
     */
    pdir = opendir ( dir );
    if ( !pdir ) {
        logger_log_message ( LOG_ERROR, 
                "media_if_delete_filenames: Could not open directory", "" );
        logger_log_message ( LOG_ERROR, dir, "" );
        free(dir);
        return -1;
    }

    while ( (entry=readdir(pdir)) ) {

        if ( !strcmp(entry->d_name,".") || !strcmp(entry->d_name, "..") )
            continue;

        /*
         * Check for file being a directory
         */
        new_file = util_make_path_alloc ( dir, entry->d_name );
        stat ( new_file, &file_status );

        if (S_ISDIR(file_status.st_mode) || !strstr(new_file, ".conf") ) {
            free(new_file);
            continue;
        }

        /* 
         * Read contents from file into memory 
         */
        if ((fp = fopen(new_file,"r")) == NULL) {
            continue;
        }

        fseek (fp, 0, SEEK_END);
        size = ftell (fp);
        fseek (fp, 0, SEEK_SET);
        buffer = malloc (size);
        if (buffer)
        {
          fread (buffer, 1, size, fp);
        }
        fclose (fp);

        /* 
         * Now open file and write, making file tag empty 
         */
        if ((fp = fopen(new_file,"w")) == NULL) {
            free(buffer);
            free(new_file);
            continue;
        }

        tag_found = 0;

        buf_ptr = buffer;
        while (eol = strchr(buf_ptr, '\n')) {

            strncpy(line, buf_ptr, eol-buf_ptr+1);
            line[eol-buf_ptr+1] = '\0';

            util_search_and_replace ( line, " ", "" );

            if( strstr(line,"file=") && line[0] != '#' ) {
                fputs("file = \"\"\n", fp);
                break;
            }
            else {
                fputs(line, fp);
            }

            buf_ptr = eol+1;

        } /* end while finding linefeed */

        /* Write rest of buffer to file */
        if (*buf_ptr) {
            fputs(buf_ptr, fp);
        }

        /* if we didn't find tag, add it, it should be there */
        if (!tag_found) {
            fputs("\nfile = \"\"\n", fp);
        }

        fclose(fp);

        free(new_file);
        free(buffer);

    } /* end while searching files */

    closedir ( pdir );
    free(dir);

    return status;

} /* media_if_clear_filenames */

/******************************************************************************
**  Function   :  media_if_create_from_file
**                                                                    
**  Objective  :  This function sets up the media struct from a file
**                                                   
**  Parameters :  file  - file to create from
**                media - media to output
**                 
**  return     :  0 if success, otherwise failure
**
******************************************************************************/ 
int media_if_create_from_file ( char *file, t_media *media ) {

    unsigned char tmp_ary[21];
    unsigned char tmp_arys[21];
    char temp_title[1024];
    char *tmp_string = 0;
    t_config *p_config = config_get_global_cfg();
    struct stat file_status;
    int i;

    stat ( file, &file_status );

    if ( util_get_all_hash ( file, &media->crc, tmp_ary, tmp_arys ) )
        return -1;

    util_alloc_string ( &media->md5, "                                " );
    for ( i = 0; i < 16; ++i )
        sprintf ( media->md5+(i*2), "%02x", tmp_ary[i] );

    util_alloc_string ( &media->sha1, "                                        " );
    for ( i = 0; i < 20; ++i )
        sprintf ( media->sha1+(i*2), "%02x", tmp_arys[i] );

    util_alloc_string ( &media->file, file );
    util_set_file_from_current_dir (media->file);
    strcpy ( temp_title, media->file );
    util_strip_to_file ( temp_title );
    util_alloc_string ( &media->title, temp_title );
    tmp_string = strrchr ( media->title, '.' );
    if ( tmp_string && (tmp_string > (media->title + strlen(media->title)-5)) ) 
        *tmp_string = '\0';

    media->mapping = CART_MIN;
    media->media_type = CART_MIN;
    media->image_size = (int)file_status.st_size;
    media->write_protect = 1;
    media->machine_type = p_config->general.machine;
    media->collections = 0;

    return 0;

} /* media_if_create_from_file */

/******************************************************************************
**  Function   :  media_if_update_file_settings_from_buffer
**                            
**  Objective  :  This function takes the input file and updates our filename,
**                sha1 and md5 accordingly.  The crc is already known. 
**
**  Parameters :  type      - machine type we are scanning for
**                flags     - scan_subs/scan_media/scan_bios
**                path      - path to update in setting
**                p_info    - file info from minizip
**                buffer    - uncompressed actual file contents
**                                                
**  return     :  0 for success, 1 if wrong type, failure otherwise
**      
******************************************************************************/
int media_if_update_file_settings_from_buffer ( e_machine_type type, int flags, 
        char *path,  unz_file_info *p_info, unsigned char *buffer ) {

    t_media tmp_media = {0};
    unsigned char tmp_ary[21];
    struct stat file_status;
    unsigned long long mask = 0;
    int i;

    tmp_media.crc = p_info->crc;

    if ( flags & MEDIA_SCAN_BIOS ) {
        pc_wait_semaphore();
        media_if_bios_check ( 0, tmp_media.crc, path );
        pc_signal_semaphore();
    }

    /*
     * Find the entry for this CRC and update if found
     */
    if ( flags & MEDIA_SCAN_MEDIA ) {

        pc_wait_semaphore();
        config_load_media_settings ( tmp_media.crc, &tmp_media );
        pc_signal_semaphore();

        if ( tmp_media.title && strlen(tmp_media.title) ) {

            if ( tmp_media.machine_type != MACHINE_TYPE_5200 &&
                 type == MACHINE_TYPE_5200 )
                return 1;

            if ( tmp_media.machine_type == MACHINE_TYPE_5200 &&
                 type != MACHINE_TYPE_5200 )
                return 1;

            util_alloc_string ( &tmp_media.file, path );

            if ( !tmp_media.image_size )
                tmp_media.image_size = p_info->uncompressed_size;

            /* Compute hashes outside the lock — in-memory ops on thread-local buffer */
            if ( !strlen(tmp_media.md5) ) {
                util_alloc_string ( &tmp_media.md5, "                                " );
                if ( buffer )
                    md5_csum( buffer, p_info->uncompressed_size, tmp_ary );
                for ( i = 0; i < 16; ++i )
                    sprintf ( tmp_media.md5+(i*2), "%02x", tmp_ary[i] );
            }

            if ( !strlen(tmp_media.sha1) ) {
                util_alloc_string ( &tmp_media.sha1, "                                        " );
                if ( buffer )
                    sha1_csum( buffer, p_info->uncompressed_size, tmp_ary );
                for ( i = 0; i < 20; ++i )
                    sprintf ( tmp_media.sha1+(i*2), "%02x", tmp_ary[i] );
            }

            mask = KCFG_MEDIA_FILE | KCFG_MEDIA_SHA1 | KCFG_MEDIA_MD5 |
                   KCFG_MEDIA_SIZE;

            pc_wait_semaphore();
            carts_found++;
            config_save_media_settings ( tmp_media.crc, &tmp_media, mask );
            pc_signal_semaphore();

            free ( tmp_media.file );
            free ( tmp_media.sha1 );
            free ( tmp_media.md5 );
        }
    }

    return 0;

} /* media_if_update_file_settings_from_buffer */

/******************************************************************************
**  Function   :  media_if_scan_zip
**                            
**  Objective  :  This function scans the input directory for cart files that
**                have matching checksums with entries in the cart db
**
**  Parameters :  type      - machine type to search for 
**                flags     - scan_subs/scan_media/scan_bios
**                file      - current zip file path
**                zf        - opened zip file ptr to search through
**                                                
**  return     :  0 for success, failure otherwise
**      
******************************************************************************/
int media_if_scan_zip ( e_machine_type type,  int flags, char *file, unzFile zf ) {

    int status = 0;
    unz_file_info file_info;
    char cur_file[KAT_PATH_MAX];
    char *zip_file;
    char *path_inside;
    char *new_path = 0;
    unsigned char *z_buffer = 0;
    unzFile zf_inside = 0;
    char seperator = '/';
    //char seperator = util_get_dir_seperator_character();

    status = unzGoToFirstFile ( zf );

    while ( status == UNZ_OK ) {

        if ( pc_get_exit() )
            break;

        unzGetCurrentFileInfo(zf, &file_info, cur_file, KAT_PATH_MAX, NULL, 0, NULL, 0);
        unzOpenCurrentFile(zf);

        /* Check if this inside file is a zip */
        if ( util_check_for_zip_alloc ( cur_file, &zip_file, &path_inside ) ) {

            z_buffer = malloc(file_info.uncompressed_size + 1);
            unzReadCurrentFile(zf, z_buffer, file_info.uncompressed_size);
            z_buffer[file_info.uncompressed_size] = 0;

            //unzClose(zf);

            //if ( strstr(new_path, "testrom9") ) {
            //    printf ( "testrom9\n" );
            //}

            zf_inside = util_open_zip_buffer(z_buffer, file_info.uncompressed_size);

            free(z_buffer);
            z_buffer = 0;

            if ( zf_inside ) {
                new_path = malloc(strlen(file) + strlen(cur_file) + 2);
                sprintf(new_path, "%s%c%s", file, seperator, cur_file);

                media_if_scan_zip ( type, flags, new_path, zf_inside );
            }

            //unzClose ( zf_inside );
        }
        else if ( file_info.uncompressed_size > 0 ) {

            total_carts++;
            z_buffer = malloc(file_info.uncompressed_size + 1);
            unzReadCurrentFile(zf, z_buffer, file_info.uncompressed_size);
            z_buffer[file_info.uncompressed_size] = 0;

            new_path = malloc(strlen(file) + strlen(cur_file) + 2);
            sprintf(new_path, "%s%c%s", file, seperator, cur_file);
            media_if_update_file_settings_from_buffer ( type, flags, new_path,  
                    &file_info, z_buffer );
        }

        free(z_buffer);
        free(new_path);
        z_buffer = 0;
        new_path = 0;

        status = unzGoToNextFile ( zf );

    } /* end while searching through files */

    unzClose ( zf );

} /* end media_if_scan_zip */

/******************************************************************************
**  Function   :  media_if_update_file_settings
**                            
**  Objective  :  This function takes the input file and updates our filename,
**                sha1 and md5 accordingly.  The crc is already known. 
**
**  Parameters :  type      - machine type we are scanning for
**                flags     - scan_subs/scan_media/scan_bios
**                path      - path to update in setting
**                file      - actually file to get sha1,md5. For stuff inside
**                            zip, this could be a tmp file
**                                                
**  return     :  0 for success, 1 if wrong type, failure otherwise
**      
******************************************************************************/
int media_if_update_file_settings ( e_machine_type type, int flags, char *path,  char *file ) {

    t_media tmp_media = {0};
    unsigned char tmp_ary[21];
    struct stat file_status;
    unsigned long long mask = 0;
    int i;

    stat ( file, &file_status );
    tmp_media.crc = util_get_crc ( file );

    if ( flags & MEDIA_SCAN_BIOS ) {
        pc_wait_semaphore();
        media_if_bios_check ( 0, tmp_media.crc, file );
        pc_signal_semaphore();
    }

    /*
     * Find the entry for this CRC and update if found
     */
    if ( flags & MEDIA_SCAN_MEDIA ) {

        pc_wait_semaphore();
        config_load_media_settings ( tmp_media.crc, &tmp_media );
        pc_signal_semaphore();

        if ( tmp_media.title && strlen(tmp_media.title) ) {

            if ( tmp_media.machine_type != MACHINE_TYPE_5200 &&
                 type == MACHINE_TYPE_5200 )
                return 1;

            if ( tmp_media.machine_type == MACHINE_TYPE_5200 &&
                 type != MACHINE_TYPE_5200 )
                return 1;

            util_alloc_string ( &tmp_media.file, path );

            if ( !tmp_media.image_size )
                tmp_media.image_size = (int)file_status.st_size;

            /* Compute hashes outside the lock — disk reads on thread-local files */
            if ( !strlen(tmp_media.md5) ) {
                util_alloc_string ( &tmp_media.md5, "                                " );
                md5_file ( file, tmp_ary );
                for ( i = 0; i < 16; ++i )
                    sprintf ( tmp_media.md5+(i*2), "%02x", tmp_ary[i] );
            }

            if ( !strlen(tmp_media.sha1) ) {
                util_alloc_string ( &tmp_media.sha1, "                                        " );
                sha1_file ( file, tmp_ary );
                for ( i = 0; i < 20; ++i )
                    sprintf ( tmp_media.sha1+(i*2), "%02x", tmp_ary[i] );
            }

            mask = KCFG_MEDIA_FILE | KCFG_MEDIA_SHA1 | KCFG_MEDIA_MD5 |
                   KCFG_MEDIA_SIZE;

            pc_wait_semaphore();
            carts_found++;
            config_save_media_settings ( tmp_media.crc, &tmp_media, mask );
            pc_signal_semaphore();

            free ( tmp_media.file );
            free ( tmp_media.sha1 );
            free ( tmp_media.md5 );
        }
    }

    return 0;

} /* media_if_update_file_settings */

/******************************************************************************
**  Function   :  media_if_scan
**                            
**  Objective  :  This function scans the input directory for cart files that
**                have matching checksums with entries in the cart db
**
**  Parameters :  type      - machine type to search for 
**                directory - directory to search
**                flags     - scan_subs/scan_media/scan_bios
**                                                
**  return     :  0 for success, failure otherwise
**      
******************************************************************************/
int media_if_scan ( e_machine_type type,  char *directory, int flags, int *scan_progress ) {

    struct stat file_status;
    char *temp_ptr = 0;
    char *zip_file;
    char *path_inside;
    char new_file[1200];
    DIR *pdir;
    struct dirent *entry;
    int i;
    unsigned char tmp_ary[21];
    t_media tmp_media;
    unzFile zf;

    //scan_progress = 0;

    /*
     * First check start to make sure its not a file
     *       If it is, strip to directory           
     */
    if ( !stat ( directory, &file_status ) ) {
        if ( !(S_ISDIR(file_status.st_mode)) ) {
            if ( (temp_ptr=strrchr(directory,'\\')) )
                *temp_ptr = '\0';
            if ( (temp_ptr=strrchr(directory,'/')) )
                *temp_ptr = '\0';
        }
    }

    /*
     * Open directory and start searching
     */
    pdir = opendir ( directory );
    if ( !pdir ) {
        sprintf ( new_file, "media_if_scan: Could not open directory \"%s\"", directory );
        logger_log_message ( LOG_ERROR, new_file, "" );
        return -1;
    }

    while ( (entry=readdir(pdir)) ) {

        if ( !strcmp(entry->d_name,".") || !strcmp(entry->d_name, "..") )
            continue;

        if ( pc_get_exit() )
            break;

        /*
         * Check for file being a directory
         */
        util_make_path ( directory, entry->d_name, new_file );
        stat ( new_file, &file_status );

        if ( S_ISDIR(file_status.st_mode) ) {
            if ( flags & MEDIA_SCAN_SUBS )
                media_if_scan ( type,  new_file, flags, scan_progress );
            continue;
        }

        /* Check for zip or normal file */

        if ( util_check_for_zip_alloc ( new_file, &zip_file, &path_inside ) && (zf=unzOpen(new_file)) ) {

            total_carts--;
            media_if_scan_zip(type, flags, new_file, zf);

        }

        else {
            media_if_update_file_settings ( type, flags, new_file, new_file );
        }
        
        pc_wait_semaphore();
        (*scan_progress)++;
        pc_signal_semaphore();
        //pc_set_thread_data ( (void *)++scan_progress );

    } /* end while searching files */

    closedir ( pdir );

    return 0;

} /* media_if_scan */

/******************************************************************************
**  Function   :  media_if_scan_directory
**                            
**  Objective  :  This function scans the input directory for cart files that
**                have matching checksums with entries in the cart db
**
**  Parameters :  type       - machine type to search for 
**                directory  - directory to search
**                scan_subs  - 1 to scan sub-directories, 0 if not
**                bios_check - 1 to set bios files if found, 0 if not
**                                                
**  return     :  0 for success, failure otherwise
**      
******************************************************************************/
int media_if_scan_directory ( t_media_scan *scan_params ) {

    char msg[200];
    char *dir = 0;
    int total_profiles = 0;

    carts_found = 0;
    total_carts = 0;

    pc_wait_semaphore();
    media_if_bios_check ( 1, 0, "" );
    pc_signal_semaphore();

    /* Count profiles in directory for total carts */
    dir = config_get_profiles_dir ( scan_params->type );
    if ( dir ) {
        total_profiles = util_count_files_in_dir ( dir, 1, ".conf" ); 
        free(dir);
    }

    media_if_scan ( scan_params->type, scan_params->directory, scan_params->flags, &scan_params->progress );

    pc_wait_semaphore();
    config_restore_loaded_media_context();
    pc_signal_semaphore();

    /*
     * BIOS/BASIC detection now writes each rom to its own slot via
     * config_save_os / config_save_basic, so no extra general.basic flush
     * is needed here.
     */

    if ( !pc_get_exit() ) {
        sprintf ( msg, "media_if_scan_directory: Found %d out of %d entries", 
                     carts_found, total_profiles );
        logger_log_message ( LOG_INFO, msg, "" );
    }

    scan_params->progress = 0;
    //pc_set_thread_data ( 0 );

    return 0;

} /* media_if_scan_directory */

/******************************************************************************
**  Function   :  media_if_add_from_directory
**                            
**  Objective  :  This function scans the input directory for cart files that
**                have matching checksums with entries in the cart db
**
**  Parameters :  directory - directory to search
**                scan_subs - 1 to scan sub-directories, 0 if not
**                                                
**  return     :  0 for success, failure otherwise
**      
******************************************************************************/
int media_if_add_from_directory ( char *directory, int scan_subs ) {

    struct stat file_status;
    char *temp_ptr = 0;
    char new_file[1200];
    int  cart_found;
    DIR *pdir;
    struct dirent *entry;
    t_media tmp_cart;

    /*
     * First check start to make sure its not a file
     *       If it is, strip to directory           
     */
    if ( !stat ( directory, &file_status ) ) {
        if ( !(S_ISDIR(file_status.st_mode)) ) {
            if ( (temp_ptr=strrchr(directory,'\\')) )
                *temp_ptr = '\0';
            if ( (temp_ptr=strrchr(directory,'/')) )
                *temp_ptr = '\0';
        }
    }

    /*
     * Open directory and start searching
     */
    pdir = opendir ( directory );
    if ( !pdir ) {
        sprintf ( new_file, "media_if_scan: Could not open directory \"%s\"", directory );
        logger_log_message ( LOG_ERROR, new_file, "" );
        return -1;
    }

    while ( (entry=readdir(pdir)) ) {

        if ( !strcmp(entry->d_name,".") || !strcmp(entry->d_name, "..") )
            continue;

        /*
         * Check for file being a directory
         */
        util_make_path ( directory, entry->d_name, new_file );
        stat ( new_file, &file_status );

        if ( S_ISDIR(file_status.st_mode)) {
            if ( scan_subs )
                media_if_add_from_directory ( new_file, scan_subs );
            continue;
        }

        /*
         * Get CRC of current file
         */
        cart_found = 0;

        config_load_media_settings ( tmp_cart.crc, &tmp_cart );

        if ( strlen(tmp_cart.title) )
            cart_found = 1;

        /*
         * If cart not found, add it
         */
        if ( !cart_found ) {
            carts_found++;
            media_if_create_cart_from_file ( new_file, &tmp_cart );

            /* write everything if this is new */
            config_save_media_settings ( tmp_cart.crc, &tmp_cart, 0xfffff );
        }

    } /* end while searching files */

    closedir ( pdir );

    return 0;

} /* media_if_add_from_directory */

/******************************************************************************
**  Function   :  media_if_bios_check                                            
**                                                                    
**  Objective  :  This function checks input CRC matching a BIOS and setting
**                configuration accordingly
**                                                   
**  Parameters :  reset - start search over if 1, continue if 0
**                crc   - CRC to check
**                filename - file of rom
**                 
**  return     :  0 
**
******************************************************************************/ 
int media_if_bios_check ( int reset, unsigned long crc, const char *filename ) {

    (void)reset;  /* every BIOS/BASIC rev now fills its own slot - no guards */

    switch ( crc ) {
        case 0x4248d3e3: /* 5200 ROM */
            config_save_os ( OS_TYPE_5200_4PORT, filename );
            break;
        case 0x72b3fed4: /* OS Rev A PAL */
            config_save_os ( OS_TYPE_A, filename );
            break;
        case 0x3e28a1fe: /* OS Rev B PAL */
            config_save_os ( OS_TYPE_B_PAL, filename );
            break;
        case 0xe86d61d:  /* OS Rev B NTSC */
            config_save_os ( OS_TYPE_B_NTSC, filename );
            break;
        case 0x643bcc98:  /* OS Rev 1 600XL */
            config_save_os ( OS_TYPE_REV_1, filename );
            break;
        case 0xc5c11546:  /* OS Rev 10 1200XL */
            config_save_os ( OS_TYPE_REV_10, filename );
            break;
        case 0x1f9cd270:  /* OS Rev 2 800XL */
            config_save_os ( OS_TYPE_REV_2, filename );
            break;
        case 0x29f133f7:  /* OS Rev 3 800XE-65XE-130XE */
            config_save_os ( OS_TYPE_REV_3, filename );
            break;
        case 0x1eaf4002:  /* OS Rev 4 XEGS */
            config_save_os ( OS_TYPE_REV_4, filename );
            break;
        case 0x4bec4de2: /* BASIC Rev A 800 */
            config_save_basic ( BASIC_TYPE_REV_A, filename );
            break;
        case 0xf0202fb3: /* BASIC Rev B XL */
            config_save_basic ( BASIC_TYPE_REV_B, filename );
            break;
        case 0x7d684184: /* BASIC Rev C XL-XE */
            config_save_basic ( BASIC_TYPE_REV_C, filename );
            break;
        default:
            break;
    }

    return 0;

} /* media_if_bios_check */

/******************************************************************************
**  Function   :  media_if_open_basic                                            
**                                                                    
**  Objective  :  This function opens up the atari basic cart file
**                                                   
**  Parameters :  filename     - file of the rom
**                enable_basic - whether or not to enable basic
**                 
**  return     :  0 if success, otherwise failure
**
******************************************************************************/ 
int media_if_open_basic ( char *filename, int enable_basic ) {

    int bytes;
    unsigned char *buffer = 0;
    unsigned long basic_crc;

    /*
     * We need to make sure it is clear if not enabling BASIC
     *   Put 0xfe since the 800 OS will inc bffc before checking for non-zero
     */
    if ( !enable_basic ) {
        buffer = malloc(sizeof(char)*32768);
        memset ( buffer, 0xfefefefe, 32768 );
        memory_write_rom_bytes ( 0xa000, buffer, 0x2000 );
        memory_write_basic_bytes ( 0xa000, buffer, 0x2000 );
        free ( buffer );
        return 0;
    }


    if ( (bytes = media_if_open_file ( filename, &buffer )) < 0 ) {
        free ( buffer );
        return ERR_BASIC_LOAD;
    }
    basic_crc = crc32(0L,buffer,bytes);

    if ( console_get_machine_type() == MACHINE_TYPE_XL )
        memory_write_basic_bytes ( 0xa000, buffer, 0x2000 );
    else
        cart_open ( filename, buffer, bytes, basic_crc, CART_PC_8K );

    free ( buffer );

    return 0;

} /* media_if_open_basic */

/******************************************************************************
**  Function   :  media_if_exe_alloc
**                                                                    
**  Objective  :  This function rearranges the exe bin and adds the bootstrap
**                  code before mounting as a floppy disk
**                                                   
**  Parameters :  buffer     - original exe buffer
**                bytes      - number of bytes in original buffer
**                bin_buffer - output buffer to fill up
**                 
**  return     :  0 if success, otherwise failure
**
******************************************************************************/ 
int media_if_exe_alloc ( unsigned char *buffer, int bytes, unsigned char *bin_buffer ) {

    int i,j;
    int start, end, byte_pos, sector;
    unsigned char *buf_pos;

    memcpy ( bin_buffer, binload_bootstrap, sizeof(binload_bootstrap));

    /*
     * Start storing stuff into our buffer
     */
    buf_pos = bin_buffer + (128 * 3);
    byte_pos = i = 0;
    sector = 4;
    while ( byte_pos < bytes ) {

        /*
         * So first find our start and end positions
         */
        j = 0;
        while ( ((byte_pos + j + 1) < bytes) && 
                  buffer[byte_pos + j] == 0xff && 
                  buffer[byte_pos + j + 1] == 0xff ) {
            j += 2;
        }

        if ( (byte_pos + j + 3) < bytes ) {
            start = buffer[byte_pos + j] | (buffer[byte_pos + j + 1] << 8);
            end = buffer[byte_pos + j + 2] | (buffer[byte_pos + j + 3] << 8);
        }
        else {
            start = end = 0;
        }

        /*
         * If something's not right, truncate
         */
        if ( start > end  || start == 0 || end == 0 ) {
            if ( i > 0 ) {
                buf_pos[125] = 0;
                buf_pos[126] = 0;
                buf_pos[127] = i;
            }
            else if ( byte_pos > 125 ) {
                buf_pos -= 125;
                buf_pos[125] = 0;
                buf_pos[126] = 0;
                buf_pos[127] = 125;
            }

            break;
        }

        /*
         * Now start storing the sector until we get 125 or start > end
         */
        start -= (4+j);
        while ( start <= end ) {

            while ( i < 125 ) {
                buf_pos[i] = buffer[byte_pos];
                byte_pos++;
                i++;
                start++;
                if ( start > end )
                    break;
                if ( byte_pos >= bytes )
                    break;
            }

            if ( i == 125 ) {
                sector++;
                buf_pos[125] = (sector) >> 8;
                buf_pos[126] = (sector) & 0xff;
                buf_pos[127] = 125;
                buf_pos += 128;
                i = 0;
            }

        } /* end while start position is less than end position */

    } /* end while buffer bytes are processed */

    /*
     * Check for left over data without a sector end
     */
    if ( i > 0 ) {
        buf_pos[125] = 0;
        buf_pos[126] = 0;
        buf_pos[127] = i;
    }

    bytes = sector * 128;

    return bytes;

} /* media_if_exe_alloc */

/******************************************************************************
**  Function   :  media_if_open_rom                                            
**                                                                    
**  Objective  :  This function opens the image indicated by the file.  What
**                  type of media is determined as follows:
**                   1.  Check media_type input here.  If 0, then
**                   2.  Check database for type.  If AUTO, then
**                   3.  Check file for a header.  If NONE, then
**                   4.  Determine by file extension.  If unable, then
**                   5.  Treat as cartridge.
**                                                   
**  Parameters :  filename   - file to open
**                media_type - type of media (0 for auto)
**                slot       - desired floppy disk slot
**                launch     - "Launch" to image instead of just attach
**                 
**  return     :  0 if success, otherwise failure
**
******************************************************************************/ 
int media_if_open_rom ( char *filename, int media_type, int slot, int launch ) {

    int bytes;
    int i, status;
    int has_cart_header = 0;
    char lowername[1100];
    unsigned char *buffer = 0, *bin_buffer;
    unsigned long save_crc;
    unsigned int cart_header_id = 0;
    e_cart_type tmp_mapping;
    int selected_hyb = 0;
    int selected_dat = 0;
    t_config *p_config = config_get_global_cfg();

    if ( (bytes = media_if_open_file ( filename, &buffer )) < 0 ) {
        free ( buffer );
        return ERR_ROM_LOAD;
    }
    g_media.crc = crc32(0L,buffer,bytes);

    strcpy ( lowername, filename );
    i = -1;
    while ( lowername[++i] ) lowername[i] = tolower(lowername[i]);
    selected_hyb = media_if_extension_is ( lowername, ".hyb" );
    selected_dat = media_if_extension_is ( lowername, ".dat" );

    save_crc = g_media.crc;
    tmp_mapping = media_type;

    /*
     * Strip CART headers before mapping selection so an explicit mapping can
     * still load a headered cartridge image correctly.
     */
    if ( (media_type == CART_MIN || media_type == CART_CART ||
          (media_type >= CART_32K && media_type <= CART_BLIZZARD)) &&
         bytes >= 16 && !strncmp((char *)buffer, "CART", 4) ) {
        has_cart_header = 1;
        cart_header_id = ((unsigned int)buffer[4] << 24) |
                         ((unsigned int)buffer[5] << 16) |
                         ((unsigned int)buffer[6] << 8) |
                         ((unsigned int)buffer[7]);
        bytes -= 16;
        memmove ( buffer, buffer + 16, bytes );
    }

    status = config_load_media_settings ( g_media.crc, &g_media );
    util_alloc_string ( &g_media.file, filename );

    /*
     * Determine media type if input is AUTO
     */
    if ( tmp_mapping == CART_MIN ) {

        /*
         * If database indicates AUTO, try to see if there is a header
         */
        if ( g_media.mapping == CART_MIN || status ) {
            if ( has_cart_header ) {
                tmp_mapping = media_if_get_cart_header_mapping ( cart_header_id );
            }

            /*
             * If no CART Header, check by filename and media_type
             */
            else {
                if ( strstr(lowername, ".atr") || strstr(lowername,".atz") ||
                     strstr(lowername, ".xfd") || strstr(lowername,".xfz") || 
                     g_media.media_type == CART_FLOPPY )
                    tmp_mapping = CART_FLOPPY;
                else if ( strstr(lowername, ".cas") || g_media.media_type == CART_CASSETTE )
                    tmp_mapping = CART_CASSETTE;
                else if ( strstr(lowername, ".xex") || strstr(lowername, ".exe") || 
                          strstr(lowername, ".com") || g_media.media_type == CART_EXE )
                    tmp_mapping = CART_EXE;
            }

        } /* end if database mapping is AUTO */

        else {
            tmp_mapping = g_media.mapping;
        }

    } /* end if input mapping is AUTO */

    /*
     * See if we can set the machine type based on this profile.  If no profile
     * resolved a machine, keep the current configured machine.
     */
    if ( !status && g_media.machine_type != MACHINE_TYPE_MIN ) {
        p_config->general.machine = g_media.machine_type;
    }

    config_refresh_machine_settings();

    config_save_general(config_get_global_table(), KCFG_GENERAL_MACHINE |
            KCFG_GENERAL_ROM);
    console_set_machine_type ( p_config->general.machine );
    console_get_ptr()->cart->loaded = 1;
    console_get_ptr()->basic_enable = 1;
    console_get_ptr()->cart->media_info.image_size = 0x2000;

    /* FIXME: Test this now that we don't have media basic enable */
    //if ( g_media.basic_enable == 2 )
    console_get_ptr()->basic_enable = p_config->general.basic_enable;
    //else
    //    console_get_ptr()->basic_enable = g_media.basic_enable;

    if ( p_config->general.machine == MACHINE_TYPE_XL || 
            !console_get_ptr()->basic_enable ) {
        console_get_ptr()->cart->loaded = 0;
    }

    if ( (selected_hyb || selected_dat) &&
         media_if_open_hybrid_pair ( filename, &buffer, &bytes, selected_dat, launch ) == 0 ) {
        free ( buffer );
        return 0;
    }

    g_media.image_size = bytes;
    g_media.crc = save_crc;
    switch ( tmp_mapping ) {
        case CART_FLOPPY:
            if ( media_if_open_basic ( p_config->general.basic, console_get_ptr()->basic_enable ) )
                return ERR_BASIC_LOAD;

            g_media.mapping = tmp_mapping;
            g_media.media_type = tmp_mapping;
            console_open_media ( &g_media, buffer, slot, launch );
            //sio_mount_image ( slot, SIO_TYPE_FLOPPY, filename, 1, buffer, bytes, launch );
            break;
        case CART_CASSETTE:
            if ( media_if_open_basic ( p_config->general.basic, console_get_ptr()->basic_enable ) )
                return ERR_BASIC_LOAD;
    
            g_media.mapping = tmp_mapping;
            g_media.media_type = tmp_mapping;
            console_open_media ( &g_media, buffer, slot, launch );
            //sio_mount_image ( 0, SIO_TYPE_CASSETTE, filename, 1, buffer, bytes, launch );
            break;
        case CART_EXE:
            if ( media_if_open_basic ( p_config->general.basic, console_get_ptr()->basic_enable ) )
                return ERR_BASIC_LOAD;

            /* calloc, not malloc: media_if_exe_alloc only writes the data and
               sector-control bytes, leaving partial-sector padding untouched.
               Zeroing keeps the synthetic boot disk deterministic (otherwise
               those padding bytes are read by sio_checksum and the binloader). */
            bin_buffer = calloc(131084, sizeof(char));

            bytes = media_if_exe_alloc ( buffer, bytes, bin_buffer );

            g_media.mapping = tmp_mapping;            /* keep CART_EXE for profile persistence */
            g_media.media_type = CART_FLOPPY;         /* route through floppy mount path */
            g_media.image_size = bytes;               /* use converted, sector-padded size */
            g_media.write_protect = 1;                /* synthetic RAM disk - never write back over the .xex */
            console_open_media ( &g_media, bin_buffer, 0, launch );  /* mount converted boot disk at D1: */

            free ( bin_buffer );
            g_media.mapping = tmp_mapping;
            break;
        default:
            if ( launch )
                sio_unmount_all ( );
            if ( !(media_type >= CART_32K && media_type <= CART_BLIZZARD) &&
                 g_media.mapping != CART_MIN )
                tmp_mapping = g_media.mapping;
            g_media.mapping = tmp_mapping;
            if ( media_type >= CART_32K && media_type <= CART_BLIZZARD )
                g_media.media_type = CART_CART;

            console_open_media ( &g_media, buffer, slot, launch );
            //cart_open ( filename, buffer, bytes, g_media.crc, tmp_mapping, launch );
            if ( g_media.mapping == CART_PC_RIGHT ) {
                if ( media_if_open_basic ( p_config->general.basic, console_get_ptr()->basic_enable ) )
                    return ERR_BASIC_LOAD;
            }
            //g_media.mapping = cart.mapping;
            console_get_ptr()->cart->loaded = 1;
            //console_get_ptr()->cart->size = bytes;
            break;
    }

    /*
     * Check for 5200 Bounty Bob 3 piece rom
     */
    if ( g_media.crc == 0x74e6a611 ||
         g_media.crc == 0xefa4915d ||
         g_media.crc == 0x02931055 ) {

        free(buffer);
        g_media.crc = 0x74e6a611;
        config_load_media_settings ( g_media.crc, &g_media );
        media_if_open_file ( g_media.file, &buffer );
        g_media.image_size = bytes;
        console_open_media ( &g_media, buffer, 0, launch );
        //cart_open ( g_media.file, buffer, bytes, g_media.crc, g_media.mapping );

        free(buffer);
        g_media.crc = 0xefa4915d;
        config_load_media_settings ( g_media.crc, &g_media );
        media_if_open_file ( g_media.file, &buffer );
        g_media.image_size = bytes;
        console_open_media ( &g_media, buffer, 0, launch );
        //cart_open (  g_media.file, buffer, bytes, g_media.crc, g_media.mapping );

        free(buffer);
        g_media.crc = 0x02931055;
        config_load_media_settings ( g_media.crc, &g_media );
        media_if_open_file ( g_media.file, &buffer );
        g_media.image_size = bytes;
        console_open_media ( &g_media, buffer, 0, launch );
        //cart_open ( g_media.file, buffer, bytes, g_media.crc, g_media.mapping );

        g_media.mapping = console_get_ptr()->cart->media_info.mapping;
    }

    /*
     * Close and get out
     */
    free ( buffer );
    
    return 0;

} /* end media_if_open_rom */

/******************************************************************************
**  Function   :  media_if_open_file                                            
**                                                                    
**  Objective  :  This function opens the file and puts contents in buffer
**                                                   
**  Parameters :  filename - file to open
**                buffer   - where to store the contents of the file
**                 
**  return     :  0 if success, otherwise failure
**
******************************************************************************/ 
int media_if_open_file ( char *filename, unsigned char **buffer ) {

    gzFile gzip_fp = 0;
    int bytes;
    int is_zip;
	int status=0;
    int still_looking = 1;
    char msg[1100];
    char *path_inside = 0, *zip_file = 0, *file = 0;
    unsigned char *z_buffer = 0;
    unz_file_info file_info;
    unzFile zf;

    is_zip = util_check_for_zip_alloc ( filename, &zip_file, &path_inside );
    if ( is_zip ) {
        zf = unzOpen(zip_file);
        if ( zf == NULL ) {
            logger_log_message ( LOG_ERROR, "Unable to open media zip file:", "" );
            logger_log_message ( LOG_ERROR, zip_file, "" );
            is_zip = 0;
            free(path_inside);
            path_inside = 0;
        }
        free(zip_file);
    }

    /* Check for a zip or a zip within a zip */
    if ( is_zip && path_inside ) {

        file = path_inside;
        while ( !status &&
                (still_looking = util_check_for_zip_alloc ( file, &zip_file, &path_inside )) ) {

            /* Check for next zip */
            if ( zf && zip_file && unzLocateFile(zf, zip_file, 2) == UNZ_OK &&
                    !unzOpenCurrentFile(zf) ) {

                unzGetCurrentFileInfo(zf, &file_info, NULL, 0, NULL, 0, NULL, 0);
                z_buffer = malloc(file_info.uncompressed_size + 1);
                unzReadCurrentFile(zf, z_buffer, file_info.uncompressed_size);
                z_buffer[file_info.uncompressed_size] = 0;

                unzClose(zf);

                zf = util_open_zip_buffer(z_buffer, file_info.uncompressed_size);

                free(z_buffer);
                z_buffer = 0;
                if ( !zf ) {
                    still_looking = 0;
                    status = -1;
                }
            }
            else {
                still_looking = 0;
                status = -1;
            }

            free(zip_file);
            file = path_inside;

        } /* end while still looking */
        
        /*
         * Now we should just be at the final inside path 
         */

        if ( !status && zf && file && unzLocateFile(zf, file, 2) == UNZ_OK &&
                !unzOpenCurrentFile(zf) ) {

            unzGetCurrentFileInfo(zf, &file_info, NULL, 0, NULL, 0, NULL, 0);
            z_buffer = malloc(file_info.uncompressed_size + 1);
            unzReadCurrentFile(zf, z_buffer, file_info.uncompressed_size);
            z_buffer[file_info.uncompressed_size] = 0;
        }
        else if ( !status ) {
            status = -1;
        }

        free(zip_file);

        if ( zf )
            unzClose(zf);

        if ( !status ) {
            bytes = file_info.uncompressed_size;

            if ( bytes && z_buffer ) {
                *buffer = malloc ( bytes );
                memcpy ( *buffer, z_buffer, bytes );
                free(z_buffer);
                z_buffer = 0;
                status = 0;
            }
            else {
                status = -1;
            }
        }
    }

    /* 
     * If not a zip, just open the file, if a single compressed file,
     *   gzip will take care of uncompressing
     */
    else if ( !status ) {
        gzip_fp = gzopen ( filename, "rb" );

        char tmp_buffer[131088];
        int total_bytes = 0;
        if ( gzip_fp ) {
            while ( (bytes = gzread ( gzip_fp, tmp_buffer, 131088 )) == 131088 ) {
                total_bytes += 131088;
            }
            total_bytes += bytes;
            gzrewind ( gzip_fp );
            *buffer = malloc ( total_bytes );
            bytes = gzread ( gzip_fp, *buffer, total_bytes );
            status = 0;
            gzclose (gzip_fp);
        }
        else {
            status = -1;
        }
    }

    /* if something went wrong error out */
    if ( status ) {
        sprintf ( msg, "media_if_open_file: Unable to Open File \"%s\"", filename );
        logger_log_message ( LOG_ERROR, msg, "" );
        return -1;
    }

    sprintf ( msg, "media_if_open_file: Opened File \"%s\"", filename );
    logger_log_message ( LOG_INFO, msg, "" );

    //g_media.crc = crc32(0L,*buffer,bytes);

    return bytes;

} /* end media_if_open_file */
