// inifile.c
 
 
 
#include "common.h"
 
 
 
 
 
// WARNING: INI SECTION NAMES ARE CASE SENSITIVE, BUT KEY NAMES ARE NOT!
 
 
 
 
 
// internal definitions
 
#define MAX_KEY_SIZE 124
 
#define MAX_VALUE_SIZE 3968
 
#define DICTIONARY_PAGE_SIZE 128
 
#define INI_SECTION_SEPARATOR ']'
 
#define INI_INVALID_KEY (wchar_t *) 0x7fffffff
 
#define WSIZEOF(a) (sizeof (a) / sizeof (wchar_t))
 
 
 
 
 
// dictionary structure definitions
 
typedef struct dictionary_entry_s
 
{
 
   wchar_t key[MAX_KEY_SIZE]; // key string
 
   unsigned long hash; // key hash value
 
   wchar_t value[MAX_VALUE_SIZE]; // value string
 
} dictionary_entry_t;
 
 
 
 
 
typedef struct dictionary_s
 
{
 
   int size; // storage size
 
   dictionary_entry_t *entries; // array of entries (mallocated)
 
   int entry_count; // number of entries in dictionary
 
} dictionary_t;
 
 
 
 
 
// dictionary function prototypes
 
static dictionary_t *Dictionary_CreateDictionary (int size);
 
static void Dictionary_DestroyDictionary (dictionary_t *dictionary);
 
static dictionary_entry_t *Dictionary_GetKey (dictionary_t *dictionary, wchar_t *key);
 
static wchar_t *Dictionary_ReadKey (dictionary_t *dictionary, wchar_t *key, wchar_t *default_value);
 
static void Dictionary_WriteKey (dictionary_t *dictionary, wchar_t *key, wchar_t *value);
 
static void Dictionary_DeleteKey (dictionary_t *dictionary, wchar_t *key);
 
static unsigned long Dictionary_ComputeHashValueForKey (wchar_t *key);
 
 
 
 
 
// ini file function prototypes
 
void *INIFile_NewINIFile (void);
 
int INIFile_GetNumberOfSections (void *ini_data);
 
wchar_t *INIFile_GetSectionName (void *ini_data, int section_index);
 
int INIFile_GetNumberOfEntries (void *ini_data, wchar_t *section);
 
wchar_t *INIFile_GetEntryName (void *ini_data, wchar_t *section, int entry_index);
 
unsigned char INIFile_ReadEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char default_value);
 
long INIFile_ReadEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long default_value);
 
unsigned long INIFile_ReadEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long default_value);
 
double INIFile_ReadEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double default_value);
 
wchar_t *INIFile_ReadEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *default_value);
 
void INIFile_WriteEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char value);
 
void INIFile_WriteEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long value);
 
void INIFile_WriteEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long value);
 
void INIFile_WriteEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double value);
 
void INIFile_WriteEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *value);
 
void INIFile_DeleteEntry (void *ini_data, wchar_t *section, wchar_t *entry);
 
void INIFile_DeleteSection (void *ini_data, wchar_t *section);
 
void *INIFile_LoadINIFile (const wchar_t *filename);
 
unsigned char INIFile_SaveINIFile (const wchar_t *filename, void *ini_data);
 
void INIFile_FreeINIFile (void *ini_data);
 
 
 
 
 
// internal variables
 
static wchar_t *INIFile_default_section = L"__default";
 
static wchar_t line_buffer[MAX_KEY_SIZE + MAX_VALUE_SIZE];
 
static wchar_t temp_key[MAX_KEY_SIZE];
 
static wchar_t number_as_string[256];
 
static wchar_t temp_section[MAX_KEY_SIZE];
 
static wchar_t temp_entry[MAX_KEY_SIZE];
 
static wchar_t temp_value[MAX_VALUE_SIZE];
 
 
 
 
 
static dictionary_t *Dictionary_CreateDictionary (int size)
 
{
 
   // this function allocates a new dictionary object of given size and returns it. If you do
 
   // not know in advance (roughly) the number of entries in the dictionary, give size = 0.
 
 
 
   dictionary_t *dictionary;
 
 
 
   // if no size was specified, allocate space for one page
 
   if (size < DICTIONARY_PAGE_SIZE)
 
      size = DICTIONARY_PAGE_SIZE;
 
 
 
   // allocate one instance of the dictionary and blank it out
 
   dictionary = (dictionary_t *) SAFE_malloc (1, sizeof (dictionary_t), true);
 
   dictionary->size = size; // dictionary can currently hold size entries
 
 
 
   // allocate space for the dictionary entries and zero all the crap out
 
   dictionary->entries = (dictionary_entry_t *) SAFE_malloc (size, sizeof (dictionary_entry_t), true);
 
 
 
   return (dictionary); // finished, return a pointer to the dictionary object
 
}
 
 
 
 
 
