text: /*         ______   ___    ___
   *        /\  _  \ /\_ \  /\_ \ 
   *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
   *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
   *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
   *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
   *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
   *                                           /\____/
   *                                           \_/__/
   *
   *      Configuration routines.
   *
   *      By Shawn Hargreaves.
   *
   *      Hook functions added by Martijn Versteegh.
   *
   *      Annie Testes lifted several hardcoded length limitations.
   *
   *      See readme.txt for copyright information.
   */
  
  
  #include "allegro.h"
  #include "allegro/internal/aintern.h"
  
  
  
  typedef struct CONFIG_ENTRY
  {
     char *name;                      /* variable name (NULL if comment) */
     char *data;                      /* variable value */
     struct CONFIG_ENTRY *next;       /* linked list */
  } CONFIG_ENTRY;
  
  
  typedef struct CONFIG
  {
     CONFIG_ENTRY *head;              /* linked list of config entries */
     char *filename;                  /* where were we loaded from? */
     int dirty;                       /* has our data changed? */
  } CONFIG;
  
  
  typedef struct CONFIG_HOOK
  {
     char *section;                   /* hooked config section info */
     int (*intgetter)(AL_CONST char *name, int def);
     AL_CONST char *(*stringgetter)(AL_CONST char *name, AL_CONST char *def);
     void (*stringsetter)(AL_CONST char *name, AL_CONST char *value);
     struct CONFIG_HOOK *next; 
  } CONFIG_HOOK;
  
  
  #define MAX_CONFIGS     4
  
  static CONFIG *config[MAX_CONFIGS] = { NULL, NULL, NULL, NULL };
  static CONFIG *config_override = NULL;
  static CONFIG *config_language = NULL;
  static CONFIG *system_config = NULL;
  
  static CONFIG_HOOK *config_hook = NULL;
  
  static int config_installed = FALSE;
  
  static char **config_argv = NULL;
  static char *argv_buf = NULL;
  static int argv_buf_size = 0;
  
  
  
  /* flush_config:
   *  Writes out a config structure to disk if the contents
   *  have changed.
   */
  static void flush_config(CONFIG *cfg)
  {
     CONFIG_ENTRY *pos;
     PACKFILE *f;
     char cr[16];
  
     usetc(cr+usetc(cr, '\n'), 0);
  
     if (cfg && cfg->filename && cfg->dirty) {
        /* write changed data to disk */
        f = pack_fopen(cfg->filename, F_WRITE);
  
        if (f) {
  	 pos = cfg->head;
  
  	 while (pos) {
  	    if (pos->name) {
  	       pack_fputs(pos->name, f);
  
  	       if (ugetc(pos->name) != '[') {
  		  pack_putc(' ', f);
  		  pack_putc('=', f);
  		  pack_putc(' ', f);
  	       }
  	    }
  
  	    if (pos->data)
  	       pack_fputs(pos->data, f);
  
  	    pack_fputs(cr, f);
  
  	    pos = pos->next;
           }
  
  	 pack_fclose(f);
  	 cfg->dirty = FALSE;
        }
     }
  }
  
  
  
  /* flush_config_file:
   *  Writes out the config file to disk if the contents
   *  have changed.
   */
  void flush_config_file(void)
  {
     flush_config(config[0]);
  }
  
  
  
  /* destroy_config:
   *  Destroys a config structure, writing it out to disk if the contents
   *  have changed.
   */
  static void destroy_config(CONFIG *cfg)
  {
     CONFIG_ENTRY *pos, *prev;
  
     if (cfg) {
        flush_config(cfg);
  
        if (cfg->filename)
  	 _AL_FREE(cfg->filename);
  
        /* destroy the variable list */
        pos = cfg->head;
  
        while (pos) {
  	 prev = pos;
  	 pos = pos->next;
  
  	 if (prev->name)
  	    _AL_FREE(prev->name);
  
  	 if (prev->data)
  	    _AL_FREE(prev->data);
  
  	 _AL_FREE(prev);
        }
  
        _AL_FREE(cfg);
     }
  }
  
  
  
  /* config_cleanup:
   *  Called at shutdown time to free memory being used by the config routines,
   *  and write any changed data out to disk.
   */
  static void config_cleanup(void)
  {
     CONFIG_HOOK *hook, *nexthook;
     int i;
  
     for (i=0; i<MAX_CONFIGS; i++) {
        if (config[i]) {
  	 destroy_config(config[i]);
  	 config[i] = NULL;
        }
     }
  
     if (config_override) {
        destroy_config(config_override);
        config_override = NULL;
     }
  
     if (config_language) {
        destroy_config(config_language);
        config_language = NULL;
     }
  
     if (system_config) {
        destroy_config(system_config);
        system_config = NULL;
     }
  
     if (config_hook) {
        hook = config_hook;
  
        while (hook) {
  	 if (hook->section)
  	    _AL_FREE(hook->section);
  
  	 nexthook = hook->next; 
  	 _AL_FREE(hook);
  	 hook = nexthook;
        }
  
        config_hook = NULL;
     }
  
     _AL_FREE(config_argv);
     config_argv = NULL;
     
     _AL_FREE(argv_buf);
     argv_buf = NULL;
     
     argv_buf_size = 0;
  
     _remove_exit_func(config_cleanup);
     config_installed = FALSE;
  }
  
  
  
  /* init_config:
   *  Sets up the configuration routines ready for use, also loading the
   *  default config file if the loaddata flag is set and no other config
   *  file is in memory.
   */
  static void init_config(int loaddata)
  {
     char filename[1024], tmp[128], *cfg_name;
  
     if (!config_installed) {
        _add_exit_func(config_cleanup, "config_cleanup");
        config_installed = TRUE;
     }
  
     if ((loaddata) && (!config[0])) {
        cfg_name = uconvert_ascii("allegro.cfg", tmp);
  
        if (find_allegro_resource(filename, cfg_name, NULL, NULL, NULL, NULL, NULL, sizeof(filename)) != 0) {
  	 get_executable_name(filename, sizeof(filename));
  	 usetc(get_filename(filename), 0);
  	 ustrzcat(filename, sizeof(filename), cfg_name);
        }
  
        set_config_file(filename);
     }
  
     if (!system_config) {
        system_config = _AL_MALLOC(sizeof(CONFIG));
        if (system_config) {
  	 system_config->head = NULL;
  	 system_config->filename = NULL;
  	 system_config->dirty = FALSE;
        }
     }
  }
  
  
  
  /* get_line:
   *  Helper for splitting files up into individual lines. Returns the length
   *  in bytes of the sequence of characters delimited by the first EOL marker
   *  in the array DATA of length LENGTH, and allocates NAME and VAL to record
   *  the name and the value of the config entry respectively; otherwise set
   *  NAME to NULL and returns a copy of the line through VAL if the line was
   *  blank or a comment. Returns -1 and set allegro_errno on failure.
   */
  static int get_line(AL_CONST char *data, int length, char **name, char **val)
  {
     char *buf;
     int buf_size=256;
     int inpos, outpos, i, j;
     int c, c2, w0;
  
     inpos = 0;
     outpos = 0;
     w0 = ucwidth(0);
  
     buf = _AL_MALLOC_ATOMIC(buf_size);
     if (!buf) {
       *allegro_errno = ENOMEM;
       return -1;
     }
  
     /* search for an EOL marker */
     while (inpos<length) {
        c = ugetc(data+inpos);
        if ((c == '\r') || (c == '\n')) {
  	 inpos += uwidth(data+inpos);
  	 if (inpos < length) {
  	    c2 = ugetc(data+inpos);
  	    if (((c == '\r') && (c2 == '\n')) || ((c == '\n') && (c2 == '\r')))
  	       inpos += uwidth(data+inpos);
  	 }
  	 break;
        }
  
        /* increase the buffer size if needed */
        if (outpos>=(int)buf_size-w0) {
  	 buf_size *= 2;
  	 buf = _al_sane_realloc(buf, buf_size);
  	 if (!buf) {
  	    *allegro_errno = ENOMEM;
  	    return -1;
  	 }
        }
  
        outpos += usetc(buf+outpos, c);
        inpos += uwidth(data+inpos);
     }
  
     usetc(buf+outpos, 0);
  
     /* skip leading spaces */
     i = 0;
     c = ugetc(buf);
  
     while ((c) && (uisspace(c))) {
        i += uwidth(buf+i);
        c = ugetc(buf+i);
     }
  
     /* read name string */
     j = 0;
  
     /* compute name length */
     while ((c) && (!uisspace(c)) && (c != '=') && (c != '#')) {
        j += ucwidth(c);
        i += uwidth(buf+i);
        c = ugetc(buf+i);
     }
  
     if (j) {
        /* got a variable */
        *name = _AL_MALLOC_ATOMIC(j+w0);
        if (!(*name)) {
  	 *allegro_errno = ENOMEM;
  	 _AL_FREE(buf);
  	 return -1;
        }
  
        ustrzcpy(*name, j+w0, buf+i-j);
  
        while ((c) && ((uisspace(c)) || (c == '='))) {
  	 i += uwidth(buf+i);
  	 c = ugetc(buf+i);
        }
  
        *val = _al_ustrdup(buf+i);
        if (!(*val)) {
  	 _AL_FREE(name);
  	 _AL_FREE(buf);
  	 return -1;
        }
  
        /* strip trailing spaces */
        i = ustrlen(*val) - 1;
        while ((i >= 0) && (uisspace(ugetat(*val, i))))
  	 usetat(*val, i--, 0);
     }
     else {
        /* blank line or comment */
        *name = NULL;
        *val = _al_ustrdup(buf);
        if (!(*val)) {
  	 _AL_FREE(buf);
  	 return -1;
        }
     }
  
     _AL_FREE(buf);
  
     return inpos;
  }
  
  
  
  /* set_config:
   *  Does the work of setting up a config structure.
   */
  static void set_config(CONFIG **config, AL_CONST char *data, int length, AL_CONST char *filename)
  {
     CONFIG_ENTRY **prev, *p;
     char *name, *val;
     int ret, pos;
  
     init_config(FALSE);
  
     if (*config) {
        destroy_config(*config);
        *config = NULL;
     }
  
     *config = _AL_MALLOC(sizeof(CONFIG));
     if (!(*config)) {
        *allegro_errno = ENOMEM;
        return;
     }
  
     (*config)->head = NULL;
     (*config)->dirty = FALSE;
  
     if (filename) {
        (*config)->filename = _al_ustrdup(filename);
        if (!(*config)->filename) {
  	 _AL_FREE(*config);
  	 *config = NULL;
  	 return;
        }
     }
     else
        (*config)->filename = NULL;
  
     prev = &(*config)->head;
     pos = 0;
  
     while (pos < length) {
        ret = get_line(data+pos, length-pos, &name, &val);
        if (ret<0) {
  	 _AL_FREE(*config);
  	 *config = NULL;
  	 return;
        }
  
        pos += ret;
  
        p = _AL_MALLOC(sizeof(CONFIG_ENTRY));
        if (!p) {
  	 *allegro_errno = ENOMEM;
  	 _AL_FREE(*config);
  	 *config = NULL;
  	 return;
        }
  
        p->name = name;
        p->data = val;
  
        p->next = NULL;
        *prev = p;
        prev = &p->next;
     }
  }
  
  
  
  /* load_config_file:
   *  Does the work of loading a config file.
   */
  static void load_config_file(CONFIG **config, AL_CONST char *filename, AL_CONST char *savefile)
  {
     char *tmp, *tmp2;
     uint64_t length;
  
     if (*config) {
        destroy_config(*config);
        *config = NULL;
     }
  
     /* Special case when allegro_init has not been called yet. */
     if (!system_driver) {
        set_config(config, NULL, 0, savefile);
        return;
     }
  
     length = file_size_ex(filename);
  
     if (length > 0) {
        PACKFILE *f = pack_fopen(filename, F_READ);
  
        if (f) {
  	 tmp = _AL_MALLOC_ATOMIC(length+1);
  
  	 if (tmp) {
  	    pack_fread(tmp, length, f);
  	    tmp[length] = 0;
  
  	    if (need_uconvert(tmp, U_UTF8, U_CURRENT)) {
  	       length = uconvert_size(tmp, U_UTF8, U_CURRENT);
  	       tmp2 = _AL_MALLOC_ATOMIC(length);
  
  	       if (tmp2)
  		  do_uconvert(tmp, U_UTF8, tmp2, U_CURRENT, length);
  
  	       length -= ucwidth(0);
  	    }
  	    else
  	       tmp2 = tmp;
  
  	    if (tmp2) {
  	       set_config(config, tmp2, length, savefile);
  
  	       if (tmp2 != tmp)
  		  _AL_FREE(tmp2);
  	    }
  
  	    _AL_FREE(tmp);
  	 }
  	 else
  	    set_config(config, NULL, 0, savefile);
  
  	 pack_fclose(f);
        }
        else
  	 set_config(config, NULL, 0, savefile);
     }
     else
        set_config(config, NULL, 0, savefile);
  }
  
  
  
  /* set_config_file:
   *  Sets the file to be used for all future configuration operations.
   */
  void set_config_file(AL_CONST char *filename)
  {
     ASSERT(filename);
     load_config_file(&config[0], filename, filename);
  }
  
  
  
  /* set_config_data:
   *  Sets the block of data to be used for all future configuration 
   *  operations.
   */
  void set_config_data(AL_CONST char *data, int length)
  {
     ASSERT(data);
     set_config(&config[0], data, length, NULL);
  }
  
  
  
  /* override_config_file:
   *  Sets the file that will override all future configuration operations.
   */
  void override_config_file(AL_CONST char *filename)
  {
     /* load other configuration file to override settings */
     if (filename)
        load_config_file(&config_override, filename, filename);
     /* destroy the current one */
     else if (config_override) {
        destroy_config(config_override);
        config_override = NULL;
     }
  }
  
  
  
  /* override_config_data:
   *  Sets the block of data that will override all future configuration 
   *  operations.
   */
  void override_config_data(AL_CONST char *data, int length)
  {
     ASSERT(data);
     set_config(&config_override, data, length, NULL);
  }
  
  
  
  /* push_config_state:
   *  Pushes the current config state onto the stack.
   */
  void push_config_state(void)
  {
     int i;
  
     if (config[MAX_CONFIGS-1])
        destroy_config(config[MAX_CONFIGS-1]);
  
     for (i=MAX_CONFIGS-1; i>0; i--)
        config[i] = config[i-1];
  
     config[0] = NULL;
  }
  
  
  
  /* pop_config_state:
   *  Pops the current config state off the stack.
   */
  void pop_config_state(void)
  {
     int i;
  
     if (config[0])
        destroy_config(config[0]);
  
     for (i=0; i<MAX_CONFIGS-1; i++)
        config[i] = config[i+1];
  
     config[MAX_CONFIGS-1] = NULL;
  }
  
  
  
  /* prettify_section_name:
   *  Helper for ensuring that a section name is enclosed by [ ] braces.
   */
  static void prettify_section_name(AL_CONST char *in, char *out, int out_size)
  {
     int p;
  
     if ((in) && (ustrlen(in))) {
        if (ugetc(in) != '[') {
  	 p = usetc(out, '[');
  	 usetc(out+p, 0);
        }
        else
  	 usetc(out, 0);
  
        ustrzcat(out, out_size - ucwidth(']'), in);
  
        out += uoffset(out, -1);
  
        if (ugetc(out) != ']') {
  	 out += uwidth(out);
  	 out += usetc(out, ']');
  	 usetc(out, 0);
        }
     }
     else
        usetc(out, 0);
  }
  
  
  
  /* hook_config_section:
   *  Hooks a config section to a set of getter/setter functions. This will 
   *  override the normal table of values, and give the provider of the hooks 
   *  complete control over that section.
   */
  void hook_config_section(AL_CONST char *section, int (*intgetter)(AL_CONST char *, int), AL_CONST char *(*stringgetter)(AL_CONST char *, AL_CONST char *), void (*stringsetter)(AL_CONST char *, AL_CONST char *))
  {
     CONFIG_HOOK *hook, **prev;
     char section_name[256];
  
     init_config(FALSE);
  
     prettify_section_name(section, section_name, sizeof(section_name));
  
     hook = config_hook;
     prev = &config_hook;
  
     while (hook) {
        if (ustricmp(section_name, hook->section) == 0) {
  	 if ((intgetter) || (stringgetter) || (stringsetter)) {
  	    /* modify existing hook */
  	    hook->intgetter = intgetter;
  	    hook->stringgetter = stringgetter;
  	    hook->stringsetter = stringsetter;
  	 }
  	 else {
  	    /* remove a hook */
  	    *prev = hook->next;
  	    _AL_FREE(hook->section);
  	    _AL_FREE(hook);
  	 }
  
  	 return;
        }
  
        prev = &hook->next;
        hook = hook->next;
     }
  
     /* add a new hook */
     hook = _AL_MALLOC(sizeof(CONFIG_HOOK));
     if (!hook)
        return;
  
     hook->section = _al_ustrdup(section_name);
     if (!(hook->section)) {
        _AL_FREE(hook);
        return;
     }
  
     hook->intgetter = intgetter;
     hook->stringgetter = stringgetter;
     hook->stringsetter = stringsetter;
  
     hook->next = config_hook;
     config_hook = hook;
  }
  
  
  
  /* is_config_hooked:
   *  Checks whether a specific section is hooked in any way.
   */
  int config_is_hooked(AL_CONST char *section)
  {
     CONFIG_HOOK *hook = config_hook;
     char section_name[256];
  
     prettify_section_name(section, section_name, sizeof(section_name));
  
     while (hook) {
        if (ustricmp(section_name, hook->section) == 0)
  	 return TRUE;
  
        hook = hook->next;
     }
  
     return FALSE;
  }
  
  
  
  /* find_config_string:
   *  Helper for finding an entry in the configuration file.
   */
  static CONFIG_ENTRY *find_config_string(CONFIG *config, AL_CONST char *section, AL_CONST char *name, CONFIG_ENTRY **prev)
  {
     CONFIG_ENTRY *p;
     int in_section;
  
     if (config) {
        p = config->head;
  
        if (prev)
  	 *prev = NULL;
  
        if (section && ugetc(section))
           in_section = FALSE;
        else
           in_section = TRUE;
  
        while (p) {
  	 if (p->name) {
  	    if ((section) && (ugetc(p->name) == '[') && (ugetat(p->name, -1) == ']')) {
  	       /* change section */
  	       in_section = (ustricmp(section, p->name) == 0);
  	    }
  	    if ((in_section) || (ugetc(name) == '[')) {
  	       /* is this the one? */
  	       if (ustricmp(p->name, name) == 0)
  		  return p;
  	    }
  	 }
  
  	 if (prev)
  	    *prev = p;
  
  	 p = p->next;
        }
     }
  
     return NULL;
  }
  
  
  
  /* get_config_string:
   *  Reads a string from the configuration file.
   */
  AL_CONST char *get_config_string(AL_CONST char *section, AL_CONST char *name, AL_CONST char *def)
  {
     char section_name[256];
     CONFIG_HOOK *hook;
     CONFIG_ENTRY *p;
  
     init_config(TRUE);
  
     prettify_section_name(section, section_name, sizeof(section_name));
  
     /* check for hooked sections */
     hook = config_hook;
  
     while (hook) {
        if (ustricmp(section_name, hook->section) == 0) {
  	 if (hook->stringgetter)
  	    return hook->stringgetter(name, def);
  	 else
  	    return def;
        }
        hook = hook->next;
     }
  
     /* find the string */
     p = find_config_string(config_override, section_name, name, NULL);
  
     if (!p) {
        if ((ugetc(name) == '#') || ((ugetc(section_name) == '[') && (ugetat(section_name, 1) == '#')))
  	 p = find_config_string(system_config, section_name, name, NULL);
        else
  	 p = find_config_string(config[0], section_name, name, NULL);
     }
  
     if (p && p->data && (ustrlen(p->data) != 0))
        return p->data;
     else
        return def;
  }
  
  
  
  /* get_config_int:
   *  Reads an integer from the configuration file.
   */
  int get_config_int(AL_CONST char *section, AL_CONST char *name, int def)
  {
     CONFIG_HOOK *hook;
     char section_name[256];
     AL_CONST char *s;
  
     prettify_section_name(section, section_name, sizeof(section_name));
  
     /* check for hooked sections */
     hook = config_hook;
  
     while (hook) {
        if (ustricmp(section_name, hook->section) == 0) {
  	 if (hook->intgetter) {
  	    return hook->intgetter(name, def);
  	 }
  	 else if (hook->stringgetter) {
  	    s = hook->stringgetter(name, NULL);
  	    if ((s) && (ugetc(s)))
  	       return ustrtol(s, NULL, 0);
  	    else
  	       return def;
  	 }
  	 else
  	    return def;
        }
        hook = hook->next;
     }
  
     /* read normal data */
     s = get_config_string(section_name, name, NULL);
  
     if ((s) && (ugetc(s)))
        return ustrtol(s, NULL, 0);
  
     return def;
  }
  
  
  
  /* get_config_hex:
   *  Reads a hexadecimal integer from the configuration file.
   */
  int get_config_hex(AL_CONST char *section, AL_CONST char *name, int def)
  {
     AL_CONST char *s = get_config_string(section, name, NULL);
     char tmp[64];
     int i;
  
     if ((s) && (ugetc(s))) {
        i = ustrtol(s, NULL, 16);
        if ((i == 0x7FFFFFFF) && (ustricmp(s, uconvert_ascii("7FFFFFFF", tmp)) != 0))
  	 i = -1;
        return i;
     }
  
     return def;
  }
  
  
  
  /* get_config_float:
   *  Reads a float from the configuration file.
   */
  float get_config_float(AL_CONST char *section, AL_CONST char *name, float def)
  {
     AL_CONST char* s = get_config_string(section, name, NULL);
  
     if ((s) && (ugetc(s)))
        return uatof(s);
  
     return def;
  }
  
  
  
  /* get_config_id:
   *  Reads a driver ID number from the configuration file.
   */
  int get_config_id(AL_CONST char *section, AL_CONST char *name, int def)
  {
     AL_CONST char *s = get_config_string(section, name, NULL);
     char tmp[4];
     char* endp;
     int val, i;
  
     if ((s) && (ugetc(s))) {
        val = ustrtol(s, &endp, 0);
        if (!ugetc(endp))
  	 return val;
  
        tmp[0] = tmp[1] = tmp[2] = tmp[3] = ' ';
  
        for (i=0; i<4; i++) {
  	 if (ugetat(s, i))
  	    tmp[i] = utoupper(ugetat(s ,i));
  	 else
  	    break;
        }
  
        return AL_ID(tmp[0], tmp[1], tmp[2], tmp[3]);
     }
  
     return def;
  }
  
  
  
  /* get_config_argv:
   *  Reads an argc/argv style token list from the configuration file.
   */
  char **get_config_argv(AL_CONST char *section, AL_CONST char *name, int *argc)
  {
     int pos, ac, q, c;
     int s_size;
     int i;
  
     AL_CONST char *s = get_config_string(section, name, NULL);
  
     if (!s) {
        *argc = 0;
        return NULL;
     }
  
     /* clean up the old argv that was allocated the last time this function was
      * called.
      */
     _AL_FREE(config_argv);
     config_argv = NULL;
  
     /* increase the buffer size if needed */
     s_size = ustrsizez(s);
     if (s_size>argv_buf_size) {
        argv_buf_size = s_size;
        argv_buf = _al_sane_realloc(argv_buf, argv_buf_size);
        if (!argv_buf) {
           *allegro_errno = ENOMEM;
           *argc = 0;
           return NULL;
        }
     }
  
     ustrzcpy(argv_buf, argv_buf_size, s);
     pos = 0;
     ac = 0;
  
     /* tokenize the buffer and count the number of words; every space character
      * as well as single and double quotes are replaced with zeros; comments * that start with # are also cut off with a 0
      */
     c = ugetc(argv_buf);
     while ((c) && (c != '#')) {
        /* replace all spaces up to the next word with 0 */
        while ((c) && (uisspace(c))) {
  	 usetat(argv_buf+pos, 0, 0);
  	 pos += ucwidth(0);
  	 c = ugetc(argv_buf+pos);
        }
  
        /* quit if we reached the end of the buffer or a comment */
        if ((c) && (c != '#')) {
           /* found another word! */
           ac++;
  
           /* cut away quotes by replacing them with 0 */
           if ((c == '\'') || (c == '"')) {
              q = c;
              usetat(argv_buf+pos, 0, 0);
              pos += ucwidth(0);
              c = ugetc(argv_buf+pos);
           }
           else {
              q = 0;
           }
  
           /* search for the end of the word */
           while ((c) && ((q) ? (c != q) : (!uisspace(c)))) {
              pos += ucwidth(c);
              c = ugetc(argv_buf+pos);
           }
        }
     }
  
     /* now that we know how many words there are in the buffer, allocate enough
      * space for a list of pointers to them, or return 0 if there are no words
      */
     if (ac > 0) {
        config_argv = _AL_MALLOC(ac*sizeof *config_argv);
     }
     else {
        *argc = 0;
        return NULL;
     }
  
     /* go through the tokenized buffer and assign pointers in argv to point to
      * the beginning of each individual word
      */
     for (i=0,pos=0,c=ugetc(argv_buf); i<ac; i++) {
        /* find next word */
        while (!c) {
           pos += ucwidth(c);
           c = ugetc(argv_buf+pos);
        }
  
        /* assign pointer */
        config_argv[i] = argv_buf+pos;
  
        /* find end of the word */
        while (c) {
           pos += ucwidth(c);
           c = ugetc(argv_buf+pos);
        }
     }
  
     *argc = ac;
     return config_argv;
  }
  
  
  
  /* insert_variable:
   *  Helper for inserting a new variable into a configuration file.
   */
  static CONFIG_ENTRY *insert_variable(CONFIG *the_config, CONFIG_ENTRY *p, AL_CONST char *name, AL_CONST char *data)
  {
     CONFIG_ENTRY *n = _AL_MALLOC(sizeof(CONFIG_ENTRY));
  
     if (!n)
        return NULL;
  
     if (name)
        n->name = _al_ustrdup(name);
     else
        n->name = NULL;
  
     if (data)
        n->data = _al_ustrdup(data);
     else
        n->data = NULL;
  
     if (p) {
        n->next = p->next;
        p->next = n; 
     }
     else {
        n->next = NULL;
        the_config->head = n;
     }
  
     return n;
  }
  
  
  
  /* set_config_string:
   *  Writes a string to the configuration file.
   */
  void set_config_string(AL_CONST char *section, AL_CONST char *name, AL_CONST char *val)
  {
     CONFIG *the_config;
     CONFIG_HOOK *hook;
     CONFIG_ENTRY *p, *prev;
     char section_name[256];
  
     init_config(TRUE);
  
     prettify_section_name(section, section_name, sizeof(section_name));
  
     /* check for hooked sections */
     hook = config_hook;
  
     while (hook) {
        if (ustricmp(section_name, hook->section) == 0) {
  	 if (hook->stringsetter)
  	    hook->stringsetter(name, val);
  	 return;
        }
        hook = hook->next;
     }
  
     /* decide which config file to use */
     if ((ugetc(name) == '#') || ((ugetc(section_name) == '[') && (ugetat(section_name, 1) == '#')))
        the_config = system_config;
     else if (config_override)
        the_config = config_override;
     else
        the_config = config[0];
  
     if (the_config) {
        p = find_config_string(the_config, section_name, name, &prev);
  
        if (p) {
  	 if ((val) && (ugetc(val))) {
  	    /* modify existing variable */
  	    if (p->data)
  	       _AL_FREE(p->data);
  
  	    p->data = _al_ustrdup(val);
  	 }
  	 else {
  	    /* delete variable */
  	    if (p->name)
  	       _AL_FREE(p->name);
  
  	    if (p->data)
  	       _AL_FREE(p->data);
  
  	    if (prev)
  	       prev->next = p->next;
  	    else
  	       the_config->head = p->next;
  
  	    _AL_FREE(p);
  	 }
        }
        else {
  	 if ((val) && (ugetc(val))) {
  	    /* add a new variable */
  	    if (ugetc(section_name)) {
  	       p = find_config_string(the_config, NULL, section_name, &prev);
  
  	       if (!p) {
  		  /* create a new section */
  		  p = the_config->head;
  		  while ((p) && (p->next))
  		     p = p->next;
  
  		  if ((p) && (p->data) && (ugetc(p->data)))
  		     p = insert_variable(the_config, p, NULL, NULL);
  
  		  p = insert_variable(the_config, p, section_name, NULL);
  	       }
  
  	       /* append to the end of the section */
  	       while ((p) && (p->next) && 
  		      (((p->next->name) && (ugetc(p->next->name))) || 
  		       ((p->next->data) && (ugetc(p->next->data)))))
  		  p = p->next;
  
  	       p = insert_variable(the_config, p, name, val);
  	    }
  	    else {
  	       /* global variable */
  	       p = the_config->head;
  	       insert_variable(the_config, NULL, name, val);
  	       the_config->head->next = p;
  	    }
  	 } 
        }
  
        the_config->dirty = TRUE;
     }
  }
  
  
  
  /* set_config_int:
   *  Writes an integer to the configuration file.
   */
  void set_config_int(AL_CONST char *section, AL_CONST char *name, int val)
  {
     char buf[32], tmp[32];
     uszprintf(buf, sizeof(buf), uconvert_ascii("%d", tmp), val);
     set_config_string(section, name, buf);
  }
  
  
  
  /* set_config_hex:
   *  Writes a hexadecimal integer to the configuration file.
   */
  void set_config_hex(AL_CONST char *section, AL_CONST char *name, int val)
  {
     char buf[32], tmp[32];
  
     if (val >= 0) {
        uszprintf(buf, sizeof(buf), uconvert_ascii("%X", tmp), val);
        set_config_string(section, name, buf);
     }
     else
        set_config_string(section, name, uconvert_ascii("-1", buf));
  }
  
  
  
  /* set_config_float:
   *  Writes a float to the configuration file.
   */
  void set_config_float(AL_CONST char *section, AL_CONST char *name, float val)
  {
     char buf[32], tmp[32];
     uszprintf(buf, sizeof(buf), uconvert_ascii("%f", tmp), val);
     set_config_string(section, name, buf);
  }
  
  
  
  /* set_config_id:
   *  Writes a driver ID to the configuration file.
   */
  void set_config_id(AL_CONST char *section, AL_CONST char *name, int val)
  {
     char buf[32], tmp[32];
     int v[4];
     int pos = 0;
     int i;
  
     if (val < 256) {
        uszprintf(buf, sizeof(buf), uconvert_ascii("%d", tmp), val);
     }
     else {
        v[0] = (val>>24)&0xFF;
        v[1] = (val>>16)&0xFF;
        v[2] = (val>>8)&0xFF;
        v[3] = val&0xFF;
  
        for (i=0; (i<4) && (v[i]) && (v[i] != ' '); i++)
  	 pos += usetc(buf+pos, v[i]);
  
        usetc(buf+pos, 0);
     }
  
     set_config_string(section, name, buf);
  }
  
  
  
  /* _reload_config:
   *  Internal helper to reload the configuration from allegro_init, in case
   *  set_config_file was called before allegro_init.
   */
  void _reload_config(void)
  {
     if (config[0]) {
        char *name = _al_ustrdup(config[0]->filename);
        set_config_file(name);
        _AL_FREE(name);
     }
  }
  
  
  
  /* reload_config_texts:
   *  Reads in a block of translated system text, looking for either a
   *  user-specified file, a ??text.cfg file, or a language.dat#??TEXT_CFG 
   *  datafile object. If new_language is not NULL, the language config
   *  variable will be set to new_language before reloading the
   *  configuration files.
   */
  void reload_config_texts(AL_CONST char *new_language)
  {
     char filename[1024], tmp1[128], tmp2[128];
     AL_CONST char *name, *ext, *datafile;
     char *namecpy;
  
     if (config_language) {
        destroy_config(config_language);
        config_language = NULL;
     }
  
     if (new_language)
        set_config_string("system", "language", new_language);
  
     name = get_config_string(uconvert_ascii("system", tmp1), uconvert_ascii("language", tmp2), NULL);
  
     if ((name) && (ugetc(name))) {
        namecpy = _al_ustrdup(name);
        ustrlwr (namecpy);
        if ((ustrlen(namecpy)<4) || (ustricmp(namecpy+uoffset(namecpy, -4), uconvert_ascii("text", tmp1)) != 0))
  	 ext = uconvert_ascii("text.cfg", tmp1);
        else
  	 ext = uconvert_ascii(".cfg", tmp1);
  
        datafile = uconvert_ascii("language.dat", tmp2);
  
        if (find_allegro_resource(filename, namecpy, ext, datafile, NULL, NULL, NULL, sizeof(filename)) == 0) {
  	 _AL_FREE(namecpy);
  	 load_config_file(&config_language, filename, NULL);
  	 return;
        }
  
        _AL_FREE(namecpy);
     }
  
     config_language = _AL_MALLOC(sizeof(CONFIG));
     if (config_language ) {
        config_language ->head = NULL;
        config_language ->filename = NULL;
        config_language ->dirty = FALSE;
     }
  }
  
  
  
  /* get_config_text:
   *  Looks up a translated version of the specified English string,
   *  returning a suitable message in the current language if one is
   *  available, or a copy of the parameter if no translation can be found.
   */
  AL_CONST char *get_config_text(AL_CONST char *msg)
  {
     char tmp1[256];
     AL_CONST char *section;
     AL_CONST char *umsg;
     AL_CONST char *s;
     AL_CONST char *ret = NULL;
     char *name;
     CONFIG_HOOK *hook;
     CONFIG_ENTRY *p;
     int c, pos, size;
     ASSERT(msg);
  
     /* Hack: the inline definition of install_allegro() from 4.2.0 calls
      * get_config_text() even before Allegro has been initialised, leading
      * to a crash in get_executable_name().  To retain binary compatibility
      * we check for this case.
      */
     if (_allegro_count == 0) {
        return msg;
     }
  
     init_config(TRUE);
  
     section = uconvert_ascii("[language]", tmp1);
  
     /* allocate memory and convert message to current encoding format */
     if (need_uconvert(msg, U_ASCII, U_CURRENT)) {
        size = uconvert_size(msg, U_ASCII, U_CURRENT);
        umsg = _AL_MALLOC_ATOMIC(size);
        if (!umsg) {
  	 *allegro_errno = ENOMEM;
  	 return empty_string;
        }
  
        name = _AL_MALLOC_ATOMIC(size);
        if (!name) {
  	 _AL_FREE((char *)umsg);  /* remove constness */
  	 *allegro_errno = ENOMEM;
  	 return empty_string;
        }
  
        do_uconvert(msg, U_ASCII, (char*)umsg, U_CURRENT, size);
     }
     else {
        umsg = msg;
        name = _AL_MALLOC_ATOMIC(ustrsizez(msg));
        if (!name) {
  	 *allegro_errno = ENOMEM;
  	 return empty_string;
        }
     }
  
     s = umsg;
     pos = 0;
  
     while ((c = ugetxc(&s)) != 0) {
        if ((uisspace(c)) || (c == '=') || (c == '#'))
  	 pos += usetc(name+pos, '_');
        else
  	 pos += usetc(name+pos, c);
     }
  
     usetc(name+pos, 0);
  
     /* check for hooked sections */
     hook = config_hook;
  
     while (hook) {
        if (ustricmp(section, hook->section) == 0) {
  	 if (hook->stringgetter) {
  	    ret = hook->stringgetter(name, umsg);
  	    break;
  	 }
        }
  
        hook = hook->next;
     }
  
     if (!ret) {
        /* find the string */
        p = find_config_string(config_override, section, name, NULL);
  
        if (!p) {
  	 p = find_config_string(config[0], section, name, NULL);
  
  	 if (!p)
  	    p = find_config_string(config_language, section, name, NULL);
        }
  
        if (p) {
  	 ret = (p->data ? p->data : empty_string);
        }
        else {
  	 /* no translation, so store off this value in the file */
  	 p = config_language->head;
  	 insert_variable(config_language, NULL, name, umsg);
  	 config_language->head->next = p;
  	 ret = config_language->head->data;
        }
     }
  
     /* free memory */
     if (umsg!=msg)
        _AL_FREE((char*) umsg);  /* remove constness */
  
     _AL_FREE(name);
  
     return ret;
  }
  
  
  
  /* add_unique_name
   *  Helper to add a name to a list of names.
   */
  static int add_unique_name(AL_CONST char ***names, int n, char const *name)
  {
     int i;
     /* FIXME: use better search algorithm */
     for (i = 0; i < n; i++)
        if (!ustrcmp((*names)[i], name))
           return n;
  
     *names = _al_sane_realloc((void *)*names, (n + 1) * sizeof **names);
     (*names)[n] = name;
     return n + 1;
  }
  
  
  
  /* attach_config_entries
   *  Helper function to attach key or section names to a list of strings.
   */
  static int attach_config_entries(CONFIG *conf, AL_CONST char *section,
     int n, AL_CONST char ***names, int list_sections)
  {
     CONFIG_ENTRY *p;
     char section_name[256];
     int in_section;
  
     prettify_section_name(section, section_name, sizeof(section_name));
  
     if (conf) {
        p = conf->head;
  
        /* If section is NULL, only initial, section-less entries are used. */
        if (ugetc(section_name))
           in_section = FALSE;
        else
           in_section = TRUE;
  
        while (p) {
           if (p->name) {
              /* a section start is just a list entry enclosed in [] */
              if (ugetc(p->name) == '[' && ugetat(p->name, -1) == ']') {
                 if (list_sections) {
                    n = add_unique_name(names, n, p->name);
                 }
                 in_section = (ustricmp(section_name, p->name) == 0);
              }
              else if (in_section && !list_sections) {
                 n = add_unique_name(names, n, p->name);
              }
           }
           p = p->next;
        }
     }
     return n;
  }
  
  
  
  /* list_config_entires:
   *  Returns the names of all config entries in a section. The names parameter is
   *  a pointer to a strings array that will contain the config keys. If it points to
   *  a NULL pointer, it will be allocated, or else re-allocated accordingly. The
   *  return value tells how many valid string pointers it contains after the
   *  function returns.
   */
  int list_config_entries(AL_CONST char *section, AL_CONST char ***names)
  {
     int n = 0;
     n = attach_config_entries(config_override, section, n, names, 0);
     n = attach_config_entries(config[0], section, n, names, 0);
     return n;
  }
  
  
  
  /* list_config_sections:
   *  Returns the names of all current config sections, enclodes in []. The names
   *  parameter and return value is like in list_config_entires above.
   */
  int list_config_sections(AL_CONST char ***names)
  {
     int n = 0;
     n = attach_config_entries(config_override, NULL, n, names, 1);
     n = attach_config_entries(config[0], NULL, n, names, 1);
     return n;
  }
  
  
  
  /* free_config_entries:
   *  Frees the entries list returned by list_config_entires or
   *  list_config_sections again.
   */
  void free_config_entries(AL_CONST char ***names)
  {
      _AL_FREE(*names);
      *names = NULL;
  }
  