
/******************************************************************************
*
* 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 = 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 */