static void Dictionary_DestroyDictionary (dictionary_t *dictionary)
 
{
 
   // frees a dictionary object and all memory associated to it.
 
 
 
   if (dictionary == NULL)
 
      return; // consistency check
 
 
 
   SAFE_free ((void **) &dictionary->entries); // free the entries array
 
   SAFE_free ((void **) &dictionary); // and finally, free the dictionary itself
 
 
 
   return; // finished
 
}
 
 
 
 
 
static dictionary_entry_t *Dictionary_GetKey (dictionary_t *dictionary, wchar_t *key)
 
{
 
   // this function locates a key in a dictionary and returns a pointer to it, or a NULL
 
   // pointer if no such key can be found in dictionary. The returned pointer points to data
 
   // internal to the dictionary object, do not try to free or modify it.
 
 
 
   unsigned long hash;
 
   register int entry_index;
 
 
 
   hash = Dictionary_ComputeHashValueForKey (key); // get the hash value for key
 
 
 
   // cycle through all entries in the dictionary
 
   for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
 
   {
 
      if (dictionary->entries[entry_index].key[0] == 0)
 
         continue; // skip empty slots
 
 
 
      // compare hash AND string, to avoid hash collisions
 
      if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
 
         return (&dictionary->entries[entry_index]); // return a pointer to the key if we find it
 
   }
 
 
 
   return (NULL); // else return a NULL pointer
 
}
 
 
 
 
 
static wchar_t *Dictionary_ReadKey (dictionary_t *dictionary, wchar_t *key, wchar_t *default_value)
 
{
 
   // this function locates a key in a dictionary and returns a pointer to its value, or the
 
   // passed 'def' pointer if no such key can be found in dictionary. The returned char pointer
 
   // points to data internal to the dictionary object, do not try to free or modify it.
 
 
 
   dictionary_entry_t *element;
 
 
 
   element = Dictionary_GetKey (dictionary, key); // query the dictionary for the key
 
   if (element != NULL)
 
      return (element->value); // if we found a valid key, return the value associed to it
 
 
 
   return (default_value); // else return the default value
 
}
 
 
 
 
 
static void Dictionary_WriteKey (dictionary_t *dictionary, wchar_t *key, wchar_t *value)
 
{
 
   // sets a value in a dictionary. If the given key is found in the dictionary, the associated
 
   // value is replaced by the provided one. If the key cannot be found in the dictionary, it
 
   // is added to it.
 
 
 
   unsigned long hash;
 
   register int entry_index;
 
 
 
   hash = Dictionary_ComputeHashValueForKey (key); // compute hash for this key
 
 
 
   // for each entry in the dictionary...
 
   for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
 
   {
 
      if (dictionary->entries[entry_index].key[0] == 0)
 
          continue; // skip empty slots
 
 
 
      // does that key have the same hash value AND the same name ?
 
      if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
 
      {
 
         // we've found the right key: modify its value and return
 
 
 
         // have we provided a valid value ?
 
         if (value != NULL)
 
            wcscpy_s (dictionary->entries[entry_index].value, WSIZEOF (dictionary->entries[entry_index].value), value); // copy the value string
 
         else
 
            dictionary->entries[entry_index].value[0] = 0; // reset the value string
 
 
 
         return; // value has been modified: return
 
      }
 
   }
 
 
 
   // here either the dictionary was empty, or we couldn't find a similar value: add a new one
 
 
 
   // cycle through all entries in the dictionary...
 
   for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
 
      if (dictionary->entries[entry_index].key[0] == 0)
 
         break; // and stop at the first empty one we find
 
 
 
   // no empty entry found, see if dictionary needs to grow or if there's still room left
 
   if (entry_index == dictionary->size)
 
   {
 
      // mallocate some more space for the dictionary and zero all that crap out
 
      dictionary->entries = (dictionary_entry_t *) SAFE_realloc (dictionary->entries, dictionary->size, dictionary->size + DICTIONARY_PAGE_SIZE, sizeof (dictionary_entry_t), false); // alloc 1 more page
 
      dictionary->size += DICTIONARY_PAGE_SIZE; // increase dictionary size
 
   }
 
 
 
   // copy the new key
 
 
 
   wcscpy_s (dictionary->entries[entry_index].key, WSIZEOF (dictionary->entries[entry_index].key), key); // copy the key string...
 
   dictionary->entries[entry_index].hash = hash; // ...and store the hash value
 
 
 
   // have we provided a valid value ?
 
   if (value != NULL)
 
      wcscpy_s (dictionary->entries[entry_index].value, WSIZEOF (dictionary->entries[entry_index].value), value); // copy the value string
 
   else
 
      dictionary->entries[entry_index].value[0] = 0; // reset the value string
 
 
 
   dictionary->entry_count++; // there is one more entry in the dictionary now
 
   return;
 
}
 
 
 
 
 
static void Dictionary_DeleteKey (dictionary_t *dictionary, wchar_t *key)
 
