
/******************************************************************************
*
* Copyright (C) 2005-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: katconfig.cpp
*
* DESCRIPTION:  This handles command line options and reading from 
*               config files
*
* AUTHORS:  bberlin
*
* CHANGE LOG
*
*  VER     DATE     CHANGED BY   DESCRIPTION OF CHANGE
* ------  --------  ----------   ------------------------------------
* 0.1     06/18/05  bberlin      Creation
* 0.2     01/23/06  bberlin      Change to XML format
* 0.2     03/09/06  bberlin      Added functions for saving, deleting, and 
*                                  finding configuration item for the GUI
* 0.2.2   03/15/06  bberlin      Added deadzone parameter to xml configuration
* 0.2.3   03/17/06  bberlin      Added sound freq and sound samples to config
* 0.2.3   03/19/06  bberlin      Changed 'config_save' to add parameters not
*                                  found (i.e. new parameters)
* 0.3.0   04/01/06  bberlin      Added sensitivity parameter to xml configuration
* 0.3.0   04/05/06  bberlin      Added simulate_analog parameter to xml 
*                                  configuration
* 0.3.0   04/07/06  bberlin      Fix 'config_save_cart_info' not saving in
*                                  alphabetical order
* 0.3.0   04/08/06  bberlin      Added 'config_update' function to handle
*                                  updates to cart list and config file
* 0.4.0   05/21/06  bberlin      Added ui_keys parameter to configuration
* 0.4.0   05/21/06  bberlin      Added Hangly Man and Pac-Man Plus to
*                                  'config_update'
* 0.4.0   06/04/06  bberlin      Added hires_artifacts parameter to configuration
* 0.8.0   04/19/17  bberlin      Added shader argument
* 0.8.0   11/25/17  bberlin      Added config file argument
* 0.9.0   10/29/18  bberlin      Changed for TOML format
******************************************************************************/
#include <ctype.h>
#include <string.h>
#include <sys/stat.h>
#include "katconfig.h"
#include "util.h"
#include "logger.h"
#include "sdl_if.h"
#include "input.h"
#include "palette.h"
#include "media_if.h"
#include "../core/debug.h"
#include "../core/pokey.h"

extern t_debug debug;

void config_load_internal_defaults ( void );
void config_load_internal_media_defaults ( void );
void config_pre_check_arguments ( int argc, char *argv[] );
static void config_apply_cmdline_overrides ( int argc, char *argv[] );
static void config_schedule_profile_seed_sync ( void );
t_toml_node * config_parse_file ( char *filename );
t_toml_node * config_parse_for_media ( unsigned int crc );
static int config_rom_table_matches_loaded_media ( void );
int config_get_int ( t_toml_node * tbl, const char *key, 
                      int *key_value, int check_error );
int config_get_long ( t_toml_node * tbl, const char *key, 
                      unsigned long *key_value, int check_error );
int config_get_float ( t_toml_node * tbl, const char *key, 
                        float *key_value, int check_error );
int config_get_bool ( t_toml_node * tbl, const char *key, 
                       int *key_value, int check_error );
int config_get_string ( t_toml_node * tbl, const char *key, 
                         char** key_value, int check_error );
void config_set_key_error ( const char *key );
void config_set_int ( t_toml_node *tbl, const char *key, 
                      int key_value, int check_error );
void config_set_float ( t_toml_node *tbl, const char *key, 
                        float key_value, int check_error );
void config_set_bool ( t_toml_node *tbl, const char *key, 
                       int key_value, int check_error );
void config_set_string ( t_toml_node *tbl, const char *key, 
                         const char* key_value, int check_error );
static char * config_alloc_string ( const char *value );
static void config_replace_node_string ( char **node_string, const char *value );
static char * config_alloc_array_continuation_indent ( t_toml_node *tbl,
                                                       const char *tag );
static void config_set_input_array_layout ( t_toml_node *tbl, const char *tag );
static void config_set_palette_array_layout ( t_toml_node *tbl );
static void config_write_escaped_dq_string ( FILE *fp, const char *value );

void config_load_media_collections ( t_toml_node *tbl );
int config_split(char *input, const char *delim, char *parts[]);
e_ctrlr_type config_get_control_type_from_string ( const char *string );
char * config_get_string_from_control_type ( int type, char *string );
int config_get_mouse_optimize_from_string ( const char *string );
char * config_get_string_from_mouse_optimize ( int value, char *string );
int config_load_input_from_str_ary (t_toml_node *, char *tag, 
        t_input_common *input);
void config_set_str_ary_from_input (t_toml_node *tbl, char *tag, 
        t_input_common *input);
char * config_get_800_key_string_name(int key, char *key_string);
void config_free_input ( t_input_common *input );

void config_check_arg_input_game ( int *index );
void config_check_arg_input_misc ( int *index );
int config_check_arg_string ( int *index, char *str1, char*str2, char **value );
int config_check_arg_float ( int *index, char *str1, char*str2, float *value );
int config_check_arg_bool ( int *index, char *str1, char*str2, int *value );
int config_check_arg_int ( int *index, char *str1, char*str2, int *value );
static int config_check_arg_mapping ( int *index, int set_mapping );
static int config_parse_mapping_arg ( const char *arg, int *mapping );
static const char * config_get_machine_key_from_type ( e_machine_type machine );
static int config_get_window_scaling_from_string ( const char *string );
static const char * config_get_string_from_window_scaling ( int scaling );
static int config_get_texture_scaling_from_string ( const char *string );
static const char * config_get_string_from_texture_scaling ( int scaling );
static int config_clamp_gui_scale ( int scale );
static float config_clamp_sound_buffer_size ( float buffer_size );
static int config_clamp_pot_value ( int value );
static void config_set_machine_pot_defaults ( e_machine_type machine );
static char * config_alloc_resolved_path ( const char *path, int use_current_dir );
static char * config_alloc_existing_dir_candidate ( const char *path,
        int use_current_dir );
static char * config_alloc_file_dir_candidate ( const char *path,
        int use_current_dir );
static void config_load_machine_pot_value ( t_toml_node *tbl,
        const char *machine_key, const char *value_key, int *value,
        unsigned long long mask, int err_chk );
static void config_save_machine_pot_value ( t_toml_node *tbl,
        const char *machine_key, const char *value_key, int *value,
        unsigned long long mask, unsigned long long changed_mask, int err_chk );

/* Global toml nodes */
t_toml_node * g_conftbl = NULL;    //kat5200.conf
t_toml_node * g_romconftbl = NULL; //ROM specific conf file

unsigned long g_crc = 0;    /* CRC g_romconftbl was parsed for; matches m->crc (unsigned long) */
e_machine_type g_romprofile_machine = MACHINE_TYPE_MIN;

char g_configfile[KAT_PATH_MAX];
char **g_argv = 0;
int  g_argc = 0;
t_config config;

/* Session-only safe-video flag set by --safe; never persisted.  Forces the
   SDL default renderer (bypassing any GPU-preferred policy) so a wedged saved
   backend can be recovered from the command line. */
static int s_safe_video_mode = 0;

int config_get_safe_video_mode ( void ) {
    return s_safe_video_mode;
}

static char * config_alloc_string ( const char *value ) {

    char *copy;

    if ( !value )
        return NULL;

    copy = malloc ( strlen(value) + 1 );
    if ( !copy )
        return NULL;

    strcpy ( copy, value );

    return copy;

} /* end config_alloc_string */

static void config_replace_node_string ( char **node_string, const char *value ) {

    char *replacement;

    if ( !node_string )
        return;

    replacement = config_alloc_string ( value );
    if ( value && !replacement )
        return;

    if ( *node_string )
        free ( *node_string );

    *node_string = replacement;

} /* end config_replace_node_string */

static char * config_alloc_array_continuation_indent ( t_toml_node *tbl,
                                                       const char *tag ) {

    t_toml_node *key_node;
    t_toml_node *array_node;
    size_t length = 0;
    char *indent;

    key_node = toml_find_node ( tbl, tag );
    if ( !key_node || key_node->type != TOML_KEY )
        return config_alloc_string ( " " );

    if ( key_node->indent_initial )
        length += strlen ( key_node->indent_initial );
    if ( key_node->value )
        length += strlen ( key_node->value );
    if ( key_node->indent_to_equals )
        length += strlen ( key_node->indent_to_equals );

    length++; /* '=' */

    array_node = key_node->child;
    if ( array_node && array_node->indent_initial )
        length += strlen ( array_node->indent_initial );

    length++; /* '[' */
    length++; /* first value's leading space */

    indent = malloc ( length + 1 );
    if ( !indent )
        return NULL;

    memset ( indent, ' ', length );
    indent[length] = '\0';

    return indent;

} /* end config_alloc_array_continuation_indent */

static void config_set_input_array_layout ( t_toml_node *tbl, const char *tag ) {

    t_toml_node *key_node;
    t_toml_node *array_node;
    t_toml_node *end_node;

    key_node = toml_find_node ( tbl, tag );
    if ( !key_node || !key_node->child )
        return;

    array_node = key_node->child;
    if ( array_node->type != TOML_ARRAY )
        return;

    config_replace_node_string ( &array_node->to_newline, NULL );

    end_node = array_node->child;
    while ( end_node && end_node->type != TOML_ARRAY_END )
        end_node = end_node->next;

    if ( end_node )
        config_replace_node_string ( &end_node->indent_initial, " " );

} /* end config_set_input_array_layout */

static void config_set_palette_array_layout ( t_toml_node *tbl ) {

    t_toml_node *colors_node;
    t_toml_node *array_node;
    t_toml_node *end_node;

    colors_node = toml_find_node ( tbl, "colors" );
    if ( !colors_node || !colors_node->child )
        return;

    array_node = colors_node->child;
    if ( array_node->type != TOML_ARRAY )
        return;

    config_replace_node_string ( &array_node->to_newline, "\n" );

    if ( array_node->child && array_node->child->type != TOML_ARRAY_END )
        config_replace_node_string ( &array_node->child->indent_initial, "  " );

    end_node = array_node->child;
    while ( end_node && end_node->type != TOML_ARRAY_END )
        end_node = end_node->next;

    if ( end_node )
        config_replace_node_string ( &end_node->indent_initial, "\n  " );

} /* end config_set_palette_array_layout */

static void config_write_escaped_dq_string ( FILE *fp, const char *value ) {

    const unsigned char *p = (const unsigned char *)(value ? value : "");

    while ( *p ) {
        switch ( *p ) {
            case '\b': fputs ( "\\b", fp ); break;
            case '\t': fputs ( "\\t", fp ); break;
            case '\n': fputs ( "\\n", fp ); break;
            case '\f': fputs ( "\\f", fp ); break;
            case '\r': fputs ( "\\r", fp ); break;
            case '"':  fputs ( "\\\"", fp ); break;
            case '\\': fputs ( "\\\\", fp ); break;
            default:
                fputc ( *p, fp );
                break;
        }
        ++p;
    }
}

static const char * config_get_machine_key_from_type ( e_machine_type machine ) {

    switch ( machine ) {
        case MACHINE_TYPE_5200: return "5200";
        case MACHINE_TYPE_800:  return "800";
        default:                return "xl";
    }

} /* end config_get_machine_key_from_type */

/******************************************************************************
**  Function   :  config_init
**                                                
**  Objective  :  This checks and acts on command line arguments and reads 
**                parameters from the configuration file
**                                               
**  Parameters :  argc - number of input arguments from command line
**                argv - array of command line arguments
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_init ( int argc, char *argv[] ) {

    g_argv = argv;
    g_argc = argc;

    config.general.collection = NULL;
    config.video.shader = NULL;
    config.video.palette_name = NULL;
    config.video.renderer_name = NULL;
    config_clear_masks();

    /* First load internal defaults */
    config_load_internal_defaults();
    config_load_internal_media_defaults();

    /* Now try to open config file and assign */
    strcpy ( g_configfile, "kat5200.conf" );
    util_set_file_to_program_dir ( g_configfile );    

    /* We check for user overriding or resetting config file */
    config_pre_check_arguments ( argc, argv );

    /* Now we open the file and assign value */
    g_conftbl = config_parse_file ( g_configfile );

    /* Now we assign initial values */
    config_load_media_collections(g_conftbl);
    config_load_general ( g_conftbl );

    /* Now handle video, sound, and input config */
    config_load_video (g_conftbl, "All" );
    config_load_sound (g_conftbl, "All" );
    config_load_game_input (config.general.machine, g_conftbl, "All" );
    config_load_ui_input ( g_conftbl );

    /* Re-apply CLI launch target so it wins over saved config values */
    config_apply_cmdline_overrides ( argc, argv );

    config_schedule_profile_seed_sync();

    config_clear_masks();

} /* end config_init */

static void config_add_profile_seed_target ( t_profile_seed_target *targets,
        int *target_count, const char *seed_tree, char *dest_dir )
{
    int i;

    if ( !seed_tree || !dest_dir || !*dest_dir )
        return;

    for ( i = 0; i < *target_count; ++i ) {
        if ( !strcmp(targets[i].seed_tree, seed_tree) &&
             !strcmp(targets[i].dest_dir, dest_dir) )
            return;
    }

    targets[*target_count].seed_tree = seed_tree;
    targets[*target_count].dest_dir = dest_dir;
    ++(*target_count);
}

static void config_schedule_profile_seed_sync ( void )
{
    struct profile_seed_machine {
        e_machine_type machine;
        const char *seed_tree;
    };
    const struct profile_seed_machine machines[] = {
        { MACHINE_TYPE_5200, "profiles.5200" },
        { MACHINE_TYPE_800,  "profiles.8bit" },
        { MACHINE_TYPE_XL,   "profiles.8bit" }
    };
    t_profile_seed_target targets[3];
    char *zip_path = NULL;
    char *profiles[3] = { NULL, NULL, NULL };
    char *dests[3] = { NULL, NULL, NULL };
    int target_count = 0;
    size_t i;

    if ( util_first_use_seed_pending() )
        return;

    for ( i = 0; i < sizeof(machines) / sizeof(machines[0]); ++i ) {
        profiles[i] = config_get_profiles_dir ( machines[i].machine );
        if ( profiles[i] && *profiles[i] )
            dests[i] = util_create_program_path_alloc ( profiles[i] );
        config_add_profile_seed_target ( targets, &target_count,
                machines[i].seed_tree, dests[i] );
    }

    zip_path = util_create_seed_path_alloc ( "profiles.zip" );
    if ( target_count > 0 && zip_path )
        util_schedule_missing_profile_seed_sync ( zip_path, targets,
                target_count );

    free ( zip_path );
    for ( i = 0; i < sizeof(machines) / sizeof(machines[0]); ++i ) {
        free ( profiles[i] );
        free ( dests[i] );
    }
}

/******************************************************************************
**  Function   :  config_exit
**                                                
**  Objective  :  This frees up any allocated memory before exiting the app.
**                                               
**  Parameters :  NONE
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_exit ( void ) {

    t_collection *tmp_collection;

    /* Free toml trees */
    toml_free_node_tree(g_romconftbl);
    toml_free_node_tree(g_conftbl);

    /* Free config allocations */
    free( config.general.basic );
    free( config.general.basic_type );
    free( config.general.state );
    free( config.general.rom );
    free( config.general.states_dir );
    free( config.general.download_url );
    free( config.machine.name );
    free( config.machine.bios );
    free( config.machine.bios_type );
    free( config.machine.bios_dir );
    free( config.machine.rom_dir );
    free( config.machine.profiles_dir );
    free( config.video.palette_name );
    free( config.video.shader );
    free( config.video.renderer_name );

    /* Free current collection list */
    while ( (tmp_collection = config.general.collection) ) {
        config.general.collection = tmp_collection->next;
        free(tmp_collection->name);
        free(tmp_collection);
    }

} /* end config_exit */

/******************************************************************************
**  Function   :  config_get_global_cfg
**                                                
**  Objective  :  This returns the global config pointer
**                                               
**  Parameters :  NONE
**                                             
**  return     :  pointer to config
******************************************************************************/ 
t_config * config_get_global_cfg ( void ) {
    return &config;
}

t_toml_node * config_get_global_table ( void ) {
    return g_conftbl;
}

t_toml_node * config_get_rom_table ( void ) {
    return config_rom_table_matches_loaded_media () ? g_romconftbl : NULL;
}

static int config_rom_table_matches_loaded_media ( void ) {

    t_media *m = media_if_get_ptr ();

    return g_romconftbl && m && m->crc && g_crc == m->crc;
}

/******************************************************************************
**  Function   :  config_ensure_rom_table
**
**  Objective  :  Return the ROM specific config table, creating a profile for
**                the currently loaded ROM if one does not exist yet.  The ROM
**                load path deliberately does not create a profile, so this is
**                used by the GUI "Save for this ROM" handlers to lazily create
**                one on first save.
**
**  Parameters :  none
**
**  return     :  pointer to g_romconftbl, or NULL if no ROM is loaded
******************************************************************************/
t_toml_node * config_ensure_rom_table ( void ) {

    t_media *m;
    unsigned long long mask;
    char *base;

    /* Need an actually loaded ROM to create a profile from */
    m = media_if_get_ptr ();
    if ( !m || !m->crc )
        return NULL;

    /* Existing profile for the loaded ROM already loaded - nothing to do */
    if ( config_rom_table_matches_loaded_media () )
        return g_romconftbl;

    toml_free_node_tree ( g_romconftbl );
    g_romconftbl = NULL;
    g_crc = 0;
    g_romprofile_machine = MACHINE_TYPE_MIN;

    /* machine_type drives the profiles directory - make sure it is valid */
    if ( m->machine_type == MACHINE_TYPE_MIN )
        m->machine_type = config_get_global_cfg()->general.machine;

    /* Avoid a blank entry in the media list - fall back to the file basename */
    if ( (!m->title || !strlen(m->title)) && m->file && strlen(m->file) ) {
        base = strrchr ( m->file, util_get_dir_seperator_character() );
        util_alloc_string ( &m->title, base ? base + 1 : m->file );
    }

    mask = KCFG_MEDIA_TITLE | KCFG_MEDIA_FILE | KCFG_MEDIA_CRC |
           KCFG_MEDIA_SIZE | KCFG_MEDIA_MAPPING | KCFG_MEDIA_TYPE |
           KCFG_MEDIA_MACHINE;
    if ( m->md5 && strlen(m->md5) )
        mask |= KCFG_MEDIA_MD5;
    if ( m->sha1 && strlen(m->sha1) )
        mask |= KCFG_MEDIA_SHA1;

    /* Creates <crc>.conf, parses it into g_romconftbl, sets g_crc, writes file */
    config_save_media_settings ( m->crc, m, mask );

    return g_romconftbl;

} /* end config_ensure_rom_table */

char * config_get_filename ( void ) {
    return g_configfile;
}

/******************************************************************************
**  Function   :  config_load_internal_defaults
**                                                
**  Objective  :  This initializes config general and machine parameters to
**                defaults
**                                               
**  Parameters :  NONE
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_load_internal_defaults ( void ) {

    /* General */
    config.general.machine = MACHINE_TYPE_5200;
    config.general.throttle = 100;
    config.general.basic = NULL;
    util_alloc_string ( &config.general.basic_type, "rev_c" );
    config.general.basic_enable = 0;
    config.general.state = NULL;
    config.general.quick_state = 0;
    config.general.rom = NULL;
    config.general.rom_mapping = CART_MIN;
    config.general.sio_patch = 1;
    config.general.exit_status = 1;
    config.general.first_use = 0;
    config.general.start_with = START_GUI_LAST_ROM;
    util_alloc_string ( &config.general.states_dir, "profiles" );
    
    /* Machine */
    config.machine.ram = 16;
    config.machine.tv_standard = NTSC;
    util_alloc_string ( &config.machine.name, "5200" );
    util_alloc_string ( &config.machine.bios, "5200.bin" );
    util_alloc_string ( &config.machine.bios_type, "4port" );
    util_alloc_string ( &config.machine.rom_dir, "roms" );
    util_alloc_string ( &config.machine.bios_dir, "bios" );
    util_alloc_string ( &config.machine.profiles_dir, "profiles.5200" );
    config_set_machine_pot_defaults ( config.general.machine );
    
} /* end config_load_internal_defaults */

/******************************************************************************
**  Function   :  config_load_internal_media_defaults
**                                                
**  Objective  :  This initializes config sound, video, and inputs to defaults
**                                               
**  Parameters :  NONE
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_load_internal_media_defaults ( void ) {

    /* Pallete */
    make_default_palette(config.video.palette);

    /* Video */
    config.video.fullscreen = 0;
    config.video.height = 480;
    config.video.width = 640;
    config.video.wide_playfield = 0;
    config.video.ntsc_artifacts = 0;
    config.video.fixed_window = 0;
    config.video.window_scaling = WINDOW_SCALING_LETTERBOX;
    config.video.texture_scaling = TEXTURE_SCALING_PIXELATED;
    config.video.x = 0;
    config.video.y = 0;
    config.video.vsync = 1;
    free(config.video.shader);
    config.video.shader = NULL; 
    free(config.video.palette_name);
    config.video.palette_name = NULL;
    free(config.video.renderer_name);
    config.video.renderer_name = NULL;
    config.video.gui_scale = 0;

    config.video.ntsc_setup.hue = 0;
    config.video.ntsc_setup.saturation = 0;
    config.video.ntsc_setup.contrast = 0;
    config.video.ntsc_setup.brightness = 0;
    config.video.ntsc_setup.sharpness = 0;
    config.video.ntsc_setup.burst_phase = 0;
    config.video.ntsc_setup.hue_warping = 0;

    /* Sound */
    config.sound.enable = 1;
    config.sound.frequency = 48000;
    config.sound.samples = 2048;
    config.sound.buffer_size = KCFG_SND_BUFFER_AUTO;
    config.sound.gain = 100;

    /* Input */
    pc_set_controller_defaults ( );
    pc_set_keyboard_defaults ( );
    pc_set_ui_defaults ( );

} /* end config_load_internal_media_defaults */

void config_clear_masks() {
    
    config.general_mask = 0;
    config.sound_mask = 0;
    config.video_mask = 0;
    for(int i = 0; i < MAX_CONTROLLER; ++i)
        config.input_mask[i] = 0;
    config.keyboard_mask = 0;
}

void config_refresh_rom_source_masks(void) {

    config_clear_masks();

    if (!config_rom_table_matches_loaded_media())
        return;

    config_load_general(g_romconftbl);
    config_load_sound(g_romconftbl, "this");
    config_load_video(g_romconftbl, "this");
    config_load_game_input(config.general.machine, g_romconftbl, "this");
}

void config_restore_loaded_media_context(void) {

    t_media *m = media_if_get_ptr();
    unsigned int crc = (m && m->crc) ? m->crc : 0;

    toml_free_node_tree(g_romconftbl);
    g_romconftbl = NULL;
    g_crc = 0;
    g_romprofile_machine = MACHINE_TYPE_MIN;

    if (crc) {
        g_romconftbl = config_parse_for_media(crc);
        if (g_romconftbl)
            g_crc = crc;
    }

    config_refresh_rom_source_masks();
}

/******************************************************************************
**  Function   :  config_parse_for_media
**                                                
**  Objective  :  This checks for media specific configuration by crc. These
**                values will override the global config
**                                               
**  Parameters :  crc - checksum of the ROM to find specific config for
**                                             
**  return     :  pointer to TOML node with media specific data
******************************************************************************/ 
t_toml_node * config_parse_for_media ( unsigned int crc ) {

    char *file = 0;
    t_toml_node * tbl = NULL;

    file = media_if_get_profile_from_crc_with_machine ( crc,
            &g_romprofile_machine );

    if ( file )
        tbl =  config_parse_file ( file );
    else {
        char error[100];
        sprintf ( error, "No profile found for crc=0x%08x", crc );
        logger_log_message (LOG_INFO, error, "");
    }

    free(file);

    return tbl;

} /* end config_parse_for_media */

/******************************************************************************
**  Function   :  config_parse_file
**                                                
**  Objective  :  This parses the input config file and logs any errors found
**                                               
**  Parameters :  filename - name of file to open and parse
**                                             
**  return     :  toml node to configuration or NULL on failure
******************************************************************************/ 
t_toml_node * config_parse_file ( char *filename ) {

    t_toml_node * tbl;

    tbl = toml_parse_file(filename);

    if (!tbl ) {
        char error[2100];
        int err_val,line_no;
        sprintf ( error, "Failed to parse file: %s", filename );
        logger_log_message (LOG_ERROR, error, "");
        err_val = toml_get_error ( &line_no);
        sprintf ( error, "    %s at Line %d", toml_get_error_string(err_val), 
                line_no );
        logger_log_message (LOG_ERROR, error, "");
    }

    return tbl;

} /* end config_parse_file */

/******************************************************************************
**  Function   :  config_write_file
**                                                
**  Objective  :  This writes the input toml table to  file. Filename should
**                be value of root node
**                                               
**  Parameters :  tbl - table to write to file
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_write_file ( t_toml_node *tbl ) {

    /* After a factory reset the on-disk global conf holds the restored
     * defaults, but the in-memory tree is still the user's old config.  Refuse
     * to write it back (here and at exit) so the defaults survive to the next
     * restart.  ROM-config and other tables are unaffected.                   */
    if ( tbl == g_conftbl && util_factory_reset_done() )
        return;

    if ( !tbl || toml_write_file(tbl, tbl->value)  ) {
        char error[2100];
        sprintf ( error, "Failed to write config file: %s", 
                tbl ? tbl->value : "kat5200.conf" );
        logger_log_message (LOG_ERROR, error, "");
    }

} /* end config_write_file */

