Subversion Repositories Games.Chess Giants

Rev

Rev 161 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. // inifile.c
  2.  
  3. #include "common.h"
  4.  
  5.  
  6. // WARNING: INI SECTION NAMES ARE CASE SENSITIVE, BUT KEY NAMES ARE NOT!
  7.  
  8.  
  9. // internal definitions
  10. #define DICTIONARY_PAGE_SIZE 128
  11. #define STRING_MAXSIZE 65536
  12. #define INI_SECTION_SEPARATOR ']'
  13. #define INI_INVALID_KEY (wchar_t *) 0x7fffffff
  14. #define WSIZEOF(a) (sizeof (a) / sizeof (wchar_t))
  15.  
  16.  
  17. // dictionary structure definitions
  18. typedef struct dictionary_entry_s
  19. {
  20.    wchar_t *key; // key string (mallocated)
  21.    unsigned long hash; // key hash value
  22.    wchar_t *value; // value string (mallocated)
  23. } dictionary_entry_t;
  24.  
  25.  
  26. typedef struct dictionary_s
  27. {
  28.    int size; // storage size
  29.    dictionary_entry_t *entries; // array of entries (mallocated)
  30.    int entry_count; // number of entries in dictionary
  31. } dictionary_t;
  32.  
  33.  
  34. // dictionary function prototypes
  35. static dictionary_t *Dictionary_CreateDictionary (int size);
  36. static void Dictionary_DestroyDictionary (dictionary_t *dictionary);
  37. static dictionary_entry_t *Dictionary_GetKey (dictionary_t *dictionary, wchar_t *key);
  38. static wchar_t *Dictionary_ReadKey (dictionary_t *dictionary, wchar_t *key, wchar_t *default_value);
  39. static void Dictionary_WriteKey (dictionary_t *dictionary, wchar_t *key, wchar_t *value);
  40. static void Dictionary_DeleteKey (dictionary_t *dictionary, wchar_t *key);
  41. static unsigned long Dictionary_ComputeHashValueForKey (wchar_t *key);
  42.  
  43.  
  44. // ini file function prototypes
  45. void *INIFile_NewINIFile (void);
  46. int INIFile_GetNumberOfSections (void *ini_data);
  47. wchar_t *INIFile_GetSectionName (void *ini_data, int section_index);
  48. int INIFile_GetNumberOfEntries (void *ini_data, wchar_t *section);
  49. wchar_t *INIFile_GetEntryName (void *ini_data, wchar_t *section, int entry_index);
  50. unsigned char INIFile_ReadEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char default_value);
  51. long INIFile_ReadEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long default_value);
  52. unsigned long INIFile_ReadEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long default_value);
  53. double INIFile_ReadEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double default_value);
  54. wchar_t *INIFile_ReadEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *default_value);
  55. void INIFile_WriteEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char value);
  56. void INIFile_WriteEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long value);
  57. void INIFile_WriteEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long value);
  58. void INIFile_WriteEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double value);
  59. void INIFile_WriteEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *value);
  60. void INIFile_DeleteEntry (void *ini_data, wchar_t *section, wchar_t *entry);
  61. void INIFile_DeleteSection (void *ini_data, wchar_t *section);
  62. void *INIFile_LoadINIFile (const wchar_t *filename);
  63. unsigned char INIFile_SaveINIFile (const wchar_t *filename, void *ini_data);
  64. void INIFile_FreeINIFile (void *ini_data);
  65.  
  66.  
  67. // internal variables
  68. static wchar_t *INIFile_default_section = L"__default"; // MUST BE A READ-WRITE VARIABLE AND NOT A DEFINE
  69.  
  70.  
  71. static dictionary_t *Dictionary_CreateDictionary (int size)
  72. {
  73.    // this function allocates a new dictionary object of given size and returns it. If you do
  74.    // not know in advance (roughly) the number of entries in the dictionary, give size = 0.
  75.  
  76.    dictionary_t *dictionary;
  77.  
  78.    // if no size was specified, allocate space for one page
  79.    if (size < DICTIONARY_PAGE_SIZE)
  80.       size = DICTIONARY_PAGE_SIZE;
  81.  
  82.    // allocate one instance of the dictionary and blank it out
  83.    dictionary = (dictionary_t *) SAFE_malloc (1, sizeof (dictionary_t), true);
  84.    dictionary->size = size; // dictionary can currently hold size entries
  85.  
  86.    // allocate space for the dictionary entries and zero all the crap out
  87.    dictionary->entries = (dictionary_entry_t *) SAFE_malloc (size, sizeof (dictionary_entry_t), true);
  88.  
  89.    return (dictionary); // finished, return a pointer to the dictionary object
  90. }
  91.  
  92.  
  93. static void Dictionary_DestroyDictionary (dictionary_t *dictionary)
  94. {
  95.    // frees a dictionary object and all memory associated to it.
  96.  
  97.    int entry_index;
  98.  
  99.    if (dictionary == NULL)
  100.       return; // consistency check
  101.  
  102.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  103.    {
  104.       SAFE_free ((void **) &dictionary->entries[entry_index].key); // free all dictionary keys
  105.       SAFE_free ((void **) &dictionary->entries[entry_index].value); // free all dictionary values
  106.    }
  107.  
  108.    SAFE_free ((void **) &dictionary->entries); // free the entries array
  109.    SAFE_free ((void **) &dictionary); // and finally, free the dictionary itself
  110.  
  111.    return; // finished
  112. }
  113.  
  114.  
  115. static dictionary_entry_t *Dictionary_GetKey (dictionary_t *dictionary, wchar_t *key)
  116. {
  117.    // this function locates a key in a dictionary and returns a pointer to it, or a NULL
  118.    // pointer if no such key can be found in dictionary. The returned pointer points to data
  119.    // internal to the dictionary object, do not try to free or modify it.
  120.  
  121.    unsigned long hash;
  122.    register int entry_index;
  123.  
  124.    hash = Dictionary_ComputeHashValueForKey (key); // get the hash value for key
  125.  
  126.    // cycle through all entries in the dictionary
  127.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  128.    {
  129.       if (dictionary->entries[entry_index].key[0] == 0)
  130.          continue; // skip empty slots
  131.  
  132.       // compare hash AND string, to avoid hash collisions
  133.       if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
  134.          return (&dictionary->entries[entry_index]); // return a pointer to the key if we find it
  135.    }
  136.  
  137.    return (NULL); // else return a NULL pointer
  138. }
  139.  
  140.  
  141. static wchar_t *Dictionary_ReadKey (dictionary_t *dictionary, wchar_t *key, wchar_t *default_value)
  142. {
  143.    // this function locates a key in a dictionary and returns a pointer to its value, or the
  144.    // passed 'def' pointer if no such key can be found in dictionary. The returned char pointer
  145.    // points to data internal to the dictionary object, do not try to free or modify it.
  146.  
  147.    dictionary_entry_t *element;
  148.  
  149.    element = Dictionary_GetKey (dictionary, key); // query the dictionary for the key
  150.    if (element != NULL)
  151.       return (element->value); // if we found a valid key, return the value associed to it
  152.  
  153.    return (default_value); // else return the default value
  154. }
  155.  
  156.  
  157. static void Dictionary_WriteKey (dictionary_t *dictionary, wchar_t *key, wchar_t *value)
  158. {
  159.    // sets a value in a dictionary. If the given key is found in the dictionary, the associated
  160.    // value is replaced by the provided one. If the key cannot be found in the dictionary, it
  161.    // is added to it.
  162.  
  163.    unsigned long hash;
  164.    register int entry_index;
  165.    size_t bufsize;
  166.  
  167.    hash = Dictionary_ComputeHashValueForKey (key); // compute hash for this key
  168.  
  169.    // for each entry in the dictionary...
  170.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  171.    {
  172.       if (dictionary->entries[entry_index].key[0] == 0)
  173.           continue; // skip empty slots
  174.  
  175.       // does that key have the same hash value AND the same name ?
  176.       if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
  177.       {
  178.          // we've found the right key: modify its value and return
  179.  
  180.          if (value == NULL)
  181.             value = L""; // if a null value was specified, use an empty string
  182.  
  183.          bufsize = wcslen (value) + 1;
  184.          dictionary->entries[entry_index].value = (wchar_t *) SAFE_realloc (dictionary->entries[entry_index].value, 0, bufsize, sizeof (wchar_t), false);
  185.          wcscpy_s (dictionary->entries[entry_index].value, bufsize, value); // copy the value string
  186.  
  187.          return; // value has been modified: return
  188.       }
  189.    }
  190.  
  191.    // here either the dictionary was empty, or we couldn't find a similar value: add a new one
  192.  
  193.    // cycle through all entries in the dictionary...
  194.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  195.       if (dictionary->entries[entry_index].key[0] == 0)
  196.          break; // and stop at the first empty one we find
  197.  
  198.    // no empty entry found, see if dictionary needs to grow or if there's still room left
  199.    if (entry_index == dictionary->size)
  200.    {
  201.       // mallocate some more space for the dictionary and zero all that crap out
  202.       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
  203.       dictionary->size += DICTIONARY_PAGE_SIZE; // increase dictionary size
  204.    }
  205.  
  206.    // copy the new key
  207.    bufsize = wcslen (key) + 1;
  208.    dictionary->entries[entry_index].key = (wchar_t *) SAFE_malloc (bufsize, sizeof (wchar_t), false);
  209.    wcscpy_s (dictionary->entries[entry_index].key, bufsize, key); // copy the key string...
  210.    dictionary->entries[entry_index].hash = hash; // ...and store the hash value
  211.  
  212.    // copy the new value
  213.    if (value == NULL)
  214.       value = L""; // if a null value was specified, use an empty string
  215.    bufsize = wcslen (value) + 1;
  216.    dictionary->entries[entry_index].value = (wchar_t *) SAFE_malloc (bufsize, sizeof (wchar_t), false);
  217.    wcscpy_s (dictionary->entries[entry_index].value, bufsize, value); // copy the value string
  218.  
  219.    dictionary->entry_count++; // there is one more entry in the dictionary now
  220.    return;
  221. }
  222.  
  223.  
  224. static void Dictionary_DeleteKey (dictionary_t *dictionary, wchar_t *key)
  225. {
  226.    // this function deletes a key in a dictionary. Nothing is done if the key cannot be found.
  227.  
  228.    unsigned long hash;
  229.    register int entry_index;
  230.  
  231.    hash = Dictionary_ComputeHashValueForKey (key); // get the hash value for key
  232.  
  233.    // cycle through all entries in the dictionary...
  234.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  235.    {
  236.       if (dictionary->entries[entry_index].key[0] == 0)
  237.          continue; // skip empty slots
  238.  
  239.       // compare hash AND string, to avoid hash collisions
  240.       if ((hash == dictionary->entries[entry_index].hash) && (wcscmp (key, dictionary->entries[entry_index].key) == 0))
  241.          break; // break as soon as we've found the key we want
  242.    }
  243.  
  244.    if (entry_index == dictionary->entry_count)
  245.       return; // if key was not found, just return
  246.  
  247.    // free its buffers, then clear the key, the hash and its value data
  248.    SAFE_free ((void **) &dictionary->entries[entry_index].key);
  249.    SAFE_free ((void **) &dictionary->entries[entry_index].value);
  250.    memset (&dictionary->entries[entry_index], 0, sizeof (dictionary_entry_t));
  251.  
  252.    dictionary->entry_count--; // there is one entry less in the dictionary now
  253.    return;
  254. }
  255.  
  256.  
  257. static unsigned long Dictionary_ComputeHashValueForKey (wchar_t *key)
  258. {
  259.    // Compute the hash key for a string. This hash function has been taken from an article in
  260.    // Dr Dobbs Journal. This is normally a collision-free function, distributing keys evenly.
  261.    // The key is stored anyway in the struct so that collision can be avoided by comparing the
  262.    // key itself in last resort. THIS FUNCTION IS TO BE USED INTERNALLY ONLY!
  263.  
  264.    register unsigned long hash;
  265.    int length;
  266.    int char_index;
  267.  
  268.    hash = 0;
  269.    length = (int) wcslen (key);
  270.  
  271.    // for each character of the string...
  272.    for (char_index = 0; char_index < length; char_index++)
  273.    {
  274.       hash += (unsigned long) key[char_index]; // take it in account and compute the hash value
  275.       hash += (hash << 10);
  276.       hash ^= (hash >> 6);
  277.    }
  278.  
  279.    hash += (hash << 3); // finalize hashing (what the hell does it do, I have no clue)
  280.    hash ^= (hash >> 11);
  281.    hash += (hash << 15);
  282.  
  283.    return (hash); // and return the hash value
  284. }
  285.  
  286.  
  287. void *INIFile_NewINIFile (void)
  288. {
  289.    // allocates a dictionary for a new INI file. Mostly a helper function.
  290.  
  291.    return ((void *) Dictionary_CreateDictionary (0));
  292. }
  293.  
  294.  
  295. int INIFile_GetNumberOfSections (void  *ini_data)
  296. {
  297.    // this function returns the number of sections found in a dictionary. The test to recognize
  298.    // sections is done on the string stored in the dictionary: a section name is given as
  299.    // "section" whereas a key is stored as "section]key", thus the test looks for entries that
  300.    // do NOT contain a ']'. This clearly fails in the case a section name contains a ']',
  301.    // but this should simply be avoided.
  302.  
  303.    int section_count;
  304.    int entry_index;
  305.    dictionary_t *dictionary;
  306.  
  307.    if (ini_data == NULL)
  308.       return (0); // consistency check
  309.  
  310.    dictionary = (dictionary_t *) ini_data;
  311.  
  312.    section_count = 0; // no sections found yet
  313.  
  314.    // for each entry in the dictionary...
  315.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  316.    {
  317.       if (dictionary->entries[entry_index].key[0] == 0)
  318.          continue; // skip empty slots
  319.  
  320.       if (wcschr (dictionary->entries[entry_index].key, INI_SECTION_SEPARATOR) == NULL)
  321.          section_count++; // if this entry has NO section separator, then it's a section name
  322.    }
  323.  
  324.    return (section_count); // return the section count we found
  325. }
  326.  
  327.  
  328. wchar_t *INIFile_GetSectionName (void *ini_data, int section_index)
  329. {
  330.    // this function locates the n-th section in a dictionary and returns its name as a pointer
  331.    // to a string allocated inside the dictionary. Do not free or modify the returned string!!
  332.    // This function returns NULL in case of error.
  333.  
  334.    int sections_found;
  335.    int entry_index;
  336.    dictionary_t *dictionary;
  337.  
  338.    if ((ini_data == NULL) || (section_index < 0))
  339.       return (NULL); // consistency check
  340.  
  341.    dictionary = (dictionary_t *) ini_data;
  342.  
  343.    sections_found = 0; // no sections found yet
  344.  
  345.    // for each entry in the dictionary...
  346.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  347.    {
  348.       if (dictionary->entries[entry_index].key[0] == 0)
  349.          continue; // skip empty slots
  350.  
  351.       // is this entry a section name ?
  352.       if (wcschr (dictionary->entries[entry_index].key, INI_SECTION_SEPARATOR) == NULL)
  353.       {
  354.          // is it the section we want ?
  355.          if (sections_found == section_index)
  356.             return (dictionary->entries[entry_index].key); // then return its name
  357.  
  358.          sections_found++; // we found one section more
  359.       }
  360.    }
  361.  
  362.    return (INIFile_default_section); // section was not found, return the default section name
  363. }
  364.  
  365.  
  366. int INIFile_GetNumberOfEntries (void  *ini_data, wchar_t *section)
  367. {
  368.    // this function returns the number of entries found in a dictionary within a given section.
  369.    // The test to recognize section entries is done on the string stored in the dictionary: a
  370.    // section name is given as "section" whereas a key is stored as "section]key", thus the test
  371.    // looks for all entries that begin with "section]".
  372.  
  373.    int entry_count;
  374.    int entry_index;
  375.    int sectionname_length;
  376.    dictionary_t *dictionary;
  377.  
  378.    if (ini_data == NULL)
  379.       return (0); // consistency check
  380.  
  381.    dictionary = (dictionary_t *) ini_data;
  382.    sectionname_length = wcslen (section);
  383.  
  384.    entry_count = 0; // no entries found yet
  385.  
  386.    // for each entry in the dictionary...
  387.    for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  388.    {
  389.       if (dictionary->entries[entry_index].key[0] == 0)
  390.          continue; // skip empty slots
  391.  
  392.       if ((wcsncmp (dictionary->entries[entry_index].key, section, sectionname_length) == 0)
  393.           && (dictionary->entries[entry_index].key[sectionname_length] == INI_SECTION_SEPARATOR))
  394.          entry_count++; // if this entry has section name + separator, then it's one of those we want
  395.    }
  396.  
  397.    return (entry_count); // return the number of entries we found in the given section
  398. }
  399.  
  400.  
  401. wchar_t *INIFile_GetEntryName (void *ini_data, wchar_t *section, int entry_index)
  402. {
  403.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  404.    // "section]key". If the key cannot be found, NULL is returned. The returned char pointer
  405.    // points to a string allocated in the dictionary, do not free or modify it.
  406.  
  407.    int i;
  408.    int length;
  409.    int found_keys;
  410.    dictionary_t *dictionary;
  411.    dictionary_entry_t *entry;
  412.  
  413.    if (ini_data == NULL)
  414.       return (NULL); // consistency check
  415.  
  416.    if (section == NULL)
  417.       section = INIFile_default_section; // if no section was provided, use empty section name
  418.  
  419.    dictionary = (dictionary_t *) ini_data; // quick access to dictionary
  420.  
  421.    length = wcslen (section);
  422.  
  423.    // cycle through all remaining entries
  424.    found_keys = 0;
  425.    for (i = 0; i < dictionary->entry_count; i++)
  426.    {
  427.       entry = &dictionary->entries[i]; // quick access to each entry
  428.  
  429.       if ((wcsncmp (entry->key, section, length) != 0) || (entry->key[length] != INI_SECTION_SEPARATOR))
  430.          continue; // if this key doesn't belong to the wanted section, skip it
  431.  
  432.       // this key belongs to the wanted section. Is it the key we want ?
  433.       if (found_keys == entry_index)
  434.          return (&entry->key[length + 1]); // if so, return its name
  435.  
  436.       found_keys++; // it wasn't the key we want, so increase counter and proceed to the next one
  437.    }
  438.  
  439.    return (NULL); // if not found, return NULL
  440. }
  441.  
  442.  
  443. unsigned char INIFile_ReadEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char default_value)
  444. {
  445.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  446.    // "section]key". If the key cannot be found, the notfound value is returned. A true boolean
  447.    // is found if a string starting with either 'y', 'Y', 't', 'T' or '1' is matched. A false
  448.    // boolean is found if a string starting with 'n', 'N', 'f', 'F' or '0' is matched.
  449.  
  450.    wchar_t *value;
  451.  
  452.    if (ini_data == NULL)
  453.       return (default_value); // consistency check
  454.  
  455.    // get the value as a string first
  456.    value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
  457.  
  458.    if (value == INI_INVALID_KEY)
  459.       return (default_value); // if the entry could not be read, return the default value
  460.  
  461.    // decide of the boolean's value by looking at the first character
  462.    if ((toupper (value[0]) == 'T') || (toupper (value[0]) == 'Y') || (value[0] == '1'))
  463.      return (1); // boolean value is TRUE
  464.    else if ((toupper (value[0]) == 'F') || (toupper (value[0]) == 'N') || (value[0] == '0'))
  465.      return (0); // boolean value is FALSE
  466.  
  467.    return (default_value); // boolean value is undefined, return default value
  468. }
  469.  
  470.  
  471. long INIFile_ReadEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long default_value)
  472. {
  473.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  474.    // "section]key". If the key cannot be found, the notfound value is returned.
  475.  
  476.    wchar_t *value;
  477.  
  478.    if (ini_data == NULL)
  479.       return (default_value); // consistency check
  480.  
  481.    // get the value as a string first
  482.    value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
  483.  
  484.    if (value == INI_INVALID_KEY)
  485.       return (default_value); // if the entry could not be read, return the default value
  486.  
  487.    return (_wtol (value)); // else convert the value to a long integer and return it
  488. }
  489.  
  490.  
  491. unsigned long INIFile_ReadEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long default_value)
  492. {
  493.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  494.    // "section]key". If the key cannot be found, the notfound value is returned.
  495.  
  496.    wchar_t *value;
  497.  
  498.    if (ini_data == NULL)
  499.       return (default_value); // consistency check
  500.  
  501.    // get the value as a string first
  502.    value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
  503.  
  504.    if (value == INI_INVALID_KEY)
  505.       return (default_value); // if the entry could not be read, return the default value
  506.  
  507.    return (wcstoul (value, NULL, 10)); // else convert the value to a long integer and return it
  508. }
  509.  
  510.  
  511. double INIFile_ReadEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double default_value)
  512. {
  513.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  514.    // "section]key". If the key cannot be found, the notfound value is returned.
  515.  
  516.    wchar_t *value;
  517.  
  518.    if (ini_data == NULL)
  519.       return (default_value); // consistency check
  520.  
  521.    // get the value as a string first
  522.    value = INIFile_ReadEntryAsString ((dictionary_t *) ini_data, section, entry, INI_INVALID_KEY);
  523.  
  524.    if (value == INI_INVALID_KEY)
  525.       return (default_value); // if the entry could not be read, return the default value
  526.  
  527.    return (_wtof (value)); // else convert the value to a double precision number and return it
  528. }
  529.  
  530.  
  531. wchar_t *INIFile_ReadEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *default_value)
  532. {
  533.    // this function queries a dictionary for a key. A key as read from an ini file is given as
  534.    // "section]key". If the key cannot be found, the pointer passed as 'def' is returned. The
  535.    // returned char pointer points to a string allocated in the dictionary, do not free or
  536.    // modify it.
  537.  
  538.    size_t length;
  539.    size_t i;
  540.    size_t keysize;
  541.    wchar_t *key;
  542.    wchar_t *value;
  543.  
  544.    if (ini_data == NULL)
  545.       return (default_value); // consistency check
  546.  
  547.    if (section == NULL)
  548.       section = INIFile_default_section; // if no section was provided, use empty section name
  549.  
  550.    // allocate some space for the dictionary key name
  551.    keysize = wcslen (section) + 1 + wcslen (entry) + 1;
  552.    key = (wchar_t *) SAFE_malloc (keysize, sizeof (wchar_t), false);
  553.  
  554.    // if we were given an entry, build a key as section]entry
  555.    if (entry != NULL)
  556.    {
  557.       swprintf_s (key, keysize, L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
  558.  
  559.       i = wcslen (key); // get the key string length
  560.       if (i < keysize)
  561.          length = i;
  562.       else
  563.          length = keysize - 1; // clamp it to a max value
  564.  
  565.       // for each character in the string after the section separator...
  566.       for (i = wcslen (section) + 1; i < length; i++)
  567.          key[i] = towlower (key[i]); // convert it to lowercase
  568.       key[i] = 0; // terminate the string
  569.    }
  570.  
  571.    // else it must be a section name
  572.    else
  573.       wcscpy_s (key, keysize, section); // copy the name into the key
  574.  
  575.    value = Dictionary_ReadKey ((dictionary_t *) ini_data, key, default_value); // query the dictionary...
  576.    SAFE_free ((void **) &key); // free the key name, we no longer need it
  577.    return (value); // ...and return the value
  578. }
  579.  
  580.  
  581. void INIFile_WriteEntryAsBool (void *ini_data, wchar_t *section, wchar_t *entry, unsigned char value)
  582. {
  583.    // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
  584.    // modified to contain the provided value. If it cannot be found, it is created.
  585.  
  586.    // according the boolean's value, write the equivalent string
  587.    if (value)
  588.       INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, L"true"); // write the new entry
  589.    else
  590.       INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, L"false"); // write the new entry
  591.  
  592.    return; // finished
  593. }
  594.  
  595.  
  596. void INIFile_WriteEntryAsLong (void *ini_data, wchar_t *section, wchar_t *entry, long value)
  597. {
  598.    // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
  599.    // modified to contain the provided value. If it cannot be found, it is created.
  600.  
  601.    wchar_t *number_as_string;
  602.  
  603.    // allocate some space for the number's string representation
  604.    number_as_string = (wchar_t *) SAFE_malloc (32, sizeof (wchar_t), false);
  605.  
  606.    // build the long integer value equivalent string (use printf facility)
  607.    swprintf_s (number_as_string, 32, L"%ld", value);
  608.    INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
  609.  
  610.    SAFE_free ((void **) &number_as_string); // free the number's string representation, we no longer need it
  611.    return; // finished
  612. }
  613.  
  614.  
  615. void INIFile_WriteEntryAsUnsignedLong (void *ini_data, wchar_t *section, wchar_t *entry, unsigned long value)
  616. {
  617.    // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
  618.    // modified to contain the provided value. If it cannot be found, it is created.
  619.  
  620.    wchar_t *number_as_string;
  621.  
  622.    // allocate some space for the number's string representation
  623.    number_as_string = (wchar_t *) SAFE_malloc (32, sizeof (wchar_t), false);
  624.  
  625.    // build the long integer value equivalent string (use printf facility)
  626.    swprintf_s (number_as_string, 32, L"%lu", value);
  627.    INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
  628.  
  629.    SAFE_free ((void **) &number_as_string); // free the number's string representation, we no longer need it
  630.    return; // finished
  631. }
  632.  
  633.  
  634. void INIFile_WriteEntryAsDouble (void *ini_data, wchar_t *section, wchar_t *entry, double value)
  635. {
  636.    // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
  637.    // modified to contain the provided value. If it cannot be found, it is created.
  638.  
  639.    wchar_t *number_as_string;
  640.  
  641.    // allocate some space for the number's string representation
  642.    number_as_string = (wchar_t *) SAFE_malloc (64, sizeof (wchar_t), false);
  643.  
  644.    // build the long integer value equivalent string (use printf facility)
  645.    swprintf_s (number_as_string, 64, L"%g", value);
  646.    INIFile_WriteEntryAsString ((dictionary_t *) ini_data, section, entry, number_as_string); // write the new entry
  647.  
  648.    SAFE_free ((void **) &number_as_string); // free the number's string representation, we no longer need it
  649.    return; // finished
  650. }
  651.  
  652.  
  653. void INIFile_WriteEntryAsString (void *ini_data, wchar_t *section, wchar_t *entry, wchar_t *value)
  654. {
  655.    // sets an entry in a dictionary. If the given entry can be found in the dictionary, it is
  656.    // modified to contain the provided value. If it cannot be found, it is created.
  657.  
  658.    size_t length;
  659.    size_t i;
  660.    size_t keysize;
  661.    wchar_t *key;
  662.  
  663.    if (ini_data == NULL)
  664.       return; // consistency check
  665.  
  666.    if (section == NULL)
  667.       section = INIFile_default_section; // if no section was provided, use empty section name
  668.  
  669.    // allocate some space for the dictionary key name
  670.    keysize = wcslen (section) + 1 + (entry != NULL ? wcslen (entry) + 1 : 0);
  671.    key = (wchar_t *) SAFE_malloc (keysize, sizeof (wchar_t), false);
  672.  
  673.    // if we were given an entry, build a key as section#entry
  674.    if (entry != NULL)
  675.    {
  676.       Dictionary_WriteKey ((dictionary_t *) ini_data, section, NULL); // create the section if it doesn't exist
  677.  
  678.       swprintf_s (key, keysize, L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
  679.  
  680.       i = wcslen (key); // get the key string length
  681.       if (i < keysize)
  682.          length = i;
  683.       else
  684.          length = keysize - 1; // clamp it to a max value
  685.  
  686.       // for each character in the string after the section separator...
  687.       for (i = wcslen (section) + 1; i < length; i++)
  688.          key[i] = towlower (key[i]); // convert it to lowercase
  689.       key[i] = 0; // terminate the string
  690.    }
  691.  
  692.    // else it must be a section name
  693.    else
  694.       wcscpy_s (key, keysize, section); // copy the name into the key
  695.  
  696.    Dictionary_WriteKey ((dictionary_t *) ini_data, key, value); // write the new key in the dictionary
  697.    SAFE_free ((void **) &key); // free the key name, we no longer need it
  698.    return; // finished
  699. }
  700.  
  701.  
  702. void INIFile_DeleteEntry (void *ini_data, wchar_t *section, wchar_t *entry)
  703. {
  704.    // deletes an entry in the dictionary
  705.  
  706.    size_t length;
  707.    size_t i;
  708.    size_t keysize;
  709.    wchar_t *key;
  710.  
  711.    if (ini_data == NULL)
  712.       return; // consistency check
  713.  
  714.    if (section == NULL)
  715.       section = INIFile_default_section; // if no section was provided, use empty section name
  716.  
  717.    // allocate some space for the dictionary key name
  718.    keysize = wcslen (section) + 1 + wcslen (entry) + 1;
  719.    key = (wchar_t *) SAFE_malloc (keysize, sizeof (wchar_t), false);
  720.  
  721.    // if we were given an entry, build a key as section#entry
  722.    if (entry != NULL)
  723.    {
  724.       swprintf_s (key, keysize, L"%s%c%s", section, INI_SECTION_SEPARATOR, entry); // compose the key
  725.  
  726.       i = wcslen (key); // get the key string length
  727.       if (i < keysize)
  728.          length = i;
  729.       else
  730.          length = keysize - 1; // clamp it to a max value
  731.  
  732.       // for each character in the string after the section separator...
  733.       for (i = wcslen (section) + 1; i < length; i++)
  734.          key[i] = towlower (key[i]); // convert it to lowercase
  735.       key[i] = 0; // terminate the string
  736.    }
  737.  
  738.    // else it must be a section name
  739.    else
  740.       wcscpy_s (key, keysize, section); // copy the name into the key
  741.  
  742.    Dictionary_DeleteKey ((dictionary_t *) ini_data, key);
  743.    SAFE_free ((void **) &key); // free the key name, we no longer need it
  744.    return;
  745. }
  746.  
  747.  
  748. void INIFile_DeleteSection (void *ini_data, wchar_t *section)
  749. {
  750.    // deletes a whole INI section in the dictionary
  751.  
  752.    int length;
  753.    int i;
  754.    dictionary_t *dictionary;
  755.    dictionary_entry_t *entry;
  756.  
  757.    if (ini_data == NULL)
  758.       return; // consistency check
  759.  
  760.    if (section == NULL)
  761.       section = INIFile_default_section; // if no section was provided, use empty section name
  762.  
  763.    dictionary = (dictionary_t *) ini_data;
  764.  
  765.    length = (int) wcslen (section); // get the section string length
  766.  
  767.    // for each entry in the dictionary...
  768.    for (i = 0; i < dictionary->entry_count; i++)
  769.    {
  770.       entry = &dictionary->entries[i]; // quick access to each entry
  771.  
  772.       if (entry->key[0] == 0)
  773.          continue; // skip empty slots
  774.  
  775.       // does this entry belong to the section we want ?
  776.       if ((wcsncmp (entry->key, section, length) == 0) && (entry->key[length] == INI_SECTION_SEPARATOR))
  777.          Dictionary_DeleteKey (dictionary, entry->key); // yes, delete it
  778.    }
  779.  
  780.    Dictionary_DeleteKey (dictionary, section); // and finally delete the section name itself
  781.    return;
  782. }
  783.  
  784.  
  785. void *INIFile_LoadINIFile (const wchar_t *filename)
  786. {
  787.    // this is the parser for ini files. This function is called, providing the name of the file
  788.    // to be read. It returns a dictionary object that should not be accessed directly, but
  789.    // through accessor functions instead.
  790.  
  791.    wchar_t *line_buffer;
  792.    FILE *fp;
  793.    int fieldstart;
  794.    int fieldstop;
  795.    int length;
  796.    int i;
  797.    dictionary_t *dictionary;
  798.    wchar_t *current_section;
  799.    wchar_t *current_entry;
  800.    wchar_t *current_value;
  801.  
  802.    // try to open the INI file in ASCII read-only mode
  803.    fp = _wfsopen (filename, L"r, ccs=UNICODE", _SH_DENYNO);
  804.    if (fp == NULL)
  805.       return (NULL); // cancel if file not found
  806.  
  807.    dictionary = Dictionary_CreateDictionary (0);
  808.  
  809.    // allocate some space for the current section, entry and values
  810.    current_section = (wchar_t *) SAFE_malloc (STRING_MAXSIZE, sizeof (wchar_t), false);
  811.    current_entry = (wchar_t *) SAFE_malloc (STRING_MAXSIZE, sizeof (wchar_t), false);
  812.    current_value = (wchar_t *) SAFE_malloc (STRING_MAXSIZE, sizeof (wchar_t), false);
  813.  
  814.    // set the default section for orphaned entries and add it to the dictionary
  815.    wcscpy_s (current_section, STRING_MAXSIZE, INIFile_default_section);
  816.    INIFile_WriteEntryAsString (dictionary, current_section, NULL, NULL);
  817.  
  818.    // allocate some space for a line buffer
  819.    line_buffer = (wchar_t *) SAFE_malloc (STRING_MAXSIZE + STRING_MAXSIZE, sizeof (wchar_t), false);
  820.  
  821.    // read line per line...
  822.    while (fgetws (line_buffer, STRING_MAXSIZE + STRING_MAXSIZE, fp) != NULL)
  823.    {
  824.       length = (int) wcslen (line_buffer); // get line length
  825.  
  826.       while ((length > 0) && ((line_buffer[length - 1] == '\n') || (line_buffer[length - 1] == '\r')))
  827.          length--; // discard trailing newlines
  828.  
  829.       fieldstart = 0; // let's now strip leading blanks
  830.       while ((fieldstart < length) && iswspace (line_buffer[fieldstart]))
  831.          fieldstart++; // ignore any tabs or spaces, going forward from the start
  832.  
  833.       fieldstop = length - 1; // let's now strip trailing blanks
  834.       while ((fieldstop >= 0) && iswspace (line_buffer[fieldstop]))
  835.          fieldstop--; // ignore any tabs or spaces, going backwards from the end
  836.  
  837.       for (i = fieldstart; i <= fieldstop; i++)
  838.          line_buffer[i - fieldstart] = line_buffer[i]; // recopy line buffer without the spaces
  839.       line_buffer[i - fieldstart] = 0; // and terminate the string
  840.  
  841.       if ((line_buffer[0] == ';') || (line_buffer[0] == '#') || (line_buffer[0] == 0))
  842.          continue; // skip comment lines
  843.  
  844.       // is it a valid section name ?
  845.       if (swscanf_s (line_buffer, L"[%[^]]", current_section, STRING_MAXSIZE) == 1)
  846.       {
  847.          length = (int) wcslen (current_section); // get the section string length
  848.  
  849.          fieldstart = 0; // let's now strip leading blanks
  850.          while ((fieldstart < length) && iswspace (current_section[fieldstart]))
  851.             fieldstart++; // ignore any tabs or spaces, going forward from the start
  852.  
  853.          fieldstop = length - 1; // let's now strip trailing blanks
  854.          while ((fieldstop >= 0) && iswspace (current_section[fieldstop]))
  855.             fieldstop--; // ignore any tabs or spaces, going backwards from the end
  856.  
  857.          for (i = fieldstart; i <= fieldstop; i++)
  858.             current_section[i - fieldstart] = current_section[i]; // recopy section name w/out spaces
  859.          current_section[i - fieldstart] = 0; // and terminate the string
  860.  
  861.          INIFile_WriteEntryAsString (dictionary, current_section, NULL, NULL); // add to dictionary
  862.       }
  863.  
  864.       // else is it a valid entry/value pair whose entry is enclosed between quotes?
  865.       else if ((swscanf_s (line_buffer, L"\"%[^\"]\" = %[^;#]", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
  866.                || (swscanf_s (line_buffer, L"'%[^']' = %[^;#]", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2))
  867.       {
  868.          // when entry is enclosed between quotes, DO NOT strip the blanks
  869.  
  870.          length = (int) wcslen (current_value); // get the value string length
  871.  
  872.          fieldstart = 0; // let's now strip leading blanks
  873.          while ((fieldstart < length) && iswspace (current_value[fieldstart]))
  874.             fieldstart++; // ignore any tabs or spaces, going forward from the start
  875.  
  876.          fieldstop = length - 1; // let's now strip trailing blanks
  877.          while ((fieldstop >= 0) && iswspace (current_value[fieldstop]))
  878.             fieldstop--; // ignore any tabs or spaces, going backwards from the end
  879.  
  880.          for (i = fieldstart; i <= fieldstop; i++)
  881.             current_value[i - fieldstart] = current_value[i]; // recopy entry name w/out spaces
  882.          current_value[i - fieldstart] = 0; // and terminate the string
  883.  
  884.          // sscanf cannot handle "" or '' as empty value, this is done here
  885.          if ((wcscmp (current_value, L"\"\"") == 0) || (wcscmp (current_value, L"''") == 0))
  886.             current_value[0] = 0; // empty string
  887.  
  888.          INIFile_WriteEntryAsString (dictionary, current_section, current_entry, current_value); // add to dictionary
  889.       }
  890.  
  891.       // else is it a valid entry/value pair whose entry AND value are enclosed between quotes?
  892.       else if ((swscanf_s (line_buffer, L"\"%[^\"]\" = \"%[^\"]\"", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
  893.                || (swscanf_s (line_buffer, L"'%[^']' = \"%[^\"]\"", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
  894.                || (swscanf_s (line_buffer, L"\"%[^\"]\" = '%[^']'", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
  895.                || (swscanf_s (line_buffer, L"'%[^']' = '%[^']'", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2))
  896.       {
  897.          // when entry is enclosed between quotes, DO NOT strip the blanks
  898.  
  899.          // when value is enclosed between quotes, DO NOT strip the blanks
  900.  
  901.          // sscanf cannot handle "" or '' as empty value, this is done here
  902.          if ((wcscmp (current_value, L"\"\"") == 0) || (wcscmp (current_value, L"''") == 0))
  903.             current_value[0] = 0; // empty string
  904.  
  905.          INIFile_WriteEntryAsString (dictionary, current_section, current_entry, current_value); // add to dictionary
  906.       }
  907.  
  908.       // else is it a valid entry/value pair whose value is enclosed between quotes?
  909.       else if ((swscanf_s (line_buffer, L"%[^=] = \"%[^\"]\"", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
  910.                || (swscanf_s (line_buffer, L"%[^=] = '%[^']'", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2))
  911.       {
  912.          length = (int) wcslen (current_entry); // get the entry string length
  913.  
  914.          fieldstart = 0; // let's now strip leading blanks
  915.          while ((fieldstart < length) && iswspace (current_entry[fieldstart]))
  916.             fieldstart++; // ignore any tabs or spaces, going forward from the start
  917.  
  918.          fieldstop = length - 1; // let's now strip trailing blanks
  919.          while ((fieldstop >= 0) && iswspace (current_entry[fieldstop]))
  920.             fieldstop--; // ignore any tabs or spaces, going backwards from the end
  921.  
  922.          for (i = fieldstart; i <= fieldstop; i++)
  923.             current_entry[i - fieldstart] = towlower (current_entry[i]); // recopy entry name w/out spaces
  924.          current_entry[i - fieldstart] = 0; // and terminate the string
  925.  
  926.          // when value is enclosed between quotes, DO NOT strip the blanks
  927.  
  928.          // sscanf cannot handle "" or '' as empty value, this is done here
  929.          if ((wcscmp (current_value, L"\"\"") == 0) || (wcscmp (current_value, L"''") == 0))
  930.             current_value[0] = 0; // empty string
  931.  
  932.          INIFile_WriteEntryAsString (dictionary, current_section, current_entry, current_value); // add to dictionary
  933.       }
  934.  
  935.       // else is it a valid entry/value pair without quotes ?
  936.       else if (swscanf_s (line_buffer, L"%[^=] = %[^;#]", current_entry, STRING_MAXSIZE, current_value, STRING_MAXSIZE) == 2)
  937.       {
  938.          length = (int) wcslen (current_entry); // get the entry string length
  939.  
  940.          fieldstart = 0; // let's now strip leading blanks
  941.          while ((fieldstart < length) && iswspace (current_entry[fieldstart]))
  942.             fieldstart++; // ignore any tabs or spaces, going forward from the start
  943.  
  944.          fieldstop = length - 1; // let's now strip trailing blanks
  945.          while ((fieldstop >= 0) && iswspace (current_entry[fieldstop]))
  946.             fieldstop--; // ignore any tabs or spaces, going backwards from the end
  947.  
  948.          for (i = fieldstart; i <= fieldstop; i++)
  949.             current_entry[i - fieldstart] = towlower (current_entry[i]); // recopy entry name w/out spaces
  950.          current_entry[i - fieldstart] = 0; // and terminate the string
  951.  
  952.          length = (int) wcslen (current_value); // get the value string length
  953.  
  954.          fieldstart = 0; // let's now strip leading blanks
  955.          while ((fieldstart < length) && iswspace (current_value[fieldstart]))
  956.             fieldstart++; // ignore any tabs or spaces, going forward from the start
  957.  
  958.          fieldstop = length - 1; // let's now strip trailing blanks
  959.          while ((fieldstop >= 0) && iswspace (current_value[fieldstop]))
  960.             fieldstop--; // ignore any tabs or spaces, going backwards from the end
  961.  
  962.          for (i = fieldstart; i <= fieldstop; i++)
  963.             current_value[i - fieldstart] = current_value[i]; // recopy entry name w/out spaces
  964.          current_value[i - fieldstart] = 0; // and terminate the string
  965.  
  966.          // sscanf cannot handle "" or '' as empty value, this is done here
  967.          if ((wcscmp (current_value, L"\"\"") == 0) || (wcscmp (current_value, L"''") == 0))
  968.             current_value[0] = 0; // empty string
  969.  
  970.          INIFile_WriteEntryAsString (dictionary, current_section, current_entry, current_value); // add to dictionary
  971.       }
  972.    }
  973.  
  974.    fclose (fp); // finished, close the file
  975.    SAFE_free ((void **) &line_buffer); // free the line buffer we allocated at the beginning of the function
  976.    SAFE_free ((void **) &current_value); // free the current value buffer we allocated at the beginning of the function
  977.    SAFE_free ((void **) &current_entry); // free the current entry buffer we allocated at the beginning of the function
  978.    SAFE_free ((void **) &current_section); // free the current section buffer we allocated at the beginning of the function
  979.  
  980.    return ((void *) dictionary); // and return a pointer to it
  981. }
  982.  
  983.  
  984. unsigned char INIFile_SaveINIFile (const wchar_t *filename, void *ini_data)
  985. {
  986.    // this function dumps a given dictionary into a loadable ini file.
  987.  
  988.    FILE *fp;
  989.    int section_index;
  990.    wchar_t *section_name;
  991.    int section_count;
  992.    int entry_index;
  993.    int length;
  994.    dictionary_t *dictionary;
  995.    dictionary_entry_t *entry;
  996.  
  997.    // try to open the INI file in ASCII write mode
  998.    fp = _wfsopen (filename, L"w, ccs=UNICODE", _SH_DENYNO);
  999.    if (fp == NULL)
  1000.       return (0); // cancel if unable to open file
  1001.  
  1002.    // get a hand on the INI data dictionary
  1003.    dictionary = (dictionary_t *) ini_data;
  1004.  
  1005.    // keep only the file name for the comment
  1006.    if (wcsrchr (filename, '/') != NULL)
  1007.       filename = wcsrchr (filename, '/') + 1;
  1008.    else if (wcsrchr (filename, '\\') != NULL)
  1009.       filename = wcsrchr (filename, '\\') + 1;
  1010.  
  1011.    // print the INI file name as a comment
  1012.    fwprintf (fp, L"# %s\n", filename);
  1013.  
  1014.    // get the number of sections there are in this INI dictionary
  1015.    section_count = INIFile_GetNumberOfSections (dictionary);
  1016.  
  1017.    // for each section...
  1018.    for (section_index = 0; section_index < section_count; section_index++)
  1019.    {
  1020.       section_name = INIFile_GetSectionName (dictionary, section_index); // read section name
  1021.  
  1022.       // is it the default section ?
  1023.       if (wcscmp (section_name, INIFile_default_section) == 0)
  1024.          fwprintf (fp, L"\n"); // don't put the default section's name in the INI file
  1025.       else
  1026.          fwprintf (fp, L"\n[%s]\n", section_name); // dump all other sections into the INI file
  1027.  
  1028.       length = (int) wcslen (section_name);
  1029.  
  1030.       // then for each entry in the dictionary...
  1031.       for (entry_index = 0; entry_index < dictionary->entry_count; entry_index++)
  1032.       {
  1033.          entry = &dictionary->entries[entry_index]; // quick access to entry
  1034.  
  1035.          if (entry->key[0] == 0)
  1036.             continue; // skip empty slots
  1037.  
  1038.          // does this key belong to the section we want ? if so, dump it
  1039.          if ((wcsncmp (entry->key, section_name, length) == 0) && (entry->key[length] == INI_SECTION_SEPARATOR))
  1040.          {
  1041.             // if value starts or ends with a space, enclose it between quotes, else don't
  1042.             if ((entry->value[0] != 0)
  1043.                 && (iswspace (entry->value[0]) || iswspace (entry->value[wcslen (entry->value) - 1])))
  1044.                fwprintf (fp, L"%s = \"%s\"\n", &entry->key[length + 1], entry->value);
  1045.             else
  1046.                fwprintf (fp, L"%s = %s\n", &entry->key[length + 1], entry->value);
  1047.          }
  1048.       }
  1049.    }
  1050.  
  1051.    fclose (fp); // finished, close the file
  1052.  
  1053.    return (1); // and return TRUE as the save occured successfully
  1054. }
  1055.  
  1056.  
  1057. void INIFile_FreeINIFile (void *ini_data)
  1058. {
  1059.    // this function frees the dictionary object used to store an INI file data
  1060.  
  1061.    if (ini_data == NULL)
  1062.       return; // consistency check
  1063.  
  1064.    Dictionary_DestroyDictionary ((dictionary_t *) ini_data); // just destroy the dictionary
  1065.    return; // finished
  1066. }
  1067.