{
 
   // this function deletes a key in a dictionary. Nothing is done if the key cannot be found.
 
 
 
   unsigned long hash;
 
   register int entry_index;
 
 
 
   hash = Dictionary_ComputeHashValueForKey (key); // get the hash value for key
 
 
 
   // cycle through all entries in the dictionary...
 
   for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
 
   {
 
      if (dictionary->entries[entry_index].key[0] == 0)
 
         continue; // skip empty slots
 
 
 
      // compare hash AND string, to avoid hash collisions
 
      if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
 
         break; // break as soon as we've found the key we want
 
   }
 
 
 
   if (entry_index == dictionary->entry_count)
 
      return; // if key was not found, just return
 
 
 
   // clear the key, the hash and its value data
 
   memset (&dictionary->entries[entry_index], 0, sizeof (dictionary_entry_t));
 
 
 
   dictionary->entry_count--; // there is one entry less in the dictionary now
 
   return;
 
}
 
 
 
 
 
static unsigned long Dictionary_ComputeHashValueForKey (wchar_t *key)
 
{
 
   // Compute the hash key for a string. This hash function has been taken from an article in
 
   // Dr Dobbs Journal. This is normally a collision-free function, distributing keys evenly.
 
   // The key is stored anyway in the struct so that collision can be avoided by comparing the
 
   // key itself in last resort. THIS FUNCTION IS TO BE USED INTERNALLY ONLY!
 
 
 
   register unsigned long hash;
 
   int length;
 
   int char_index;
 
 
 
   hash = 0;
 
   length = (int) wcslen (key);
 
 
 
   // for each character of the string...
 
   for (char_index = 0; char_index < length; char_index++)
 
   {
 
      hash += (unsigned long) key[char_index]; // take it in account and compute the hash value
 
      hash += (hash << 10);
 
      hash ^= (hash >> 6);
 
   }
 
 
 
   hash += (hash << 3); // finalize hashing (what the hell does it do, I have no clue)
 
   hash ^= (hash >> 11);
 
   hash += (hash << 15);
 
 
 
   return (hash); // and return the hash value
 
}
 
 
 
 
 
void *INIFile_NewINIFile (void)
 
{
 
   // allocates a dictionary for a new INI file. Mostly a helper function.
 
 
 
   return ((void *) Dictionary_CreateDictionary (0));
 
}
 
 
 
 
 
int INIFile_GetNumberOfSections (void  *ini_data)
 
{
 
   // this function returns the number of sections found in a dictionary. The test to recognize
 
   // sections is done on the string stored in the dictionary: a section name is given as
 
   // "section" whereas a key is stored as "section]key", thus the test looks for entries that
 
   // do NOT contain a ']'. This clearly fails in the case a section name contains a ']',
 
   // but this should simply be avoided.
 
 
 
   int section_count;
 
   int entry_index;
 
   dictionary_t *dictionary;
 
 
 
   if (ini_data == NULL)
 
      return (0); // consistency check
 
 
 
   dictionary = (dictionary_t *) ini_data;
 
 
 
   section_count = 0; // no sections found yet
 
 
 
   // for each entry in the dictionary...
 
   for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
 
   {
 
      if (dictionary->entries[entry_index].key[0] == 0)
 
         continue; // skip empty slots
 
 
 
      if (wcschr (dictionary->entries[entry_index].key, INI_SECTION_SEPARATOR) == NULL)
 
         section_count++; // if this entry has NO section separator, then it's a section name
 
   }
 
 
 
   return (section_count); // return the section count we found
 
}
 
 
 
 
 
wchar_t *INIFile_GetSectionName (void *ini_data, int section_index)
 
{
 
   // this function locates the n-th section in a dictionary and returns its name as a pointer
 
   // to a string allocated inside the dictionary. Do not free or modify the returned string!!
 
   // This function returns NULL in case of error.
 
 
 
   int sections_found;
 
   int entry_index;
 
   dictionary_t *dictionary;
 
 
 
   if ((ini_data == NULL) || (section_index < 0))
 
      return (NULL); // consistency check
 
 
 
   dictionary = (dictionary_t *) ini_data;
 
 
 
   sections_found = 0; // no sections found yet
 
 
 
   // for each entry in the dictionary...
 
   for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
 
   {
 
      if (dictionary->entries[entry_index].key[0] == 0)
 
         continue; // skip empty slots
 
 
 
      // is this entry a section name ?
 
      if (wcschr (dictionary->entries[entry_index].key, INI_SECTION_SEPARATOR) == NULL)
 
      {
 
         // is it the section we want ?
 
         if (sections_found == section_index)
 
            return (dictionary->entries[entry_index].key); // then return its name
 
 
 
         sections_found++; // we found one section more
 
      }
 
   }
 
 
 
   return (INIFile_default_section); // section was not found, return the default section name
 
}
 
 
 
 
 