void config_load_directories_by_machine ( t_toml_node *tbl, e_machine_type m_type, 
    char **rom_dir, char **bios_dir ) {

    char tmp_key1[30];
    char tmp_key2[70];
    int result;
    int err_chk = (tbl == g_conftbl) ? 1 : 0;

    switch(m_type) {
        case MACHINE_TYPE_5200: strcpy(tmp_key1, "5200"); break;
        case MACHINE_TYPE_800: strcpy(tmp_key1, "800"); break;
        case MACHINE_TYPE_XL: strcpy(tmp_key1, "xl"); break;
    }

    sprintf ( tmp_key2, "machine.%s.bios_dir", tmp_key1);
    result = config_get_string ( tbl, tmp_key2, 
                         bios_dir, err_chk );

    sprintf ( tmp_key2, "machine.%s.rom_dir", tmp_key1);
    result = config_get_string ( tbl, tmp_key2, 
                         rom_dir, err_chk );

} /* end config_load_directories_by_machine */

void config_save_directories_by_machine ( t_toml_node *tbl, unsigned long long mask, 
    e_machine_type m_type, char *rom_dir, char *bios_dir ) {

    char tmp_key1[512];
    char tmp_key2[512];
    char tmp_string[128];
    int err_chk = (tbl == g_conftbl) ? 1 : 0;

    switch(m_type) {
        case MACHINE_TYPE_5200: strcpy(tmp_key1, "5200"); break;
        case MACHINE_TYPE_800: strcpy(tmp_key1, "800"); break;
        case MACHINE_TYPE_XL: strcpy(tmp_key1, "xl"); break;
    }
    if ( mask & KCFG_MACHINE_BIOS_DIR ) {
        sprintf ( tmp_key2, "machine.%s.bios_dir", tmp_key1);
        config_set_string ( tbl, tmp_key2, bios_dir, err_chk );
    }

    if ( mask & KCFG_MACHINE_ROM_DIR ) {
        sprintf ( tmp_key2, "machine.%s.rom_dir", tmp_key1);
        config_set_string ( tbl, tmp_key2, rom_dir, err_chk );
    }
}

static int config_path_is_absolute ( const char *path ) {

    return path && path[0] &&
        ( path[0] == '/' || path[0] == '\\' || path[1] == ':' );

} /* end config_path_is_absolute */

static int config_path_has_dir_component ( const char *path ) {

    return path && ( strrchr(path, '/') || strrchr(path, '\\') );

} /* end config_path_has_dir_component */

static int config_stat_is_dir ( const char *path ) {

    struct stat file_status;

    return path && path[0] && !stat(path, &file_status) &&
        S_ISDIR(file_status.st_mode);

} /* end config_stat_is_dir */

static int config_stat_exists ( const char *path ) {

    struct stat file_status;

    return path && path[0] && !stat(path, &file_status);

} /* end config_stat_exists */

static char * config_alloc_resolved_path ( const char *path, int use_current_dir ) {

    char resolved[KAT_PATH_MAX];
    const char *program_path;

    if ( !path || !path[0] )
        return NULL;

    snprintf ( resolved, sizeof(resolved), "%s", path );

    if ( config_path_is_absolute(resolved) )
        return config_alloc_string(resolved);

    if ( use_current_dir ) {
        util_set_file_to_current_dir(resolved);
    }
    else {
        program_path = util_get_program_path();
        if ( program_path && program_path[0] )
            util_make_path((char *)program_path, path, resolved);
        else
            util_set_file_to_current_dir(resolved);
    }

    return config_alloc_string(resolved);

} /* end config_alloc_resolved_path */

static char * config_alloc_existing_dir_candidate ( const char *path,
        int use_current_dir ) {

    char *candidate = config_alloc_resolved_path(path, use_current_dir);

    if ( candidate && config_stat_is_dir(candidate) )
        return candidate;

    free(candidate);
    return NULL;

} /* end config_alloc_existing_dir_candidate */

static char * config_alloc_file_dir_candidate ( const char *path,
        int use_current_dir ) {

    char *candidate;
    int explicit_parent;

    if ( !path || !path[0] )
        return NULL;

    explicit_parent = config_path_is_absolute(path) ||
        config_path_has_dir_component(path);
    candidate = config_alloc_resolved_path(path, use_current_dir);

    if ( !candidate )
        return NULL;

    if ( config_stat_is_dir(candidate) )
        return candidate;

    if ( config_stat_exists(candidate) || explicit_parent ) {
        util_strip_file_to_directory(candidate);
        if ( config_stat_is_dir(candidate) )
            return candidate;
    }

    free(candidate);
    return NULL;

} /* end config_alloc_file_dir_candidate */

char * config_alloc_rom_browser_start_dir ( void ) {

    char *dir;

    dir = config_alloc_file_dir_candidate(config.general.rom, 1);
    if ( dir )
        return dir;

    dir = config_alloc_existing_dir_candidate(config.machine.rom_dir, 0);
    if ( dir )
        return dir;

    return config_alloc_file_dir_candidate(config.machine.bios, 0);

} /* end config_alloc_rom_browser_start_dir */

/******************************************************************************
**  Function   :  config_load_machine_settings
**
**  Objective  :  Load the machine-specific config block for the already
**                selected config.general.machine.
******************************************************************************/
void config_load_machine_settings ( t_toml_node *tbl ) {

    char tmp_key2[552];
    char *tmp_string = 0;
    const char *tmp_key1 = config_get_machine_key_from_type ( config.general.machine );
    int err_chk = (tbl == g_conftbl) ? 1 : 0;
    int result;

    if ( tbl == g_conftbl )
        config_set_machine_pot_defaults ( config.general.machine );

    sprintf ( tmp_key2, "machine.%s.bios", tmp_key1);
    result = config_get_string ( tbl, tmp_key2,
                         &config.machine.bios_type, err_chk );
    if (!result)
        config.general_mask |= KCFG_MACHINE_BIOS_TYP;

    sprintf ( tmp_key2, "machine.%s.ram", tmp_key1);
    config_get_int ( tbl, tmp_key2,
                         &config.machine.ram, err_chk );

     /* The TV standard is a string in the conf file, convert to int */
    sprintf ( tmp_key2, "machine.%s.tv_standard", tmp_key1);
    result = config_get_string ( tbl, tmp_key2,
                         &tmp_string, err_chk );
    if (!result)
        config.general_mask |= KCFG_MACHINE_TV_STD;

    if ( tmp_string ) {
        char *p = tmp_string;
        for ( ; *p; ++p) *p = tolower(*p);
        if (strstr(tmp_string, "ntsc"))
            config.machine.tv_standard = NTSC;
        else
            config.machine.tv_standard = PAL;
    }

    config_load_os_file ( tbl );

    sprintf ( tmp_key2, "machine.%s.name", tmp_key1);
    result = config_get_string ( tbl, tmp_key2,
                         &config.machine.name, err_chk );
    if (!result)
        config.general_mask |= KCFG_MACHINE_NAME;

    sprintf ( tmp_key2, "machine.%s.bios_dir", tmp_key1);
    result = config_get_string ( tbl, tmp_key2,
                         &config.machine.bios_dir, err_chk );
    if (!result)
        config.general_mask |= KCFG_MACHINE_BIOS_DIR;

    sprintf ( tmp_key2, "machine.%s.rom_dir", tmp_key1);
    result = config_get_string ( tbl, tmp_key2,
                         &config.machine.rom_dir, err_chk );
    if (!result)
        config.general_mask |= KCFG_MACHINE_ROM_DIR;

    sprintf ( tmp_key2, "machine.%s.profiles_dir", tmp_key1);
    result = config_get_string ( tbl, tmp_key2,
                         &config.machine.profiles_dir, err_chk );
    if (!result)
        config.general_mask |= KCFG_MACHINE_PROF_DIR;

    config_load_machine_pot_value ( tbl, tmp_key1, "joystick_hpot_center",
            &config.machine.joystick_hpot_center, KCFG_MACHINE_JOY_H_CTR, err_chk );
    config_load_machine_pot_value ( tbl, tmp_key1, "joystick_vpot_center",
            &config.machine.joystick_vpot_center, KCFG_MACHINE_JOY_V_CTR, err_chk );
    config_load_machine_pot_value ( tbl, tmp_key1, "joystick_hpot_low",
            &config.machine.joystick_hpot_low, KCFG_MACHINE_JOY_H_LOW, err_chk );
    config_load_machine_pot_value ( tbl, tmp_key1, "joystick_hpot_high",
            &config.machine.joystick_hpot_high, KCFG_MACHINE_JOY_H_HIGH, err_chk );
    config_load_machine_pot_value ( tbl, tmp_key1, "joystick_vpot_low",
            &config.machine.joystick_vpot_low, KCFG_MACHINE_JOY_V_LOW, err_chk );
    config_load_machine_pot_value ( tbl, tmp_key1, "joystick_vpot_high",
            &config.machine.joystick_vpot_high, KCFG_MACHINE_JOY_V_HIGH, err_chk );
    if ( config.general.machine == MACHINE_TYPE_5200 ) {
        config_load_machine_pot_value ( tbl, tmp_key1, "trackball_hpot_center",
                &config.machine.trackball_hpot_center, KCFG_MACHINE_TRK_H_CTR, err_chk );
        config_load_machine_pot_value ( tbl, tmp_key1, "trackball_vpot_center",
                &config.machine.trackball_vpot_center, KCFG_MACHINE_TRK_V_CTR, err_chk );
        config_load_machine_pot_value ( tbl, tmp_key1, "trackball_dpot_min",
                &config.machine.trackball_dpot_min, KCFG_MACHINE_TRK_DPOT_MIN, err_chk );
        config_load_machine_pot_value ( tbl, tmp_key1, "trackball_dpot_max",
                &config.machine.trackball_dpot_max, KCFG_MACHINE_TRK_DPOT_MAX, err_chk );
    }

    if (tmp_string)
        free(tmp_string);

} /* config_load_machine_settings */

/******************************************************************************
**  Function   :  config_load_basic_settings
**
**  Objective  :  Load the selected BASIC type, file, and enable flag.
******************************************************************************/
void config_load_basic_settings ( t_toml_node *tbl ) {

    int err_chk = (tbl == g_conftbl) ? 1 : 0;
    int result;

    result = config_get_string ( tbl, "general.basic_type",
                         &config.general.basic_type, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_BASIC_TYP;

    /* Load the active BASIC rom file from its bios.basic.{rev} slot */
    config_load_basic_file ( tbl );

    result = config_get_bool ( tbl, "general.basic_enable",
                         &config.general.basic_enable, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_BASIC_EN;

} /* config_load_basic_settings */

void config_refresh_machine_settings ( void ) {

    if ( g_conftbl ) {
        config_load_machine_settings ( g_conftbl );
        if ( config_rom_table_matches_loaded_media () )
            config_load_machine_settings ( g_romconftbl );

        config_load_basic_settings ( g_conftbl );
        if ( config_rom_table_matches_loaded_media () )
            config_load_basic_settings ( g_romconftbl );
    }

} /* config_refresh_machine_settings */

/******************************************************************************
**  Function   :  config_load_general
**                                                
**  Objective  :  This reads from the global toml config node the general 
**                configuration parameters
**                                               
**  Parameters :  tbl - parsed toml table, this can be either global config
**                      or rom specific file
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_load_general ( t_toml_node *tbl ) {

    char *tmp_string = 0;
    int err_chk = (tbl == g_conftbl) ? 1 : 0;
    int result;

    result = config_get_string ( tbl, "general.machine", 
                         &tmp_string, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_MACHINE;

    if (tmp_string) {
        if (strstr(tmp_string, "5200"))
            config.general.machine = MACHINE_TYPE_5200;
        else if (strstr(tmp_string, "800"))
            config.general.machine = MACHINE_TYPE_800;
        else
            config.general.machine = MACHINE_TYPE_XL;
    }

    config_load_machine_settings ( tbl );

    result = config_get_int ( tbl, "general.throttle", 
                         &config.general.throttle, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_THROTTLE;

    config_load_basic_settings ( tbl );

    result = config_get_string ( tbl, "general.state", 
                         &config.general.state, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_STATE;

    result = config_get_string ( tbl, "general.states_dir",
                         &config.general.states_dir, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_STTS_DIR;

    result = config_get_string ( tbl, "general.download",
                         &config.general.download_url, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_DL_URL;

    result = config_get_string ( tbl, "general.rom", 
                         &config.general.rom, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_ROM;

    result = config_get_bool ( tbl, "general.sio_patch", 
                         &config.general.sio_patch, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_SIOP_EN;

    result = config_get_int ( tbl, "general.quick_state", 
                         &config.general.quick_state, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_QK_STAT;

    result = config_get_string ( tbl, "general.start_with", 
                         &tmp_string, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_START_WT;

    if ( tmp_string ) {
        util_strlwr(tmp_string);
        if ( strstr(tmp_string, "file_browser") ) 
            config.general.start_with = START_GUI_BROWSER;
        else if ( strstr(tmp_string, "rom_list") ) 
            config.general.start_with = START_GUI_ROMS;
        else
            config.general.start_with = START_GUI_LAST_ROM;
    }

    result = config_get_bool ( tbl, "general.first_use", 
                         &config.general.first_use, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_FST_USE;

    result = config_get_int ( tbl, "general.exit_status", 
                         &config.general.exit_status, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_EXIT;

    /* Free any tmp memory used */
    if (tmp_string)
        free(tmp_string);

} /* config_load_general */

/******************************************************************************
**  Function   :  config_save_general
**                                                
**  Objective  :  This writes general configuration parameters to the input
**                table and then writes the file
**                                               
**  Parameters :  tbl - parsed toml table, this can be either global config
**                      or rom specific file
**                mask - which parameters to set  
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_save_general ( t_toml_node *tbl, unsigned long long mask ) {

    char tmp_key2[512];
    const char *tmp_key1 = config_get_machine_key_from_type ( config.general.machine );
    int err_chk = (tbl == g_conftbl) ? 1 : 0;

    if ( mask & KCFG_GENERAL_MACHINE )
        config_set_string ( tbl, "general.machine", tmp_key1, err_chk );

    if ( mask & KCFG_MACHINE_BIOS_TYP ) {
        sprintf ( tmp_key2, "machine.%s.bios", tmp_key1);
        config_set_string ( tbl, tmp_key2, 
                             config.machine.bios_type, err_chk );
    }

    if ( mask & KCFG_MACHINE_BIOS ) {
        sprintf ( tmp_key2, "bios.%s.%s", tmp_key1,
                  config_resolve_bios_tag ( config.machine.bios_type,
                                            config.machine.tv_standard ) );
        config_set_string ( tbl, tmp_key2,
                             config.machine.bios, err_chk );
    }

    if ( mask & KCFG_MACHINE_RAM ) {
        sprintf ( tmp_key2, "machine.%s.ram", tmp_key1);
        config_set_int ( tbl, tmp_key2, config.machine.ram, err_chk );
    }

    if ( mask & KCFG_MACHINE_TV_STD ) {
        sprintf ( tmp_key2, "machine.%s.tv_standard", tmp_key1);
        config_set_string ( tbl, tmp_key2, 
                config.machine.tv_standard == NTSC ? "ntsc" : "pal", err_chk );
    }

    if ( mask & KCFG_MACHINE_NAME ) {
        sprintf ( tmp_key2, "machine.%s.name", tmp_key1);
        config_set_string ( tbl, tmp_key2, config.machine.name, err_chk );
    }

    if ( mask & KCFG_MACHINE_BIOS_DIR ) {
        sprintf ( tmp_key2, "machine.%s.bios_dir", tmp_key1);
        config_set_string ( tbl, tmp_key2, config.machine.bios_dir, err_chk );
    }

    if ( mask & KCFG_MACHINE_ROM_DIR ) {
        sprintf ( tmp_key2, "machine.%s.rom_dir", tmp_key1);
        config_set_string ( tbl, tmp_key2, config.machine.rom_dir, err_chk );
    }

    if ( mask & KCFG_MACHINE_PROF_DIR ) {
        sprintf ( tmp_key2, "machine.%s.profiles_dir", tmp_key1);
        config_set_string ( tbl, tmp_key2, config.machine.profiles_dir, err_chk );
    }

    config_save_machine_pot_value ( tbl, tmp_key1, "joystick_hpot_center",
            &config.machine.joystick_hpot_center, KCFG_MACHINE_JOY_H_CTR, mask, err_chk );
    config_save_machine_pot_value ( tbl, tmp_key1, "joystick_vpot_center",
            &config.machine.joystick_vpot_center, KCFG_MACHINE_JOY_V_CTR, mask, err_chk );
    config_save_machine_pot_value ( tbl, tmp_key1, "joystick_hpot_low",
            &config.machine.joystick_hpot_low, KCFG_MACHINE_JOY_H_LOW, mask, err_chk );
    config_save_machine_pot_value ( tbl, tmp_key1, "joystick_hpot_high",
            &config.machine.joystick_hpot_high, KCFG_MACHINE_JOY_H_HIGH, mask, err_chk );
    config_save_machine_pot_value ( tbl, tmp_key1, "joystick_vpot_low",
            &config.machine.joystick_vpot_low, KCFG_MACHINE_JOY_V_LOW, mask, err_chk );
    config_save_machine_pot_value ( tbl, tmp_key1, "joystick_vpot_high",
            &config.machine.joystick_vpot_high, KCFG_MACHINE_JOY_V_HIGH, mask, err_chk );
    config_save_machine_pot_value ( tbl, tmp_key1, "trackball_hpot_center",
            &config.machine.trackball_hpot_center, KCFG_MACHINE_TRK_H_CTR, mask, err_chk );
    config_save_machine_pot_value ( tbl, tmp_key1, "trackball_vpot_center",
            &config.machine.trackball_vpot_center, KCFG_MACHINE_TRK_V_CTR, mask, err_chk );
    config_save_machine_pot_value ( tbl, tmp_key1, "trackball_dpot_min",
            &config.machine.trackball_dpot_min, KCFG_MACHINE_TRK_DPOT_MIN, mask, err_chk );
    config_save_machine_pot_value ( tbl, tmp_key1, "trackball_dpot_max",
            &config.machine.trackball_dpot_max, KCFG_MACHINE_TRK_DPOT_MAX, mask, err_chk );

    if ( mask & KCFG_GENERAL_THROTTLE ) 
        config_set_int ( tbl, "general.throttle", config.general.throttle, err_chk );
    if ( mask & KCFG_GENERAL_BASIC ) {
        sprintf ( tmp_key2, "bios.basic.%s",
                  config.general.basic_type ? config.general.basic_type : "rev_c" );
        config_set_string ( tbl, tmp_key2, config.general.basic, err_chk );
    }
    if ( mask & KCFG_GENERAL_BASIC_TYP )
        config_set_string ( tbl, "general.basic_type",
                             config.general.basic_type, err_chk );
    if ( mask & KCFG_GENERAL_STATE ) 
        config_set_string ( tbl, "general.state", config.general.state, err_chk );
    if ( mask & KCFG_GENERAL_STTS_DIR ) 
        config_set_string ( tbl, "general.states_dir", 
                config.general.states_dir, err_chk );

    if ( mask & KCFG_GENERAL_ROM ) 
        config_set_string ( tbl, "general.rom", config.general.rom, err_chk );

    if ( mask & KCFG_GENERAL_BASIC_EN ) 
        config_set_bool ( tbl, "general.basic_enable", 
                             config.general.basic_enable, err_chk );

    if ( mask & KCFG_GENERAL_SIOP_EN ) 
        config_set_bool ( tbl, "general.sio_patch", 
                             config.general.sio_patch, err_chk );

    if ( mask & KCFG_GENERAL_QK_STAT )
        config_set_int ( tbl, "general.quick_state",
                             config.general.quick_state, err_chk );

    if ( mask & KCFG_GENERAL_START_WT ) { 
        char tmp_string[20];
        switch (config.general.start_with) {
            case START_GUI_BROWSER: strcpy(tmp_string,"file_browser"); break;
            case START_GUI_LAST_ROM: strcpy(tmp_string,"last_rom"); break;
            case START_GUI_ROMS: strcpy(tmp_string,"rom_list"); break;
        }
        config_set_string ( tbl, "general.start_with", 
                             tmp_string, err_chk );
    }

    if ( mask & KCFG_GENERAL_FST_USE ) 
        config_set_bool ( tbl, "general.first_use", 
                             config.general.first_use, err_chk );

    if ( mask & KCFG_GENERAL_EXIT ) 
        config_set_int ( tbl, "general.exit_status", 
                             config.general.exit_status, err_chk );

    config_write_file ( tbl );

} /* config_save_general */

static e_machine_type config_machine_from_os_type(int os_type)
{
    switch (os_type) {
        case OS_TYPE_5200_4PORT:
        case OS_TYPE_5200_2PORT:
            return MACHINE_TYPE_5200;
        case OS_TYPE_A:
        case OS_TYPE_B:
        case OS_TYPE_B_NTSC:
        case OS_TYPE_B_PAL:
            return MACHINE_TYPE_800;
        default:
            return MACHINE_TYPE_XL;
    }
}

static int config_os_type_is_current(int os_type)
{
    const char *type = config_get_os_type_string(os_type);

    /*
     * The physical Rev B slots are "current" when the logical "rev_b" is
     * selected on the 800 and the TV standard picks that variant.  This lets
     * auto-detect's config_save_os(OS_TYPE_B_NTSC/PAL) trigger a live reload.
     */
    if (os_type == OS_TYPE_B_NTSC || os_type == OS_TYPE_B_PAL) {
        int want_pal = (os_type == OS_TYPE_B_PAL);
        return config.general.machine == MACHINE_TYPE_800 &&
               config.machine.bios_type &&
               !strcmp(config.machine.bios_type, "rev_b") &&
               (config.machine.tv_standard == PAL) == want_pal;
    }

    return type &&
           config.general.machine == config_machine_from_os_type(os_type) &&
           config.machine.bios_type &&
           !strcmp(config.machine.bios_type, type);
}

/******************************************************************************
**  Function   :  config_save_os
**                                                
**  Objective  :  This writes the input file to the configuration based on
**                os type
**                                               
**  Parameters :  os_type - the bios type per the system
**                file    - file name for the bios to save
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_save_os ( int os_type, const char *file ) {
    
    char tag[20];
    t_toml_node *tbl;

    tbl = toml_find_node ( g_conftbl, "bios" );

    if ( !tbl )
        return;

    switch ( os_type ) {
        case OS_TYPE_5200_4PORT: strcpy(tag,"5200.4port"); break;
        case OS_TYPE_5200_2PORT: strcpy(tag,"5200.2port"); break;
        case OS_TYPE_A: strcpy(tag,"800.rev_a"); break;
        case OS_TYPE_B: strcpy(tag,"800.rev_b"); break;
        case OS_TYPE_B_NTSC: strcpy(tag,"800.rev_b_ntsc"); break;
        case OS_TYPE_B_PAL: strcpy(tag,"800.rev_b_pal"); break;
        case OS_TYPE_REV_1: strcpy(tag,"xl.rev_1"); break;
        case OS_TYPE_REV_2: strcpy(tag,"xl.rev_2"); break;
        case OS_TYPE_REV_3: strcpy(tag,"xl.rev_3"); break;
        case OS_TYPE_REV_4: strcpy(tag,"xl.rev_4"); break;
        case OS_TYPE_REV_10: strcpy(tag,"xl.rev_10"); break;
        case OS_TYPE_REV_11: strcpy(tag,"xl.rev_11"); break;
    }

    config_set_string ( tbl, tag, file, 1 );
    if (config_os_type_is_current(os_type)) {
        config_load_os_file(g_conftbl);
        if (config_rom_table_matches_loaded_media())
            config_load_os_file(g_romconftbl);
    }

    config_write_file ( g_conftbl );

} /* config_save_os */

/******************************************************************************
**  Function   :  config_get_basic_type_string
**
**  Objective  :  Return the constant basic rev tag string for the value.
******************************************************************************/
const char * config_get_basic_type_string ( int basic_type ) {

    switch ( basic_type ) {
        case BASIC_TYPE_REV_A: return "rev_a";
        case BASIC_TYPE_REV_B: return "rev_b";
        case BASIC_TYPE_REV_C: return "rev_c";
    }

    return "rev_c";

} /* config_get_basic_type_string */

/******************************************************************************
**  Function   :  config_get_basic_type_from_string
**
**  Objective  :  Return the basic rev from its tag string.  Null/unknown
**                safely defaults to Rev C.
******************************************************************************/
int config_get_basic_type_from_string ( const char *type ) {

    if ( type ) {
        if ( strstr ( type, "rev_a" ) )
            return BASIC_TYPE_REV_A;
        if ( strstr ( type, "rev_b" ) )
            return BASIC_TYPE_REV_B;
        if ( strstr ( type, "rev_c" ) )
            return BASIC_TYPE_REV_C;
    }

    return BASIC_TYPE_REV_C;

} /* config_get_basic_type_from_string */

static int config_basic_type_is_current ( int basic_type ) {

    const char *type = config_get_basic_type_string ( basic_type );

    return type && config.general.basic_type &&
           !strcmp ( config.general.basic_type, type );

} /* config_basic_type_is_current */

/******************************************************************************
**  Function   :  config_load_basic_file
**
**  Objective  :  Load the active BASIC rom file from its bios.basic.{rev}
**                slot into config.general.basic.  Mirror of
**                config_load_os_file.
******************************************************************************/
int config_load_basic_file ( t_toml_node *tbl ) {

    char tmp_key[80];
    int err_chk = (tbl == g_conftbl) ? 1 : 0;

    sprintf ( tmp_key, "bios.basic.%s",
              config.general.basic_type ? config.general.basic_type : "rev_c" );
    int result = config_get_string ( tbl, tmp_key,
                         &config.general.basic, err_chk );
    if (!result)
        config.general_mask |= KCFG_GENERAL_BASIC;

    return result;

} /* config_load_basic_file */

/******************************************************************************
**  Function   :  config_save_basic
**
**  Objective  :  Write a BASIC rom file to its bios.basic.{rev} slot.
**                Mirror of config_save_os.
******************************************************************************/
void config_save_basic ( int basic_type, const char *file ) {

    char tag[20];
    t_toml_node *tbl;

    tbl = toml_find_node ( g_conftbl, "bios" );

    if ( !tbl )
        return;

    switch ( basic_type ) {
        case BASIC_TYPE_REV_A: strcpy(tag,"basic.rev_a"); break;
        case BASIC_TYPE_REV_B: strcpy(tag,"basic.rev_b"); break;
        case BASIC_TYPE_REV_C: strcpy(tag,"basic.rev_c"); break;
    }

    config_set_string ( tbl, tag, file, 1 );
    if (config_basic_type_is_current(basic_type)) {
        config_load_basic_file(g_conftbl);
        if (config_rom_table_matches_loaded_media())
            config_load_basic_file(g_romconftbl);
    }

    config_write_file ( g_conftbl );

} /* config_save_basic */

/******************************************************************************
**  Function   :  config_load_os_file
**                                                
**  Objective  :  This loads the os file, should be called when type changes
**                os type
**                                               
**  Parameters :  tbl - parsed toml table, this can be either global config
**                      or rom specific file
**                                             
**  return     :  NONE
*****************************************************************************/ 
int config_load_os_file ( t_toml_node *tbl ) {

    char tmp_key2[80];
    const char *tmp_key1 = config_get_machine_key_from_type ( config.general.machine );
    int err_chk = (tbl == g_conftbl) ? 1 : 0;

    sprintf ( tmp_key2, "bios.%s.%s", tmp_key1,
              config_resolve_bios_tag ( config.machine.bios_type,
                                        config.machine.tv_standard ) );
    int result = config_get_string ( tbl, tmp_key2,
                         &config.machine.bios, err_chk );
    if (!result)
        config.general_mask |= KCFG_MACHINE_BIOS;

    return result;

} /* config_load_os_file */

/******************************************************************************
**  Function   :  config_get_os_type_string
**                                                
**  Objective  :  This returns the constant os type string from the value
**                                               
**  Parameters :  os_type - the bios type per the system
**                file    - file name for the bios to save
**                                             
**  return     :  NONE
******************************************************************************/ 
const char * config_get_os_type_string ( int os_type ) {
    
    switch ( os_type ) {
        case OS_TYPE_5200_4PORT: return "4port"; break;
        case OS_TYPE_5200_2PORT: return "2port"; break;
        case OS_TYPE_A: return "rev_a"; break;
        case OS_TYPE_B: return "rev_b"; break;
        case OS_TYPE_B_NTSC: return "rev_b_ntsc"; break;
        case OS_TYPE_B_PAL: return "rev_b_pal"; break;
        case OS_TYPE_REV_1: return "rev_1"; break;
        case OS_TYPE_REV_2: return "rev_2"; break;
        case OS_TYPE_REV_3: return "rev_3"; break;
        case OS_TYPE_REV_4: return "rev_4"; break;
        case OS_TYPE_REV_10: return "rev_10"; break;
        case OS_TYPE_REV_11: return "rev_11"; break;
    }

} /* config_get_os_type_string */

/******************************************************************************
**  Function   :  config_get_os_type_from_string
**                                                
**  Objective  :  This returns the os type from the input os type string
**                os type
**                                               
**  Parameters :  type - the bios type string
**                                             
**  return     :  bios type
*****************************************************************************/ 
int config_get_os_type_from_string ( const char *type ) {

    if ( strstr(type, "4port") )
        return OS_TYPE_5200_4PORT;
    if ( strstr(type, "2port") )
        return OS_TYPE_5200_2PORT;
    if ( strstr(type, "rev_a") )
        return OS_TYPE_A;
    if ( strstr(type, "rev_b_pal") )
        return OS_TYPE_B_PAL;
    if ( strstr(type, "rev_b_ntsc") )
        return OS_TYPE_B_NTSC;
    if ( strstr(type, "rev_b") )
        return OS_TYPE_B;
    if ( strstr(type, "rev_10") )
        return OS_TYPE_REV_10;
    if ( strstr(type, "rev_11") )
        return OS_TYPE_REV_11;
    if ( strstr(type, "rev_1") )
        return OS_TYPE_REV_1;
    if ( strstr(type, "rev_2") )
        return OS_TYPE_REV_2;
    if ( strstr(type, "rev_3") )
        return OS_TYPE_REV_3;
    if ( strstr(type, "rev_4") )
        return OS_TYPE_REV_4;

    return OS_TYPE_5200_4PORT;

} /* end config_get_os_type_from_string */

/******************************************************************************
**  Function   :  config_resolve_bios_tag
**
**  Objective  :  Resolve a (possibly logical) bios_type into the physical
**                slot tag used as the bios.{machine}.{tag} key.  The only
**                logical value is "rev_b", which maps to "rev_b_ntsc" or
**                "rev_b_pal" depending on the TV standard.  Everything else
**                passes through unchanged.
**
**  Parameters :  bios_type   - the stored bios type (e.g. "rev_b", "rev_2")
**                tv_standard - NTSC or PAL
**
**  return     :  physical slot tag string
******************************************************************************/
const char * config_resolve_bios_tag ( const char *bios_type, int tv_standard ) {

    if ( bios_type && !strcmp ( bios_type, "rev_b" ) )
        return ( tv_standard == PAL ) ? "rev_b_pal" : "rev_b_ntsc";

    return bios_type;

} /* end config_resolve_bios_tag */

/******************************************************************************
**  Function   :  config_get_profiles_dir
**                                                
**  Objective  :  This gets the ROM/media profile folder based on the machine
**                type
**                                               
**  Parameters :  machine - 5200, 800, or XL
**                                             
**  return     :  profiles directory (allocated, caller needs to free)
******************************************************************************/ 
char * config_get_profiles_dir ( e_machine_type machine ) {

    char *tmp_string = 0;
    char tmp_key2[50];

    if ( machine == MACHINE_TYPE_5200 )
        strcpy ( tmp_key2, "machine.5200.profiles_dir");
    else if ( machine == MACHINE_TYPE_XL )
        strcpy ( tmp_key2, "machine.xl.profiles_dir");
    else
        strcpy ( tmp_key2, "machine.800.profiles_dir");

    config_get_string ( g_conftbl, tmp_key2, 
                         &tmp_string, 1 );

    return tmp_string;

} /* end config_get_profiles_dir */

/******************************************************************************
**  Function   :  config_load_media_collections
**                                                
**  Objective  :  This loads the media collection strings and values from the 
**                global configuration file
**                                               
**  Parameters :  tbl - parsed toml table, this should always be global cfg
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_load_media_collections ( t_toml_node *tbl ) {

    t_toml_node *array_node, *collect_node;
    char *tmp_string = 0;
    char *tmp_low_string = 0;
    t_collection *tmp_collection = NULL;

    collect_node = toml_find_node ( tbl, "media.collections" );
    array_node = toml_find_node ( tbl, "media.collections.strings" );

    if (!array_node)
        return;

    /* Free current collection list */
    while ( (tmp_collection = config.general.collection) ) {
        config.general.collection = tmp_collection->next;
        free(tmp_collection->name);
        free(tmp_collection);
    }

    /* Get collections from config */
    for ( int i = 0; i < toml_get_num_array_elements(array_node); ++i ) {
        tmp_string = toml_get_array_value_by_index(array_node, NULL, i); 
        if ( tmp_string ) {
            if ( i == 0 ) {
                config.general.collection = malloc (sizeof(t_collection) );
                tmp_collection = config.general.collection;
            }
            else {
                tmp_collection->next = malloc (sizeof(t_collection) );
                tmp_collection = tmp_collection->next;
            }
                 
            tmp_collection->next = NULL;
            tmp_collection->name = malloc ( strlen(tmp_string)+1 );
            tmp_low_string = malloc ( strlen(tmp_string)+1 );
            strcpy ( tmp_collection->name, tmp_string );
            strcpy ( tmp_low_string, tmp_string );
            util_strlwr ( tmp_low_string );
            config_get_long ( collect_node, tmp_low_string, &tmp_collection->value, 0 );
            free ( tmp_low_string );
        }
    }

} /* end config_load_media_collections */

