diff --git a/Jupiter/Config.cpp b/Jupiter/Config.cpp new file mode 100644 index 0000000..b8b6969 --- /dev/null +++ b/Jupiter/Config.cpp @@ -0,0 +1,145 @@ +/** + * Copyright (C) 2016 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include "Config.h" + +const Jupiter::Config o_null_section; + +const Jupiter::ReadableString &Jupiter::Config::get(const Jupiter::ReadableString &in_key, const Jupiter::ReadableString &in_default_value) const +{ + const Jupiter::ReadableString *result = m_table.get(in_key); + + if (result == nullptr) + return in_default_value; + + return *result; +} + +const Jupiter::ReadableString *Jupiter::Config::getValue(const Jupiter::ReadableString &in_key) const +{ + return m_table.get(in_key); +} + +Jupiter::Config *Jupiter::Config::getSection(const Jupiter::ReadableString &in_key) const +{ + return m_sections.get(in_key); +} + +Jupiter::Config &Jupiter::Config::getSectionReference(const Jupiter::ReadableString &in_key) +{ + Config *section = m_sections.get(in_key); + + if (section == nullptr) + { + m_sections.set(in_key); + section = m_sections.get(in_key); + section->m_name = in_key; + return *section; + } + + return *section; +} + +bool Jupiter::Config::set(const Jupiter::ReadableString &in_key, const Jupiter::ReadableString &in_value) +{ + return m_table.set(in_key, in_value); +} + +bool Jupiter::Config::remove(const Jupiter::ReadableString &in_key) +{ + return m_table.remove(in_key); +} + +const Jupiter::ReadableString &Jupiter::Config::getName() const +{ + return m_name; +} + +void Jupiter::Config::erase() +{ + m_table.erase(); + m_sections.erase(); +} + +bool Jupiter::Config::read(const char *in_filename) +{ + m_name = in_filename; + return this->read_internal(in_filename); +} + +bool Jupiter::Config::read(const Jupiter::ReadableString &in_filename) +{ + m_name = in_filename; + return this->read_internal(m_name.c_str()); +} + +bool Jupiter::Config::write() +{ + return this->write(m_name.c_str()); +} + +bool Jupiter::Config::write(const char *) +{ + return false; +} + +bool Jupiter::Config::write(const Jupiter::ReadableString &in_filename) +{ + return this->write(Jupiter::CStringS(in_filename).c_str()); +} + +bool Jupiter::Config::reload() +{ + this->erase(); + return this->read_internal(m_name.c_str()); +} + +bool Jupiter::Config::reload(const char *in_filename) +{ + this->erase(); + return this->read(in_filename); +} + +bool Jupiter::Config::reload(const Jupiter::ReadableString &in_filename) +{ + this->erase(); + return this->read(in_filename); +} + +/** Operators */ +Jupiter::Config &Jupiter::Config::operator[](const Jupiter::ReadableString &in_key) +{ + return this->getSectionReference(in_key); +} + +/** Private functions */ + +bool Jupiter::Config::read_internal(const char *) +{ + return false; +} + +void Jupiter::Config::Buffer::set_length(size_t in_length) +{ + this->length = in_length; +} + +char *Jupiter::Config::Buffer::get_str() const +{ + return this->str; +} diff --git a/Jupiter/Config.h b/Jupiter/Config.h new file mode 100644 index 0000000..48357dd --- /dev/null +++ b/Jupiter/Config.h @@ -0,0 +1,253 @@ +/** + * Copyright (C) 2016 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#if !defined JUPITER_CONFIG_H_HEADER +#define JUPITER_CONFIG_H_HEADER + +/** + * @file Config.h + * @brief Defines the Config class, which provides common functionality for Config files in differing formats. + */ + +#include "Jupiter.h" +#include "Hash_Table.h" +#include "Reference_String.h" +#include "CString.h" + +/** DLL Linkage Nagging */ +#if defined _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4251) +#endif + +namespace Jupiter +{ + /** + * @brief Base class for all Config type files + */ + class JUPITER_API Config + { + public: + /** Hash_Table type for sections */ + typedef Hash_Table SectionHashTable; + + /** + * @brief Fetches the value of an entry. + * + * @param in_key Key of the entry to fetch + * @param in_default_value Value to return if no such entry exists + * @return Value of the entry if it exists, an empty string otherwise. + */ + const Jupiter::ReadableString &get(const Jupiter::ReadableString &in_key, const Jupiter::ReadableString &in_default_value = Jupiter::ReferenceString::empty) const; + + /** + * @brief Fetches the value of an entry + * + * @param in_key Key of the entry to fetch + * @return Pointer to the value of the entry on success, nullptr otherwise + */ + const Jupiter::ReadableString *getValue(const Jupiter::ReadableString &in_key) const; + + /** + * @brief Fetches the value of an entry and interprets it as another type. + * + * @param T Type to interpret the value as + * + * @param in_key Key of the entry to fetch + * @param in_default_value Value to return if no such entry exists + * @return Value of the entry if it exists, 0 otherwise. + */ + template T get(const Jupiter::ReadableString &in_key, T in_default_value = 0); + + /** + * @brief Fetches a section based on its name + * + * @param in_key Name of the section to fetch + * @return Pointer to a section if it exists, nullptr otherwise + */ + Config *getSection(const Jupiter::ReadableString &in_key) const; + + /** + * @brief Fetches a section based on its name + * Note: This will create new sections as necessary if they do not exist + * + * @param in_key Name of the section to fetch + * @return Reference to the section + */ + Config &getSectionReference(const Jupiter::ReadableString &in_key); + + /** + * @brief Sets an entry's value in the table + * + * @param in_key Key of the entry to write to + * @param in_value Value to write to the entry + * @return True if a new entry was added, false if an entry was overwritten + */ + bool set(const Jupiter::ReadableString &in_key, const Jupiter::ReadableString &in_value); + + /** + * @brief Removes an entry from the table + * + * @param in_key Key of the entry to remove + * @return True if an entry was removed, false otherwise + */ + bool remove(const Jupiter::ReadableString &in_key); + + /** + * @brief Fetches the name of this config section + * Note: This is the filename on the root node + * + * @return Name of this section + */ + const Jupiter::ReadableString &getName() const; + + /** + * @brief Erases all data from this section + */ + void erase(); + + /** + * @brief Reads config data from a file and stores it in this config + * + * @param in_filename Name of the file to read from + * @return True on success, false otherwise + */ + bool read(const char *in_filename); + + /** + * @brief Reads config data from a file and stores it in this config + * + * @param in_filename Name of the file to read from + * @return True on success, false otherwise + */ + bool read(const Jupiter::ReadableString &in_filename); + + /** + * @brief Writes config data to the last read file + * + * @return True on success, false otherwise + */ + bool write(); + + /** + * @brief Writes config data to a file + * + * @param in_filename Name of the file to write to + * @return True on success, false otherwise + */ + virtual bool write(const char *in_filename); + + /** + * @brief Writes config data to a file + * + * @param in_filename Name of the file to write to + * @return True on success, false otherwise + */ + bool write(const Jupiter::ReadableString &in_filename); + + /** + * @brief Empties config data from memory and reads from the last read file + * + * @return True on success, false otherwise + */ + bool reload(); + + /** + * @brief Empties config data from memory and reads from a file + * + * @param in_filename Name of the file to read from + * @return True on success, false otherwise + */ + bool reload(const char *in_filename); + + /** + * @brief Empties config data from memory and reads from a file + * + * @param in_filename Name of the file to read from + * @return True on success, false otherwise + */ + bool reload(const Jupiter::ReadableString &in_filename); + + /** + * @brief Fetches a reference to this config's entry table + * + * @return Reference to m_table + */ + inline const HashTable &getTable() const { return m_table; } + + /** + * @brief Fetches a reference to this config's subsections + * + * @return Reference to m_sections + */ + inline const SectionHashTable getSections() const { return m_sections; } + + /** Subscript operator */ + Config &operator[](const Jupiter::ReadableString &in_key); + + /** Used for low-level string operations */ + class Buffer : public Jupiter::StringL + { + public: + /** + * @brief Sets the length of the string + * + * @param in_length Length of the string + */ + void set_length(size_t in_length); + + /** + * @brief Returns a pointer to the underlying string + * + * @return Pointer to an array of characters + */ + char *get_str() const; + }; + + protected: + virtual bool read_internal(const char *in_filename); + + /** Name of this Config section. This is empty or the filename at the root level. */ + Jupiter::CStringS m_name; + + /** Table of entries within this section */ + HashTable m_table; + + /** Table of sections within this section */ + SectionHashTable m_sections; + }; +} + +/** Template function implementations */ + +template inline T Jupiter::Config::get(const Jupiter::ReadableString &in_key, T in_default_value) +{ + const Jupiter::ReadableString *result = m_table.get(in_key); + + if (result == nullptr) + return in_default_value; + + return static_cast(*result); +} + +/** Re-enable warnings */ +#if defined _MSC_VER +#pragma warning(pop) +#endif + +#endif // JUPITER_CONFIG_H_HEADER \ No newline at end of file diff --git a/Jupiter/Hash_Table.h b/Jupiter/Hash_Table.h index 4ec2776..81fb230 100644 --- a/Jupiter/Hash_Table.h +++ b/Jupiter/Hash_Table.h @@ -59,6 +59,7 @@ namespace Jupiter KeyT key; ValueT value; + Entry(const InKeyT &in_key); Entry(const InKeyT &in_key, const InValueT &in_value); }; @@ -78,6 +79,7 @@ namespace Jupiter * @return True if a new entry was added, false if an entry was overwritten */ bool set(const InKeyT &in_key, const InValueT &in_value); + bool set(const InKeyT &in_key); /** * @brief Removes an entry from the bucket @@ -164,12 +166,13 @@ namespace Jupiter * @return True if a new entry was added, false if an entry was overwritten */ bool set(const InKeyT &in_key, const InValueT &in_value); + bool set(const InKeyT &in_key); /** - * @brief Removes an entry from the table and returns its value + * @brief Removes an entry from the table * * @param in_key Key of the entry to remove - * @return Value of the entry that was removed if it exists, nullptr otherwise + * @return True if an entry was removed, false otherwise */ bool remove(const InKeyT &in_key); diff --git a/Jupiter/Hash_Table_Imp.h b/Jupiter/Hash_Table_Imp.h index 49ce408..4676538 100644 --- a/Jupiter/Hash_Table_Imp.h +++ b/Jupiter/Hash_Table_Imp.h @@ -53,6 +53,12 @@ template inline size_t Jupiter::default_hash_function(const T &in) /** Hash_Table::Bucket::Entry */ +template +Jupiter::Hash_Table::Bucket::Entry::Entry(const InKeyT &in_key) +{ + key = in_key; +} + template Jupiter::Hash_Table::Bucket::Entry::Entry(const InKeyT &in_key, const InValueT &in_value) { @@ -86,6 +92,17 @@ bool Jupiter::Hash_Table::Bucket::set(con return true; } +template +bool Jupiter::Hash_Table::Bucket::set(const InKeyT &in_key) +{ + for (Jupiter::SLList::Node *node = m_entries.getHead(); node != nullptr; node = node->next) + if (node->data->key == in_key) + return false; + + m_entries.add(new Entry(in_key)); + return true; +} + template bool Jupiter::Hash_Table::Bucket::remove(const InKeyT &in_key) { @@ -196,6 +213,20 @@ bool Jupiter::Hash_Table::set(const InKey return false; } +template +bool Jupiter::Hash_Table::set(const InKeyT &in_key) +{ + if (m_buckets[HashF(in_key) % m_buckets_size].set(in_key)) + { + if (++m_length == m_buckets_size) + expand(); + + return true; + } + + return false; +} + template bool Jupiter::Hash_Table::remove(const InKeyT &in_key) { diff --git a/Jupiter/INIConfig.cpp b/Jupiter/INIConfig.cpp new file mode 100644 index 0000000..96fc8a8 --- /dev/null +++ b/Jupiter/INIConfig.cpp @@ -0,0 +1,285 @@ +/** + * Copyright (C) 2016 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include +#include +#include "INIConfig.h" +#include "Socket.h" + +void Jupiter::INIConfig::write_helper(FILE *in_file, const Jupiter::Config *in_section, size_t in_depth) +{ + size_t index; + + if (in_depth != 0) + { + // Write header + + fputs("\r\n", in_file); + + // Tabs + for (index = 1; index < in_depth; ++index) + fputc('\t', in_file); + + for (index = 0; index != in_depth; ++index) + fputc('[', in_file); + + in_section->getName().print(in_file); + + for (index = 0; index != in_depth; ++index) + fputc(']', in_file); + + fputs("\r\n", in_file); + } + + // Write table entries + { + auto bucket_itr = in_section->getTable().begin(); + auto bucket_end = in_section->getTable().end(); + Jupiter::SLList::Node *entry_itr; + + while (bucket_itr != bucket_end) + { + for (entry_itr = bucket_itr->m_entries.getHead(); entry_itr != nullptr; entry_itr = entry_itr->next) + { + // Tabs + for (index = 1; index < in_depth; ++index) + fputc('\t', in_file); + + // Write entry + entry_itr->data->key.print(in_file); + fputs(" = ", in_file); + entry_itr->data->value.println(in_file); + } + + ++bucket_itr; + } + } + + // Write subsections + { + auto bucket_itr = in_section->getSections().begin(); + auto bucket_end = in_section->getSections().end(); + Jupiter::SLList::Node *entry_itr; + + while (bucket_itr != bucket_end) + { + for (entry_itr = bucket_itr->m_entries.getHead(); entry_itr != nullptr; entry_itr = entry_itr->next) + write_helper(in_file, &entry_itr->data->value, in_depth + 1); + + ++bucket_itr; + } + } +} + +bool Jupiter::INIConfig::write(const char *in_filename) +{ + // Open file + FILE *file = fopen(in_filename, "wb"); + + if (file == nullptr) + return false; + + // Iterate through table and sections + write_helper(file, this, 0); + + // File writing completed; close and return + fclose(file); + return true; +} + +bool Jupiter::INIConfig::read_internal(const char *in_filename) +{ + Jupiter::ReferenceString line; + std::stack section_stack; + + section_stack.push(this); + + auto process_line = [&line, §ion_stack]() + { + const char *itr = line.ptr(); + const char *end = itr + line.size(); // guaranteed to be greater than itr + + // Shift to right of spaces + + while (isspace(*itr)) + if (++itr == end) + return; // Line is purely whitespace + + line.shiftRight(itr - line.ptr()); + + if (*itr == ';') + return; // Comment + + if (*itr == '[') + { + // Parse header + + size_t depth = 1; + + for (++itr; itr != end && *itr == '['; ++itr) + ++depth; + + line.shiftRight(itr - line.ptr()); + + while (end != itr) + { + --end; + if (*end != ']' && isspace(*end) == 0) + { + ++end; + break; + } + } + + line.set(itr, end - itr); + + // Add section to stack; pop sections or push blanks as necessary + + while (depth < section_stack.size()) + section_stack.pop(); + + while (depth > section_stack.size()) + section_stack.push(std::addressof(section_stack.top()->getSectionReference(Jupiter::ReferenceString::empty))); + + section_stack.push(§ion_stack.top()->getSectionReference(line)); + } + else + { + // Truncate spaces + --end; + + while (isspace(*end)) + --end; // don't need a safety check since we know there is at least 1 non-space character + // end now points to a non-space character within the bounds + + ++end; + line.truncate(itr + line.size() - end); + + // Parse key (can be empty) + + while (*itr != '=') + if (++itr == end) + return; // Error: no assignment exists; ignore line + + Jupiter::ReferenceString key; + + if (itr != line.ptr()) + { + // Truncate spaces from key; a non-space character is guaranteed + end = itr - 1; + + while (isspace(*end)) + --end; + + key = line.substring(size_t{ 0 }, end + 1 - line.ptr()); + + end = line.ptr() + line.size(); + } + + // Parse value (can be empty) + + if (++itr != end) + { + // Shift to right of spaces; a non-space character is guaranteed + while (isspace(*itr)) + ++itr; + + line.shiftRight(itr - line.ptr()); + } + else + line = Jupiter::ReferenceString::empty; + + // Add entry to current table on stack + section_stack.top()->set(key, line); + } + }; + + constexpr size_t READ_CHUNK_SIZE = 1024; + size_t read_count = READ_CHUNK_SIZE; + Buffer buffer; + char *itr; + char *end; + + // Open file + FILE *file = fopen(in_filename, "rb"); + + if (file == nullptr) + return false; + + buffer.setBufferSize(READ_CHUNK_SIZE * 2); + + // Parse file contents + while (read_count == READ_CHUNK_SIZE) + { + // Ensure the buffer has at least READ_CHUNK_SIZE space remaining + buffer.setBufferSize(buffer.size() + READ_CHUNK_SIZE); + + // Read data from file to end of buffer + itr = buffer.get_str(); + read_count = fread(itr + buffer.size(), sizeof(char), READ_CHUNK_SIZE, file); + buffer.set_length(buffer.size() + read_count); + end = itr + buffer.size(); + + // Reset line + line.set(buffer.ptr(), 0); + + // Parse buffer for lines + while (itr != end) + { + // Check if the line is over + if (*itr == '\n' || *itr == '\r') + { + // Process line + line.set(buffer.ptr(), itr - buffer.ptr()); + if (line.isNotEmpty()) + process_line(); + + // Keep iterating until next non-newline character + while (true) + { + ++itr; + + if (itr == end) + { + // No data remains to be parsed in buffer; erase buffer and break + buffer.erase(); + break; + } + + if (*itr != '\n' && *itr != '\r') + { + // Shift buffer and break + buffer.shiftRight(itr - buffer.ptr()); + break; + } + } + } + else + ++itr; + } + } + + // Process data remaining in buffer as a line + line.set(buffer.ptr(), buffer.size()); + if (line.isNotEmpty()) + process_line(); + + // File has been successfully read, or an error occurred. + fclose(file); + return true; +} diff --git a/Jupiter/INIConfig.h b/Jupiter/INIConfig.h new file mode 100644 index 0000000..56b8eac --- /dev/null +++ b/Jupiter/INIConfig.h @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2016 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#if !defined JUPITER_INICONFIG_H_HEADER +#define JUPITER_INICONFIG_H_HEADER + +/** + * @file INIConfig.h + * @brief Provides an INI config implementation for the Config interface. + */ + +#include "Config.h" + +/** DLL Linkage Nagging */ +#if defined _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4251) +#endif + +namespace Jupiter +{ + /** + * @brief INI-based Config class + */ + class JUPITER_API INIConfig : public Jupiter::Config + { + public: + /** + * @brief Writes config data to a file + * + * @param in_filename Name of the file to write to + * @return True on success, false otherwise + */ + bool write(const char *in_filename); + + private: + bool read_internal(const char *in_filename); + void write_helper(FILE *in_file, const Jupiter::Config *in_section, size_t in_depth); + }; +} + +/** Re-enable warnings */ +#if defined _MSC_VER +#pragma warning(pop) +#endif + +#endif // JUPITER_INICONFIG_H_HEADER \ No newline at end of file diff --git a/Jupiter/Jupiter.h b/Jupiter/Jupiter.h index 4a2bb51..f1dba2b 100644 --- a/Jupiter/Jupiter.h +++ b/Jupiter/Jupiter.h @@ -36,7 +36,7 @@ #define JUPITER_API #endif // _WIN32 -#define JUPITER_VERSION "Jupiter 0.18.1" /** Version of this program at compile time. */ +#define JUPITER_VERSION "Jupiter 0.19-dev" /** Version of this program at compile time. */ #if defined __cplusplus extern "C" diff --git a/Jupiter/Jupiter.vcxproj b/Jupiter/Jupiter.vcxproj index e50b556..8f3f76b 100644 --- a/Jupiter/Jupiter.vcxproj +++ b/Jupiter/Jupiter.vcxproj @@ -179,6 +179,7 @@ + @@ -198,11 +199,13 @@ + + @@ -247,6 +250,7 @@ + diff --git a/Jupiter/Jupiter.vcxproj.filters b/Jupiter/Jupiter.vcxproj.filters index a69808f..629c77f 100644 --- a/Jupiter/Jupiter.vcxproj.filters +++ b/Jupiter/Jupiter.vcxproj.filters @@ -58,6 +58,12 @@ {7db21243-8b23-4dd2-9888-0bdd3c5a1edd} + + {da033f3a-f5fd-4592-900f-f17bb3520547} + + + {b5b148d5-c636-41eb-838a-7555114e1589} + @@ -126,6 +132,12 @@ Source Files + + Source Files\Files\Configs + + + Source Files\Files\Configs + @@ -269,6 +281,12 @@ Header Files\Hash + + Header Files\Files\Configs + + + Header Files\Files\Configs +