int INIFile_GetNumberOfEntries (void  *ini_data, wchar_t *section)
 
{
 
   // this function returns the number of entries found in a dictionary within a given section.
 
   // The test to recognize section entries is done on the string stored in the dictionary: a
 
   // section name is given as "section" whereas a key is stored as "section]key", thus the test
 
   // looks for all entries that begin with "section]".
 
 
 
   int entry_count;
 
   int entry_index;
 
   int sectionname_length;
 
   dictionary_t *dictionary;
 
 
 
   if (ini_data == NULL)
 
      return (0); // consistency check
 
 
 
   dictionary = (dictionary_t *) ini_data;
 
   sectionname_length = wcslen (section);
 
 
 
   entry_count = 0; // no entries found yet
 
 
 
   // for each entry in the dictionary...
 
   for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
 
   {
 
      if (dictionary->entries[entry_index].key[0] == 0)
 
         continue; // skip empty slots
 
 
 
      if ((wcsncmp (dictionary->entries[entry_index].key, section, sectionname_length) == 0)
 
          && (dictionary->entries[entry_index].key[sectionname_length] == INI_SECTION_SEPARATOR))
 
         entry_count++; // if this entry has section name + separator, then it's one of those we want
 
   }
 
 
 
   return (entry_count); // return the number of entries we found in the given section
 
}
 
 
 
 
 
wchar_t *INIFile_GetEntryName (void *ini_data, wchar_t *section, int entry_index)
 
{
 
   // this function queries a dictionary for a key. A key as read from an ini file is given as
 
   // "section]key". If the key cannot be found, NULL is returned. The returned char pointer
 
   // points to a string allocated in the dictionary, do not free or modify it.
 
 
 
   int i;
 
   int length;
 
   int found_keys;
 
   dictionary_t *dictionary;
 
   dictionary_entry_t *entry;
 
 
 
   if (ini_data == NULL)
 
      return (NULL); // consistency check
 
 
 
   if (section == NULL)
 
      section = INIFile_default_section; // if no section was provided, use empty section name
 
 
 
   dictionary = (dictionary_t *) ini_data; // quick access to dictionary
 
 
 
   // build the key entry prefix
 
   swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c", section, INI_SECTION_SEPARATOR); // compose the key
 
   length = wcslen (temp_key);
 
 
 
   // cycle through all remaining entries
 
   found_keys = 0;
 
   for (i = 0; i < dictionary->entry_count; i++)
 
   {
 
      entry = &dictionary->entries[i]; // quick access to each entry
 
 
 
      if (wcsncmp (entry->key, temp_key, length) != 0)
 
         continue; // if this key doesn't belong to the wanted section, skip it
 
 
 
      // this key belongs to the wanted section. Is it the key we want ?
 
      if (found_keys == entry_index)
 
         return (&entry->key[length]); // if so, return its name
 
 
 
      found_keys++; // it wasn't the key we want, so increase counter and proceed to the next one
 
   }
 
 
 
   return (NULL); // if not found, return NULL
 
}
 
 
 
 
 
unsigned char INIFile_ReadEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char default_value)
 
{
 
   // this function queries a dictionary for a key. A key as read from an ini file is given as
 
   // "section]key". If the key cannot be found, the notfound value is returned. A true boolean
 
   // is found if a string starting with either 'y', 'Y', 't', 'T' or '1' is matched. A false
 
   // boolean is found if a string starting with 'n', 'N', 'f', 'F' or '0' is matched.
 
 
 
   wchar_t *value;
 
 
 
   if (ini_data == NULL)
 
      return (default_value); // consistency check
 
 
 
   // get the value as a string first
 
   value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
 
 
 
   if (value == INI_INVALID_KEY)
 
      return (default_value); // if the entry could not be read, return the default value
 
 
 
   // decide of the boolean's value by looking at the first character
 
   if ((toupper (value[0]) == 'T') || (toupper (value[0]) == 'Y') || (value[0] == '1'))
 
     return (1); // boolean value is TRUE
 
   else if ((toupper (value[0]) == 'F') || (toupper (value[0]) == 'N') || (value[0] == '0'))
 
     return (0); // boolean value is FALSE
 
 
 
   return (default_value); // boolean value is undefined, return default value
 
}
 
 
 
 
 
long INIFile_ReadEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long default_value)
 
{
 
   // this function queries a dictionary for a key. A key as read from an ini file is given as
 
   // "section]key". If the key cannot be found, the notfound value is returned.
 
 
 
   wchar_t *value;
 
 
 
   if (ini_data == NULL)
 
      return (default_value); // consistency check
 
 
 
   // get the value as a string first
 
   value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
 
 
 
   if (value == INI_INVALID_KEY)
 
      return (default_value); // if the entry could not be read, return the default value
 
 
 
   return (_wtol (value)); // else convert the value to a long integer and return it
 
}
 
 
 
 
 