/******************************************************************************
**  Function   :  config_save_media_collections
**                                                
**  Objective  :  This writes media collection strings and values to the input
**                table and then writes the file
**                                               
**  Parameters :  tbl - parsed toml table, this should always be global cfg
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_save_media_collections ( t_toml_node *tbl ) {

    t_collection *tmp_collection = NULL;
    t_toml_node *p_tbl;
    char *tmp_low_string;
    char value[10];

    p_tbl = toml_find_node ( tbl, "media.collections" );

    if ( !p_tbl ) {

        p_tbl = toml_add_table ( tbl, "media.collections", TOML_TABLE, "" );
    }

    /* Let's just delete all the current keys */
    if ( p_tbl->child )
        toml_free_node_tree(p_tbl->child->next);

    tmp_collection = config.general.collection;
    while ( tmp_collection ) {

        /* Must have valid name */
        if ( !tmp_collection->name || !strlen(tmp_collection->name) ) {
            tmp_collection = tmp_collection->next;
            continue;
        }

        /* Add string to array */
        int result = toml_insert_array_value_by_index ( p_tbl, "strings", 
                tmp_collection->name, " ", " ", " ", -1 );

        if (result) {

            toml_start_array_with_value ( p_tbl, "strings", TOML_DQ_STRING, 
                                        tmp_collection->name, " ", " " );
        }
        
        /* Add key with value */
        tmp_low_string = malloc ( strlen(tmp_collection->name)+1 );
        strcpy ( tmp_low_string, tmp_collection->name );
        util_strlwr ( tmp_low_string );
        sprintf ( value, "0x%06lx", tmp_collection->value );
        toml_add_key ( p_tbl, tmp_low_string, TOML_DQ_STRING, value, "", " " );
        free ( tmp_low_string );

        tmp_collection = tmp_collection->next;
    }

} /* end config_save_media_collections */

/******************************************************************************
**  Function   :  config_save_media_to_collection
**                                                
**  Objective  :  This writes the media title and crc to the collection list
**                file.  Each collection has a file in the profile directory.
**                                               
**  Parameters :  p_media - media struct with title and crc of media
**                profile - this is the profile filename, we use directory
**                name    - collection name
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_save_media_to_collection ( t_media *p_media, char *profile, 
        char *name ) {

    char *line;
    char *collection_file;
    char crc[20];
    const char *title = p_media->title ? p_media->title : "";

    /* Create the line to save */
    line = malloc(strlen(title)+15);
    sprintf ( line, "\"%s\", 0x%08x", title, p_media->crc );

    /* Figure out the collection csv file to save to */
    collection_file = malloc(strlen(profile)+strlen(name)+5);
    strcpy(collection_file, profile);
    util_strip_file_to_directory ( collection_file );
    if ( *collection_file )
        sprintf(collection_file, "%s%c%s.csv", collection_file, util_get_dir_seperator_character(), name);
    else
        sprintf(collection_file, "%s.csv", name);

    /* See if already there and just exit without writing */
    if ( util_check_for_line_in_file ( collection_file, line ) ) {
        free(line);
        free(collection_file);
        return;
    }

    sprintf ( crc, "0x%08x", p_media->crc );

    /* If writing to available list and file empty, lets delete entry if there */
    if ( strcmp(name, MEDIA_LIST_AVAILABLE) && (!p_media->file || !strlen(p_media->file)) ) {
        free(line);
        line = 0;
    }

    /* Now put our line in file */
    if ( util_save_line_alpha_to_file ( collection_file, line, crc,
                UTIL_CREATE_FILE | UTIL_DELETE_ON_STR ) ) {

        char *error;
        error = malloc(strlen(collection_file)+100);
        sprintf ( error, "Unable to write to collection file: %s", collection_file );
        logger_log_message (LOG_ERROR, error, "");
        free(error);
    }

    free(line);
    free(collection_file);

} /* end config_save_media_to_collection */

/******************************************************************************
**  Function   :  config_load_media_settings
**                                                
**  Objective  :  This grabs the media metadata from the rom specific TOML 
**                profile file
**                                               
**  Parameters :  crc     - checksum of the ROM 
**                p_media - pointer to the media struct 
**                                             
**  return     :  0 on success, -1 on error
******************************************************************************/ 
int config_load_media_settings ( unsigned int crc, t_media *p_media ) {

    char *tmp_crc = 0;
    char *tmp_string = 0;
    char *tmp_low_string = 0;
    int tmp_int = 0;
    t_toml_node *array_node;

    /* Set media defaults */
    p_media->mapping = CART_MIN;
    p_media->media_type = CART_MIN;
    p_media->image_size = 0;
    p_media->collections = 0;
    p_media->write_protect = 1;
    p_media->machine_type = MACHINE_TYPE_MIN;

    /* Try to find profile and fail if we can't */
    if ( g_crc != crc ) {
        toml_free_node_tree(g_romconftbl);
        g_romconftbl = config_parse_for_media ( crc );
        /* Keep g_crc honest with the table so a NULL parse isn't cached */
        g_crc = g_romconftbl ? crc : 0;
    }

    if ( !g_romconftbl )
        return -1;

    config_get_string ( g_romconftbl, "title", &p_media->title, 0 );
    config_get_string ( g_romconftbl, "file", &p_media->file, 0 );
    config_get_string ( g_romconftbl, "sha1", &p_media->sha1, 0 );
    config_get_string ( g_romconftbl, "md5", &p_media->md5, 0 );
    config_get_string ( g_romconftbl, "crc", &tmp_crc, 0 );
    if ( tmp_crc )
        p_media->crc = strtoul(tmp_crc, NULL, 16);

    config_get_int ( g_romconftbl, "image_size", &p_media->image_size, 0 );
    config_get_bool ( g_romconftbl, "write_protect", 
                        &p_media->write_protect, 0 );

    /* Grab collections strings, get value fron conf and set media value */
    array_node = toml_find_node ( g_romconftbl, "collections" );
    for ( int i = 0; i < toml_get_num_array_elements(array_node); ++i ) {
        tmp_string = toml_get_array_value_by_index(array_node, NULL, i); 
        if ( tmp_string ) {
            tmp_low_string = malloc ( strlen(tmp_string)+1+strlen("media.collections.") );
            sprintf ( tmp_low_string, "media.collections.%s", tmp_string );
            util_strlwr ( tmp_low_string );
            config_get_int ( g_conftbl, tmp_low_string, &tmp_int, 0 );
            p_media->collections |= tmp_int;
            free(tmp_low_string);
        }
    }
    tmp_string = 0;

    /* Mapping */
    config_get_string ( g_romconftbl, "mapping", &tmp_string, 0 );
    p_media->mapping = media_if_get_mapping_from_string ( tmp_string );
    free(tmp_string);
    tmp_string = 0;

    /* Media Type */
    config_get_string ( g_romconftbl, "media_type", &tmp_string, 0 );
    p_media->media_type = media_if_get_media_type_from_string ( tmp_string );
    free(tmp_string);
    tmp_string = 0;

    /* Machine Type
     *
     * Resolution order (highest precedence first):
     *   1. A per-machine bios/os set in the profile forces that machine.  The
     *      os type is tied to a machine (e.g. rev_b => 800) via
     *      config_machine_from_os_type().
     *   2. An explicit machine = "5200" / "xl" forces that machine.
     *   3. machine = "800" is the legacy default and is treated as "soft": keep
     *      the user's current 8-bit machine (800 or XL), only falling back to
     *      XL when the current machine is not 8-bit.
     *   4. No machine key falls back to the profile's directory origin.
     */
    {
        const char *bios_keys[] = { "machine.5200.bios",
                                    "machine.800.bios",
                                    "machine.xl.bios" };
        int forced_by_bios = 0;
        unsigned int k;

        for ( k = 0; k < sizeof(bios_keys)/sizeof(bios_keys[0]); ++k ) {
            config_get_string ( g_romconftbl, bios_keys[k], &tmp_string, 0 );
            if ( tmp_string && tmp_string[0] ) {
                p_media->machine_type = config_machine_from_os_type (
                        config_get_os_type_from_string ( tmp_string ) );
                forced_by_bios = 1;
            }
            free ( tmp_string );
            tmp_string = 0;
            if ( forced_by_bios )
                break;
        }

        if ( !forced_by_bios ) {
            config_get_string ( g_romconftbl, "machine", &tmp_string, 0 );
            if ( tmp_string && strstr(tmp_string, "5200") )
                p_media->machine_type = MACHINE_TYPE_5200;
            else if ( tmp_string && strstr(tmp_string, "xl") )
                p_media->machine_type = MACHINE_TYPE_XL;
            else if ( tmp_string && strstr(tmp_string, "800") )
                /* legacy default: keep the current 8-bit machine, else XL */
                p_media->machine_type =
                    ( config.general.machine == MACHINE_TYPE_800 ||
                      config.general.machine == MACHINE_TYPE_XL )
                    ? config.general.machine : MACHINE_TYPE_XL;
            else if ( tmp_string )
                p_media->machine_type = MACHINE_TYPE_XL;
            else if ( g_romprofile_machine != MACHINE_TYPE_MIN )
                p_media->machine_type = g_romprofile_machine;
            free ( tmp_string );
            tmp_string = 0;
        }
    }

    free(tmp_string);
    if (tmp_crc)
        free(tmp_crc);

    if ( !strlen(p_media->title) )
        return -1;

    return 0;

} /* end config_load_media_settings */

/******************************************************************************
**  Function   :  config_save_media_settings
**                                                
**  Objective  :  This grabs the media metadata from the rom specific TOML 
**                profile file
**                                               
**  Parameters :  crc     - checksum of the ROM 
**                p_media - pointer to the media struct 
**                                             
**  return     :  0 on success, -1 on error
******************************************************************************/ 
int config_save_media_settings ( unsigned int crc, t_media *p_media, 
        unsigned long long mask ) {

    char tmp_crc[15];
    t_toml_node *tmp_node;
    t_collection *tmp_collection;
    unsigned long coll_value;

    /* Try to find profile and create if we can't */
    if ( g_crc != crc ) {
        toml_free_node_tree(g_romconftbl);
        g_romconftbl = config_parse_for_media ( crc );
        /* Keep g_crc honest with the table so a NULL parse isn't cached */
        g_crc = g_romconftbl ? crc : 0;
    }

    if ( !g_romconftbl ) {
        media_if_create_profile_from_crc ( crc, p_media );
        g_romconftbl = config_parse_for_media ( crc );
        if ( !g_romconftbl ) {
            g_crc = 0;
            return -1;
        }
    }

    g_crc = crc;

    if ( mask & KCFG_MEDIA_TITLE )
        config_set_string ( g_romconftbl, "title", p_media->title, 0 );
    if ( mask & KCFG_MEDIA_FILE )
        config_set_string ( g_romconftbl, "file", p_media->file, 0 );
    if ( mask & KCFG_MEDIA_SHA1 )
        config_set_string ( g_romconftbl, "sha1", p_media->sha1, 0 );
    if ( mask & KCFG_MEDIA_MD5 )
        config_set_string ( g_romconftbl, "md5", p_media->md5, 0 );
    if ( mask & KCFG_MEDIA_CRC ) {
        sprintf ( tmp_crc, "%08lx", p_media->crc );
        config_set_string ( g_romconftbl, "crc", tmp_crc, 0 );
    }

    if ( mask & KCFG_MEDIA_SIZE )
        config_set_int ( g_romconftbl, "image_size", p_media->image_size, 0 );
    if ( mask & KCFG_MEDIA_WRT_PROT ) {
        config_set_bool ( g_romconftbl, "write_protect", 
                        p_media->write_protect, 0 );
    }

    /* Set collections strings, get value fron conf and set media value */
    if ( mask & KCFG_MEDIA_COLLECTS ) {


        /* Find array and create if it does not exist */
        tmp_node = toml_get_key_array_values_start ( g_romconftbl, "collections" );
        if (!tmp_node) {
            toml_start_array_with_value ( g_romconftbl, "collections", 
                    TOML_DQ_STRING, "", "", " " );
        }

        /* delete all current values */
        while ( !toml_delete_array_value_by_index(g_romconftbl, "collections", -1) ); 

        /* go bit by bit and find set collections */
        for ( int i = 0; i < 32; ++i ) {
            if ( (coll_value = (p_media->collections & (1 << i))) ) {
                tmp_collection = config.general.collection;
                while ( tmp_collection ) {
                    if ( tmp_collection->value == coll_value ) { 
                        toml_insert_array_value_by_index ( g_romconftbl, 
                                "collections", tmp_collection->name, " ", " ", 
                                " ", -1 );
                        config_save_media_to_collection ( p_media, g_romconftbl->value, tmp_collection->name );
                    }
                    tmp_collection = tmp_collection->next;
                }
            }
        }
    }

    /* Mapping */
    if ( mask & KCFG_MEDIA_MAPPING ) {
        config_set_string ( g_romconftbl, "mapping", 
                media_if_get_string_from_mapping(p_media->mapping), 0 );
    }

    /* Media Type */
    if ( mask & KCFG_MEDIA_TYPE ) {
        config_set_string ( g_romconftbl, "media_type", 
                media_if_get_string_from_media_type(p_media->media_type), 0 );
    }

    /* Machine Type */
    if ( mask & KCFG_MEDIA_MACHINE ) {

        if ( p_media->machine_type == MACHINE_TYPE_5200 ) 
            strcpy ( tmp_crc, "5200" );
        else if ( p_media->machine_type == MACHINE_TYPE_800 ) 
            strcpy(tmp_crc, "800");
        else 
            strcpy(tmp_crc, "xl");

        config_set_string ( g_romconftbl, "machine", tmp_crc, 0 );

    }

    /* Save to main profile list and to available */
    config_save_media_to_collection ( p_media, g_romconftbl->value, MEDIA_LIST );
    config_save_media_to_collection ( p_media, g_romconftbl->value, MEDIA_LIST_AVAILABLE );

    /* Save the file */
    config_write_file ( g_romconftbl );

    return 0;

} /* end config_save_media_settings */

/******************************************************************************
**  Function   :  config_reset_media_custom_settings
**
**  Objective  :  Reset a ROM profile to defaults while keeping identity fields
**                (title, file, crc, md5, sha1, machine, mapping, image_size,
**                write_protect, media_type, collections).
**
**  Parameters :  crc - checksum of the ROM
**
**  return     :  NONE
******************************************************************************/
void config_reset_media_custom_settings(unsigned int crc)
{
    char *path = media_if_get_profile_from_crc(crc);
    if (!path)
        return;

    t_toml_node *root = toml_parse_file(path);
    if (!root) {
        free(path);
        return;
    }

    FILE *fp = fopen(path, "w");
    if (!fp) {
        toml_free_node_tree(root);
        free(path);
        return;
    }

    /* Write identity fields */
    char *val;

    val = toml_get_key_value(root, "title");
    fputs("\ntitle = \"", fp);
    config_write_escaped_dq_string(fp, val);
    fputs("\"\n", fp);
    val = toml_get_key_value(root, "file");
    fputs("file = \"", fp);
    config_write_escaped_dq_string(fp, val);
    fputs("\"\n", fp);
    val = toml_get_key_value(root, "crc");
    fputs("crc = \"", fp);
    config_write_escaped_dq_string(fp, val);
    fputs("\"\n", fp);
    val = toml_get_key_value(root, "md5");
    fputs("md5 = \"", fp);
    config_write_escaped_dq_string(fp, val);
    fputs("\"\n", fp);
    val = toml_get_key_value(root, "sha1");
    fputs("sha1 = \"", fp);
    config_write_escaped_dq_string(fp, val);
    fputs("\"\n", fp);
    val = toml_get_key_value(root, "machine");
    fputs("machine = \"", fp);
    config_write_escaped_dq_string(fp, val);
    fputs("\"\n", fp);
    val = toml_get_key_value(root, "mapping");
    fputs("mapping = \"", fp);
    config_write_escaped_dq_string(fp, val);
    fputs("\"\n", fp);
    val = toml_get_key_value(root, "media_type");
    fputs("media_type = \"", fp);
    config_write_escaped_dq_string(fp, val ? val : "cartridge");
    fputs("\"\n", fp);
    val = toml_get_key_value(root, "image_size");
    fprintf(fp, "image_size = %s\n", val ? val : "0");
    val = toml_get_key_value(root, "write_protect");
    fprintf(fp, "write_protect = %s\n", val ? val : "false");

    /* Write collections array, preserving existing values */
    int n = toml_get_num_array_elements(toml_find_node(root, "collections"));
    if (n == 0) {
        fprintf(fp, "collections = [ ]\n");
    } else {
        fprintf(fp, "collections = [");
        for (int i = 0; i < n; i++) {
            char *item = toml_get_array_value_by_index(root, "collections", i);
            if (item) {
                fputs(" \"", fp);
                config_write_escaped_dq_string(fp, item);
                fputs("\"", fp);
            }
            if (i < n - 1)
                fprintf(fp, ",");
        }
        fprintf(fp, " ]\n");
    }

    /* Write default custom sections */
    fprintf(fp,
        "\nsound_tags = [ ]\nvideo_tags = [ ]\ninput_tags = [ ]\n"
        "\n[general]\n\n[machine.5200]\n\n[machine.800]\n\n[machine.xl]\n"
        "\n[video]\ntag = \"this\"\n\n[sound]\ntag = \"this\"\n"
        "\n[input.misc]\n\n[input]\ntag = \"this\"\n\n");

    fclose(fp);
    toml_free_node_tree(root);
    free(path);

    /* Invalidate the cached parsed profile so the next config access reads the
     * rewritten file rather than the now-stale in-memory document. */
    toml_free_node_tree(g_romconftbl);
    g_romconftbl = NULL;
    g_crc = 0;
    g_romprofile_machine = MACHINE_TYPE_MIN;

} /* end config_reset_media_custom_settings */