unsigned long INIFile_ReadEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long default_value)
 
{
 
   // this function queries a dictionary for a key. A key as read from an ini file is given as
 
   // "section]key". If the key cannot be found, the notfound value is returned.
 
 
 
   wchar_t *value;
 
 
 
   if (ini_data == NULL)
 
      return (default_value); // consistency check
 
 
 
   // get the value as a string first
 
   value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
 
 
 
   if (value == INI_INVALID_KEY)
 
      return (default_value); // if the entry could not be read, return the default value
 
 
 
   return (wcstoul (value, NULL, 10)); // else convert the value to a long integer and return it
 
}
 
 
 
 
 
double INIFile_ReadEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double default_value)
 
{
 
   // this function queries a dictionary for a key. A key as read from an ini file is given as
 
   // "section]key". If the key cannot be found, the notfound value is returned.
 
 
 
   wchar_t *value;
 
 
 
   if (ini_data == NULL)
 
      return (default_value); // consistency check
 
 
 
   // get the value as a string first
 
   value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
 
 
 
   if (value == INI_INVALID_KEY)
 
      return (default_value); // if the entry could not be read, return the default value
 
 
 
   return (_wtof (value)); // else convert the value to a double precision number and return it
 
}
 
 
 
 
 
wchar_t *INIFile_ReadEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *default_value)
 
{
 
   // this function queries a dictionary for a key. A key as read from an ini file is given as
 
   // "section]key". If the key cannot be found, the pointer passed as 'def' is returned. The
 
   // returned char pointer points to a string allocated in the dictionary, do not free or
 
   // modify it.
 
 
 
   int length;
 
   int i;
 
   wchar_t *value;
 
 
 
   if (ini_data == NULL)
 
      return (default_value); // consistency check
 
 
 
   if (section == NULL)
 
      section = INIFile_default_section; // if no section was provided, use empty section name
 
 
 
   // if we were given an entry, build a key as section]entry
 
   if (entry != NULL)
 
   {
 
      swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
 
 
 
      i = (int) wcslen (temp_key); // get the key string length
 
      if (i < WSIZEOF (temp_key))
 
         length = i;
 
      else
 
         length = WSIZEOF (temp_key) - 1; // clamp it to a max value
 
 
 
      // for each character in the string after the section separator...
 
      for (i = (int) wcslen (section) + 1; i < length; i++)
 
         temp_key[i] = towlower (temp_key[i]); // convert it to lowercase
 
      temp_key[i] = 0; // terminate the string
 
   }
 
 
 
   // else it must be a section name
 
   else
 
      wcscpy_s (temp_key, WSIZEOF (temp_key), section); // copy the name into the key
 
 
 
   value = Dictionary_ReadKey ((dictionary_t *) ini_data, temp_key, default_value); // query the dictionary...
 
   return (value); // ...and return the value
 
}
 
 
 
 
 
void INIFile_WriteEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char value)
 
{
 
   // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
 
   // modified to contain the provided value. If it cannot be found, it is created.
 
 
 
   // according the boolean's value, write the equivalent string
 
   if (value)
 
      INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, L"true"); // write the new entry
 
   else
 
      INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, L"false"); // write the new entry
 
 
 
   return; // finished
 
}
 
 
 
 
 
void INIFile_WriteEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long value)
 
{
 
   // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
 
   // modified to contain the provided value. If it cannot be found, it is created.
 
 
 
   // build the long integer value equivalent string (use printf facility)
 
   swprintf_s (number_as_string, WSIZEOF (number_as_string), L"%ld", value);
 
 
 
   INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
 
   return; // finished
 
}
 
 
 
 
 
void INIFile_WriteEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long value)
 
{
 
   // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
 
   // modified to contain the provided value. If it cannot be found, it is created.
 
 
 
   // build the long integer value equivalent string (use printf facility)
 
   swprintf_s (number_as_string, WSIZEOF (number_as_string), L"%lu", value);
 
 
 
   INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
 
   return; // finished
 
}
 
 
 
 
 
void INIFile_WriteEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double value)
 
{
 
   // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
 
   // modified to contain the provided value. If it cannot be found, it is created.
 
 
 
   // build the long integer value equivalent string (use printf facility)
 
   swprintf_s (number_as_string, WSIZEOF (number_as_string), L"%g", value);
 
 
 
   INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
 
   return; // finished
 
}
 
 
 
 
 
void INIFile_WriteEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *value)
 
{
 
   // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
 
   // modified to contain the provided value. If it cannot be found, it is created.
 
 
 
   int length;
 
   int i;
 
 
 
   if (ini_data == NULL)
 
      return; // consistency check
 
 
 
   if (section == NULL)
 
      section = INIFile_default_section; // if no section was provided, use empty section name
 
 
 
   // if we were given an entry, build a key as section#entry
 
   if (entry != NULL)
 
   {
 
      Dictionary_WriteKey ((dictionary_t *) ini_data, section, NULL); // create the section if it doesn't exist
 
 
 
      swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
 
 
 
      i = (int) wcslen (temp_key); // get the key string length
 
      if (i < WSIZEOF (temp_key))
 
         length = i;
 
      else
 
         length = WSIZEOF (temp_key) - 1; // clamp it to a max value
 
 
 
      // for each character in the string after the section separator...
 
      for (i = (int) wcslen (section) + 1; i < length; i++)
 
         temp_key[i] = towlower (temp_key[i]); // convert it to lowercase
 
      temp_key[i] = 0; // terminate the string
 
   }
 
 
 
   // else it must be a section name
 
   else
 
      wcscpy_s (temp_key, WSIZEOF (temp_key), section); // copy the name into the key
 
 
 
   Dictionary_WriteKey ((dictionary_t *) ini_data, temp_key, value); // write the new key in the dictionary
 
   return; // finished
 
}
 
 
 
 
 
void INIFile_DeleteEntry (void *ini_data, wchar_t *section, wchar_t *entry)
 
{
 
   // deletes an entry in the dictionary
 
 
 
   int length;
 
   int i;
 
 
 
   if (ini_data == NULL)
 
      return; // consistency check
 
 
 
   if (section == NULL)
 
      section = INIFile_default_section; // if no section was provided, use empty section name
 
 
 
   // if we were given an entry, build a key as section#entry
 
   if (entry != NULL)
 
   {
 
      swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
 
 
 
      i = (int) wcslen (temp_key); // get the key string length
 
      if (i < WSIZEOF (temp_key))
 
         length = i;
 
      else
 
         length = WSIZEOF (temp_key) - 1; // clamp it to a max value
 
 
 
      // for each character in the string after the section separator...
 
      for (i = (int) wcslen (section) + 1; i < length; i++)
 
         temp_key[i] = towlower (temp_key[i]); // convert it to lowercase
 
      temp_key[i] = 0; // terminate the string
 
   }
 
 
 
   // else it must be a section name
 
   else
 
      wcscpy_s (temp_key, WSIZEOF (temp_key), section); // copy the name into the key
 
 
 
   Dictionary_DeleteKey ((dictionary_t *) ini_data, temp_key);
 
   return;
 
}
 
 
 
 
 
void INIFile_DeleteSection (void *ini_data, wchar_t *section)
 
{
 
   // deletes a whole INI section in the dictionary
 
 
 
   int length;
 
   int i;
 
   dictionary_t *dictionary;
 
 
 
   if (ini_data == NULL)
 
      return; // consistency check
 
 
 
   if (section == NULL)
 
      section = INIFile_default_section; // if no section was provided, use empty section name
 
 
 
   dictionary = (dictionary_t *) ini_data;
 
 
 
   swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c", section, INI_SECTION_SEPARATOR); // compose the key
 
   length = (int) wcslen (temp_key); // get the key string length
 
 
 
   // for each entry in the dictionary...
 
   for (i = 0; i < dictionary->entry_count; i++)
 
   {
 
      if (dictionary->entries[i].key[0] == 0)
 
         continue; // skip empty slots
 
 
 
      // does this entry belong to the section we want ?
 
      if (wcsncmp (dictionary->entries[i].key, temp_key, length) == 0)
 
         Dictionary_DeleteKey (dictionary, dictionary->entries[i].key); // yes, delete it
 
   }
 
 
 
   Dictionary_DeleteKey (dictionary, section); // and finally delete the section name itself
 
   return;
 
}
 
 
 
 
 
void *INIFile_LoadINIFile (const wchar_t *filename)
 