/******************************************************************************
**  Function   :  config_load_for_media
**                                                
**  Objective  :  This sets the video, sound, and input configuration based
**                on the specific ROM profile.  It sets in the following order
**                internal, global config, ROM config
**                                               
**  Parameters :  crc     - checksum of the ROM 
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_load_for_media ( unsigned int crc ) {

    int i;
    t_toml_node *array_node;

    toml_free_node_tree(g_romconftbl);
    g_romconftbl = config_parse_for_media ( crc );
    g_crc = g_romconftbl ? crc : 0;

    /*
     * Find any general settings that changed
     *  FIXME: Make sure command line things are respected?
     */
    config_clear_masks();
    config_load_general ( g_conftbl );
    config_load_general ( g_romconftbl );

    /* 
     * We start with internal defaults, overwrite with tag values from
     * conf file and finally with this profiles contents
     */
    config_load_internal_media_defaults();
    config_load_video (g_conftbl, "All" );
    config_load_sound (g_conftbl, "All" );
    config_load_game_input (config.general.machine, g_conftbl, "All" );

    /* Using tags from the profile, find those profiles in main conf */
    array_node = toml_find_node ( g_romconftbl, "sound_tags" );
    for ( i = 0; i < toml_get_num_array_elements(array_node); ++i ) {
        config_load_sound ( g_conftbl, 
                toml_get_array_value_by_index(array_node, NULL, i)); 
    }

    array_node = toml_find_node ( g_romconftbl, "video_tags" );
    for ( i = 0; i < toml_get_num_array_elements(array_node); ++i ) {
        config_load_video ( g_conftbl,
                toml_get_array_value_by_index(array_node, NULL, i) );
    }

    array_node = toml_find_node ( g_romconftbl, "input_tags" );
    for ( i = 0; i < toml_get_num_array_elements(array_node); ++i ) {
        config_load_game_input ( config.general.machine, g_conftbl, 
                toml_get_array_value_by_index(array_node, NULL, i)); 
    }

    /* Now get direct ROM settings and leave source masks for the GUI. */
    config_refresh_rom_source_masks();

} /* end config_load_for_media */

/******************************************************************************
**  Function   :  config_load_a800_palette
**                                                
**  Objective  :  This sets the palette to use.  It loads the atari800 palette
**                from the filename
**                                               
**  Parameters :  file    - palette file 
**                palette - array of 256 colors to set
**                                             
**  return     :  0 on success, otherwise failure
******************************************************************************/ 
int config_load_a800_palette (const char *file, int *palette ) {

    int i,j,c;
    int loaded[256];
    FILE *fp;

    if (!file || !palette)
        return -1;

    if ((fp = fopen(file,"rb")) == NULL) {
        char *msg = malloc(strlen(file) + 100);
        sprintf ( msg, "Could not open Atari800 Pallete file: %s", file );
        logger_log_message ( LOG_ERROR, msg, "" );
        free(msg);
        return -1;
    }
    for (i = 0; i < 256; ++i ) {
        loaded[i] = 0;
        for (j = 16; j >= 0; j -= 8) {
            c = fgetc(fp);
            if (c == EOF) {
                fclose(fp);
                return -1;
            }
            loaded[i] |= c << j;
        }
    }

    fclose(fp);
    memcpy(palette, loaded, sizeof(loaded));
    return 0;

} /* end config_load_a800_palette */

/******************************************************************************
**  Function   :  config_load_palette
**                                                
**  Objective  :  This sets the palette to use.  It searches the palette 
**                table array for having a name key equal input name.
**                                               
**  Parameters :  tbl  - checksum of the ROM 
**                name - the key name to identify the correct palette table
**                palette - array of 256 colors to set
**                                             
**  return     :  0 on success, otherwise failure
******************************************************************************/ 
int config_load_palette (t_toml_node * tbl, const char *name, int *palette ) {

    char *tmp_string, *rest;
    int loaded[256];

    if (!tbl || !name || !palette || !strlen(name))
        return -1;

    /* Grab the table by name */
    t_toml_node *pal_ary = toml_get_table_array_by_key (
            tbl->child, "palette", "name", name);

    if (!pal_ary && tbl != g_conftbl && g_conftbl) {
        pal_ary = toml_get_table_array_by_key (
                g_conftbl->child, "palette", "name", name);
    }

    if (!pal_ary) {
        return config_load_a800_palette (name, palette );
    }

    /* Grab the 256 color palette */
    for ( int i = 0; i < 256; ++i ) {
        tmp_string = toml_get_array_value_by_index ( pal_ary, "colors", i );
        if (!tmp_string)
            return -1;
        int val = strtol(tmp_string, &rest, 0);
        if (tmp_string != rest )
            loaded[i] = val;
        else
            return -1;
    }

    memcpy(palette, loaded, sizeof(loaded));
    return 0;

} /* end config_load_palette */

/******************************************************************************
**  Function   :  config_get_palette_names
**
**  Objective  :  This iterates through all [[palette]] table array entries
**                and collects the "name" key value from each.
**
**  Parameters :  tbl       - root toml node
**                names     - array of char* to fill with name pointers
**                max_names - maximum number of names to collect
**
**  return     :  count of palette names found
******************************************************************************/
int config_get_palette_names (t_toml_node *tbl, char **names, int max_names) {

    int count = 0;
    t_toml_node *node;

    /* The application can continue with internal defaults when the global
       configuration file is missing or fails to parse.  Graphics and palette
       menus still enumerate named palettes in that state, so treat an absent
       table (or invalid output buffer) as an empty list rather than
       dereferencing it. */
    if (!tbl || !names || max_names <= 0)
        return 0;

    node = tbl->child;

    while (node && count < max_names) {
        node = toml_search_for_node(node, "palette", "palette");
        if (node) {
            char *name = toml_get_key_value(node, "name");
            if (name)
                names[count++] = name;
            node = node->next;
        }
    }

    return count;

} /* end config_get_palette_names */

/******************************************************************************
**  Function   :  config_palette_exists
**
**  Objective  :  Return whether a palette table-array entry exists for name.
**
**  Parameters :  tbl  - root toml node
**                name - exact palette name
**
**  return     :  1 if found, 0 otherwise
******************************************************************************/
int config_palette_exists (t_toml_node *tbl, const char *name) {

    if (!tbl || !name || !strlen(name))
        return 0;

    return toml_get_table_array_by_key(tbl->child, "palette", "name", name)
        ? 1 : 0;

} /* end config_palette_exists */

static int config_delete_palette_nodes (t_toml_node *node, const char *name) {

    int deleted = 0;

    while (node) {
        t_toml_node *next = node->next;

        if (node->type == TOML_TABLE_ARY && node->value &&
                !strcmp(node->value, "palette")) {
            char *pal_name = toml_get_key_value(node, "name");

            if (pal_name && !strcmp(pal_name, name)) {
                toml_free_node_tree(node->child);
                node->child = NULL;
                toml_free_single_node(node);
                deleted++;
                node = next;
                continue;
            }
        }

        if (node->child)
            deleted += config_delete_palette_nodes(node->child, name);

        node = next;
    }

    return deleted;

} /* end config_delete_palette_nodes */

/******************************************************************************
**  Function   :  config_delete_palette
**
**  Objective  :  Delete exact matching palette table-array entries.
**
**  Parameters :  tbl  - root toml node
**                name - exact palette name
**
**  return     :  0 on success, otherwise failure
******************************************************************************/
int config_delete_palette (t_toml_node *tbl, const char *name) {

    int deleted = 0;

    if (!tbl || !name || !strlen(name))
        return -1;

    deleted = config_delete_palette_nodes(tbl->child, name);

    if (!deleted)
        return -1;

    config_write_file(tbl);

    return 0;

} /* end config_delete_palette */

void config_save_active_palette_name (t_toml_node *tbl, const char *tag,
                                      const char *name) {

    t_toml_node *tmptbl;
    const char *key;
    const char *value = name ? name : "";
    int result;

    if (!tbl || !tag)
        return;

    tmptbl = toml_get_table_array_by_key(tbl->child, "video", "tag", tag);
    if (!tmptbl) {
        tmptbl = toml_add_table(tbl, "video", TOML_TABLE_ARY, "");
        if (!tmptbl)
            return;

        result = toml_add_key(tmptbl, "tag", TOML_DQ_STRING, tag, "", " ");
        if (result)
            return;
    }

    key = config.machine.tv_standard == NTSC ? "ntsc.palette" : "pal.palette";
    config_set_string(tmptbl, key, value, 0);
    config_write_file(tbl);

} /* end config_save_active_palette_name */

int config_fallback_active_palette_after_delete (t_toml_node *tbl,
                                                 const char *deleted_name) {

    if (!tbl || !deleted_name || !config.video.palette_name ||
            strcmp(config.video.palette_name, deleted_name))
        return 0;

    free(config.video.palette_name);
    config.video.palette_name = NULL;
    make_default_palette(config.video.palette);
    config_save_active_palette_name(tbl, "All", "");

    return 1;

} /* end config_fallback_active_palette_after_delete */

/******************************************************************************
**  Function   :  config_save_palette
**                                                
**  Objective  :  This writes the palette to the general or rom specific
**                config file
**                                               
**  Parameters :  tbl - parsed toml table, this can be either global config
**                      or rom specific file
**                name    - name of palette to save 
**                palette - palette to set
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_save_palette (t_toml_node * tbl, const char *name, int *palette ) {

    char tmp_string[20];
    int i, result;

    /* Grab the table by name */
    t_toml_node *pal_ary = toml_get_table_array_by_key (
            tbl->child, "palette", "name", name);

    /* if not found, create along with keys and color array */
    if (!pal_ary) {

        pal_ary = toml_add_table ( tbl, "palette", TOML_TABLE_ARY, "" );
        if (!pal_ary)
            return;

        /* Add name key */
        result = toml_add_key ( pal_ary, "name", TOML_DQ_STRING, 
                                name, "", " " );
        if (result)
            return;
    } 

    /* else we delete the colors as we will re-create */
    else {
        toml_delete_node_and_children ( pal_ary, "colors");
    }

    /* Create palette array */
    sprintf ( tmp_string, "0x%06x", palette[0] );
    toml_start_array_with_value ( pal_ary, "colors", TOML_INT, 
                                tmp_string, "", " " );
    config_set_palette_array_layout ( pal_ary );

    /* Set the colors */
    for ( i = 1; i < 256; ++i ) {

        const char *indent_initial;
        const char *to_newline;

        sprintf ( tmp_string, "0x%06x", palette[i] );

        if ( i % 8 == 0 ) {
            indent_initial = "  ";
            to_newline = "\n";
        }
        else {
            indent_initial = "";
            to_newline = NULL;
        }
            
        toml_insert_array_value_by_index ( pal_ary, "colors", 
                                tmp_string, indent_initial, to_newline,
                                "\n  ", -1 );
    }
    config_set_palette_array_layout ( pal_ary );

    config_write_file ( tbl );

} /* end config_save_palette */

static t_toml_node * config_find_shader_bundle_pass ( t_toml_node *bundle_tbl,
                                                      int pass_index ) {

    char index_string[20];

    if ( !bundle_tbl )
        return NULL;

    sprintf ( index_string, "%d", pass_index );

    return toml_get_table_array_by_key ( bundle_tbl->child,
            "shaderbundle.pass", "index", index_string );

} /* end config_find_shader_bundle_pass */

void config_load_shader_bundle_info ( t_toml_node *tbl, t_shader_bundle *bundle,
                                      int reset_defaults ) {

    t_toml_node *bundle_tbl, *pass_tbl;

    if ( !tbl || !bundle || !bundle->id )
        return;

    bundle_tbl = toml_get_table_array_by_key ( tbl->child, "shaderbundle",
            "id", bundle->id );

    if ( reset_defaults )
        shader_bundle_reset_params ( bundle );

    if ( !bundle_tbl ) {
        shader_bundle_clamp_params ( bundle );
        return;
    }

    for ( int i = 0; i < bundle->num_passes; ++i ) {
        t_shader_bundle_pass *pass = &bundle->passes[i];

        pass_tbl = config_find_shader_bundle_pass ( bundle_tbl, pass->index );
        if ( !pass_tbl )
            continue;

        for ( int j = 0; j < pass->num_params; ++j ) {
            if ( pass->params[j].name )
                config_get_float ( pass_tbl, pass->params[j].name,
                        &pass->params[j].val, 0 );
        }
    }

    shader_bundle_clamp_params ( bundle );

} /* end config_load_shader_bundle_info */

/******************************************************************************
**  Function   :  config_shader_bundle_info
**                                                
**  Objective  :  Load or save user-edited shader bundle parameters.  Bundle
**                files own metadata/defaults; the config stores overrides.
**                                               
**  Parameters :  tbl    - parent TOML node to search from
**                bundle - loaded shader bundle metadata and values
**                action - either KCFG_SAVE or KCFG_LOAD
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_shader_bundle_info ( t_toml_node *tbl, t_shader_bundle *bundle,
                                 int action ) {

    int result;
    char string_val[50];
    t_toml_node *bundle_tbl, *pass_tbl;

    if ( !tbl || !bundle || !bundle->id )
        return;

    bundle_tbl = toml_get_table_array_by_key ( tbl->child, "shaderbundle",
            "id", bundle->id );

    if ( action == KCFG_LOAD ) {
        config_load_shader_bundle_info ( tbl, bundle, 1 );
        return;
    }

    if ( action != KCFG_SAVE )
        return;

    shader_bundle_clamp_params ( bundle );

    if ( !bundle_tbl ) {
        bundle_tbl = toml_add_table ( tbl, "shaderbundle", TOML_TABLE_ARY, "" );
        if ( !bundle_tbl )
            return;

        result = toml_add_key ( bundle_tbl, "id", TOML_DQ_STRING,
                bundle->id, "", " " );
        if ( result )
            return;

        toml_add_generic_node ( bundle_tbl, TOML_NEWLINE, NULL, NULL );
    }

    if ( bundle->source_hash ) {
        result = toml_add_key ( bundle_tbl, "source_hash", TOML_DQ_STRING,
                bundle->source_hash, "", " " );
        if ( result )
            return;
    }

    for ( int i = 0; i < bundle->num_passes; ++i ) {
        t_shader_bundle_pass *pass = &bundle->passes[i];

        pass_tbl = config_find_shader_bundle_pass ( bundle_tbl, pass->index );
        if ( !pass_tbl ) {
            sprintf ( string_val, "%d", pass->index );

            pass_tbl = toml_add_table ( bundle_tbl, "shaderbundle.pass",
                    TOML_TABLE_ARY, "  " );
            if ( !pass_tbl )
                return;

            result = toml_add_key ( pass_tbl, "index", TOML_INT,
                    string_val, "  ", " " );
            if ( result )
                return;
        }

        for ( int j = 0; j < pass->num_params; ++j ) {
            t_shader_bundle_param *param = &pass->params[j];

            if ( !param->name )
                continue;

            if ( shader_bundle_param_is_default(param) ) {
                toml_delete_node_and_children ( pass_tbl, param->name );
                continue;
            }

            sprintf ( string_val, shader_get_float_format(param->step),
                    param->val );

            result = toml_add_key ( pass_tbl, param->name, TOML_FLOAT,
                    string_val, "  ", " " );
            if ( result )
                return;
        }
    }

    config_write_file ( tbl );

} /* end config_shader_bundle_info */

static int config_get_window_scaling_from_string ( const char *string ) {

    char tmp_string[64];

    if ( !string )
        return WINDOW_SCALING_LETTERBOX;

    util_trim_whitespace(tmp_string, sizeof(tmp_string), string);
    util_strlwr(tmp_string);

    if ( strstr(tmp_string, "stretch") )
        return WINDOW_SCALING_STRETCH;
    else if ( strstr(tmp_string, "fit_window") ||
              strstr(tmp_string, "fit-window") ||
              strstr(tmp_string, "fit window") )
        return WINDOW_SCALING_FIT_WINDOW;
    else
        return WINDOW_SCALING_LETTERBOX;
}

static const char * config_get_string_from_window_scaling ( int scaling ) {

    switch ( scaling ) {
        case WINDOW_SCALING_STRETCH:    return "stretch";
        case WINDOW_SCALING_FIT_WINDOW: return "fit_window";
        default:                        return "letterbox";
    }
}

static int config_get_texture_scaling_from_string ( const char *string ) {

    char tmp_string[64];

    if ( !string )
        return TEXTURE_SCALING_PIXELATED;

    util_trim_whitespace(tmp_string, sizeof(tmp_string), string);
    util_strlwr(tmp_string);

    if ( strstr(tmp_string, "smooth") )
        return TEXTURE_SCALING_SMOOTH;
    else
        return TEXTURE_SCALING_PIXELATED;
}

static const char * config_get_string_from_texture_scaling ( int scaling ) {

    switch ( scaling ) {
        case TEXTURE_SCALING_SMOOTH: return "smooth";
        default:                     return "pixelated";
    }
}

static int config_clamp_gui_scale ( int scale ) {

    if ( scale <= 0 )
        return 0;
    if ( scale < 25 )
        return 25;
    if ( scale > 300 )
        return 300;
    return scale;
}

static float config_clamp_sound_buffer_size ( float buffer_size ) {

    if ( buffer_size <= KCFG_SND_BUFFER_AUTO )
        return KCFG_SND_BUFFER_AUTO;
    if ( buffer_size < KCFG_SND_BUFFER_MIN )
        return KCFG_SND_BUFFER_MIN;
    if ( buffer_size > KCFG_SND_BUFFER_MAX )
        return KCFG_SND_BUFFER_MAX;
    return buffer_size;
}

static int config_clamp_pot_value ( int value ) {

    if ( value < 0 )
        return 0;
    if ( value > 227 )
        return 227;
    return value;

} /* end config_clamp_pot_value */

static void config_set_machine_pot_defaults ( e_machine_type machine ) {

    int center = machine == MACHINE_TYPE_5200 ?
        POT_CENTER_VALUE : POT_800_CENTER_VALUE;
    int low = machine == MACHINE_TYPE_5200 ?
        POT_LOW_VALUE : POT_800_LOW_VALUE;
    int high = machine == MACHINE_TYPE_5200 ?
        POT_HIGH_VALUE : POT_800_HIGH_VALUE;

    config.machine.joystick_hpot_center = center;
    config.machine.joystick_vpot_center = center;
    config.machine.joystick_hpot_low = low;
    config.machine.joystick_hpot_high = high;
    config.machine.joystick_vpot_low = low;
    config.machine.joystick_vpot_high = high;
    config.machine.trackball_hpot_center = POT_CENTER_VALUE;
    config.machine.trackball_vpot_center = POT_CENTER_VALUE;
    config.machine.trackball_dpot_min = 4;
    config.machine.trackball_dpot_max = 36;

} /* end config_set_machine_pot_defaults */

static void config_load_machine_pot_value ( t_toml_node *tbl,
        const char *machine_key, const char *value_key, int *value,
        unsigned long long mask, int err_chk ) {

    char key[552];
    int result;

    sprintf ( key, "machine.%s.%s", machine_key, value_key );
    result = config_get_int ( tbl, key, value, err_chk );
    *value = config_clamp_pot_value ( *value );

    if ( !result )
        config.general_mask |= mask;

} /* end config_load_machine_pot_value */

static void config_save_machine_pot_value ( t_toml_node *tbl,
        const char *machine_key, const char *value_key, int *value,
        unsigned long long mask, unsigned long long changed_mask, int err_chk ) {

    char key[552];

    if ( !(changed_mask & mask) )
        return;

    *value = config_clamp_pot_value ( *value );
    sprintf ( key, "machine.%s.%s", machine_key, value_key );
    config_set_int ( tbl, key, *value, err_chk );

} /* end config_save_machine_pot_value */