{
 
   // this is the parser for ini files. This function is called, providing the name of the file
 
   // to be read. It returns a dictionary object that should not be accessed directly, but
 
   // through accessor functions instead.
 
 
 
   FILE *fp;
 
   int fieldstart;
 
   int fieldstop;
 
   int length;
 
   int i;
 
   dictionary_t *dictionary;
 
 
 
   // try to open the INI file in ASCII read-only mode
 
   fp = _wfsopen (filename, L"r, ccs=UNICODE", _SH_DENYNO);
 
   if (fp == NULL)
 
      return (NULL); // cancel if file not found
 
 
 
   dictionary = Dictionary_CreateDictionary (0);
 
 
 
   // set the default section for orphaned entries and add it to the dictionary
 
   wcscpy_s (temp_section, WSIZEOF (temp_section), INIFile_default_section);
 
   INIFile_WriteEntryAsString (dictionary, temp_section, NULL, NULL);
 
 
 
   // read line per line...
 
   while (fgetws (line_buffer, WSIZEOF (line_buffer), fp) != NULL)
 
   {
 
      length = (int) wcslen (line_buffer); // get line length
 
 
 
      while ((length > 0) && ((line_buffer[length - 1] == '\n') || (line_buffer[length - 1] == '\r')))
 
         length--; // discard trailing newlines
 
 
 
      fieldstart = 0; // let's now strip leading blanks
 
      while ((fieldstart < length) && iswspace (line_buffer[fieldstart]))
 
         fieldstart++; // ignore any tabs or spaces, going forward from the start
 
 
 
      fieldstop = length - 1; // let's now strip trailing blanks
 
      while ((fieldstop >= 0) && iswspace (line_buffer[fieldstop]))
 
         fieldstop--; // ignore any tabs or spaces, going backwards from the end
 
 
 
      for (i = fieldstart; i <= fieldstop; i++)
 
         line_buffer[i - fieldstart] = line_buffer[i]; // recopy line buffer without the spaces
 
      line_buffer[i - fieldstart] = 0; // and terminate the string
 
 
 
      if ((line_buffer[0] == ';') || (line_buffer[0] == '#') || (line_buffer[0] == 0))
 
         continue; // skip comment lines
 
 
 
      // is it a valid section name ?
 
      if (swscanf_s (line_buffer, L"[%[^]]", temp_section, WSIZEOF (temp_section)) == 1)
 
      {
 
         length = (int) wcslen (temp_section); // get the section string length
 
 
 
         fieldstart = 0; // let's now strip leading blanks
 
         while ((fieldstart < length) && iswspace (temp_section[fieldstart]))
 
            fieldstart++; // ignore any tabs or spaces, going forward from the start
 
 
 
         fieldstop = length - 1; // let's now strip trailing blanks
 
         while ((fieldstop >= 0) && iswspace (temp_section[fieldstop]))
 
            fieldstop--; // ignore any tabs or spaces, going backwards from the end
 
 
 
         for (i = fieldstart; i <= fieldstop; i++)
 
            temp_section[i - fieldstart] = temp_section[i]; // recopy section name w/out spaces
 
         temp_section[i - fieldstart] = 0; // and terminate the string
 
 
 
         INIFile_WriteEntryAsString (dictionary, temp_section, NULL, NULL); // add to dictionary
 
      }
 
 
 
      // else is it a valid entry/value pair that is enclosed between quotes?
 
      else if (swscanf_s (line_buffer, L"%[^=] = \"%[^\"]\"", temp_entry, WSIZEOF (temp_entry), temp_value, WSIZEOF (temp_value)) == 2)
 
      {
 
         length = (int) wcslen (temp_entry); // get the entry string length
 
 
 
         fieldstart = 0; // let's now strip leading blanks
 
         while ((fieldstart < length) && iswspace (temp_entry[fieldstart]))
 
            fieldstart++; // ignore any tabs or spaces, going forward from the start
 
 
 
         fieldstop = length - 1; // let's now strip trailing blanks
 
         while ((fieldstop >= 0) && iswspace (temp_entry[fieldstop]))
 
            fieldstop--; // ignore any tabs or spaces, going backwards from the end
 
 
 
         for (i = fieldstart; i <= fieldstop; i++)
 
            temp_entry[i - fieldstart] = towlower (temp_entry[i]); // recopy entry name w/out spaces
 
         temp_entry[i - fieldstart] = 0; // and terminate the string
 
 
 
         // when value is enclosed between quotes, DO NOT strip the blanks
 
 
 
         // sscanf cannot handle "" or '' as empty value, this is done here
 
         if ((wcscmp (temp_value, L"\"\"") == 0) || (wcscmp (temp_value, L"''") == 0))
 
            temp_value[0] = 0; // empty string
 
 
 
         INIFile_WriteEntryAsString (dictionary, temp_section, temp_entry, temp_value); // add to dictionary
 
      }
 
 
 
      // else is it a valid entry/value pair without quotes ?
 
      else if ((swscanf_s (line_buffer, L"%[^=] = '%[^\']'", temp_entry, WSIZEOF (temp_entry), temp_value, WSIZEOF (temp_value)) == 2)
 
               || (swscanf_s (line_buffer, L"%[^=] = %[^;#]", temp_entry, WSIZEOF (temp_entry), temp_value, WSIZEOF (temp_value)) == 2))
 
      {
 
         length = (int) wcslen (temp_entry); // get the entry string length
 
 
 
         fieldstart = 0; // let's now strip leading blanks
 
         while ((fieldstart < length) && iswspace (temp_entry[fieldstart]))
 
            fieldstart++; // ignore any tabs or spaces, going forward from the start
 
 
 
         fieldstop = length - 1; // let's now strip trailing blanks
 
         while ((fieldstop >= 0) && iswspace (temp_entry[fieldstop]))
 
            fieldstop--; // ignore any tabs or spaces, going backwards from the end
 
 
 
         for (i = fieldstart; i <= fieldstop; i++)
 
            temp_entry[i - fieldstart] = towlower (temp_entry[i]); // recopy entry name w/out spaces
 
         temp_entry[i - fieldstart] = 0; // and terminate the string
 
 
 
         length = (int) wcslen (temp_value); // get the value string length
 
 
 
         fieldstart = 0; // let's now strip leading blanks
 
         while ((fieldstart < length) && iswspace (temp_value[fieldstart]))
 
            fieldstart++; // ignore any tabs or spaces, going forward from the start
 
 
 
         fieldstop = length - 1; // let's now strip trailing blanks
 
         while ((fieldstop >= 0) && iswspace (temp_value[fieldstop]))
 
            fieldstop--; // ignore any tabs or spaces, going backwards from the end
 
 
 
         for (i = fieldstart; i <= fieldstop; i++)
 
            temp_value[i - fieldstart] = temp_value[i]; // recopy entry name w/out spaces
 
         temp_value[i - fieldstart] = 0; // and terminate the string
 
 
 
         // sscanf cannot handle "" or '' as empty value, this is done here
 
         if ((wcscmp (temp_value, L"\"\"") == 0) || (wcscmp (temp_value, L"''") == 0))
 
            temp_value[0] = 0; // empty string
 
 
 
         INIFile_WriteEntryAsString (dictionary, temp_section, temp_entry, temp_value); // add to dictionary
 
      }
 
   }
 
 
 
   fclose (fp); // finished, close the file
 
 
 
   return ((void *) dictionary); // and return
 
}
 
 
 
 
 