/******************************************************************************
**  Function   :  config_load_video
**                config_save_video
**                                                
**  Objective  :  This loads or writes video configuration parameters to the 
**                video table and then writes the file
**                                               
**  Parameters :  tbl - parsed toml table, this can be either global config
**                      or rom specific file
**                tag - the name tag to search for
**                mask - which parameters to set (save function) 
**                p_shader - shader to save or NULL 
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_load_video (t_toml_node *tbl, const char *tag ) {

    char *tmp_string = 0;
    int result;
    t_toml_node *tmptbl;

    if ( !tbl )
        return;

    tmptbl = toml_get_table_array_by_key ( tbl->child, "video", "tag", tag );

    if (!tmptbl)
        return;

    /* 
     * Use the found table to set video, only overwrite where we find a value 
     */

    if (config.machine.tv_standard == NTSC) {
        result = config_get_string ( tmptbl, "ntsc.palette",
                             &config.video.palette_name, 0 );
        if (!result)
            config.video_mask |= KCFG_VIDEO_PALETTE;
    }
    else {
        result = config_get_string ( tmptbl, "pal.palette",
                             &config.video.palette_name, 0 );
        if (!result)
            config.video_mask |= KCFG_VIDEO_PALETTE;
    }

    /*
     * Grab the palette based on TV Standard.  A present blank string is the
     * explicit "internal default" palette; a missing key leaves the currently
     * loaded/default palette untouched so tag inheritance still works.
     */
    if (!result && config.video.palette_name &&
            !strlen(config.video.palette_name)) {
        make_default_palette(config.video.palette);
    }
    else if (!result && config.video.palette_name &&
            strlen(config.video.palette_name)) {
        if (config_load_palette (tbl, config.video.palette_name,
                                  config.video.palette )) {
            free(config.video.palette_name);
            config.video.palette_name = NULL;
            make_default_palette(config.video.palette);
        }
    }

    /* Renderer backend is a host property - global ("All") table only */
    if (!strcmp(tag, "All")) {
        result = config_get_string ( tmptbl, "renderer",
                             &config.video.renderer_name, 0 );
        if (!result)
            config.video_mask |= KCFG_VIDEO_RENDERER;

        result = config_get_int ( tmptbl, "gui_scale",
                         &config.video.gui_scale, 0 );
        if (!result) {
            config.video.gui_scale =
                config_clamp_gui_scale(config.video.gui_scale);
            config.video_mask |= KCFG_VIDEO_GUI_SCALE;
        }
    }

    result = config_get_bool ( tmptbl, "fullscreen",
                         &config.video.fullscreen, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_FULLSCREEN;

    result = config_get_int ( tmptbl, "height", 
                         &config.video.height, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_HEIGHT;

    result = config_get_int ( tmptbl, "width", 
                         &config.video.width, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_WIDTH;

    result = config_get_bool ( tmptbl, "wide_playfield", 
                         &config.video.wide_playfield, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_WIDE;

    result = config_get_bool ( tmptbl, "ntsc_artifacts", 
                         &config.video.ntsc_artifacts, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_NTSC;

    result = config_get_bool ( tmptbl, "fixed_window",
                         &config.video.fixed_window, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_FIXED_WIN;

    result = config_get_string ( tmptbl, "window_scaling",
                         &tmp_string, 0 );
    if (!result) {
        config.video_mask |= KCFG_VIDEO_WINDOW_SCALING;
        config.video.window_scaling =
            config_get_window_scaling_from_string(tmp_string);
    }
    free(tmp_string);
    tmp_string = NULL;

    result = config_get_string ( tmptbl, "texture_scaling",
                         &tmp_string, 0 );
    if (!result) {
        config.video_mask |= KCFG_VIDEO_TEXTURE_SCALING;
        config.video.texture_scaling =
            config_get_texture_scaling_from_string(tmp_string);
    }
    free(tmp_string);
    tmp_string = NULL;

    result = config_get_bool ( tmptbl, "vsync",
                         &config.video.vsync, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_VSYNC;

    result = config_get_int ( tmptbl, "x", 
                         &config.video.x, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_X;

    result = config_get_int ( tmptbl, "y", 
                         &config.video.y, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_Y;

    result = config_get_string ( tmptbl, "shader", 
                         &tmp_string, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_SHADER;

    /* Store the configured shader reference; SDL GPU bundles are resolved and
     * rendered later at runtime from this name. */
    if ( tmp_string && strlen(tmp_string) )
        config.video.shader = tmp_string;
    else
        free(tmp_string);

    /*
     * NTSC setup
     */
    result = config_get_float ( tmptbl, "ntsc_setup.hue", 
                         &config.video.ntsc_setup.hue, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_HUE;

    result = config_get_float ( tmptbl, "ntsc_setup.saturation", 
                         &config.video.ntsc_setup.saturation, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_SATURATION;

    result = config_get_float ( tmptbl, "ntsc_setup.contrast", 
                         &config.video.ntsc_setup.contrast, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_CONTRAST;

    result = config_get_float ( tmptbl, "ntsc_setup.brightness", 
                         &config.video.ntsc_setup.brightness, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_BRIGHTNESS;

    result = config_get_float ( tmptbl, "ntsc_setup.sharpness", 
                         &config.video.ntsc_setup.sharpness, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_SHARPNESS;

    result = config_get_float ( tmptbl, "ntsc_setup.burst_phase", 
                         &config.video.ntsc_setup.burst_phase, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_BRST_PHASE;

    result = config_get_float ( tmptbl, "ntsc_setup.hue_warping", 
                         &config.video.ntsc_setup.hue_warping, 0 );
    if (!result)
        config.video_mask |= KCFG_VIDEO_HUE_WARP;

} /* end config_load_video */

void config_save_video ( t_toml_node *tbl, const char *tag,
        unsigned long long mask ) {

    t_toml_node *tmptbl;
    int result;
    int err_chk = (tbl == g_conftbl) ? 1 : 0;

    if ( !tbl )
        return;

    /* Find table and create if not exists */
    tmptbl = toml_get_table_array_by_key ( tbl->child, "video", "tag", tag );

    if (!tmptbl) {

        tmptbl = toml_add_table ( tbl, "video", TOML_TABLE_ARY, "" );
        if (!tmptbl)
            return;

        /* Add file key */
        result = toml_add_key ( tmptbl, "tag", TOML_DQ_STRING, 
                                tag, "", " " );
        if (result)
            return;
    }

    /* Now check for params to set */
    if ( mask & KCFG_VIDEO_PALETTE ) {
        const char *palette_name = config.video.palette_name ?
            config.video.palette_name : "";

        config_set_string ( tmptbl,
                config.machine.tv_standard == NTSC ? "ntsc.palette" : "pal.palette",
                             palette_name, err_chk );

        if (strlen(palette_name))
            config_save_palette ( tbl, palette_name, config.video.palette );
    }

    if ( mask & KCFG_VIDEO_FULLSCREEN ) {
        /* Must be config_set_bool: the loader uses config_get_bool, which only
           accepts "true"/"false".  config_set_int wrote "1"/"0", which failed
           to read back and silently reverted to the default. */
        config_set_bool ( tmptbl, "fullscreen", config.video.fullscreen, err_chk );
    }

    if ( mask & KCFG_VIDEO_HEIGHT ) {
        config_set_int ( tmptbl, "height", config.video.height, err_chk );
    }

    if ( mask & KCFG_VIDEO_WIDTH ) {
        config_set_int ( tmptbl, "width", config.video.width, err_chk );
    }

    if ( mask & KCFG_VIDEO_WIDE ) {
        config_set_bool ( tmptbl, "wide_playfield", 
                config.video.wide_playfield, err_chk );
    }

    if ( mask & KCFG_VIDEO_NTSC ) {
        config_set_bool ( tmptbl, "ntsc_artifacts", 
                config.video.ntsc_artifacts, err_chk );
    }

    if ( mask & KCFG_VIDEO_FIXED_WIN ) {
        config_set_bool ( tmptbl, "fixed_window",
                config.video.fixed_window, err_chk );
    }

    if ( mask & KCFG_VIDEO_WINDOW_SCALING ) {
        config_set_string ( tmptbl, "window_scaling",
                config_get_string_from_window_scaling(config.video.window_scaling),
                0 );
    }

    if ( mask & KCFG_VIDEO_TEXTURE_SCALING ) {
        config_set_string ( tmptbl, "texture_scaling",
                config_get_string_from_texture_scaling(config.video.texture_scaling),
                0 );
    }

    /* Renderer backend is a host property - global ("All") table only.
       Default (NULL/empty) removes the key so load falls back to SDL default. */
    if ( (mask & KCFG_VIDEO_RENDERER) && !strcmp(tag, "All") ) {
        if ( config.video.renderer_name && strlen(config.video.renderer_name) )
            config_set_string ( tmptbl, "renderer",
                    config.video.renderer_name, 0 );
        else
            toml_delete_node_and_children ( tmptbl, "renderer" );
    }

    if ( (mask & KCFG_VIDEO_GUI_SCALE) && !strcmp(tag, "All") ) {
        config.video.gui_scale = config_clamp_gui_scale(config.video.gui_scale);
        config_set_int ( tmptbl, "gui_scale", config.video.gui_scale, 0 );
    }

    if ( mask & KCFG_VIDEO_VSYNC ) {
        config_set_bool ( tmptbl, "vsync", 
                config.video.vsync, err_chk );
    }

    if ( mask & KCFG_VIDEO_X ) {
        config_set_int ( tmptbl, "x", config.video.x, err_chk );
    }

    if ( mask & KCFG_VIDEO_Y ) {
        config_set_int ( tmptbl, "y", config.video.y, err_chk );
    }

    if ( mask & KCFG_VIDEO_SHADER ) {
        if ( config.video.shader && strlen(config.video.shader) )
            config_set_string ( tmptbl, "shader",
                                config.video.shader, err_chk );
        else
            toml_delete_node_and_children ( tmptbl, "shader" );
    }

    if ( mask & KCFG_VIDEO_HUE ) {
        config_set_float ( tmptbl, "ntsc_setup.hue", 
                config.video.ntsc_setup.hue, err_chk );
    }

    if ( mask & KCFG_VIDEO_SATURATION ) {
        config_set_float ( tmptbl, "ntsc_setup.saturation", 
                config.video.ntsc_setup.saturation, err_chk );
    }

    if ( mask & KCFG_VIDEO_CONTRAST ) {
        config_set_float ( tmptbl, "ntsc_setup.contrast", 
                config.video.ntsc_setup.contrast, err_chk );
    }

    if ( mask & KCFG_VIDEO_BRIGHTNESS ) {
        config_set_float ( tmptbl, "ntsc_setup.brightness", 
                config.video.ntsc_setup.brightness, err_chk );
    }

    if ( mask & KCFG_VIDEO_SHARPNESS ) {
        config_set_float ( tmptbl, "ntsc_setup.sharpness", 
                config.video.ntsc_setup.sharpness, err_chk );
    }

    if ( mask & KCFG_VIDEO_BRST_PHASE ) {
        config_set_float ( tmptbl, "ntsc_setup.burst_phase", 
                config.video.ntsc_setup.burst_phase, err_chk );
    }

    if ( mask & KCFG_VIDEO_HUE_WARP ) {
        config_set_float ( tmptbl, "ntsc_setup.hue_warping", 
                config.video.ntsc_setup.hue_warping, err_chk );
    }

    config_write_file ( tbl );

} /* end config_save_video */

/******************************************************************************
**  Function   :  config_load_sound
**                config_save_sound
**                                                
**  Objective  :  This loads or writes sound configuration parameters to the sound
**                table and then writes the file if saving
**                                               
**  Parameters :  tbl - parsed toml table, this can be either global config
**                      or rom specific file
**                tag - the name tag to search for
**                mask - which parameters to set for save function
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_load_sound (t_toml_node *tbl, const char *tag ) {

    t_toml_node *tmptbl;
    int result;

    if ( !tbl )
        return;

    tmptbl = toml_get_table_array_by_key ( tbl->child, "sound", "tag", tag );

    if (!tmptbl)
        return;

    /* Use the found table to set sound, only overwrite where we find a value */
    result = config_get_bool ( tmptbl, "enable", 
                         &config.sound.enable, 0 );
    if (!result)
        config.sound_mask |= KCFG_SND_EN;

    result = config_get_int ( tmptbl, "frequency", 
                         &config.sound.frequency, 0 );
    if (!result)
        config.sound_mask |= KCFG_SND_FREQ;

    result = config_get_int ( tmptbl, "samples", 
                         &config.sound.samples, 0 );
    if (!result)
        config.sound_mask |= KCFG_SND_SAMPLES;

    result = config_get_float ( tmptbl, "buffer_size",
                         &config.sound.buffer_size, 0 );
    if (!result) {
        config.sound.buffer_size =
            config_clamp_sound_buffer_size(config.sound.buffer_size);
        config.sound_mask |= KCFG_SND_BUFFER;
    }

    result = config_get_int ( tmptbl, "gain",
                         &config.sound.gain, 0 );
    if (!result)
        config.sound_mask |= KCFG_SND_GAIN;

    /* Clamp - TOML value is unbounded */
    if ( config.sound.gain < 0 )
        config.sound.gain = 0;
    if ( config.sound.gain > KCFG_SND_GAIN_MAX )
        config.sound.gain = KCFG_SND_GAIN_MAX;

} /* end config_load_sound */

void config_save_sound ( t_toml_node *tbl, const char *tag, 
        unsigned long long mask ) {

    t_toml_node *tmptbl;
    int result;
    int err_chk = (tbl == g_conftbl) ? 1 : 0;

    if ( !tbl )
        return;

    /* Find table and create if not exists */
    tmptbl = toml_get_table_array_by_key ( tbl->child, "sound", "tag", tag );

    if (!tmptbl) {

        tmptbl = toml_add_table ( tbl, "sound", TOML_TABLE_ARY, "" );
        if (!tmptbl)
            return;

        /* Add file key */
        result = toml_add_key ( tmptbl, "tag", TOML_DQ_STRING, 
                                tag, "", " " );
        if (result)
            return;
    }

    /* Now check for params to set */
    if ( mask & KCFG_SND_EN ) {
        config_set_bool ( tmptbl, "enable", 
                config.sound.enable, err_chk );
    }

    if ( mask & KCFG_SND_FREQ ) {
        config_set_int ( tmptbl, "frequency", 
                config.sound.frequency, err_chk );
    }

    if ( mask & KCFG_SND_SAMPLES ) {
        config_set_int ( tmptbl, "samples", 
                config.sound.samples, err_chk );
    }

    if ( mask & KCFG_SND_BUFFER ) {
        config.sound.buffer_size =
            config_clamp_sound_buffer_size(config.sound.buffer_size);
        config_set_float ( tmptbl, "buffer_size",
                config.sound.buffer_size, err_chk );
    }

    /* Use err_chk=0 so existing configs lacking 'gain' get the key added */
    if ( mask & KCFG_SND_GAIN ) {
        config_set_int ( tmptbl, "gain",
                config.sound.gain, 0 );
    }

    config_write_file ( tbl );

} /* end config_save_sound */

/******************************************************************************
**  Function   :  config_load_game_input
**                config_save_game_input
**                                                
**  Objective  :  This loads or writes input configuration parameters to the 
**                input table and then writes the file for save function
**                                               
**  Parameters :  type - Either 5200 or 800/xl/xe
**                tbl  - parsed toml table, this can be either global config
**                      or rom specific file
**                tag - the name tag to search for
**                mask - which parameters to set  (save function)
**                keymask - which 800 keyboard keys to set (save function)
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_save_game_input (e_machine_type type, t_toml_node *tbl,
        const char *tag, unsigned long long *mask, unsigned long long keymask ) {

    /* FIXME: should player be an input here */
    char input_id[40];

    t_toml_node *tmptbl, *p_tbl, *p_joytbl;
    int result;
    char *tmp_string = 0;

    if ( !tbl )
        return;

    /* Nothing to write — don't create an empty tag entry */
    unsigned long long any_mask = keymask;
    for ( int i = 0; i < MAX_CONTROLLER; i++ ) any_mask |= mask[i];
    if ( !any_mask )
        return;

    tmptbl = toml_get_table_array_by_key ( tbl->child, "input.game", "tag", tag );

    if (!tmptbl) {

        tmptbl = toml_add_table ( tbl, "input.game", TOML_TABLE_ARY, "" );
        if (!tmptbl)
            return;

        /* Add file key */
        result = toml_add_key ( tmptbl, "tag", TOML_DQ_STRING, 
                                tag, "", " " );
        if (result)
            return;
    }

    /*
     * Navigate through 4 controller ports
     */
    for ( int i = 0; i < MAX_CONTROLLER; i++ ) {

        /* Skip player entirely if nothing changed */
        if (!mask[i])
            continue;

        /* Get player table or add if not there */
        sprintf(input_id, "input.game.p%d", i);
        p_tbl = toml_find_node ( tmptbl, input_id );
        if (!p_tbl) {
            p_tbl = toml_add_table ( tmptbl, input_id, TOML_TABLE_ARY, "  " );
            if ( !p_tbl )
                continue;
        }

        /* 
         * Set common config 
         */
        if ( mask[i] & KCFG_INP_JOY_EN ) {
            config_set_bool ( p_tbl, "joy.enable", 
                                 config.input.game.players[i].joy_enable, 0 );
        }
        if ( mask[i] & KCFG_INP_JOY_SENS ) {
            config_set_int ( p_tbl, "joy.sensitivity", 
                              config.input.game.players[i].joy_sensitivity, 0 );
        }
        if ( mask[i] & KCFG_INP_JOY_DEAD ) {
            config_set_int ( p_tbl, "joy.deadzone", 
                              config.input.game.players[i].joy_deadzone, 0 );
        }
        if ( mask[i] & KCFG_INP_JOY_GUID ) {
            config_set_string ( p_tbl, "joy.guid", 
                              config.input.game.players[i].joy_guid, 0 );
        }
        if ( mask[i] & KCFG_INP_JOY_SERIAL ) {
            config_set_string ( p_tbl, "joy.serial", 
                              config.input.game.players[i].joy_serial, 0 );
        }
        if ( mask[i] & KCFG_INP_SIM_AN ) {
            config_set_bool ( p_tbl, "analog_stick_acts_digital", 
                 config.input.game.players[i].analog_stick_acts_digital, 0 );
        }
        if ( mask[i] & KCFG_INP_DPAD_EN ) {
            config_set_bool ( p_tbl, "dpad.enable", 
                             config.input.game.players[i].dpad_enable, 0 );
        }
        if ( mask[i] & KCFG_INP_DPAD_AN_EN ) {
            config_set_bool ( p_tbl, "dpad.analog.enable", 
                         config.input.game.players[i].digdir_analog_enable, 0 );
        }
        if ( mask[i] & KCFG_INP_DPAD_AN_SPD ) {
            config_set_int ( p_tbl, "dpad.analog.speed", 
                          config.input.game.players[i].digdir_analog_speed, 0 );
        }
        if ( mask[i] & KCFG_INP_MOUSE_EN ) {
            config_set_bool ( p_tbl, "mouse.enable", 
                             config.input.game.players[i].mouse_enable, 0 );
        }
        if ( mask[i] & KCFG_INP_MOUSE_SPD ) {
            config_set_int ( p_tbl, "mouse.speed",
                          config.input.game.players[i].mouse_speed, 0 );
        }
        if ( mask[i] & KCFG_INP_AUTO_FIRE ) {
            config_set_bool ( p_tbl, "auto_fire",
                             config.input.game.players[i].auto_fire, 0 );
        }
        if ( mask[i] & KCFG_INP_AUTO_FIRE_SPD ) {
            config_set_int ( p_tbl, "auto_fire_speed",
                          config.input.game.players[i].auto_fire_speed, 0 );
        }
        if ( mask[i] & KCFG_INP_MOUSE_OPT ) {
            char opt_str[16];
            config_get_string_from_mouse_optimize (
                config.input.game.players[i].mouse_optimize, opt_str );
            config_set_string ( p_tbl, "mouse.optimize", opt_str, 0 );
        }
        if ( mask[i] & KCFG_INP_KEYBD_EN ) {
            config_set_bool ( p_tbl, "keyboard.enable", 
                             config.input.game.players[i].keyboard_enable, 0 );
        }

        if ( mask[i] & KCFG_INP_TYPE ) {
            char control_type[50];
            config_get_string_from_control_type ( config.input.game.players[i].type, control_type );
            if (type == MACHINE_TYPE_5200) 
                config_set_string ( p_tbl, "5200.type", control_type, 0 );
            else 
                config_set_string ( p_tbl, "800.type", control_type, 0 );
        }

        /*
         * Joystick
         */
        char type_str[7];
        char full_str[100];
        if (type == MACHINE_TYPE_5200)
            strcpy ( type_str, "5200" );
        else
            strcpy ( type_str, "800" );

        /* Make sure joystick table header there */
        sprintf ( full_str, "%s.%s.%s", input_id, type_str, "joystick" );
        p_joytbl = toml_find_node ( p_tbl, full_str );
        if (!p_joytbl) {
            p_joytbl = toml_add_table ( p_tbl, full_str, TOML_TABLE_ARY, "  " );
            if ( !p_joytbl )
                continue;
        }

        if ( mask[i] & KCFG_INP_JOY_LEFT ) {
            sprintf ( full_str, "%s.%s.%s", input_id, type_str, "joystick.left" );
            config_set_str_ary_from_input (p_tbl, full_str,
                &config.input.game.players[i].stick[DIR_LEFT]);
        }

        if ( mask[i] & KCFG_INP_JOY_RIGHT ) {
            sprintf ( full_str, "%s.%s.%s", input_id, type_str, "joystick.right" );
            config_set_str_ary_from_input (p_tbl, full_str,
                &config.input.game.players[i].stick[DIR_RIGHT]);
        }

        if ( mask[i] & KCFG_INP_JOY_UP ) {
            sprintf ( full_str, "%s.%s.%s", input_id, type_str, "joystick.up" );
            config_set_str_ary_from_input (p_tbl, full_str,
                &config.input.game.players[i].stick[DIR_UP]);
        }

        if ( mask[i] & KCFG_INP_JOY_DOWN ) {
            sprintf ( full_str, "%s.%s.%s", input_id, type_str, "joystick.down" );
            config_set_str_ary_from_input (p_tbl, full_str,
                &config.input.game.players[i].stick[DIR_DOWN]);
        }

        /* 
         * For Atari 5200, get the buttons and keypad
         */
        if (type == MACHINE_TYPE_5200) {

            if ( mask[i] & KCFG_INP_JOY_BTM ) {
                sprintf ( full_str, "%s.%s.%s", input_id, type_str, 
                    "joystick.bottom_button" );
                config_set_str_ary_from_input (p_tbl, full_str,
                    &config.input.game.players[i].bottom_button);
            }

            if ( mask[i] & KCFG_INP_JOY_TOP ) {
                sprintf ( full_str, "%s.%s.%s", input_id, type_str, 
                    "joystick.top_button" );
                config_set_str_ary_from_input (p_tbl, full_str,
                    &config.input.game.players[i].top_button);
            }

            for ( int k = 0; k < MAX_KEYPAD_KEYS; ++k ) {

                int keyset = 0;

                int key = (k << 1) | 1;

                switch (key) {
                    case KEY_START: keyset = mask[i] & KCFG_INP_JOY_KEY_STT; break;
                    case KEY_PAUSE: keyset = mask[i] & KCFG_INP_JOY_KEY_PAU; break;
                    case KEY_RESET: keyset = mask[i] & KCFG_INP_JOY_KEY_RST; break;
                    case KEY_STAR: keyset = mask[i] & KCFG_INP_JOY_KEY_STR; break;
                    case KEY_POUND: keyset = mask[i] & KCFG_INP_JOY_KEY_PND; break;
                    case KEY_0: keyset = mask[i] & KCFG_INP_JOY_KEY_0; break;
                    case KEY_1: keyset = mask[i] & KCFG_INP_JOY_KEY_1; break;
                    case KEY_2: keyset = mask[i] & KCFG_INP_JOY_KEY_2; break;
                    case KEY_3: keyset = mask[i] & KCFG_INP_JOY_KEY_3; break;
                    case KEY_4: keyset = mask[i] & KCFG_INP_JOY_KEY_4; break;
                    case KEY_5: keyset = mask[i] & KCFG_INP_JOY_KEY_5; break;
                    case KEY_6: keyset = mask[i] & KCFG_INP_JOY_KEY_6; break;
                    case KEY_7: keyset = mask[i] & KCFG_INP_JOY_KEY_7; break;
                    case KEY_8: keyset = mask[i] & KCFG_INP_JOY_KEY_8; break;
                    case KEY_9: keyset = mask[i] & KCFG_INP_JOY_KEY_9; break;
                }

                if ( keyset ) {

                    sprintf ( full_str, "%s.%s.%s.", input_id, type_str, "joystick" );

                    switch (key) {
                        case KEY_START: strcat(full_str, "key_start"); break;
                        case KEY_PAUSE: strcat(full_str, "key_pause"); break;
                        case KEY_RESET: strcat(full_str, "key_reset"); break;
                        case KEY_STAR: strcat(full_str, "key_star"); break;
                        case KEY_POUND: strcat(full_str, "key_pound"); break;
                        case KEY_0: strcat(full_str, "key_0"); break;
                        case KEY_1: strcat(full_str, "key_1"); break;
                        case KEY_2: strcat(full_str, "key_2"); break;
                        case KEY_3: strcat(full_str, "key_3"); break;
                        case KEY_4: strcat(full_str, "key_4"); break;
                        case KEY_5: strcat(full_str, "key_5"); break;
                        case KEY_6: strcat(full_str, "key_6"); break;
                        case KEY_7: strcat(full_str, "key_7"); break;
                        case KEY_8: strcat(full_str, "key_8"); break;
                        case KEY_9: strcat(full_str, "key_9"); break;
                    }

                    config_set_str_ary_from_input (p_tbl, full_str,
                        &config.input.game.players[i].keypad[k]);
                }

            } /* end for each keypad key */

            /*
             * Trackball
             */

            /* Make sure trackball table header there */
            sprintf ( full_str, "%s.%s.%s", input_id, type_str, "trackball" );
            p_joytbl = toml_find_node ( p_tbl, full_str );
            if (!p_joytbl) {
                p_joytbl = toml_add_table ( p_tbl, full_str, TOML_TABLE_ARY, "  " );
                if ( !p_joytbl )
                    continue;
            }

            if ( mask[i] & KCFG_INP_TRK_LEFT ) {
                sprintf ( full_str, "%s.%s.%s", input_id, type_str, "trackball.left" );
                config_set_str_ary_from_input (p_tbl, full_str,
                    &config.input.game.players[i].trackball[DIR_LEFT]);
            }

            if ( mask[i] & KCFG_INP_TRK_RIGHT ) {
                sprintf ( full_str, "%s.%s.%s", input_id, type_str, "trackball.right" );
                config_set_str_ary_from_input (p_tbl, full_str,
                    &config.input.game.players[i].trackball[DIR_RIGHT]);
            }
    
            if ( mask[i] & KCFG_INP_TRK_UP ) {
                sprintf ( full_str, "%s.%s.%s", input_id, type_str, "trackball.up" );
                config_set_str_ary_from_input (p_tbl, full_str,
                    &config.input.game.players[i].trackball[DIR_UP]);
            }
    
            if ( mask[i] & KCFG_INP_TRK_DOWN ) {
                sprintf ( full_str, "%s.%s.%s", input_id, type_str, "trackball.down" );
                config_set_str_ary_from_input (p_tbl, full_str,
                    &config.input.game.players[i].trackball[DIR_DOWN]);
            }

        } /* end if atari 5200 */

        /*
         * Else get button for 800 and store in bottom_button struct
         */
        else {

            if ( mask[i] & KCFG_INP_JOY_BTM ) {
                sprintf ( full_str, "%s.%s.%s", input_id, type_str, "joystick.button" );
                config_set_str_ary_from_input (p_tbl, full_str,
                    &config.input.game.players[i].bottom_button);
            }

            /*
             * Paddles for 800
             */

            /* Make sure paddles table header there */
            sprintf ( full_str, "%s.%s.%s", input_id, type_str, "paddles" );
            p_joytbl = toml_find_node ( p_tbl, full_str );
            if (!p_joytbl) {
                p_joytbl = toml_add_table ( p_tbl, full_str, TOML_TABLE_ARY, "  " );
                if ( !p_joytbl )
                    continue;
            }

            for (int j = 0; j < 2; ++j) {

                if ( (!j && (mask[i] & KCFG_INP_PADD_1_LEFT)) || 
                     (j && (mask[i] & KCFG_INP_PADD_2_LEFT)) ) {
                    sprintf ( full_str, "%s.%s.%s.%d.%s", input_id, type_str, 
                        "800.paddles",i+1, "left" );
                    config_set_str_ary_from_input (p_tbl, full_str,
                        &config.input.game.players[i].paddles[j].left);
                }

                if ( (!j && (mask[i] & KCFG_INP_PADD_1_RIGHT)) || 
                     (j && (mask[i] & KCFG_INP_PADD_2_RIGHT)) ) {
                    sprintf ( full_str, "%s.%s.%s.%d.%s", input_id, type_str, 
                        "800.paddles",i+1, "right" );
                    config_set_str_ary_from_input (p_tbl, full_str,
                        &config.input.game.players[i].paddles[j].right);
                }

                if ( (!j && (mask[i] & KCFG_INP_PADD_1_BUTN)) || 
                     (j && (mask[i] & KCFG_INP_PADD_2_BUTN)) ) {
                    sprintf ( full_str, "%s.%s.%s.%d.%s", input_id, type_str, 
                        "800.paddles",i+1, "button" );
                    config_set_str_ary_from_input (p_tbl, full_str,
                        &config.input.game.players[i].paddles[j].button);
                }

            } /* end for paddles 1 and 2 */

        } /* end else 800 machine specific joy stuff */

    } /* end for each controller port */

    /*
     * Keyboard for 800
     */
    unsigned long long key_bits = KCFG_INP_KEY_SHIFT | KCFG_INP_KEY_CTRL | KCFG_INP_KEY_BREAK |
                                  KCFG_INP_KEY_START | KCFG_INP_KEY_SELECT | KCFG_INP_KEY_OPTION |
                                  KCFG_INP_KEY_RESET;
    if (keymask || (mask[0] & key_bits)) {

    p_tbl = toml_find_node ( tmptbl, "input.game.keyboard" );
    if (!p_tbl) {
        p_tbl = toml_add_table ( tmptbl, "input.game.keyboard", TOML_TABLE_ARY, "  " );
    }

    if (p_tbl != NULL) {
        for (int j = 0; j < 64; ++j) {

            char key_id[50];
            config_get_800_key_string_name(j, key_id);
            if ( key_id == NULL || strlen(key_id) == 0 )
                continue;
    
            if ( (1ULL << j) & keymask ) {
                config_set_str_ary_from_input (p_tbl, key_id, 
                    &config.input.game.keyboard[j]);
            }

        }

        if ( mask[0] & KCFG_INP_KEY_SHIFT ) {
            config_set_str_ary_from_input (p_tbl, "key_shift",
                &config.input.game.shift_key);
        }

        if ( mask[0] & KCFG_INP_KEY_CTRL ) {
            config_set_str_ary_from_input (p_tbl, "key_ctrl", 
                &config.input.game.ctrl_key);
        }

        if ( mask[0] & KCFG_INP_KEY_BREAK ) {
            config_set_str_ary_from_input (p_tbl, "key_break", 
                &config.input.game.break_key);
        }

        if ( mask[0] & KCFG_INP_KEY_START ) {
            config_set_str_ary_from_input (p_tbl, "key_start", 
                &config.input.game.start_key);
        }

        if ( mask[0] & KCFG_INP_KEY_SELECT ) {
            config_set_str_ary_from_input (p_tbl, "key_select", 
                &config.input.game.select_key);
        }

        if ( mask[0] & KCFG_INP_KEY_OPTION ) {
            config_set_str_ary_from_input (p_tbl, "key_option", 
                &config.input.game.option_key);
        }

        if ( mask[0] & KCFG_INP_KEY_RESET ) {
            config_set_str_ary_from_input (p_tbl, "key_reset",
                &config.input.game.reset_key);
        }

    } /* end if keyboard table exists */

    } /* end if keyboard mask set */

    if (tmp_string)
        free(tmp_string);

    config_write_file ( tbl );

} /* end config_save_game_input */

void config_load_game_input (e_machine_type type, t_toml_node *tbl, 
        const char *tag ) {

    int j;
    int result;
    char input_id[40];

    t_toml_node *tmptbl, *p_tbl;
    char *tmp_string = 0;

    if ( !tbl )
        return;

    tmptbl = toml_get_table_array_by_key ( tbl->child, "input.game", "tag", tag );

    if (!tmptbl)
        return;
    /*
     * Navigate through 4 controller ports
     */
    for ( int i = 0; i < MAX_CONTROLLER; i++ ) {

        /* Get player table */
        sprintf(input_id, "input.game.p%d", i);
        p_tbl = toml_find_node ( tmptbl, input_id );
        if (p_tbl == NULL)
            continue;

        /* 
         * Get common config 
         */
        result = config_get_bool ( p_tbl, "joy.enable", 
                             &config.input.game.players[i].joy_enable, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_JOY_EN;

        result = config_get_int ( p_tbl, "joy.sensitivity", 
                          &config.input.game.players[i].joy_sensitivity, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_JOY_SENS;

        result = config_get_int ( p_tbl, "joy.deadzone", 
                          &config.input.game.players[i].joy_deadzone, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_JOY_DEAD;

        char *tmp_str = NULL;
        result = config_get_string ( p_tbl, "joy.guid", &tmp_str, 0 );
        if (!result) {
            strncpy ( config.input.game.players[i].joy_guid, tmp_str,
                      sizeof(config.input.game.players[i].joy_guid) - 1 );
            config.input.game.players[i].joy_guid[sizeof(config.input.game.players[i].joy_guid) - 1] = '\0';
            free(tmp_str);
            tmp_str = NULL;
            config.input_mask[i] |= KCFG_INP_JOY_GUID;
        }

        result = config_get_string ( p_tbl, "joy.serial", &tmp_str, 0 );
        if (!result) {
            strncpy ( config.input.game.players[i].joy_serial, tmp_str,
                      sizeof(config.input.game.players[i].joy_serial) - 1 );
            config.input.game.players[i].joy_serial[sizeof(config.input.game.players[i].joy_serial) - 1] = '\0';
            free(tmp_str);
            config.input_mask[i] |= KCFG_INP_JOY_SERIAL;
        }

        result = config_get_bool ( p_tbl, "analog_stick_acts_digital", 
                             &config.input.game.players[i].analog_stick_acts_digital, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_SIM_AN;

        result = config_get_bool ( p_tbl, "dpad.enable", 
                             &config.input.game.players[i].dpad_enable, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_DPAD_EN;

        result = config_get_bool ( p_tbl, "dpad.analog.enable", 
                             &config.input.game.players[i].digdir_analog_enable, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_DPAD_AN_EN;

        result = config_get_int ( p_tbl, "dpad.analog.speed", 
                          &config.input.game.players[i].digdir_analog_speed, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_DPAD_AN_SPD;

        result = config_get_bool ( p_tbl, "mouse.enable", 
                             &config.input.game.players[i].mouse_enable, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_MOUSE_EN;

        result = config_get_int ( p_tbl, "mouse.speed",
                          &config.input.game.players[i].mouse_speed, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_MOUSE_SPD;

        result = config_get_bool ( p_tbl, "auto_fire",
                             &config.input.game.players[i].auto_fire, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_AUTO_FIRE;

        result = config_get_int ( p_tbl, "auto_fire_speed",
                          &config.input.game.players[i].auto_fire_speed, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_AUTO_FIRE_SPD;

        result = config_get_string ( p_tbl, "mouse.optimize", &tmp_string, 0 );
        if (!result) {
            config.input_mask[i] |= KCFG_INP_MOUSE_OPT;
            config.input.game.players[i].mouse_optimize =
                config_get_mouse_optimize_from_string ( tmp_string );
        }

        result = config_get_bool ( p_tbl, "keyboard.enable",
                             &config.input.game.players[i].keyboard_enable, 0 );
        if (!result)
            config.input_mask[i] |= KCFG_INP_KEYBD_EN;

        if (type == MACHINE_TYPE_5200) 
            result = config_get_string ( p_tbl, "5200.type", &tmp_string, 0 );
        else 
            result = config_get_string ( p_tbl, "800.type", &tmp_string, 0 );

        if (!result) {
            config.input_mask[i] |= KCFG_INP_TYPE;
            config.input.game.players[i].type = 
                config_get_control_type_from_string ( tmp_string );
        }

        /*
         * Joystick
         */
        char type_str[7];
        char full_str[100];
        if (type == MACHINE_TYPE_5200)
            strcpy ( type_str, "5200" );
        else
            strcpy ( type_str, "800" );

        sprintf ( full_str, "%s.%s.%s", input_id, type_str, "joystick.left" );
        result = config_load_input_from_str_ary (p_tbl, full_str,
                &config.input.game.players[i].stick[DIR_LEFT]); 
        if (!result)
            config.input_mask[i] |= KCFG_INP_JOY_LEFT;

        sprintf ( full_str, "%s.%s.%s", input_id, type_str, "joystick.right" );
        result = config_load_input_from_str_ary (p_tbl, full_str,
                &config.input.game.players[i].stick[DIR_RIGHT]); 
        if (!result)
            config.input_mask[i] |= KCFG_INP_JOY_RIGHT;

        sprintf ( full_str, "%s.%s.%s", input_id, type_str, "joystick.up" );
        result = config_load_input_from_str_ary (p_tbl, full_str,
                &config.input.game.players[i].stick[DIR_UP]); 
        if (!result)
            config.input_mask[i] |= KCFG_INP_JOY_UP;

        sprintf ( full_str, "%s.%s.%s", input_id, type_str, "joystick.down" );
        result = config_load_input_from_str_ary (p_tbl, full_str,
                &config.input.game.players[i].stick[DIR_DOWN]); 
        if (!result)
            config.input_mask[i] |= KCFG_INP_JOY_DOWN;

        /* 
         * For Atari 5200, get the buttons and keypad
         */
        if (type == MACHINE_TYPE_5200) {

            sprintf ( full_str, "%s.%s.%s", input_id, type_str, 
                    "joystick.bottom_button" );
            result = config_load_input_from_str_ary (p_tbl, full_str,
                    &config.input.game.players[i].bottom_button); 
            if (!result)
                config.input_mask[i] |= KCFG_INP_JOY_BTM;

            sprintf ( full_str, "%s.%s.%s", input_id, type_str, 
                    "joystick.top_button" );
            result = config_load_input_from_str_ary (p_tbl, full_str,
                    &config.input.game.players[i].top_button); 
            if (!result)
                config.input_mask[i] |= KCFG_INP_JOY_TOP;

            for ( j = 0; j < MAX_KEYPAD_KEYS; ++j ) {
                int key = (j << 1) | 1;

                sprintf ( full_str, "%s.%s.%s.", input_id, type_str, "joystick" );

                unsigned long long tmp_mask = 0;
                switch (key) {
                    case KEY_START: tmp_mask = KCFG_INP_JOY_KEY_STT; break;
                    case KEY_PAUSE: tmp_mask = KCFG_INP_JOY_KEY_PAU; break;
                    case KEY_RESET: tmp_mask = KCFG_INP_JOY_KEY_RST; break;
                    case KEY_STAR: tmp_mask = KCFG_INP_JOY_KEY_STR; break;
                    case KEY_POUND: tmp_mask = KCFG_INP_JOY_KEY_PND; break;
                    case KEY_0: tmp_mask = KCFG_INP_JOY_KEY_0; break;
                    case KEY_1: tmp_mask = KCFG_INP_JOY_KEY_1; break;
                    case KEY_2: tmp_mask = KCFG_INP_JOY_KEY_2; break;
                    case KEY_3: tmp_mask = KCFG_INP_JOY_KEY_3; break;
                    case KEY_4: tmp_mask = KCFG_INP_JOY_KEY_4; break;
                    case KEY_5: tmp_mask = KCFG_INP_JOY_KEY_5; break;
                    case KEY_6: tmp_mask = KCFG_INP_JOY_KEY_6; break;
                    case KEY_7: tmp_mask = KCFG_INP_JOY_KEY_7; break;
                    case KEY_8: tmp_mask = KCFG_INP_JOY_KEY_8; break;
                    case KEY_9: tmp_mask = KCFG_INP_JOY_KEY_9; break;
                }

                switch (key) {
                    case KEY_START: strcat(full_str, "key_start"); break;
                    case KEY_PAUSE: strcat(full_str, "key_pause"); break;
                    case KEY_RESET: strcat(full_str, "key_reset"); break;
                    case KEY_STAR: strcat(full_str, "key_star"); break;
                    case KEY_POUND: strcat(full_str, "key_pound"); break;
                    case KEY_0: strcat(full_str, "key_0"); break;
                    case KEY_1: strcat(full_str, "key_1"); break;
                    case KEY_2: strcat(full_str, "key_2"); break;
                    case KEY_3: strcat(full_str, "key_3"); break;
                    case KEY_4: strcat(full_str, "key_4"); break;
                    case KEY_5: strcat(full_str, "key_5"); break;
                    case KEY_6: strcat(full_str, "key_6"); break;
                    case KEY_7: strcat(full_str, "key_7"); break;
                    case KEY_8: strcat(full_str, "key_8"); break;
                    case KEY_9: strcat(full_str, "key_9"); break;
                }

                result = config_load_input_from_str_ary (p_tbl, full_str,
                        &config.input.game.players[i].keypad[j]); 
                if (!result)
                    config.input_mask[i] |= tmp_mask;

            } /* end for each keypad key */

            /*
             * Trackball
             */
            sprintf ( full_str, "%s.%s.%s", input_id, type_str, "trackball.left" );
            result = config_load_input_from_str_ary (p_tbl, full_str,
                    &config.input.game.players[i].trackball[DIR_LEFT]); 
            if (!result)
                config.input_mask[i] |= KCFG_INP_TRK_LEFT;

            sprintf ( full_str, "%s.%s.%s", input_id, type_str, "trackball.right" );
            result = config_load_input_from_str_ary (p_tbl, full_str,
                    &config.input.game.players[i].trackball[DIR_RIGHT]); 
            if (!result)
                config.input_mask[i] |= KCFG_INP_TRK_RIGHT;
    
            sprintf ( full_str, "%s.%s.%s", input_id, type_str, "trackball.up" );
            result = config_load_input_from_str_ary (p_tbl, full_str,
                    &config.input.game.players[i].trackball[DIR_UP]); 
            if (!result)
                config.input_mask[i] |= KCFG_INP_TRK_UP;
    
            sprintf ( full_str, "%s.%s.%s", input_id, type_str, "trackball.down" );
            result = config_load_input_from_str_ary (p_tbl, full_str,
                    &config.input.game.players[i].trackball[DIR_DOWN]); 
            if (!result)
                config.input_mask[i] |= KCFG_INP_TRK_DOWN;

        } /* end if atari 5200 */

        /*
         * Else get button for 800 and store in bottom_button struct
         */
        else {
            sprintf ( full_str, "%s.%s.%s", input_id, type_str, "joystick.button" );
            result = config_load_input_from_str_ary (p_tbl, full_str,
                    &config.input.game.players[i].bottom_button);
            if (!result)
                config.input_mask[i] |= KCFG_INP_JOY_BTM;
        }

        /*
         * Paddles for 800
         */
        for (j = 0; j < 2; ++j) {
            sprintf ( full_str, "%s.%s.%d.%s", input_id, 
                    "800.paddles",j+1, "left" );
            result = config_load_input_from_str_ary (p_tbl, full_str,
                    &config.input.game.players[i].paddles[j].left);
            if (!result) {
                if (j == 0)
                    config.input_mask[i] |= KCFG_INP_PADD_1_LEFT;
                else
                    config.input_mask[i] |= KCFG_INP_PADD_2_LEFT;
            }

            sprintf ( full_str, "%s.%s.%d.%s", input_id, 
                    "800.paddles",j+1, "right" );
            result = config_load_input_from_str_ary (p_tbl, full_str,
                    &config.input.game.players[i].paddles[j].right);
            if (!result) {
                if (j == 0)
                    config.input_mask[i] |= KCFG_INP_PADD_1_RIGHT;
                else
                    config.input_mask[i] |= KCFG_INP_PADD_2_RIGHT;
            }

            sprintf ( full_str, "%s.%s.%d.%s", input_id, 
                    "800.paddles",j+1, "button" );
            result = config_load_input_from_str_ary (p_tbl, full_str,
                    &config.input.game.players[i].paddles[j].button);
            if (!result) {
                if (j == 0)
                    config.input_mask[i] |= KCFG_INP_PADD_1_BUTN;
                else
                    config.input_mask[i] |= KCFG_INP_PADD_2_BUTN;
            }
        }

    } /* end for each controller port */

    /*
     * Keyboard for 800
     */

    p_tbl = toml_find_node ( tmptbl, "input.game.keyboard" );

    if (p_tbl != NULL) {
        for (int j = 0; j < 64; ++j) {

            char key_id[50];
            config_get_800_key_string_name(j, key_id);
            if ( key_id == NULL || strlen(key_id) == 0 )
                continue;
    
            result = config_load_input_from_str_ary (p_tbl, key_id, 
                    &config.input.game.keyboard[j]); 
            if (!result)
                config.keyboard_mask |= 1ULL << j;

        }
        result = config_load_input_from_str_ary (p_tbl, "key_shift",
                &config.input.game.shift_key);
        if (!result)
            config.input_mask[0] |= KCFG_INP_KEY_SHIFT;

        result = config_load_input_from_str_ary (p_tbl, "key_ctrl", 
                &config.input.game.ctrl_key);
        if (!result)
            config.input_mask[0] |= KCFG_INP_KEY_CTRL;

        result = config_load_input_from_str_ary (p_tbl, "key_break", 
                &config.input.game.break_key);
        if (!result)
            config.input_mask[0] |= KCFG_INP_KEY_BREAK;

        result = config_load_input_from_str_ary (p_tbl, "key_start", 
                &config.input.game.start_key);
        if (!result)
            config.input_mask[0] |= KCFG_INP_KEY_START;

        result = config_load_input_from_str_ary (p_tbl, "key_select",
                &config.input.game.select_key);
        if (!result)
            config.input_mask[0] |= KCFG_INP_KEY_SELECT;

        result = config_load_input_from_str_ary (p_tbl, "key_option", 
                &config.input.game.option_key);
        if (!result)
            config.input_mask[0] |= KCFG_INP_KEY_OPTION;

        result = config_load_input_from_str_ary (p_tbl, "key_reset",
                &config.input.game.reset_key);
        if (!result)
            config.input_mask[0] |= KCFG_INP_KEY_RESET;

    } /* end if keyboard table exists */

    if (tmp_string)
        free(tmp_string);

} /* end config_load_game_input */

/******************************************************************************
**  Function   :  config_load_ui_input
**                config_save_ui_input
**                                                
**  Objective  :  This loads or writes ui configuration parameters to the ui
**                table and then writes the file if saving
**                                               
**  Parameters :  tbl - parsed toml table, this can be either global config
**                      or rom specific file
**                mask - which parameters to set for save function
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_load_ui_input (t_toml_node *tbl ) {

    char input_id[100];

    config_load_input_from_str_ary (tbl, "input.misc.exit", &config.input.ui.exit);
    config_load_input_from_str_ary (tbl, "input.misc.back", &config.input.ui.back); 
    config_load_input_from_str_ary (tbl, "input.misc.fullscreen", 
            &config.input.ui.fullscreen); 
    config_load_input_from_str_ary (tbl, "input.misc.screenshot", 
            &config.input.ui.screenshot); 
    config_load_input_from_str_ary (tbl, "input.misc.debugger", 
            &config.input.ui.debugger); 

    for ( int i = 0; i < MAX_UI_STATES; ++i ) {

        sprintf ( input_id, "input.misc.load.state.%d", i+1 );
        config_load_input_from_str_ary (tbl, input_id, &config.input.ui.state_load[i]); 

        sprintf ( input_id, "input.misc.save.state.%d", i+1 );
        config_load_input_from_str_ary (tbl, input_id, &config.input.ui.state_save[i]); 

    } /* end for each ui key */

} /* end config_load_ui_input */

void config_save_ui_input (t_toml_node *tbl, unsigned long long mask ) {

    char input_id[100];
    t_toml_node *tmptbl;

    if (!tbl)
        return;

    if (!mask)
        return;

    tmptbl = toml_find_node ( tbl, "input.misc" );
    if (!tmptbl) {

        tmptbl = toml_add_table ( tbl, "input.misc", TOML_TABLE, "" );
        if (!tmptbl)
            return;
    }

    if ( mask & KCFG_UI_EXIT )
        config_set_str_ary_from_input (tbl, "input.misc.exit", &config.input.ui.exit);

    if ( mask & KCFG_UI_BACK )
        config_set_str_ary_from_input (tbl, "input.misc.back", &config.input.ui.back); 

    if ( mask & KCFG_UI_FULLSCREEN )
        config_set_str_ary_from_input (tbl, "input.misc.fullscreen", 
            &config.input.ui.fullscreen); 

    if ( mask & KCFG_UI_SCREENSHOT )
        config_set_str_ary_from_input (tbl, "input.misc.screenshot", 
            &config.input.ui.screenshot); 

    if ( mask & KCFG_UI_DEBUGGER )
        config_set_str_ary_from_input (tbl, "input.misc.debugger", 
            &config.input.ui.debugger); 

    for ( int i = 0; i < MAX_UI_STATES; ++i ) {

        if ( mask & (KCFG_UI_LOAD_1 << i) ) {
            sprintf ( input_id, "input.misc.load.state.%d", i+1 );
            config_set_str_ary_from_input (tbl, input_id, 
                    &config.input.ui.state_load[i]); 
        }

        if ( mask & (KCFG_UI_SAVE_1 << i) ) {
            sprintf ( input_id, "input.misc.save.state.%d", i+1 );
            config_set_str_ary_from_input (tbl, input_id, 
                    &config.input.ui.state_save[i]); 
        }

    } /* end for each ui key */

    config_write_file ( tbl );

} /* end config_save_ui_input */

/******************************************************************************
**  Function   :  config_load_debugger_window_pos
**
**  Objective  :  Load the position and size for a debugger window from config.
**                Keys are stored as "debugger.<window_key>.x", ".y", ".w", ".h"
**
**  Parameters :  window_key - the window identifier (e.g., "gtia", "cpu")
**                x          - pointer to store x position
**                y          - pointer to store y position
**                w          - pointer to store width
**                h          - pointer to store height
**
**  return     :  NONE
******************************************************************************/
void config_load_debugger_window_pos ( const char *window_key, int *x, int *y, int *w, int *h ) {

    char key[64];
    t_toml_node *dbg_tbl;

    if (!g_conftbl)
        return;

    dbg_tbl = toml_find_node(g_conftbl, "debugger");
    if (!dbg_tbl)
        return;

    sprintf(key, "%s.x", window_key);
    config_get_int(dbg_tbl, key, x, 0);

    sprintf(key, "%s.y", window_key);
    config_get_int(dbg_tbl, key, y, 0);

    sprintf(key, "%s.w", window_key);
    config_get_int(dbg_tbl, key, w, 0);

    sprintf(key, "%s.h", window_key);
    config_get_int(dbg_tbl, key, h, 0);

} /* end config_load_debugger_window_pos */

/******************************************************************************
**  Function   :  config_save_debugger_window_pos
**
**  Objective  :  Save the position and size for a debugger window to config.
**                Keys are stored as "debugger.<window_key>.x", ".y", ".w", ".h"
**
**  Parameters :  window_key - the window identifier (e.g., "gtia", "cpu")
**                x          - x position to save
**                y          - y position to save
**                w          - width to save
**                h          - height to save
**
**  return     :  NONE
******************************************************************************/
void config_save_debugger_window_pos ( const char *window_key, int x, int y, int w, int h ) {

    char key[64];
    t_toml_node *dbg_tbl;

    if (!g_conftbl)
        return;

    dbg_tbl = toml_find_node(g_conftbl, "debugger");
    if (!dbg_tbl) {
        dbg_tbl = toml_add_table(g_conftbl, "debugger", TOML_TABLE, "");
        if (!dbg_tbl)
            return;
    }

    sprintf(key, "%s.x", window_key);
    config_set_int(dbg_tbl, key, x, 0);

    sprintf(key, "%s.y", window_key);
    config_set_int(dbg_tbl, key, y, 0);

    sprintf(key, "%s.w", window_key);
    config_set_int(dbg_tbl, key, w, 0);

    sprintf(key, "%s.h", window_key);
    config_set_int(dbg_tbl, key, h, 0);

    config_write_file(g_conftbl);

} /* end config_save_debugger_window_pos */

/******************************************************************************
**  Function   :  config_load_filebrowser_size
**
**  Objective  :  Load the saved filebrowser window size from config.
**
**  Parameters :  w - pointer to width  (unchanged if key missing)
**                h - pointer to height (unchanged if key missing)
**
**  return     :  NONE
******************************************************************************/
void config_load_filebrowser_size ( int *w, int *h ) {

    t_toml_node *fb_tbl;

    if (!g_conftbl)
        return;

    fb_tbl = toml_find_node(g_conftbl, "filebrowser");
    if (!fb_tbl)
        return;

    config_get_int(fb_tbl, "w", w, 0);
    config_get_int(fb_tbl, "h", h, 0);

} /* end config_load_filebrowser_size */

/******************************************************************************
**  Function   :  config_save_filebrowser_size
**
**  Objective  :  Save the filebrowser window size to config.
**
**  Parameters :  w - width to save
**                h - height to save
**
**  return     :  NONE
******************************************************************************/
void config_save_filebrowser_size ( int w, int h ) {

    t_toml_node *fb_tbl;

    if (!g_conftbl)
        return;

    fb_tbl = toml_find_node(g_conftbl, "filebrowser");
    if (!fb_tbl) {
        fb_tbl = toml_add_table(g_conftbl, "filebrowser", TOML_TABLE, "");
        if (!fb_tbl)
            return;
    }

    config_set_int(fb_tbl, "w", w, 0);
    config_set_int(fb_tbl, "h", h, 0);

    config_write_file(g_conftbl);

} /* end config_save_filebrowser_size */

/******************************************************************************
**  Function   :  config_load_debugger_breakpoints
**
**  Objective  :  Load breakpoints from the [debugger] breakpoints array in
**                kat5200.conf into the debug struct.
**
**  Parameters :  NONE
**
**  return     :  NONE
******************************************************************************/
void config_load_debugger_breakpoints ( void ) {

    t_toml_node *dbg_tbl;
    t_toml_node *array_node;
    char *value_str;
    char buf[64];
    char *type_str, *subtype_str, *val_str;
    int slot = 0;
    int i;

    if (!g_conftbl)
        return;

    /* Clear all breakpoints */
    for (i = 0; i < DEBUG_MAX_BK; ++i) {
        debug.breakpoint[i] = BK_NONE;
        debug.breakpoint_place[i] = 0;
    }
    debug.breakpoint_flag = 0;

    dbg_tbl = toml_find_node(g_conftbl, "debugger");
    if (!dbg_tbl)
        return;

    array_node = toml_get_key_array_values_start(dbg_tbl, "breakpoints");
    if (!array_node)
        return;

    for (i = 0; slot < DEBUG_MAX_BK; ++i) {
        value_str = toml_get_value_by_index_from_array_node(array_node, i);
        if (!value_str)
            break;

        /* Copy to local buffer since strtok modifies in-place */
        strncpy(buf, value_str, sizeof(buf) - 1);
        buf[sizeof(buf) - 1] = '\0';

        type_str = strtok(buf, "|");
        subtype_str = strtok(NULL, "|");
        val_str = strtok(NULL, "|");

        if (!type_str || !subtype_str || !val_str)
            continue;

        if (strcmp(type_str, "Break") == 0 && strcmp(subtype_str, "Memory") == 0) {
            debug.breakpoint[slot] = BK_MEM;
            debug.breakpoint_place[slot] = (int)strtol(val_str, NULL, 16);
        } else if (strcmp(type_str, "Break") == 0 && strcmp(subtype_str, "Scanline") == 0) {
            debug.breakpoint[slot] = BK_SCANLINE;
            debug.breakpoint_place[slot] = atoi(val_str);
        } else if (strcmp(type_str, "Watch") == 0 && strcmp(subtype_str, "Memory") == 0) {
            debug.breakpoint[slot] = BK_WATCH;
            debug.breakpoint_place[slot] = (int)strtol(val_str, NULL, 16);
        } else {
            continue;
        }

        debug.breakpoint_flag = 1;
        ++slot;
    }

} /* end config_load_debugger_breakpoints */

/******************************************************************************
**  Function   :  config_save_debugger_breakpoint
**
**  Objective  :  Append a single breakpoint to the [debugger] breakpoints
**                array in kat5200.conf.
**
**  Parameters :  bp_type  - breakpoint type (BK_MEM, BK_SCANLINE, BK_WATCH)
**                bp_value - breakpoint value (address or scanline number)
**
**  return     :  NONE
******************************************************************************/
void config_save_debugger_breakpoint ( int bp_type, int bp_value ) {

    t_toml_node *dbg_tbl;
    char entry[32];
    int result;

    if (!g_conftbl)
        return;

    /* Format the pipe-delimited string */
    if (bp_type == BK_MEM)
        sprintf(entry, "Break|Memory|%04X", bp_value);
    else if (bp_type == BK_SCANLINE)
        sprintf(entry, "Break|Scanline|%d", bp_value);
    else if (bp_type == BK_WATCH)
        sprintf(entry, "Watch|Memory|%04X", bp_value);
    else
        return;

    /* Find or create [debugger] table */
    dbg_tbl = toml_find_node(g_conftbl, "debugger");
    if (!dbg_tbl) {
        dbg_tbl = toml_add_table(g_conftbl, "debugger", TOML_TABLE, "");
        if (!dbg_tbl)
            return;
    }

    /* Try to append to existing array */
    result = toml_insert_array_value_by_index(dbg_tbl, "breakpoints",
            entry, " ", " ", " ", -1);

    if (result) {
        /* Array doesn't exist yet, create it with this value */
        toml_start_array_with_value(dbg_tbl, "breakpoints", TOML_DQ_STRING,
                entry, " ", " ");
    }

    config_write_file(g_conftbl);

} /* end config_save_debugger_breakpoint */

/******************************************************************************
**  Function   :  config_delete_debugger_breakpoint
**
**  Objective  :  Remove a single breakpoint from the [debugger] breakpoints
**                array in kat5200.conf.
**
**  Parameters :  bp_type  - breakpoint type (BK_MEM, BK_SCANLINE, BK_WATCH)
**                bp_value - breakpoint value (address or scanline number)
**
**  return     :  NONE
******************************************************************************/
void config_delete_debugger_breakpoint ( int bp_type, int bp_value ) {

    t_toml_node *dbg_tbl;
    t_toml_node *array_node;
    char entry[32];
    char *value_str;
    int i;

    if (!g_conftbl)
        return;

    /* Format the pipe-delimited string to match */
    if (bp_type == BK_MEM)
        sprintf(entry, "Break|Memory|%04X", bp_value);
    else if (bp_type == BK_SCANLINE)
        sprintf(entry, "Break|Scanline|%d", bp_value);
    else if (bp_type == BK_WATCH)
        sprintf(entry, "Watch|Memory|%04X", bp_value);
    else
        return;

    dbg_tbl = toml_find_node(g_conftbl, "debugger");
    if (!dbg_tbl)
        return;

    array_node = toml_get_key_array_values_start(dbg_tbl, "breakpoints");
    if (!array_node)
        return;

    /* Search for matching entry and delete it */
    for (i = 0; ; ++i) {
        value_str = toml_get_value_by_index_from_array_node(array_node, i);
        if (!value_str)
            break;

        if (strcmp(value_str, entry) == 0) {
            toml_delete_array_value_by_index(dbg_tbl, "breakpoints", i);
            config_write_file(g_conftbl);
            return;
        }
    }

} /* end config_delete_debugger_breakpoint */

/******************************************************************************
**  Function   :  config_get_int
**                config_get_float
**                config_get_bool
**                config_get_string
**                                                
**  Objective  :  This functions grab the string value from the input key and 
**                then convert to the given type.  If the key is not found, 
**                the value for the input will not be changed. The caller can
**                also set the check_error flag to have any errors written
**                to the log.  This should be used only when the key is 
**                required. For the string value, any current set memory is
**                freed.
**                                               
**  Parameters :  tbl         - the TOML node to start searching from
**                key         - the name of the key to find
**                key_value   - pointer to store returned value
**                check_error - whether to log errors
**                                             
**  return     :  0 on success, otherwise error
******************************************************************************/ 
int config_get_int ( t_toml_node *tbl, const char *key, 
                      int *key_value, int check_error ) {

    int i;
    char *ptr = toml_get_key_value ( tbl, key );
    char *endptr = ptr;

    if (ptr) {
        i = strtol ( ptr, &endptr, 0 );
        if (endptr != ptr)
            *key_value = i;
    }

    if ( endptr == ptr ) {
        if (check_error)
            config_set_key_error ( key );

        return -1;
    }

    return 0;

} /* end config_get_int */

int config_get_long ( t_toml_node *tbl, const char *key, 
                      unsigned long *key_value, int check_error ) {

    int i;
    char *ptr = toml_get_key_value ( tbl, key );
    char *endptr = ptr;

    if (ptr) {
        i = strtol ( ptr, &endptr, 0 );
        if (endptr != ptr)
            *key_value = i;
    }

    if ( endptr == ptr ) {
        if (check_error)
            config_set_key_error ( key );

        return -1;
    }

    return 0;

} /* end config_get_long */

int config_get_float ( t_toml_node *tbl, const char *key, 
                        float *key_value, int check_error ) {

    float f;
    char *ptr = toml_get_key_value ( tbl, key );
    char *endptr = ptr;

    if (ptr) {
        f = strtof ( ptr, &endptr );
        if (endptr != ptr)
            *key_value = f;
    }

    if (endptr == ptr) {
        if (check_error)
            config_set_key_error ( key );

        return -1;
    }

    return 0;

} /* end config_get_float */

int config_get_bool ( t_toml_node *tbl, const char *key, 
                       int *key_value, int check_error ) {

    int found = 0;
    char *ptr = toml_get_key_value ( tbl, key );

    if (ptr) { 
        if (strstr(ptr, "true")) {
            found = 1;
            *key_value = 1;
        }
        else if (strstr(ptr, "false")) {
            found = 1;
            *key_value = 0;
        }
    }

    if (!found) {
        if (check_error)
            config_set_key_error ( key );

        return -1;
    }

    return 0;

} /* end config_get_bool */

int config_get_string ( t_toml_node *tbl, const char *key, 
                         char** key_value, int check_error ) {

    char *ptr = toml_get_key_value ( tbl, key );

    if (ptr) {
        if (*key_value)
            free(*key_value);

        *key_value = (char *)malloc(strlen((ptr)) + 1);
        strcpy ( *key_value, ptr );
    }
    else {
        if (check_error)
            config_set_key_error ( key );

        return -1;
    }

    return 0;

} /* end config_get_string */

/******************************************************************************
**  Function   :  config_set_int
**                config_set_float
**                config_set_bool
**                config_set_string
**                                                
**  Objective  :  This functions take the given input type and convert it to
**                a string to save in the current toml tree. The caller can
**                also set the check_error flag to have any errors written
**                to the log.  This should be used only when the key is 
**                required.  If check_error is not set, we assume we will add
**                the key if not found (for rom specific info)
**                                               
**  Parameters :  tbl         - the TOML node to start searching from
**                key         - the name of the key to find
**                key_value   - actual value to store
**                check_error - whether to log errors
**                                             
**  return     :  NONE
******************************************************************************/ 
void config_set_int ( t_toml_node *tbl, const char *key, 
                      int key_value, int check_error ) {

    char s_value[20];
    int error;

    sprintf ( s_value, "%d", key_value );

    if ( (error = toml_set_value(tbl, key, s_value)) && check_error ) {
        config_set_key_error ( key );
    }
    else if ( error ) {
        toml_add_key ( tbl, key, TOML_INT, s_value, "", " " );
    }

} /* end config_set_int */

void config_set_float ( t_toml_node *tbl, const char *key, 
                        float key_value, int check_error ) {

    char s_value[20];
    int error;

    sprintf ( s_value, "%.4f", key_value );

    if ( (error = toml_set_value(tbl, key, s_value)) && check_error ) {
        config_set_key_error ( key );
    }
    else if ( error ) {
        toml_add_key ( tbl, key, TOML_FLOAT, s_value, "", " " );
    }

} /* end config_set_float */

void config_set_bool ( t_toml_node *tbl, const char *key, 
                       int key_value, int check_error ) {

    char s_value[20];
    int error;

    if ( key_value )
        strcpy ( s_value, "true" );
    else
        strcpy ( s_value, "false" );

    if ( (error = toml_set_value(tbl, key, s_value)) && check_error ) {
        config_set_key_error ( key );
    }
    else if ( error ) {
        toml_add_key ( tbl, key, TOML_BOOLEAN, s_value, "", " " );
    }

} /* end config_set_bool */

void config_set_string ( t_toml_node *tbl, const char *key, 
                         const char* key_value, int check_error ) {

    int error;

    if ( (error = toml_set_value(tbl, key, key_value)) && check_error ) {
        config_set_key_error ( key );
    }
    else if ( error ) {
        toml_add_key ( tbl, key, TOML_DQ_STRING, key_value, "", " " );
    }

} /* end config_set_string */

void config_load_single_input_from_string ( t_input_common *tmp_input, const char *string ) {

    char *inputval;
    char *parts[9] = {0};
    int size;

    inputval = malloc(strlen(string)+1);
    strcpy ( inputval, string );

    util_strlwr ( inputval );

    size = config_split(inputval, "|", parts);

    tmp_input->combo = 0;

    /* interate over regular and combo inputs */
    for ( int j = 0; j < 2; ++j ) {

        if (size < 4)
            break;

        /* check for full combo input and get out if not there */
        if (j == 1 && size < 8)
            break;
        else if ( j == 1 )
            tmp_input->combo = 1;

        /* if not enabled skip and go to next string */
        if (strstr(parts[0], "enable"))
            tmp_input->enable = 1;
        else
            tmp_input->enable = 0;

        /* Device */
        if (strstr(parts[1+j*4],"key") == parts[1+j*4])
            tmp_input->device[j] = DEV_KEYBOARD;
        else if (parts[1+j*4][0] == 'j')
            tmp_input->device[j] = DEV_JOYSTICK;
        else if (parts[1+j*4][0] == 'm')
            tmp_input->device[j] = DEV_MOUSE;
        else if (parts[1+j*4][0] == 'c')
            tmp_input->device[j] = DEV_CONTROLLER;
        else
            tmp_input->device[j] = DEV_KEYBOARD;

        /* 
         * Device num, for the keyboard if a mod key is used, this is the 
         *    mod key 
         */
        tmp_input->device_num[j] = 0;
        if (tmp_input->device[j] != DEV_KEYBOARD && strlen(parts[1+j*4]) > 1) {
            char *numString = parts[1+j*4] + 1;
            tmp_input->device_num[j] = atoi(numString);
        }

        char *end_pos = strstr(parts[2+j*4],"-");
        if ( end_pos ) {
            *end_pos = '\0';
            tmp_input->device_num[j] = pc_interpret_mod_key(parts[2+j*4]);
        }

        /* Part Type */
        if (tmp_input->device[j] == DEV_KEYBOARD)
            tmp_input->part_type[j] = PART_TYPE_KEY;
        else if (parts[2+j*4][0] == 'a')
            tmp_input->part_type[j] = PART_TYPE_AXIS;
        else if (parts[2+j*4][0] == 't')
            tmp_input->part_type[j] = PART_TYPE_BALL;
        else if (parts[2+j*4][0] == 'd')
            tmp_input->part_type[j] = PART_TYPE_HAT;
        else if (parts[2+j*4][0] == 'b')
            tmp_input->part_type[j] = PART_TYPE_BUTTON;
        else
            tmp_input->part_type[j] = PART_TYPE_KEY;

        /* 
         * Part num 
         */
        tmp_input->part_num[j] = 0;
        if (tmp_input->device[j] == DEV_KEYBOARD) {

            /* Now we get second half of key combo if '-' is in there */
            char *tmp_key;
            if ( end_pos) 
                tmp_key = end_pos+1;
            else
                tmp_key = parts[2+j*4];
            tmp_input->part_num[j] = pc_interpret_key(tmp_key);
        }
        else {

            /* Game controller buttons use underscore to indicate */
            char *start_pos = strstr ( parts[2+j*4],"_" );
            if ( start_pos ) {

                if ( tmp_input->part_type[j] == PART_TYPE_BUTTON )
                    tmp_input->part_num[j] = 
                        pc_interpret_controller_button(start_pos+1);

                if ( tmp_input->part_type[j] == PART_TYPE_AXIS )
                    tmp_input->part_num[j] = 
                        pc_interpret_controller_axis(start_pos+1);
            }

            /* Else just grab the axis or button number */
            else
                tmp_input->part_num[j] = atoi(&parts[2+j*4][1]);
        }

        /*
         * Direction
         */
        tmp_input->direction[j] = 
            config_get_direction_from_string ( parts[3+j*4] );

    } /* end for regular and combo input common */

    free(inputval);

} /* end config_load_single_input_from_string */

int config_load_input_from_str_ary (t_toml_node *tbl, char *tag, t_input_common *input) {

    char *parts[9] = {0};
    char *result_value;
    char *inputval;
    int index = -1;
    t_toml_node *tmp_node;
    t_input_common *tmp_input = input;

    /* Find the array and verify it contains values*/
    tmp_node = toml_get_key_array_values_start ( tbl, tag );
    if (!tmp_node)
        return -1;

    result_value = toml_get_value_by_index_from_array_node ( tmp_node, 0 );
    if (!result_value)
        return -1;

    /*
     * As long as we have one entry we will completely replace the current
     *  input struct, so free all entries 
     */
    config_free_input(input);

    while ( (result_value = toml_get_value_by_index_from_array_node(tmp_node, ++index)) ) {

        if ( index > 0 ) {
            tmp_input->next = (t_input_common*)malloc(sizeof(t_input_common));
            tmp_input = tmp_input->next;
        }
        tmp_input->next = 0;

        config_load_single_input_from_string ( tmp_input, result_value );

    } /* while finding array results */

    return 0;

} /* end config_load_input_from_str_ary */ 

void config_set_str_ary_from_input (t_toml_node *tbl, char *tag, t_input_common *input) {

    char value[257] = {0};
    char mod[30] = {0};
    char *continuation_indent;
    int first_value = 1;
    t_toml_node *tmp_node;
    t_input_common *tmp_input = input;

    /* Find the array and create if it does not exist */
    tmp_node = toml_get_key_array_values_start ( tbl, tag );
    if (!tmp_node) {

        toml_start_array_with_value ( tbl, tag, TOML_DQ_STRING, 
                                    "", " ", " " );
    }

    /* Delete all current values */
    while ( !toml_delete_array_value_by_index(tbl, tag, -1) ); 

    config_set_input_array_layout ( tbl, tag );
    continuation_indent = config_alloc_array_continuation_indent ( tbl, tag );

    /* Go through input values */
    while ( tmp_input ) {

        if (tmp_input->enable)
            strcpy ( value, "enable|");
        else
            strcpy ( value, "disable|");

        /* interate over regular and combo inputs */
        for ( int j = 0; j < 2; ++j ) {

            /* Device */
            if ( tmp_input->device[j] == DEV_JOYSTICK )
                strcat ( value, "j");
            else if ( tmp_input->device[j] == DEV_MOUSE )
                strcat ( value, "m");
            else if ( tmp_input->device[j] == DEV_CONTROLLER )
                strcat ( value, "c");
            else
                strcat ( value, "key");

            /* Device Num */
            if ( tmp_input->device[j] != DEV_KEYBOARD ) {
                sprintf ( value, "%s%d", value, tmp_input->device_num[j] );
            }
            strcat ( value, "|" );

            if ( tmp_input->device[j] == DEV_KEYBOARD && 
                    tmp_input->device_num[j] ) {
                strcat ( value, 
                        pc_interpret_mod_key_value(tmp_input->device_num[j], mod) );
                strcat ( value, "-" );
            }

            /* Part Type */
            if ( tmp_input->part_type[j] == PART_TYPE_AXIS )
                strcat ( value, "a" );
            else if ( tmp_input->part_type[j] == PART_TYPE_BALL )
                strcat ( value, "t" );
            else if ( tmp_input->part_type[j] == PART_TYPE_HAT )
                strcat ( value, "d" );
            else if ( tmp_input->part_type[j] == PART_TYPE_BUTTON )
                strcat ( value, "b" );

            /* 
             * Part num 
             */
            if (tmp_input->device[j] == DEV_KEYBOARD) {

                strcat ( value, pc_interpret_key_value(tmp_input->part_num[j], mod) );
            }
            else {

                if ( tmp_input->device[j] == DEV_CONTROLLER ) {
                    strcat ( value, "_" );
                    if ( tmp_input->part_type[j] == PART_TYPE_AXIS ) {
                        strcat ( value, 
                            pc_interpret_controller_axis_value (tmp_input->part_num[j], mod) );
                    }
                    if ( tmp_input->part_type[j] == PART_TYPE_BUTTON ) {
                        strcat ( value, 
                            pc_interpret_controller_button_value (tmp_input->part_num[j], mod) );
                    }
                }
                else {
                    sprintf ( value, "%s%d", value, tmp_input->part_num[j] );
                }
            }

            /*
             * Direction
             */
            sprintf ( value, "%s|%s", value, 
                    config_get_string_from_direction(tmp_input->direction[j], mod) );
            
            /* Get out of for loop if not a combo */
            if ( !tmp_input->combo)
                break;
            else
                strcat ( value, "|combo|" );

        } /* end for regular and combo input common */

        /* Write the value to array */
        toml_insert_array_value_by_index ( tbl, tag, value,
                ( first_value || !continuation_indent ) ? " " : continuation_indent,
                "\n", " ", -1 );
        first_value = 0;

        tmp_input = tmp_input->next;

    } /* while finding array results */

    if ( continuation_indent )
        free ( continuation_indent );

    config_set_input_array_layout ( tbl, tag );

} /* end config_set_str_ary_from_input */ 

e_direction config_get_direction_from_string ( const char *string ) {

    char tmp[50];

    util_trim_whitespace(tmp, 50, string);

    char *p = tmp;
    for ( ; *p; ++p) *p = tolower(*p);

    if (!strcmp(string,"left")) 
        return DIR_LEFT;
    else if (!strcmp(string,"right")) 
        return DIR_RIGHT;
    else if (!strcmp(string,"up")) 
        return DIR_UP;
    else if (!strcmp(string,"down")) 
        return DIR_DOWN;
    else if (!strcmp(string,"-")) 
        return DIR_MINUS;
    else if (!strcmp(string,"+")) 
        return DIR_PLUS;
    else if (!strcmp(string,"minus")) 
        return DIR_MINUS;
    else if (!strcmp(string,"plus")) 
        return DIR_PLUS;
    else if (!strcmp(string,"release")) 
        return DIR_RELEASE;
    else
        return DIR_PRESS;
}

char * config_get_string_from_direction( int direction, char *string ) {

    switch (direction) {
        case DIR_LEFT: strcpy ( string, "left" ); break;
        case DIR_RIGHT: strcpy ( string, "right" ); break;
        case DIR_UP: strcpy ( string, "up" ); break;
        case DIR_DOWN: strcpy ( string, "down" ); break;
        case DIR_MINUS: strcpy ( string, "-" ); break;
        case DIR_PLUS: strcpy ( string, "+" ); break;
        case DIR_RELEASE: strcpy ( string, "release" ); break;
        case DIR_PRESS: strcpy ( string, "press" ); break;
    }

    return string;
}

e_ctrlr_type config_get_control_type_from_string ( const char *string ) {

    char tmp[50];

    util_trim_whitespace(tmp, 50, string);

    char *p = tmp;
    for ( ; *p; ++p) *p = tolower(*p);

    if (!strcmp(tmp,"trackball")) 
        return CTRLR_TYPE_TRACKBALL;
    else if (!strcmp(tmp,"paddles")) 
        return CTRLR_TYPE_PADDLES;
    else if (!strcmp(tmp,"keypad")) 
        return CTRLR_TYPE_KEYPAD;
    else if (!strcmp(tmp,"joystick")) 
        return CTRLR_TYPE_JOYSTICK;
    else 
        return CTRLR_TYPE_NONE;
}

char * config_get_string_from_control_type ( int type, char *string ) {

    switch (type) {
        case CTRLR_TYPE_JOYSTICK: strcpy ( string, "Joystick" ); break;
        case CTRLR_TYPE_TRACKBALL: strcpy ( string, "Trackball" ); break;
        case CTRLR_TYPE_PADDLES: strcpy ( string, "Paddles" ); break;
        case CTRLR_TYPE_KEYPAD: strcpy ( string, "Keypad" ); break;
        default : strcpy ( string, "None" ); break;
    }

    return string;
}

int config_get_mouse_optimize_from_string ( const char *string ) {

    char tmp[50];

    util_trim_whitespace(tmp, 50, string);

    char *p = tmp;
    for ( ; *p; ++p) *p = tolower(*p);

    if (!strcmp(tmp,"cursor"))
        return MOUSE_OPTIMIZE_CURSOR;
    else
        return MOUSE_OPTIMIZE_PADDLES;
}

char * config_get_string_from_mouse_optimize ( int value, char *string ) {

    switch (value) {
        case MOUSE_OPTIMIZE_CURSOR: strcpy ( string, "cursor" ); break;
        default : strcpy ( string, "paddles" ); break;
    }

    return string;
}

void config_set_key_error ( const char *key ) {

    char error[256];
    sprintf ( error, "Failed to find config key: %s", key );
    logger_log_message (LOG_ERROR, error, "");
}

void config_free_input ( t_input_common *input ) {

    t_input_common *tmp = input->next;
    t_input_common *tmp2;

    while (tmp) {
        tmp2 = tmp;
        tmp = tmp->next;
        free(tmp2);
    }

    input->next = 0;
}

int config_split(char *input, const char *delim, 
                                    char *parts[]) {
    int num_elements = 0;
    char *tmpstring = input;

    while ( num_elements < 8 && (parts[num_elements] = strtok(tmpstring, delim)) ) {
        tmpstring = NULL;
        util_trim_whitespace(parts[num_elements], strlen(parts[num_elements])+1, 
                    parts[num_elements]);
        num_elements++;
    }

    return num_elements;
}

char * config_get_800_key_string_name(int key, char *key_string)
{
    input_get_key_string( key, key_string );

    return key_string;
}

void config_argument_error ( const char *arg ) {

    char *err_string = malloc(strlen(arg)+20);
    sprintf ( err_string, "Invalid argument: %s", arg );
    logger_log_message ( LOG_ERROR, err_string, "" );
    free(err_string);

} /* end config_argument_error */

/******************************************************************************
**  Function   :  config_set_shader_cli
**
**  Objective  :  Apply a --shader / --video.shader value to the in-memory
**                config.  Accepts a bundle list name, a bundle directory, or
**                none/off to disable.  The value is stored verbatim; an
**                unresolvable bundle is handled gracefully at startup
**                (pc_get_renderer / pc_set_shader leave the shader disabled),
**                so a typo cannot wedge boot.
**
**  Parameters :  val - raw command line value
**
**  return     :  NONE
**
******************************************************************************/
static void config_set_shader_cli ( const char *val ) {

    if ( !val || !strlen(val) || !SDL_strcasecmp(val, "none") ||
            !SDL_strcasecmp(val, "off") ) {
        util_alloc_string ( &config.video.shader, SHADER_NONE );
        return;
    }

    util_alloc_string ( &config.video.shader, val );

} /* end config_set_shader_cli */

/******************************************************************************
**  Function   :  config_set_backend_cli
**
**  Objective  :  Apply a --backend / --video.renderer_name value to the
**                in-memory config.  default/auto clears the name so SDL
**                chooses.  The name is stored verbatim - it is validated
**                later in pc_get_renderer() once SDL is initialized.
**
**  Parameters :  val - raw command line value
**
**  return     :  NONE
**
******************************************************************************/
static void config_set_backend_cli ( const char *val ) {

    if ( !val || !strlen(val) || !SDL_strcasecmp(val, "default") ||
            !SDL_strcasecmp(val, "auto") ) {
        free ( config.video.renderer_name );
        config.video.renderer_name = NULL;
        return;
    }

    util_alloc_string ( &config.video.renderer_name, val );

} /* end config_set_backend_cli */

static void config_set_start_with_cli ( const char *val ) {

    char tmp_string[64];

    if ( !val )
        return;

    util_trim_whitespace ( tmp_string, sizeof(tmp_string), val );
    util_strlwr ( tmp_string );

    if ( strstr(tmp_string, "file_browser") ||
            strstr(tmp_string, "file-browser") ||
            strstr(tmp_string, "file browser") )
        config.general.start_with = START_GUI_BROWSER;
    else if ( strstr(tmp_string, "rom_list") ||
            strstr(tmp_string, "rom-list") ||
            strstr(tmp_string, "rom list") )
        config.general.start_with = START_GUI_ROMS;
    else
        config.general.start_with = START_GUI_LAST_ROM;

} /* end config_set_start_with_cli */

/******************************************************************************
**  Function   :  config_set_safe_video
**
**  Objective  :  Apply the --safe baseline - SDL default renderer (bypassing
**                any GPU-preferred policy) with the shader disabled.  Explicit
**                --backend / --shader values may override afterward.
**
**  Parameters :  NONE
**
**  return     :  NONE
**
******************************************************************************/
static void config_set_safe_video ( void ) {

    s_safe_video_mode = 1;
    free ( config.video.renderer_name );
    config.video.renderer_name = NULL;
    util_alloc_string ( &config.video.shader, SHADER_NONE );
    logger_log_message ( LOG_INFO,
            "Safe mode: SDL default renderer, shader disabled", "" );

} /* end config_set_safe_video */

/******************************************************************************
**  Function   :  config_prescan_safe_video
**
**  Objective  :  Apply the --safe baseline before either argument pass walks
**                its options, so explicit --backend / --shader always override
**                --safe regardless of where they appear on the command line.
**                Both passes call this and then treat --safe as a no-op.
**
**  Parameters :  argc - number of arguments
**                argv - argument list
**
**  return     :  NONE
**
******************************************************************************/
static void config_prescan_safe_video ( int argc, char *argv[] ) {

    int i;

    for ( i = 1; i < argc; ++i ) {
        if ( !strcmp(argv[i], "--safe") ) {
            config_set_safe_video ();
            return;
        }
    }

} /* end config_prescan_safe_video */

/******************************************************************************
**  Function   :  config_check_arguments
**
**  Objective  :  This function checks command line arguments skipping config
**                file argument
**
**  Parameters :  NONE
**                                                
**  return     :  NONE
**      
******************************************************************************/
void config_check_arguments ( void ) {

    int i;
    char *tmp_string = NULL;

    /* Apply --safe baseline first so explicit --backend / --shader below win
       regardless of argv order. */
    config_prescan_safe_video ( g_argc, g_argv );

    for ( i = 1; i < g_argc; ++i ) {
        if ( strstr(g_argv[i], "--debug") )
            console_set_debug_mode (1);

        /* --safe applied via pre-scan above; consume the flag here */
        else if ( !strcmp(g_argv[i], "--safe") )
            ;

        /* General */
        else if ( strstr(g_argv[i],"--start_with") || 
                strstr(g_argv[i],"--general.start_with") ) {
            if ( ++i < g_argc ) {
                config_set_start_with_cli ( g_argv[i] );
            }
        }
        else if ( config_check_arg_int ( &i, "--throttle", 
                    "--general.throttle", &config.general.throttle ) );
        else if ( config_check_arg_bool ( &i, "--sio_patch", 
                    "--general.sio_patch", &config.general.sio_patch ) );
        else if ( config_check_arg_bool ( &i, "--basic_enable", 
                    "--general.basic_enable", &config.general.basic_enable ) );
        else if ( !strcmp(g_argv[i],"--machine") || 
                !strcmp(g_argv[i],"--general.machine") ) {
            if ( ++i < g_argc ) {
                util_strlwr ( g_argv[i] );
                if ( strstr(g_argv[i], "xl") || strstr(g_argv[i], "xe") )
                    config.general.machine = MACHINE_TYPE_XL;
                else if ( strstr(g_argv[i], "800") )
                    config.general.machine = MACHINE_TYPE_800;
                else
                    config.general.machine = MACHINE_TYPE_5200;
            }
        }
        else if ( config_check_arg_mapping ( &i, 0 ) );

        /* Machine */
        else if ( strstr(g_argv[i],"--machine.tv_standard") ) {
            if ( ++i < g_argc ) {
                util_strlwr ( g_argv[i] );
                if ( strstr(g_argv[i], "pal") )
                    config.machine.tv_standard = PAL;
                else
                    config.machine.tv_standard = NTSC;
            }
        }
        else if ( config_check_arg_int ( &i, "--machine.ram", 
                    NULL, &config.machine.ram ) );
        else if ( config_check_arg_string ( &i, "--machine.bios", 
                    NULL, &config.machine.bios ) );
        else if ( config_check_arg_string ( &i, "--machine.profiles_dir", 
                    NULL, &config.machine.profiles_dir ) );

        /* Video */
        else if ( config_check_arg_bool ( &i, "--video.fullscreen", 
                    NULL, &config.video.fullscreen ) );
        else if ( config_check_arg_bool ( &i, "--video.fixed_window", 
                    NULL, &config.video.fixed_window ) );
        else if ( config_check_arg_int ( &i, "--video.width", 
                    NULL, &config.video.width ) );
        else if ( config_check_arg_int ( &i, "--video.height", 
                    NULL, &config.video.height ) );
        else if ( config_check_arg_int ( &i, "--video.x", 
                    NULL, &config.video.x ) );
        else if ( config_check_arg_int ( &i, "--video.y", 
                    NULL, &config.video.y ) );
        else if ( config_check_arg_bool ( &i, "--video.wide_playfield", 
                    NULL, &config.video.wide_playfield ) );
        else if ( config_check_arg_int ( &i, "--video.gui_scale",
                    NULL, &config.video.gui_scale ) ) {
            config.video.gui_scale =
                config_clamp_gui_scale(config.video.gui_scale);
        }
        else if ( config_check_arg_string ( &i, "--video.window_scaling",
                    NULL, &tmp_string ) ) {
            config.video.window_scaling =
                config_get_window_scaling_from_string(tmp_string);
            free(tmp_string);
            tmp_string = NULL;
        }
        else if ( config_check_arg_string ( &i, "--video.texture_scaling",
                    NULL, &tmp_string ) ) {
            config.video.texture_scaling =
                config_get_texture_scaling_from_string(tmp_string);
            free(tmp_string);
            tmp_string = NULL;
        }
        else if ( config_check_arg_string ( &i, "--video.palette", 
                    NULL, &config.video.palette_name ) ) {
            config_load_palette (g_conftbl, config.video.palette_name, config.video.palette );
        }
        else if ( config_check_arg_string ( &i, "--shader",
                    "--video.shader", &tmp_string ) ) {
            config_set_shader_cli ( tmp_string );
            free ( tmp_string );
            tmp_string = NULL;
        }
        else if ( config_check_arg_string ( &i, "--backend",
                    "--video.renderer_name", &tmp_string ) ) {
            config_set_backend_cli ( tmp_string );
            free ( tmp_string );
            tmp_string = NULL;
        }
        else if ( config_check_arg_bool ( &i, "--video.vsync", 
                    NULL, &config.video.vsync ) );
        else if ( config_check_arg_bool ( &i, "--video.ntsc_artifacts", 
                    NULL, &config.video.ntsc_artifacts ) );
        else if ( config_check_arg_float ( &i, "--video.ntsc_setup.hue", 
                    NULL, &config.video.ntsc_setup.hue ) );
        else if ( config_check_arg_float ( &i, "--video.ntsc_setup.saturation", 
                    NULL, &config.video.ntsc_setup.saturation ) );
        else if ( config_check_arg_float ( &i, "--video.ntsc_setup.contrast", 
                    NULL, &config.video.ntsc_setup.contrast ) );
        else if ( config_check_arg_float ( &i, "--video.ntsc_setup.brightness", 
                    NULL, &config.video.ntsc_setup.brightness ) );
        else if ( config_check_arg_float ( &i, "--video.ntsc_setup.sharpness", 
                    NULL, &config.video.ntsc_setup.sharpness ) );
        else if ( config_check_arg_float ( &i, "--video.ntsc_setup.burst_phase", 
                    NULL, &config.video.ntsc_setup.burst_phase ) );
        else if ( config_check_arg_float ( &i, "--video.ntsc_setup.hue_warping", 
                    NULL, &config.video.ntsc_setup.hue_warping ) );

        /* Sound */
        else if ( config_check_arg_bool ( &i, "--sound.enable", 
                    NULL, &config.sound.enable ) );
        else if ( config_check_arg_int ( &i, "--sound.frequency", 
                    NULL, &config.sound.frequency ) );
        else if ( config_check_arg_int ( &i, "--sound.samples", 
                    NULL, &config.sound.samples ) );
        else if ( config_check_arg_float ( &i, "--sound.buffer_size",
                    NULL, &config.sound.buffer_size) );
        else if ( config_check_arg_int ( &i, "--sound.gain",
                    NULL, &config.sound.gain ) );

        /* Input Misc */
        else if ( strstr(g_argv[i],"--input.misc") ) {
            config_check_arg_input_misc ( &i );
        }

        /* Input Games */
        else if ( strstr(g_argv[i],"--input.game") ) {
            config_check_arg_input_game ( &i );
        }

        /* Skip over pre check args */
        else if ( strstr(g_argv[i],"--version") || !strcmp(g_argv[i], "-V") );
        else if ( strstr(g_argv[i],"--help") || !strcmp(g_argv[i], "-help") );
        else if ( strstr(g_argv[i],"--reset_config") );
        else if ( strstr(g_argv[i],"--config") ) ++i;
        else if ( strstr(g_argv[i],"--state") || 
                !strcmp(g_argv[i], "--general.state") ) ++i;
        else if ( strstr(g_argv[i],"--quick_state") || 
                !strcmp(g_argv[i], "--general.quick_state") ) ++i;

        /* Must be invalid, log it */
        else if ( !strncmp(g_argv[i], "--", 2) || g_argv[i][0] == '-' ) {
            config_argument_error ( g_argv[i] );
        }
    }

    /* Clamp sound gain - command line value is unbounded */
    if ( config.sound.gain < 0 )
        config.sound.gain = 0;
    if ( config.sound.gain > KCFG_SND_GAIN_MAX )
        config.sound.gain = KCFG_SND_GAIN_MAX;
    config.sound.buffer_size =
        config_clamp_sound_buffer_size(config.sound.buffer_size);

} /* config_check_arguments */

/******************************************************************************
**  Function   :  config_pre_check_arguments
**                            
**  Objective  :  This function checks command line arguments for alternate
**                config profile
**
**  Parameters :  argc - number of arguments
**                argv - argument list
**                                                
**  return     : 0                                
**      
******************************************************************************/
void config_pre_check_arguments ( int argc, char *argv[] ) {

    int i;

    /*
     * Only handle file-selection args here.  These must run before the config
     * file is parsed.  Value args that must win over saved config (ROM, state,
     * quick_state) are applied after config_load_general via
     * config_apply_cmdline_overrides.
     */
    for ( i = 1; i < argc; ++i ) {
        if ( strstr(argv[i],"--config") ) {
            if ( ++i < argc ) {
                strcpy ( g_configfile, argv[i] );
                util_set_program_path ( g_configfile );
            }
        }
        /* --reset_config is handled earlier in main() as a synchronous,
         * exit-immediately recovery path, so it never reaches config_init. */
    }

} /* end config_pre_check_arguments */

/******************************************************************************
**  Function   :  config_apply_cmdline_overrides
**
**  Objective  :  Re-apply command line launch targets (--start_with, ROM,
**                --state, --quick_state) after the config file has been
**                loaded so that command line values win over saved config
**                values.  Run once from config_init at startup.
**
**  Parameters :  argc - number of arguments
**                argv - argument list
**
**  return     :  NONE
**
******************************************************************************/
static void config_apply_cmdline_overrides ( int argc, char *argv[] ) {

    int i;
    char tmp_file[KAT_PATH_MAX];
    int cli_state = 0, cli_quick = 0, cli_rom = 0;
    char *rom_arg = 0;
    char *tmp_string = NULL;

    /*
     * Video overrides are applied here (early, in config_init) rather than in
     * config_check_arguments so they take effect before the GUI creates the
     * renderer.  --safe is pre-scanned first to set a conservative baseline;
     * explicit --backend / --shader below then override it regardless of the
     * order they appear on the command line.
     */
    config_prescan_safe_video ( argc, argv );

    /*
     * Walk arguments, skipping options and their operands.  The last bare
     * (non-option) token is the ROM.  Also pick up --state / --quick_state.
     */
    for ( i = 1; i < argc; ++i ) {

        /* startup selector / state / quick_state win over saved values */
        if ( !strcmp(argv[i], "--start_with") ||
                !strcmp(argv[i], "--general.start_with") ) {
            if ( ++i < argc )
                config_set_start_with_cli ( argv[i] );
        }
        else if ( config_check_arg_string ( &i, "--state",
                    "--general.state", &config.general.state ) ) {
            cli_state = 1;
        }
        else if ( config_check_arg_int ( &i, "--quick_state",
                    "--general.quick_state", &config.general.quick_state ) ) {
            cli_quick = 1;
        }
        else if ( config_check_arg_mapping ( &i, 1 ) );

        /* Video overrides - applied before GUI renderer creation */
        else if ( config_check_arg_string ( &i, "--shader",
                    "--video.shader", &tmp_string ) ) {
            config_set_shader_cli ( tmp_string );
            free ( tmp_string );
            tmp_string = NULL;
        }
        else if ( config_check_arg_string ( &i, "--backend",
                    "--video.renderer_name", &tmp_string ) ) {
            config_set_backend_cli ( tmp_string );
            free ( tmp_string );
            tmp_string = NULL;
        }

        /* No-operand flags: consume only the flag itself */
        else if ( strstr(argv[i],"--debug") ||
                  strstr(argv[i],"--version") || !strcmp(argv[i],"-V") ||
                  strstr(argv[i],"--help") || !strcmp(argv[i],"-help") ||
                  !strcmp(argv[i],"--safe") ||
                  strstr(argv[i],"--reset_config") )
            ;

        /* Any other option takes an operand: skip it too */
        else if ( argv[i][0] == '-' )
            ++i;

        /* Otherwise it is a bare token: candidate ROM (last one wins) */
        else
            rom_arg = argv[i];
    }

    if ( rom_arg ) {
        if ( strstr(rom_arg,":") || (rom_arg[0]=='\\') || (rom_arg[0]=='/') )
            util_alloc_string ( &config.general.rom, rom_arg );
        else {
            strcpy ( tmp_file, rom_arg );
            util_set_file_to_current_dir ( tmp_file );
            util_alloc_string ( &config.general.rom, tmp_file );
        }

        /* If rom input on command line, we don't care about exit status */
        config.general.exit_status = 0;
        cli_rom = 1;
    }

    /*
     * Clear a competing *saved* value so it can't beat the CLI.  Only clear the
     * competitor when it was NOT itself given on the CLI (if the user passes
     * both --state and --quick_state, keep both - kat5200_launch gives
     * quick_state priority).
     */
    if ( cli_quick && !cli_state )
        util_alloc_string ( &config.general.state, "" );
    if ( cli_state && !cli_quick )
        config.general.quick_state = 0;
    if ( cli_rom && !cli_state && !cli_quick ) {
        util_alloc_string ( &config.general.state, "" );
        config.general.quick_state = 0;
    }

} /* end config_apply_cmdline_overrides */

static int config_parse_mapping_arg ( const char *arg, int *mapping ) {

    char tmp_mapping[KAT_PATH_MAX];

    if ( !arg )
        return 0;

    if ( strlen(arg) >= sizeof(tmp_mapping) )
        return 0;

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

    if ( !strcmp(tmp_mapping, "") || !strcmp(tmp_mapping, "auto") ) {
        *mapping = CART_MIN;
        return 1;
    }

    *mapping = media_if_get_mapping_from_string ( tmp_mapping );

    return *mapping != CART_MIN;

} /* end config_parse_mapping_arg */

static int config_check_arg_mapping ( int *index, int set_mapping ) {

    int mapping;
    const char *arg = g_argv[*index];

    if ( strcmp(g_argv[*index], "--mapping") )
        return 0;

    if ( ++(*index) < g_argc ) {
        if ( set_mapping ) {
            if ( config_parse_mapping_arg ( g_argv[*index], &mapping ) )
                config.general.rom_mapping = mapping;
            else {
                config.general.rom_mapping = CART_MIN;
                config_argument_error ( g_argv[*index] );
            }
        }
    }
    else
        config_argument_error ( arg );

    return 1;

} /* end config_check_arg_mapping */

void config_check_arg_input_misc ( int *index ) {

    int num = 0;
    char *tmp_string = 0;
    char *num_string;
    t_input_common *tmp_input = 0;

    if ( strstr(g_argv[*index], "--input.misc.exit") )
        tmp_input = &config.input.ui.exit;
    else if ( strstr(g_argv[*index], "--input.misc.back") )
        tmp_input = &config.input.ui.back;
    else if ( strstr(g_argv[*index], "--input.misc.fullscreen") )
        tmp_input = &config.input.ui.fullscreen;
    else if ( strstr(g_argv[*index], "--input.misc.screenshot") )
        tmp_input = &config.input.ui.screenshot;
    else if ( strstr(g_argv[*index], "--input.misc.debugger") )
        tmp_input = &config.input.ui.debugger;
    else if ( strstr(g_argv[*index], "--input.misc.load.state") ) {
        if ( num_string = strrchr(g_argv[*index], '.') ) {
            if ( (num = atoi(num_string+1)) > 0 && (num < 10) )
            tmp_input = &config.input.ui.state_load[num-1];
        }
    }
    else if ( strstr(g_argv[*index], "--input.misc.save.state") ) {
        if ( num_string = strrchr(g_argv[*index], '.') ) {
            if ( (num = atoi(num_string+1)) > 0 && (num < 10) )
            tmp_input = &config.input.ui.state_save[num-1];
        }
    }

    if ( tmp_input && ++(*index) < g_argc ) {
        config_free_input ( tmp_input );
        config_load_single_input_from_string ( tmp_input, g_argv[*index] );
    }
    else
        config_argument_error ( g_argv[*index] );

} /* end config_check_arg_input_misc */

void config_check_arg_input_game ( int *index ) {

    int num = 0;
    char *tmp_string = 0;
    char *num_string;
    char *joystring;
    char player_string[2];
    t_input_common *tmp_input = 0;
    int player = 0;
    char *orig_string = g_argv[*index];
    int orig_index = *index;

    /* Check for keyboard */
    tmp_string = strstr(g_argv[*index], "game.keyboard.");
    if ( tmp_string ) {
        tmp_string = strrchr(g_argv[*index], '.');
        tmp_string += 1;
        if ( !strcmp(tmp_string, "key_ctrl") )
            tmp_input = &config.input.game.ctrl_key;
        else if ( !strcmp(tmp_string, "key_shift") )
            tmp_input = &config.input.game.shift_key;
        else if ( !strcmp(tmp_string, "key_break") )
            tmp_input = &config.input.game.break_key;
        else if ( !strcmp(tmp_string, "key_start") )
            tmp_input = &config.input.game.start_key;
        else if ( !strcmp(tmp_string, "key_option") )
            tmp_input = &config.input.game.option_key;
        else if ( !strcmp(tmp_string, "key_reset") )
            tmp_input = &config.input.game.reset_key;
        else if ( !strcmp(tmp_string, "key_select") )
            tmp_input = &config.input.game.select_key;
        else {
            num = input_get_key_value ( tmp_string ); 
            if ( num >= 0 ) 
                tmp_input = &config.input.game.keyboard[num];
            else
                config_argument_error ( orig_string );
        }
    }

    /* Else check for player */
    else {
        tmp_string = strstr(g_argv[*index], ".p");
        if (!tmp_string) {
            config_argument_error ( orig_string );
            return;
        }

        player_string[0] = *(tmp_string+2);
        player_string[1] = '\0';
    
        player = atoi(player_string);

        if ( player < 0 || player > 3 ) {
            config_argument_error ( orig_string );
            return;
        }

        tmp_string += 4;
        g_argv[*index] = tmp_string;
        if ( config_check_arg_bool ( index, "joy.enable", 
                    NULL, &config.input.game.players[player].joy_enable ) );
        else if ( config_check_arg_int ( index, "joy.sensitivity", 
                    NULL, &config.input.game.players[player].joy_sensitivity ) );
        else if ( config_check_arg_int ( index, "joy.deadzone", 
                    NULL, &config.input.game.players[player].joy_deadzone ) );
        else if ( config_check_arg_bool ( index, "analog_stick_acts_digital", 
                    NULL, &config.input.game.players[player].analog_stick_acts_digital ) );
        else if ( config_check_arg_bool ( index, "dpad.enable", 
                    NULL, &config.input.game.players[player].dpad_enable ) );
        else if ( config_check_arg_bool ( index, "dpad.analog.enable", 
                    NULL, &config.input.game.players[player].digdir_analog_enable ) );
        else if ( config_check_arg_int ( index, "dpad.analog.speed", 
                    NULL, &config.input.game.players[player].digdir_analog_speed ) );
        else if ( config_check_arg_bool ( index, "mouse.enable", 
                    NULL, &config.input.game.players[player].mouse_enable ) );
        else if ( config_check_arg_int ( index, "mouse.speed",
                    NULL, &config.input.game.players[player].mouse_speed ) );
        else if ( config_check_arg_bool ( index, "auto_fire",
                    NULL, &config.input.game.players[player].auto_fire ) );
        else if ( config_check_arg_int ( index, "auto_fire_speed",
                    NULL, &config.input.game.players[player].auto_fire_speed ) );
        else if ( !strcmp(tmp_string, "mouse.optimize") ) {
            if ( ++(*index) < g_argc )
                config.input.game.players[player].mouse_optimize =
                    config_get_mouse_optimize_from_string ( g_argv[*index] );
        }
        else if ( config_check_arg_bool ( index, "keyboard.enable",
                    NULL, &config.input.game.players[player].keyboard_enable ) );
        else if ( !strcmp(tmp_string,"5200.type") || 
                !strcmp(tmp_string, "800.type" ) || !strcmp(tmp_string, "type") ) {
            if ( ++(*index) < g_argc )
                config.input.game.players[player].type = 
                    config_get_control_type_from_string ( g_argv[*index] );
        }
        else if ( strstr(tmp_string-1, ".joystick.") ) {
            joystring = strrchr(tmp_string, '.') + 1;
            if ( !strcmp(joystring, "left") )
                tmp_input = &config.input.game.players[player].stick[DIR_LEFT];
            else if ( !strcmp(joystring, "right") )
                tmp_input = &config.input.game.players[player].stick[DIR_RIGHT];
            else if ( !strcmp(joystring, "up") )
                tmp_input = &config.input.game.players[player].stick[DIR_UP];
            else if ( !strcmp(joystring, "down") )
                tmp_input = &config.input.game.players[player].stick[DIR_DOWN];
            else if ( !strcmp(joystring, "bottom_button") || 
                    !strcmp(joystring, "button") )
                tmp_input = &config.input.game.players[player].bottom_button;
            else if ( !strcmp(joystring, "top_button") )
                tmp_input = &config.input.game.players[player].top_button;
            else {
                num = input_get_keypad_value ( joystring ) >> 1;
                if ( num >= 0 ) {
                    tmp_input = &config.input.game.players[player].keypad[num];
                }
            }
        }
        else if ( strstr(tmp_string-1, ".trackball.") ) {
            joystring = strrchr(tmp_string, '.') + 1;
            if ( !strcmp(joystring, "left") )
                tmp_input = &config.input.game.players[player].trackball[DIR_LEFT];
            else if ( !strcmp(joystring, "right") )
                tmp_input = &config.input.game.players[player].trackball[DIR_RIGHT];
            else if ( !strcmp(joystring, "up") )
                tmp_input = &config.input.game.players[player].trackball[DIR_UP];
            else if ( !strcmp(joystring, "down") )
                tmp_input = &config.input.game.players[player].trackball[DIR_DOWN];
        }
        else if ( num_string = strstr(tmp_string-1, ".paddles.") ) {
            num_string += 9; 
            num = atoi(num_string);
            if ( num < 1 || num > 2 )
                return;

            num -= 1;
            joystring = strrchr(tmp_string, '.') + 1;
            if ( !strcmp(joystring, "left") )
                tmp_input = &config.input.game.players[player].paddles[num].left;
            else if ( !strcmp(joystring, "right") )
                tmp_input = &config.input.game.players[player].paddles[num].right;
            else if ( !strcmp(joystring, "button") )
                tmp_input = &config.input.game.players[player].paddles[num].button;
        }
        else
            config_argument_error ( orig_string );
    }

    g_argv[orig_index] = orig_string;

    if ( tmp_input && ++(*index) < g_argc ) {
        config_free_input ( tmp_input );
        config_load_single_input_from_string ( tmp_input, g_argv[*index] );
    }

} /* end config_check_arg_input_game */

/******************************************************************************
**  Function   :  config_check_arg_bool
**                config_check_arg_int
**                config_check_arg_float
**                config_check_arg_string
**                            
**  Objective  :  These functions check command line arguments for the input
**                strings and store in pointer to value
**
**  Parameters :  index - where we are in index of command line params
**                str1  - first string to search for
**                str2  - second string to search for, input NULL if not used
**                value - pointer to store value (next argument in list)
**                                                
**  return     : 1 if string found , 0 if not                                
**      
******************************************************************************/
int config_check_arg_bool ( int *index, char *str1, char*str2, int *value ) {

    if ( !strcmp(g_argv[*index], str1) || (str2 && !strcmp(g_argv[*index], str2)) ) {
        if ( ++(*index) < g_argc ) {
            if ( !strcmp(g_argv[*index], "true") )
                *value = 1;
            else if ( !strcmp(g_argv[*index], "false") )
                *value = 0;
            else
                *value = atoi(g_argv[*index]); // should be 1 or 0
        }
        return 1;
    }

    return 0;

} /* end config_check_arg_bool */

int config_check_arg_int ( int *index, char *str1, char*str2, int *value ) {

    if ( !strcmp(g_argv[*index], str1) || (str2 && !strcmp(g_argv[*index], str2))) {

        if ( ++(*index) < g_argc ) {
            *value = atoi(g_argv[*index]);
        }

        return 1;
    }

    return 0;

} /* end config_check_arg_int */


int config_check_arg_float ( int *index, char *str1, char*str2, float *value ) {

    float f;
    char *endptr;

    if ( !strcmp(g_argv[*index], str1) || (str2 && !strcmp(g_argv[*index], str2)) ) {

        if ( ++(*index) < g_argc ) {

            endptr = g_argv[*index];
            f = strtof ( g_argv[*index], &endptr );
            if (endptr != g_argv[*index])
                *value = f;
        }

        return 1;
    }
    return 0;

} /* end config_check_arg_float */


int config_check_arg_string ( int *index, char *str1, char*str2, char **value ) {

    if ( !strcmp(g_argv[*index], str1) || (str2 && !strcmp(g_argv[*index], str2)) ) {
        if ( ++(*index) < g_argc ) {
            util_alloc_string ( value, g_argv[*index] );
        }

        return 1;
    }

    return 0;

} /* end config_check_arg_string */