unsigned char INIFile_SaveINIFile (const wchar_t *filename, void *ini_data)
 
{
 
   // this function dumps a given dictionary into a loadable ini file.
 
 
 
   FILE *fp;
 
   int section_index;
 
   wchar_t *section_name;
 
   int section_count;
 
   int entry_index;
 
   int length;
 
   dictionary_t *dictionary;
 
   dictionary_entry_t *entry;
 
 
 
   // try to open the INI file in ASCII write mode
 
   fp = _wfsopen (filename, L"w, ccs=UNICODE", _SH_DENYNO);
 
   if (fp == NULL)
 
      return (0); // cancel if unable to open file
 
 
 
   // get a hand on the INI data dictionary
 
   dictionary = (dictionary_t *) ini_data;
 
 
 
   // keep only the file name for the comment
 
   if (wcsrchr (filename, '/') != NULL)
 
      filename = wcsrchr (filename, '/') + 1;
 
   else if (wcsrchr (filename, '\\') != NULL)
 
      filename = wcsrchr (filename, '\\') + 1;
 
 
 
   // print the INI file name as a comment
 
   fwprintf (fp, L"# %s\n", filename);
 
 
 
   // get the number of sections there are in this INI dictionary
 
   section_count = INIFile_GetNumberOfSections (dictionary);
 
 
 
   // for each section...
 
   for (section_index = 0; section_index < section_count; section_index++)
 
   {
 
      section_name = INIFile_GetSectionName (dictionary, section_index); // read section name
 
 
 
      // is it the default section ?
 
      if (wcscmp (section_name, INIFile_default_section) == 0)
 
         fwprintf (fp, L"\n"); // don't put the default section's name in the INI file
 
      else
 
         fwprintf (fp, L"\n[%s]\n", section_name); // dump all other sections into the INI file
 
 
 
      // build the section identifier to be used when looking up the dictionary
 
      swprintf_s (temp_key, WSIZEOF (temp_key), L"%s%c", section_name, INI_SECTION_SEPARATOR);
 
      length = (int) wcslen (temp_key);
 
 
 
      // then for each entry in the dictionary...
 
      for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
 
      {
 
         entry = &dictionary->entries[entry_index]; // quick access to entry
 
 
 
         if (entry->key[0] == 0)
 
            continue; // skip empty slots
 
 
 
         // does this key belong to the section we want ? if so, dump it
 
         if (wcsncmp (entry->key, temp_key, length) == 0)
 
         {
 
            // if key starts or ends with a space, enclose it between quotes, else don't
 
            if ((entry->value[0] != 0)
 
                && (iswspace (entry->value[0]) || iswspace (entry->value[wcslen (entry->value) - 1])))
 
               fwprintf (fp, L"%s = \"%s\"\n", entry->key + length, entry->value);
 
            else
 
               fwprintf (fp, L"%s = %s\n", entry->key + length, entry->value);
 
         }
 
      }
 
   }
 
 
 
   fclose (fp); // finished, close the file
 
 
 
   return (1); // and return TRUE as the save occured successfully
 
}
 
 
 
 
 
void INIFile_FreeINIFile (void *ini_data)
 
{
 
   // this function frees the dictionary object used to store an INI file data
 
 
 
   if (ini_data == NULL)
 
      return; // consistency check
 
 
 
   Dictionary_DestroyDictionary ((dictionary_t *) ini_data); // just destroy the dictionary
 
   return; // finished
 
}