diff --git a/autogen/convert_constants.py b/autogen/convert_constants.py
index 73b9ed176..4ab580d9e 100644
--- a/autogen/convert_constants.py
+++ b/autogen/convert_constants.py
@@ -234,7 +234,7 @@ def build_files(processed_files):
def build_to_c(built_files):
txt = ''
- with open(get_path(in_filename), 'r') as f:
+ with open(get_path(in_filename), 'r', newline='\n') as f:
txt = f.read()
txt += '\n' + built_files
@@ -327,7 +327,7 @@ def def_constant(processed_constant):
def build_to_def(processed_files):
s = '-- AUTOGENERATED FOR CODE EDITORS --\n\n'
- with open(get_path(in_filename), 'r') as f:
+ with open(get_path(in_filename), 'r', newline='\n') as f:
s += f.read()
s += '\n'
@@ -347,15 +347,15 @@ def main():
built_c = build_to_c(built_files)
- with open(get_path(out_filename), 'w') as out:
+ with open(get_path(out_filename), 'w', newline='\n') as out:
out.write(built_c)
doc = doc_files(processed_files)
- with open(get_path(out_filename_docs), 'w') as out:
+ with open(get_path(out_filename_docs), 'w', newline='\n') as out:
out.write(doc)
defs = build_to_def(processed_files)
- with open(get_path(out_filename_defs), 'w') as out:
+ with open(get_path(out_filename_defs), 'w', newline='\n') as out:
out.write(defs)
global totalConstants
diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py
index b0a14bdbd..6f52f5353 100644
--- a/autogen/convert_functions.py
+++ b/autogen/convert_functions.py
@@ -52,6 +52,7 @@ in_files = [
"src/game/object_list_processor.h",
"src/game/behavior_actions.h",
"src/game/mario_misc.h",
+ "src/pc/mods/mod_storage.h"
"src/pc/utils/misc.h",
"src/game/level_update.h"
]
@@ -820,7 +821,7 @@ def doc_files(processed_files):
buffer = buffer.replace('$[FUNCTION_NAV_HERE', function_nav)
- with open(get_path(out_filename_docs % page_name), 'w') as out:
+ with open(get_path(out_filename_docs % page_name), 'w', newline='\n') as out:
out.write(buffer)
############################################################################
@@ -869,7 +870,7 @@ def def_files(processed_files):
for def_pointer in def_pointers:
s += '--- @class %s\n' % def_pointer
- with open(get_path(out_filename_defs), 'w') as out:
+ with open(get_path(out_filename_defs), 'w', newline='\n') as out:
out.write(s)
############################################################################
@@ -888,7 +889,7 @@ def main():
.replace("$[BINDS]", built_binds) \
.replace("$[INCLUDES]", built_includes)
- with open(filename, 'w') as out:
+ with open(filename, 'w', newline='\n') as out:
out.write(gen)
print('REJECTS:\n%s' % rejects)
diff --git a/autogen/convert_structs.py b/autogen/convert_structs.py
index bb15a278d..397c150ca 100644
--- a/autogen/convert_structs.py
+++ b/autogen/convert_structs.py
@@ -424,7 +424,7 @@ def doc_structs(structs):
continue
s += doc_struct(struct) + '\n'
- with open(get_path(out_filename_docs), 'w') as out:
+ with open(get_path(out_filename_docs), 'w', newline='\n') as out:
out.write(s)
############################################################################
@@ -472,7 +472,7 @@ def def_structs(structs):
for def_pointer in def_pointers:
s += '--- @class %s\n' % def_pointer
- with open(get_path(out_filename_defs), 'w') as out:
+ with open(get_path(out_filename_defs), 'w', newline='\n') as out:
out.write(s)
############################################################################
@@ -494,11 +494,11 @@ def build_files():
built_include = build_includes()
out_c_filename = get_path(out_filename_c)
- with open(out_c_filename, 'w') as out:
+ with open(out_c_filename, 'w', newline='\n') as out:
out.write(c_template.replace("$[BODY]", built_body).replace('$[INCLUDES]', built_include))
out_h_filename = get_path(out_filename_h)
- with open(out_h_filename, 'w') as out:
+ with open(out_h_filename, 'w', newline='\n') as out:
out.write(h_template.replace("$[BODY]", built_enum))
doc_structs(parsed)
diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua
index d3c5c6fd6..c671421e8 100644
--- a/autogen/lua_definitions/functions.lua
+++ b/autogen/lua_definitions/functions.lua
@@ -5211,6 +5211,19 @@ function vec3f_set_dist_and_angle(from, to, dist, pitch, yaw)
-- ...
end
+--- @param key string
+--- @return string
+function mod_storage_load(key)
+ -- ...
+end
+
+--- @param key string
+--- @param value string
+--- @return boolean
+function mod_storage_save(key, value)
+ -- ...
+end
+
--- @return nil
function update_all_mario_stars()
-- ...
@@ -5276,6 +5289,12 @@ function network_player_set_description(np, description, r, g, b, a)
-- ...
end
+--- @param localIndex integer
+--- @return string
+function network_discord_id_from_local_index(localIndex)
+ -- ...
+end
+
--- @param localIndex integer
--- @return string
function network_get_player_text_color_string(localIndex)
diff --git a/docs/lua/functions-3.md b/docs/lua/functions-3.md
index ff2bc0339..ae9b6faeb 100644
--- a/docs/lua/functions-3.md
+++ b/docs/lua/functions-3.md
@@ -7095,25 +7095,10 @@
----
-# functions from misc.h
-## [update_all_mario_stars](#update_all_mario_stars)
-
-### Lua Example
-`update_all_mario_stars()`
-
-### Parameters
-- None
-
-### Returns
-- None
-
-### C Prototype
-`void update_all_mario_stars(void);`
[:arrow_up_small:](#)
@@ -7301,6 +7286,26 @@
+## [network_discord_id_from_local_index](#network_discord_id_from_local_index)
+
+### Lua Example
+`local stringValue = network_discord_id_from_local_index(localIndex)`
+
+### Parameters
+| Field | Type |
+| ----- | ---- |
+| localIndex | `integer` |
+
+### Returns
+- `string`
+
+### C Prototype
+`char* network_discord_id_from_local_index(u8 localIndex);`
+
+[:arrow_up_small:](#)
+
+
+
## [network_get_player_text_color_string](#network_get_player_text_color_string)
### Lua Example
diff --git a/docs/lua/functions.md b/docs/lua/functions.md
index 2bf3e70a1..3a8ced33f 100644
--- a/docs/lua/functions.md
+++ b/docs/lua/functions.md
@@ -1012,8 +1012,6 @@
-- misc.h
- - [update_all_mario_stars](functions-3.md#update_all_mario_stars)
@@ -1030,6 +1028,7 @@
- network_utils.h
+ - [network_discord_id_from_local_index](functions-3.md#network_discord_id_from_local_index)
- [network_get_player_text_color_string](functions-3.md#network_get_player_text_color_string)
- [network_global_index_from_local](functions-3.md#network_global_index_from_local)
- [network_is_moderator](functions-3.md#network_is_moderator)
diff --git a/src/game/save_file.c b/src/game/save_file.c
index 0480eedff..a4435b96f 100644
--- a/src/game/save_file.c
+++ b/src/game/save_file.c
@@ -11,7 +11,6 @@
#include "course_table.h"
#include "rumble_init.h"
#include "macros.h"
-#include "pc/ini.h"
#include "pc/network/network.h"
#include "pc/lua/utils/smlua_level_utils.h"
#include "pc/utils/misc.h"
diff --git a/src/pc/configini.c b/src/pc/configini.c
new file mode 100644
index 000000000..7ef01907b
--- /dev/null
+++ b/src/pc/configini.c
@@ -0,0 +1,1223 @@
+/*
+ libconfigini - an ini formatted configuration parser library
+ Copyright (C) 2013-present Taner YILMAZ
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of copyright holders nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "configini.h"
+#include "queue.h"
+
+
+#define COMMENT_CHARS "#" /* default comment chars */
+#define KEYVAL_SEP '=' /* default key-val seperator character */
+#define STR_TRUE "1" /* default string valu of true */
+#define STR_FALSE "0" /* default string valu of false */
+
+#define CONFIG_INIT_MAGIC 0x12F0ED1
+
+#define UNUSED __attribute__((unused))
+
+/**
+ * \brief Configuration key-value
+ */
+typedef struct ConfigKeyValue
+{
+ char *key;
+ char *value;
+ TAILQ_ENTRY(ConfigKeyValue) next;
+} ConfigKeyValue;
+
+/**
+ * \brief Configuration section
+ */
+typedef struct ConfigSection
+{
+ char *name;
+ int numofkv;
+ TAILQ_HEAD(, ConfigKeyValue) kv_list;
+ TAILQ_ENTRY(ConfigSection) next;
+} ConfigSection;
+
+/**
+ * \brief Configuration handle
+ */
+struct Config
+{
+ char *comment_chars;
+ char keyval_sep;
+ char *true_str;
+ char *false_str;
+ int initnum;
+ int numofsect;
+ TAILQ_HEAD(, ConfigSection) sect_list;
+};
+
+
+
+
+static int StrSafeCopy(char *dst, const char *src, int size)
+{
+ char *d = dst;
+ const char *s = src;
+ int n = size;
+
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ if (n == 0) {
+ if (size != 0)
+ *d = '\0';
+ while (*s++)
+ ;
+ }
+
+ return (s - src - 1);
+}
+
+static bool StrIsTypeOfTrue(const char *s)
+{
+ if ( !strcasecmp(s, "true") || !strcasecmp(s, "yes") || !strcasecmp(s, "1") )
+ return true;
+ return false;
+}
+
+static bool StrIsTypeOfFalse(const char *s)
+{
+ if ( !strcasecmp(s, "false") || !strcasecmp(s, "no") || !strcasecmp(s, "0") )
+ return true;
+ return false;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+const char *ConfigRetToString(ConfigRet ret)
+{
+ switch(ret) {
+ case CONFIG_OK: return "OK";
+ case CONFIG_ERR_FILE: return "File IO error";
+ case CONFIG_ERR_NO_SECTION: return "No section";
+ case CONFIG_ERR_NO_KEY: return "No key";
+ case CONFIG_ERR_MEMALLOC: return "Memory allocation failed";
+ case CONFIG_ERR_INVALID_PARAM: return "Invalid parameter";
+ case CONFIG_ERR_INVALID_VALUE: return "Invalid value";
+ case CONFIG_ERR_PARSING: return "Parse error";
+ default: return NULL;
+ }
+}
+
+/**
+ * \brief ConfigSetCommentCharset() sets comment characters
+ *
+ * \param cfg config handle
+ * \param comment_ch charaters to consider as comments
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigSetCommentCharset(Config *cfg, const char *comment_ch)
+{
+ char *p;
+
+ if (!cfg || !comment_ch)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ if ((p = strdup(comment_ch)) == NULL)
+ return CONFIG_ERR_MEMALLOC;
+
+ if (cfg->comment_chars)
+ free(cfg->comment_chars);
+ cfg->comment_chars = p;
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief ConfigSetCommentCharset() sets comment characters
+ *
+ * \param cfg config handle
+ * \param ch charater to consider as key-value seperator
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigSetKeyValSepChar(Config *cfg, char ch)
+{
+ if (!cfg)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ cfg->keyval_sep = ch;
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief ConfigSetCommentCharset() sets comment characters
+ *
+ * \param cfg config handle
+ * \param true_str string value of boolean true (must be one of these: "true", "yes", "1")
+ * \param false_str string value of boolean false (must be one of these: "false", "no", "0")
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigSetBoolString(Config *cfg, const char *true_str, const char *false_str)
+{
+ char *t, *f;
+
+ if ( !cfg ||
+ !true_str || !*true_str || !StrIsTypeOfTrue(true_str) ||
+ !false_str || !*false_str || !StrIsTypeOfFalse(false_str) )
+ return CONFIG_ERR_INVALID_PARAM;
+
+ if ((t = strdup(true_str)) == NULL)
+ return CONFIG_ERR_MEMALLOC;
+
+ if ((f = strdup(false_str)) == NULL) {
+ free(t);
+ return CONFIG_ERR_MEMALLOC;
+ }
+
+ if (cfg->true_str)
+ free(cfg->true_str);
+ if (cfg->false_str)
+ free(cfg->false_str);
+
+ cfg->true_str = t;
+ cfg->false_str = f;
+
+ return CONFIG_OK;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * \brief ConfigGetSection() gets the requested section
+ *
+ * \param cfg config handle to search in
+ * \param section section name to search for
+ * \param sect pointer to ConfigSection* searched for to save
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+static ConfigRet ConfigGetSection(const Config *cfg, const char *section, ConfigSection **sect)
+{
+ if (!cfg || !sect)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ TAILQ_FOREACH(*sect, &cfg->sect_list, next) {
+ if ( (section && (*sect)->name && !strcmp((*sect)->name, section)) ||
+ (!section && !(*sect)->name) ) {
+ return CONFIG_OK;
+ }
+ }
+
+ return CONFIG_ERR_NO_SECTION;
+}
+
+/**
+ * \brief Checks whether section exists
+ *
+ * \param cfg config handle to search in
+ * \param section section name to search for
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+bool ConfigHasSection(const Config *cfg, const char *section)
+{
+ ConfigSection *sect = NULL;
+
+ return ( (ConfigGetSection(cfg, section, §) == CONFIG_OK) ? true : false );
+}
+
+/**
+ * \brief ConfigGetKeyValue() gets the ConfigKeyValue *
+ *
+ * \param cfg config handle
+ * \param sect section to search in
+ * \param key key to search for
+ * \param kv pointer to ConfigKeyValue* searched for to save
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+static ConfigRet ConfigGetKeyValue(UNUSED const Config *cfg, ConfigSection *sect, const char *key,
+ ConfigKeyValue **kv)
+{
+ if (!sect || !key || !kv)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ TAILQ_FOREACH(*kv, §->kv_list, next) {
+ if (!strcmp((*kv)->key, key))
+ return CONFIG_OK;
+ }
+
+ return CONFIG_ERR_NO_KEY;
+}
+
+/**
+ * \brief ConfigGetSectionCount() gets number of sections
+ *
+ * \param cfg config handle to search in
+ *
+ * \return Returns number of sections on success, -1 on failure.
+ */
+int ConfigGetSectionCount(const Config *cfg)
+{
+ if (!cfg)
+ return -1;
+
+ return (TAILQ_FIRST(&cfg->sect_list)->numofkv > 0 ? cfg->numofsect : cfg->numofsect - 1);
+}
+
+/**
+ * \brief ConfigGetKeyCount() gets number of keys
+ *
+ * \param cfg config handle to search in
+ * \param section section name to search for
+ *
+ * \return Returns number of keys on success, -1 on failure.
+ */
+int ConfigGetKeyCount(const Config *cfg, const char *section)
+{
+ ConfigSection *sect = NULL;
+
+ if (!cfg)
+ return -1;
+
+ if (ConfigGetSection(cfg, section, §) != CONFIG_OK)
+ return -1;
+
+ return sect->numofkv;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * \brief ConfigReadString() reads a string value from the cfg.
+ * If any error occurs default value is copied to 'value' buffer and
+ * returns reason. If key is optional and does not exists in config,
+ * the 'value' is default value and return is CONFIG_ERR_NO_KEY
+ *
+ * \param cfg config handle
+ * \param section section to search in
+ * \param key key to search for
+ * \param value value to save in
+ * \param size value buffer size
+ * \param dfl_value default value to copy back if any error occurs
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigReadString(const Config *cfg, const char *section, const char *key,
+ char *value, int size, const char *dfl_value)
+{
+ ConfigSection *sect = NULL;
+ ConfigKeyValue *kv = NULL;
+ ConfigRet ret = CONFIG_OK;
+
+ if (!cfg || !key || !value || (size < 1))
+ return CONFIG_ERR_INVALID_PARAM;
+
+ *value = '\0';
+
+ if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
+ ((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
+ if (dfl_value)
+ StrSafeCopy(value, dfl_value, size);
+ return ret;
+ }
+
+ StrSafeCopy(value, kv->value, size);
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief ConfigReadInt() reads an integer value from the cfg.
+ * If any error occurs default value is copied to 'value' buffer and
+ * returns reason. If key is optional and does not exists in config,
+ * the 'value' is default value and return is CONFIG_ERR_NO_KEY
+ *
+ * \param cfg config handle
+ * \param section section to search in
+ * \param key key to search for
+ * \param value value to save in
+ * \param dfl_value default value to copy back if any error occurs
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigReadInt(const Config *cfg, const char *section, const char *key,
+ int *value, int dfl_value)
+{
+ ConfigSection *sect = NULL;
+ ConfigKeyValue *kv = NULL;
+ ConfigRet ret = CONFIG_OK;
+ char *p = NULL;
+
+ if (!cfg || !key || !value)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ *value = dfl_value;
+
+ if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
+ ((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
+ return ret;
+ }
+
+ *value = (int) strtol(kv->value, &p, 10);
+ if (*p || (errno == ERANGE))
+ return CONFIG_ERR_INVALID_VALUE;
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief ConfigReadUnsignedInt() reads an unsigned integer value from the cfg.
+ * If any error occurs default value is copied to 'value' buffer and
+ * returns reason. If key is optional and does not exists in config,
+ * the 'value' is default value and return is CONFIG_ERR_NO_KEY
+ *
+ * \param cfg config handle
+ * \param section section to search in
+ * \param key key to search for
+ * \param value value to save in
+ * \param dfl_value default value to copy back if any error occurs
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigReadUnsignedInt(const Config *cfg, const char *section, const char *key,
+ unsigned int *value, unsigned int dfl_value)
+{
+ ConfigSection *sect = NULL;
+ ConfigKeyValue *kv = NULL;
+ ConfigRet ret = CONFIG_OK;
+ char *p = NULL;
+
+ if (!cfg || !key || !value)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ *value = dfl_value;
+
+ if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
+ ((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
+ return ret;
+ }
+
+ *value = (unsigned int) strtoul(kv->value, &p, 10);
+ if (*p || (errno == ERANGE))
+ return CONFIG_ERR_INVALID_VALUE;
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief ConfigReadFloat() reads a float value from the cfg.
+ * If any error occurs default value is copied to 'value' buffer and
+ * returns reason. If key is optional and does not exists in config,
+ * the 'value' is default value and return is CONFIG_ERR_NO_KEY
+ *
+ * \param cfg config handle
+ * \param section section to search in
+ * \param key key to search for
+ * \param value value to save in
+ * \param dfl_value default value to copy back if any error occurs
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigReadFloat(const Config *cfg, const char *section, const char *key,
+ float *value, float dfl_value)
+{
+ ConfigSection *sect = NULL;
+ ConfigKeyValue *kv = NULL;
+ ConfigRet ret = CONFIG_OK;
+ char *p = NULL;
+
+ if (!cfg || !key || !value)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ *value = dfl_value;
+
+ if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
+ ((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
+ return ret;
+ }
+
+ *value = strtof(kv->value, &p);
+ if (*p || (errno == ERANGE))
+ return CONFIG_ERR_INVALID_VALUE;
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief ConfigReadDouble() reads a double value from the cfg.
+ * If any error occurs default value is copied to 'value' buffer and
+ * returns reason. If key is optional and does not exists in config,
+ * the 'value' is default value and return is CONFIG_ERR_NO_KEY
+ *
+ * \param cfg config handle
+ * \param section section to search in
+ * \param key key to search for
+ * \param value value to save in
+ * \param dfl_value default value to copy back if any error occurs
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigReadDouble(const Config *cfg, const char *section, const char *key,
+ double *value, double dfl_value)
+{
+ ConfigSection *sect = NULL;
+ ConfigKeyValue *kv = NULL;
+ ConfigRet ret = CONFIG_OK;
+ char *p = NULL;
+
+ if (!cfg || !key || !value)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ *value = dfl_value;
+
+ if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
+ ((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
+ return ret;
+ }
+
+ *value = strtod(kv->value, &p);
+ if (*p || (errno == ERANGE))
+ return CONFIG_ERR_INVALID_VALUE;
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief ConfigReadBool() reads a boolean value from the cfg.
+ * If any error occurs default value is copied to 'value' buffer and
+ * returns reason. If key is optional and does not exists in config,
+ * the 'value' is default value and return is CONFIG_ERR_NO_KEY
+ *
+ * \param cfg config handle
+ * \param section section to search in
+ * \param key key to search for
+ * \param value value to save in
+ * \param dfl_value default value to copy back if any error occurs
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigReadBool(const Config *cfg, const char *section, const char *key,
+ bool *value, bool dfl_value)
+{
+ ConfigSection *sect = NULL;
+ ConfigKeyValue *kv = NULL;
+ ConfigRet ret = CONFIG_OK;
+
+ if (!cfg || !key || !value)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ *value = dfl_value;
+
+ if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
+ ((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
+ return ret;
+ }
+
+ if (StrIsTypeOfTrue(kv->value))
+ *value = true;
+ else if (StrIsTypeOfFalse(kv->value))
+ *value = false;
+ else
+ return CONFIG_ERR_INVALID_VALUE;
+
+ return CONFIG_OK;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * \brief ConfigAddSection() creates a section in the cfg
+ *
+ * \param cfg config handle
+ * \param section section to add
+ * \param sect pointer to added ConfigSection* or NULL if not needed
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+static ConfigRet ConfigAddSection(Config *cfg, const char *section, ConfigSection **sect)
+{
+ ConfigSection *_sect = NULL;
+ ConfigRet ret = CONFIG_OK;
+
+ if (!cfg)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ if (!sect)
+ sect = &_sect;
+
+ if ((ret = ConfigGetSection(cfg, section, sect)) != CONFIG_ERR_NO_SECTION)
+ return ret;
+
+ *sect = calloc(1, sizeof(ConfigSection));
+ if (*sect == NULL)
+ return CONFIG_ERR_MEMALLOC;
+
+ if (section) {
+ if (((*sect)->name = strdup(section)) == NULL) {
+ free(*sect);
+ return CONFIG_ERR_MEMALLOC;
+ }
+ }
+
+ TAILQ_INIT(&(*sect)->kv_list);
+ TAILQ_INSERT_TAIL(&cfg->sect_list, *sect, next);
+ ++(cfg->numofsect);
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief ConfigAddString() adds the key with string value to the cfg
+ *
+ * \param cfg config handle
+ * \param section section to add in
+ * \param key key to save as
+ * \param value value to save as
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigAddString(Config *cfg, const char *section, const char *key, const char *value)
+{
+ ConfigSection *sect = NULL;
+ ConfigKeyValue *kv = NULL;
+ ConfigRet ret = CONFIG_OK;
+ const char *p = NULL;
+ const char *q = NULL;
+
+ if (!cfg || !key || !value)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ if ((ret = ConfigAddSection(cfg, section, §)) != CONFIG_OK)
+ return ret;
+
+ switch (ret = ConfigGetKeyValue(cfg, sect, key, &kv)) {
+ case CONFIG_OK:
+ if (kv->value) {
+ free(kv->value);
+ kv->value = NULL;
+ }
+ break;
+
+ case CONFIG_ERR_NO_KEY:
+ if ((kv = calloc(1, sizeof(ConfigKeyValue))) == NULL)
+ return CONFIG_ERR_MEMALLOC;
+ if ((kv->key = strdup(key)) == NULL) {
+ free(kv);
+ return CONFIG_ERR_MEMALLOC;
+ }
+ TAILQ_INSERT_TAIL(§->kv_list, kv, next);
+ ++(sect->numofkv);
+ break;
+
+ default:
+ return ret;
+ }
+
+ for (p = value; *p && isspace(*p); ++p)
+ ;
+ for (q = p; *q && (*q != '\r') && (*q != '\n') && !strchr(cfg->comment_chars, *q); ++q)
+ ;
+ while (*q && (q > p) && isspace(*(q - 1)))
+ --q;
+
+ kv->value = (char *) malloc(q - p + 1);
+ if (kv->value == NULL) {
+ TAILQ_REMOVE(§->kv_list, kv, next);
+ --(sect->numofkv);
+ free(kv->key);
+ free(kv);
+ return CONFIG_ERR_MEMALLOC;
+ }
+ memcpy(kv->value, p, q - p);
+ kv->value[q - p] = '\0';
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief ConfigAddInt() adds the key with integer value to the cfg
+ *
+ * \param cfg config handle
+ * \param section section to add in
+ * \param key key to save as
+ * \param value value to save as
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigAddInt(Config *cfg, const char *section, const char *key, int value)
+{
+ char buf[64];
+
+ snprintf(buf, sizeof(buf), "%d", value);
+
+ return ConfigAddString(cfg, section, key, buf);
+}
+
+/**
+ * \brief ConfigAddUnsignedInt() adds the key with unsigned integer value to the cfg
+ *
+ * \param cfg config handle
+ * \param section section to add in
+ * \param key key to save as
+ * \param value value to save as
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigAddUnsignedInt(Config *cfg, const char *section, const char *key, unsigned int value)
+{
+ char buf[64];
+
+ snprintf(buf, sizeof(buf), "%u", value);
+
+ return ConfigAddString(cfg, section, key, buf);
+}
+
+/**
+ * \brief ConfigAddFloat() adds the key with float value to the cfg
+ *
+ * \param cfg config handle
+ * \param section section to add in
+ * \param key key to save as
+ * \param value value to save as
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigAddFloat(Config * cfg, const char *section, const char *key, float value)
+{
+ char buf[64];
+
+ snprintf(buf, sizeof(buf), "%f", value);
+
+ return ConfigAddString(cfg, section, key, buf);
+}
+
+/**
+ * \brief ConfigAddDouble() adds the key with double value to the cfg
+ *
+ * \param cfg config handle
+ * \param section section to add in
+ * \param key key to save as
+ * \param value value to save as
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigAddDouble(Config *cfg, const char *section, const char *key, double value)
+{
+ char buf[64];
+
+ snprintf(buf, sizeof(buf), "%f", value);
+
+ return ConfigAddString(cfg, section, key, buf);
+}
+
+/**
+ * \brief ConfigAddBool() adds the key with blooean value to the cfg
+ *
+ * \param cfg config handle
+ * \param section section to add in
+ * \param key key to save as
+ * \param value value to save as
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigAddBool(Config * cfg, const char *section, const char *key, bool value)
+{
+ return ConfigAddString(cfg, section, key, value ? cfg->true_str : cfg->false_str);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+static void _ConfigRemoveKey(ConfigSection *sect, ConfigKeyValue *kv)
+{
+ TAILQ_REMOVE(§->kv_list, kv, next);
+ --(sect->numofkv);
+
+ if (kv->key)
+ free(kv->key);
+ if (kv->value)
+ free(kv->value);
+ free(kv);
+}
+
+/**
+ * \brief ConfigRemoveKey() removes the key which exists under section from the cfg
+ *
+ * \param cfg config handle
+ * \param section section to seach in
+ * \param key key to remove
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigRemoveKey(Config *cfg, const char *section, const char *key)
+{
+ ConfigSection *sect = NULL;
+ ConfigKeyValue *kv = NULL;
+ ConfigRet ret = CONFIG_OK;
+
+ if (!cfg || !key)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ if ((ret = ConfigGetSection(cfg, section, §)) == CONFIG_OK) {
+ if ((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) == CONFIG_OK)
+ _ConfigRemoveKey(sect, kv);
+ }
+
+ return ret;
+}
+
+static void _ConfigRemoveSection(Config *cfg, ConfigSection *sect)
+{
+ ConfigKeyValue *kv, *t_kv;
+
+ if (!cfg || !sect)
+ return;
+
+ TAILQ_REMOVE(&cfg->sect_list, sect, next);
+ --(cfg->numofsect);
+
+ TAILQ_FOREACH_SAFE(kv, §->kv_list, next, t_kv) {
+ _ConfigRemoveKey(sect, kv);
+ }
+
+ if (sect->name)
+ free(sect->name);
+ free(sect);
+}
+
+/**
+ * \brief ConfigRemoveSection() removes section from the cfgfile
+ *
+ * \param cfg config handle
+ * \param section section to remove
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigRemoveSection(Config *cfg, const char *section)
+{
+ ConfigSection *sect = NULL;
+ ConfigRet ret = CONFIG_OK;
+
+ if (!cfg)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ if ((ret = ConfigGetSection(cfg, section, §)) == CONFIG_OK)
+ _ConfigRemoveSection(cfg, sect);
+
+ return ret;
+}
+
+/**
+ * \brief ConfigNew() creates a cfg handle with
+ * default section which has no section name
+ *
+ * \return Config* handle on success, NULL on failure
+ */
+Config *ConfigNew()
+{
+ Config *cfg = NULL;
+
+ cfg = calloc(1, sizeof(Config));
+ if (cfg == NULL)
+ return NULL;
+
+ TAILQ_INIT(&cfg->sect_list);
+
+ /* add default section */
+ if (ConfigAddSection(cfg, CONFIG_SECTION_FLAT, NULL) != CONFIG_OK) {
+ free(cfg);
+ return NULL;
+ }
+
+ cfg->comment_chars = strdup(COMMENT_CHARS);
+ cfg->keyval_sep = KEYVAL_SEP;
+ cfg->true_str = strdup(STR_TRUE);
+ cfg->false_str = strdup(STR_FALSE);
+ cfg->initnum = CONFIG_INIT_MAGIC;
+
+ return cfg;
+}
+
+/**
+ * \brief ConfigFree() frees the memory for the cfg handle
+ *
+ * \param cfg config handle
+ */
+void ConfigFree(Config *cfg)
+{
+ ConfigSection *sect, *t_sect;
+
+ if (cfg == NULL)
+ return;
+
+ TAILQ_FOREACH_SAFE(sect, &cfg->sect_list, next, t_sect) {
+ _ConfigRemoveSection(cfg, sect);
+ }
+
+ if (cfg->comment_chars) free(cfg->comment_chars);
+ if (cfg->true_str) free(cfg->true_str);
+ if (cfg->false_str) free(cfg->false_str);
+
+ free(cfg);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * \brief Gets section name on the buffer p
+ *
+ * \param cfg config handle
+ * \param p read buffer
+ * \param section pointer address to section
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+static ConfigRet GetSectName(Config *cfg, char *p, char **section)
+{
+ char *q, *r;
+
+ if (!cfg || !p || !*p || !section)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ *section = NULL;
+
+ /* get section name */
+ while (*p && isspace(*p))
+ ++p;
+
+ if (*p != '[')
+ return CONFIG_ERR_PARSING;
+
+ ++p;
+ while (*p && isspace(*p))
+ ++p;
+
+ for (q = p;
+ *q && (*q != '\r') && (*q != '\n') && (*q != ']') && !strchr(cfg->comment_chars, *q);
+ ++q)
+ ;
+
+ if (*q != ']')
+ return CONFIG_ERR_PARSING;
+
+ r = q + 1;
+
+ while (*q && (q > p) && isspace(*(q - 1)))
+ --q;
+
+ if (q == p) /* section has no name */
+ return CONFIG_ERR_PARSING;
+
+ *q = '\0';
+ *section = p;
+
+ /* check rest of section */
+ while (*r && isspace(*r))
+ ++r;
+
+ /* there are unrecognized trailing data */
+ if (*r && !strchr(cfg->comment_chars, *r))
+ return CONFIG_ERR_PARSING;
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief Gets key and value on the buffer p
+ *
+ * \param cfg config handle
+ * \param p read buffer
+ * \param key pointer address to key
+ * \param val pointer address to value
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+static ConfigRet GetKeyVal(Config *cfg, char *p, char **key, char **val)
+{
+ char *q, *v;
+
+ if (!cfg || !p || !*p || !key || !val)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ *key = *val = NULL;
+
+ /* get key */
+ while (*p && isspace(*p))
+ ++p;
+
+ for (q = p;
+ *q && (*q != '\r') && (*q != '\n') && (*q != cfg->keyval_sep) && !strchr(cfg->comment_chars, *q);
+ ++q)
+ ;
+
+ if (*q != cfg->keyval_sep)
+ return CONFIG_ERR_PARSING;
+
+ v = q + 1;
+
+ while (*q && (q > p) && isspace(*(q - 1)))
+ --q;
+
+ if (q == p) /* no key name */
+ return CONFIG_ERR_PARSING;
+
+ *q = '\0';
+ *key = p;
+
+ /* get value */
+ while (*v && isspace(*v))
+ ++v;
+
+ for (q = v;
+ *q && (*q != '\r') && (*q != '\n') && !strchr(cfg->comment_chars, *q);
+ ++q)
+ ;
+
+ while (*q && (q > v) && isspace(*(q - 1)))
+ --q;
+
+ if (q == v) /* no value */
+ return CONFIG_ERR_INVALID_VALUE;
+
+ *q = '\0';
+ *val = v;
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief ConfigRead() reads the stream and populates the entire content to cfg handle
+ *
+ * \param fp FILE handle to read
+ * \param cfg pointer to config handle.
+ * If not NULL a handle created with ConfigNew() must be given.
+ * If cfg is NULL a new one is created and saved to cfg.
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigRead(FILE *fp, Config **cfg)
+{
+ ConfigSection *sect = NULL;
+ char *p = NULL;
+ char *section = NULL;
+ char *key = NULL;
+ char *val = NULL;
+ char buf[4096];
+ Config *_cfg = NULL;
+ bool newcfg = false;
+ ConfigRet ret = CONFIG_OK;
+
+ if ( !fp || !cfg || (*cfg && ((*cfg)->initnum != CONFIG_INIT_MAGIC)) )
+ return CONFIG_ERR_INVALID_PARAM;
+
+ if (*cfg == NULL) {
+ _cfg = ConfigNew();
+ if (_cfg == NULL)
+ return CONFIG_ERR_MEMALLOC;
+ *cfg = _cfg;
+ newcfg = true;
+ }
+ else
+ _cfg = *cfg;
+
+ while (!feof(fp)) {
+ if (fgets(buf, sizeof(buf), fp) == NULL)
+ continue;
+
+ for (p = buf; *p && isspace(*p) ; ++p)
+ ;
+ if (!*p || strchr(_cfg->comment_chars, *p))
+ continue;
+
+ if (*p == '[') {
+ if ((ret = GetSectName(_cfg, p, §ion)) != CONFIG_OK)
+ goto error;
+
+ if ((ret = ConfigAddSection(_cfg, section, §)) != CONFIG_OK)
+ goto error;
+ }
+ else {
+ if ((ret = GetKeyVal(_cfg, p, &key, &val)) != CONFIG_OK)
+ goto error;
+
+ if ((ret = ConfigAddString(_cfg, sect->name, key, val)) != CONFIG_OK)
+ goto error;
+ }
+ }
+
+ return CONFIG_OK;
+
+error:
+ if (newcfg) {
+ ConfigFree(_cfg);
+ *cfg = NULL;
+ }
+
+ return ret;
+}
+
+/**
+ * \brief ConfigReadFile() opens and reads the file and populates the
+ * entire content to cfg handle
+ *
+ * \param filename name of file to open and load
+ * \param cfg pointer to config handle.
+ * If not NULL a handle created with ConfigNew() must be given.
+ * If cfg is NULL a new one is created and saved to cfg.
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigReadFile(const char *filename, Config **cfg)
+{
+ FILE *fp = NULL;
+ ConfigRet ret = CONFIG_OK;
+
+ if ( !filename || !cfg || (*cfg && ((*cfg)->initnum != CONFIG_INIT_MAGIC)) )
+ return CONFIG_ERR_INVALID_PARAM;
+
+ if ((fp = fopen(filename, "r")) == NULL)
+ return CONFIG_ERR_FILE;
+
+ ret = ConfigRead(fp, cfg);
+
+ fclose(fp);
+
+ return ret;
+}
+
+/**
+ * \brief ConfigPrint() prints all cfg content to the stream
+ *
+ * \param cfg config handle
+ * \param stream stream to print
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigPrint(const Config *cfg, FILE *stream)
+{
+ ConfigSection *sect = NULL;
+ ConfigKeyValue *kv = NULL;
+
+ if (!cfg || !stream)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ TAILQ_FOREACH(sect, &cfg->sect_list, next) {
+ if (sect->name)
+ fprintf(stream, "[%s]\n", sect->name);
+
+ TAILQ_FOREACH(kv, §->kv_list, next)
+ fprintf(stream, "%s=%s\n", kv->key, kv->value);
+
+ fprintf(stream, "\n");
+ }
+
+ return CONFIG_OK;
+}
+
+/**
+ * \brief ConfigPrintToFile() prints (saves) all cfg content to the file
+ *
+ * \param cfg config handle
+ * \param filename filename to save in
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigPrintToFile(const Config *cfg, char *filename)
+{
+ FILE *fp = NULL;
+ ConfigRet ret = CONFIG_OK;
+
+ if (!cfg || !filename)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ if ((fp = fopen(filename, "wb")) == NULL)
+ return CONFIG_ERR_FILE;
+
+ ret = ConfigPrint(cfg, fp);
+
+ fclose(fp);
+
+ return ret;
+}
+
+/**
+ * \brief ConfigPrintSettings() prints settings to the stream
+ *
+ * \param cfg config handle
+ * \param stream stream to print
+ *
+ * \return Returns CONFIG_RET_OK as success, otherwise is an error.
+ */
+ConfigRet ConfigPrintSettings(const Config *cfg, FILE *stream)
+{
+ if (!cfg || !stream)
+ return CONFIG_ERR_INVALID_PARAM;
+
+ fprintf(stream, "\n");
+ fprintf(stream, "Configuration settings: \n");
+ fprintf(stream, " Comment characters : %s\n", cfg->comment_chars);
+ fprintf(stream, " Key-Value seperator: %c\n", cfg->keyval_sep);
+ fprintf(stream, " True-False strings : %s-%s\n", cfg->true_str, cfg->false_str);
+ fprintf(stream, "\n");
+
+ return CONFIG_OK;
+}
+
diff --git a/src/pc/configini.h b/src/pc/configini.h
new file mode 100644
index 000000000..e46aa4554
--- /dev/null
+++ b/src/pc/configini.h
@@ -0,0 +1,110 @@
+/*
+ libconfigini - an ini formatted configuration parser library
+ Copyright (C) 2013-present Taner YILMAZ
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of copyright holders nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CONFIGINI_H_
+#define CONFIGINI_H_
+
+#include
+#include
+
+
+typedef struct Config Config;
+
+
+#define CONFIG_SECTION_FLAT NULL /* config is flat data (has no section) */
+
+
+/**
+ * \brief Return types
+ */
+typedef enum
+{
+ CONFIG_OK, /* ok (no error) */
+ CONFIG_ERR_FILE, /* file io error (file not exists, cannot open file, ...) */
+ CONFIG_ERR_NO_SECTION, /* section does not exist */
+ CONFIG_ERR_NO_KEY, /* key does not exist */
+ CONFIG_ERR_MEMALLOC, /* memory allocation failed */
+ CONFIG_ERR_INVALID_PARAM, /* invalid parametrs (as NULL) */
+ CONFIG_ERR_INVALID_VALUE, /* value of key is invalid (inconsistent data, empty data) */
+ CONFIG_ERR_PARSING, /* parsing error of data (does not fit to config format) */
+} ConfigRet;
+
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+Config* ConfigNew (void);
+void ConfigFree (Config *cfg);
+
+const char *ConfigRetToString (ConfigRet ret);
+
+ConfigRet ConfigRead (FILE *fp, Config **cfg);
+ConfigRet ConfigReadFile (const char *filename, Config **cfg);
+
+ConfigRet ConfigPrint (const Config *cfg, FILE *stream);
+ConfigRet ConfigPrintToFile (const Config *cfg, char *filename);
+ConfigRet ConfigPrintSettings (const Config *cfg, FILE *stream);
+
+int ConfigGetSectionCount (const Config *cfg);
+int ConfigGetKeyCount (const Config *cfg, const char *sect);
+
+ConfigRet ConfigSetCommentCharset(Config *cfg, const char *comment_ch);
+ConfigRet ConfigSetKeyValSepChar (Config *cfg, char ch);
+ConfigRet ConfigSetBoolString (Config *cfg, const char *true_str, const char *false_str);
+
+ConfigRet ConfigReadString (const Config *cfg, const char *sect, const char *key, char * val, int size, const char * dfl_val);
+ConfigRet ConfigReadInt (const Config *cfg, const char *sect, const char *key, int * val, int dfl_val);
+ConfigRet ConfigReadUnsignedInt (const Config *cfg, const char *sect, const char *key, unsigned int *val, unsigned int dfl_val);
+ConfigRet ConfigReadFloat (const Config *cfg, const char *sect, const char *key, float * val, float dfl_val);
+ConfigRet ConfigReadDouble (const Config *cfg, const char *sect, const char *key, double * val, double dfl_val);
+ConfigRet ConfigReadBool (const Config *cfg, const char *sect, const char *key, bool * val, bool dfl_val);
+
+ConfigRet ConfigAddString (Config *cfg, const char *sect, const char *key, const char *val);
+ConfigRet ConfigAddInt (Config *cfg, const char *sect, const char *key, int val);
+ConfigRet ConfigAddUnsignedInt (Config *cfg, const char *sect, const char *key, unsigned int val);
+ConfigRet ConfigAddFloat (Config *cfg, const char *sect, const char *key, float val);
+ConfigRet ConfigAddDouble (Config *cfg, const char *sect, const char *key, double val);
+ConfigRet ConfigAddBool (Config *cfg, const char *sect, const char *key, bool val);
+
+bool ConfigHasSection (const Config *cfg, const char *sect);
+
+ConfigRet ConfigRemoveSection (Config *cfg, const char *sect);
+ConfigRet ConfigRemoveKey (Config *cfg, const char *sect, const char *key);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* CONFIGINI_H_ */
diff --git a/src/pc/djui/djui_panel_player.c b/src/pc/djui/djui_panel_player.c
index 729b56b04..b9569e060 100644
--- a/src/pc/djui/djui_panel_player.c
+++ b/src/pc/djui/djui_panel_player.c
@@ -113,7 +113,7 @@ void djui_panel_player_edit_palette_destroy(struct DjuiBase* caller) {
}
static void djui_panel_player_edit_palette_create(struct DjuiBase* caller) {
- char* sPartStrings[PLAYER_PART_MAX] = { "Pants", "Shirt", "Gloves", "Shoes", "Hair", "Skin" };
+ char* sPartStrings[PLAYER_PART_MAX] = { "Overalls", "Shirt", "Gloves", "Shoes", "Hair", "Skin" };
f32 bodyHeight = 32 * 5 + 64 * 1 + 16 * 5;
diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c
index a1db9a5d1..2fb7ea86d 100644
--- a/src/pc/lua/smlua_functions_autogen.c
+++ b/src/pc/lua/smlua_functions_autogen.c
@@ -33,6 +33,7 @@
#include "src/game/mario_misc.h"
#include "src/pc/utils/misc.h"
#include "src/game/level_update.h"
+#include "src/pc/mods/mod_storage.h"
////////////////////////
@@ -11718,15 +11719,6 @@ int smlua_func_vec3s_to_vec3f(lua_State* L) {
}
*/
- ////////////
- // misc.h //
-////////////
-
-int smlua_func_update_all_mario_stars(UNUSED lua_State* L) {
- if(!smlua_functions_valid_param_count(L, 0)) { return 0; }
-
-
- update_all_mario_stars();
return 1;
}
@@ -11867,6 +11859,17 @@ int smlua_func_network_player_set_description(lua_State* L) {
// network_utils.h //
/////////////////////
+int smlua_func_network_discord_id_from_local_index(lua_State* L) {
+ if(!smlua_functions_valid_param_count(L, 1)) { return 0; }
+
+ u8 localIndex = smlua_to_integer(L, 1);
+ if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter 1 for function 'network_discord_id_from_local_index'"); return 0; }
+
+ lua_pushstring(L, network_discord_id_from_local_index(localIndex));
+
+ return 1;
+}
+
int smlua_func_network_get_player_text_color_string(lua_State* L) {
if(!smlua_functions_valid_param_count(L, 1)) { return 0; }
@@ -19202,8 +19205,6 @@ void smlua_bind_functions_autogen(void) {
//smlua_bind_function(L, "vec3s_sum", smlua_func_vec3s_sum); <--- UNIMPLEMENTED
//smlua_bind_function(L, "vec3s_to_vec3f", smlua_func_vec3s_to_vec3f); <--- UNIMPLEMENTED
- // misc.h
- smlua_bind_function(L, "update_all_mario_stars", smlua_func_update_all_mario_stars);
// network_player.h
smlua_bind_function(L, "get_network_player_from_area", smlua_func_get_network_player_from_area);
@@ -19216,6 +19217,7 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "network_player_set_description", smlua_func_network_player_set_description);
// network_utils.h
+ smlua_bind_function(L, "network_discord_id_from_local_index", smlua_func_network_discord_id_from_local_index);
smlua_bind_function(L, "network_get_player_text_color_string", smlua_func_network_get_player_text_color_string);
smlua_bind_function(L, "network_global_index_from_local", smlua_func_network_global_index_from_local);
smlua_bind_function(L, "network_is_moderator", smlua_func_network_is_moderator);
diff --git a/src/pc/mods/mod_storage.c b/src/pc/mods/mod_storage.c
new file mode 100644
index 000000000..6ade649b3
--- /dev/null
+++ b/src/pc/mods/mod_storage.c
@@ -0,0 +1,165 @@
+#include "mod_storage.h"
+
+#include
+#include "pc/platform.h"
+#include "pc/configini.h" // for writing
+#include "pc/ini.h" // for parsing
+#include "pc/lua/smlua.h"
+#include "pc/mods/mods_utils.h"
+#include "pc/debuglog.h"
+
+void strdelete(char string[], char substr[]) {
+ // i is used to loop through the string
+ u16 i = 0;
+
+ // store the lengths of the string and substr
+ u16 string_length = strlen(string);
+ u16 substr_length = strlen(substr);
+
+ // loop through starting at the first index
+ while (i < string_length) {
+ // if we find the substr at the current index, delete it
+ if (strstr(&string[i], substr) == &string[i]) {
+ // determine the string's new length after removing the substr occurrence
+ string_length -= substr_length;
+ // shift forward the remaining characters in the string after the substr
+ // occurrence by the length of substr, effectively removing it!
+ for (u16 j = i; j < string_length; j++) {
+ string[j] = string[j + substr_length];
+ }
+ } else {
+ i++;
+ }
+ }
+
+ string[i] = '\0';
+}
+
+bool char_valid(char* buffer) {
+ if (buffer[0] == '\0') { return false; }
+ while (*buffer != '\0') {
+ if ((*buffer >= 'a' && *buffer <= 'z') || (*buffer >= 'A' && *buffer <= 'Z') || (*buffer >= '0' && *buffer <= '9') || *buffer == '_' || *buffer == '.') {
+ buffer++;
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+u32 key_count(char* filename) {
+ FILE *file;
+ file = fopen(filename, "r");
+ if (file == NULL) { return 0; }
+
+ u32 lines = 1;
+ char c;
+ do {
+ c = fgetc(file);
+ if (c == '\n') { lines++; }
+ } while (c != EOF);
+
+ fclose(file);
+
+ return lines - 4;
+}
+
+char *mod_storage_get_filename(char* dest) {
+ const char *path = sys_user_path(); // get base sm64ex-coop appdata dir
+ snprintf(dest, SYS_MAX_PATH - 1, "%s/sav/%s", path, gLuaActiveMod->relativePath); // append sav folder
+ strdelete(dest, ".lua"); // delete ".lua" from sav name
+ strcat(dest, SAVE_EXTENSION); // append SAVE_EXTENSION
+ normalize_path(dest); // fix any out of place slashes
+}
+
+bool mod_storage_save(const char *key, const char *value) {
+ if (strlen(key) > MAX_KEY_VALUE_LENGTH || strlen(value) > MAX_KEY_VALUE_LENGTH) {
+ LOG_LUA_LINE("Too long of a key and or value for mod_storage_save()");
+ return false;
+ }
+ if (!char_valid((char *)key) || !char_valid((char *)value)) {
+ LOG_LUA_LINE("Invalid key and or value passed to mod_storage_save()");
+ return false;
+ }
+
+ FILE *file;
+ Config *cfg = NULL;
+ char *filename;
+ filename = (char *)malloc((SYS_MAX_PATH - 1) * sizeof(char));
+ mod_storage_get_filename(filename);
+
+ // ensure savPath exists
+ char savPath[SYS_MAX_PATH] = { 0 };
+ if (snprintf(savPath, SYS_MAX_PATH - 1, "%s", fs_get_write_path(SAVE_DIRECTORY)) < 0) {
+ LOG_ERROR("Failed to concat sav path");
+ free(filename);
+ return false;
+ }
+ if (!fs_sys_dir_exists(savPath)) { fs_sys_mkdir(savPath); }
+
+ bool exists = path_exists(filename);
+ file = fopen(filename, exists ? "r+" : "w");
+ cfg = ConfigNew();
+ if (exists) {
+ if (ConfigReadFile(filename, &cfg) != CONFIG_OK) {
+ ConfigFree(cfg);
+ fclose(file);
+ free(filename);
+ return false;
+ }
+ if (key_count(filename) > MAX_KEYS) {
+ LOG_LUA_LINE("Tried to save more than MAX_KEYS with mod_storage_save()");
+ ConfigFree(cfg);
+ fclose(file);
+ free(filename);
+ return false;
+ }
+ }
+
+ ConfigRemoveKey(cfg, "storage", key);
+ ConfigAddString(cfg, "storage", key, value);
+
+ ConfigPrint(cfg, file);
+ ConfigFree(cfg);
+ fclose(file);
+ free(filename);
+
+ return true;
+}
+
+const char *mod_storage_load(const char *key) {
+ if (strlen(key) > MAX_KEY_VALUE_LENGTH) {
+ LOG_LUA_LINE("Too long of a key for mod_storage_load()");
+ return NULL;
+ }
+ if (!char_valid((char *)key)) {
+ LOG_LUA_LINE("Invalid key passed to mod_storage_save()");
+ return NULL;
+ }
+
+ char *filename;
+ filename = (char *)malloc((SYS_MAX_PATH - 1) * sizeof(char));
+ mod_storage_get_filename(filename);
+ static char value[MAX_KEY_VALUE_LENGTH];
+ ini_t *storage;
+
+ if (!path_exists(filename)) {
+ free(filename);
+ return NULL;
+ }
+
+ storage = ini_load(filename);
+ if (storage == NULL) {
+ ini_free(storage);
+ free(filename);
+ return NULL;
+ }
+ snprintf(value, MAX_KEY_VALUE_LENGTH, "%s", ini_get(storage, "storage", key));
+
+ ini_free(storage);
+ free(filename);
+
+ if (strstr(value, "(null)") != NULL) { return NULL; }
+
+ return value;
+}
diff --git a/src/pc/mods/mod_storage.h b/src/pc/mods/mod_storage.h
new file mode 100644
index 000000000..feddb7f9f
--- /dev/null
+++ b/src/pc/mods/mod_storage.h
@@ -0,0 +1,14 @@
+#ifndef MOD_STORAGE_H
+#define MOD_STORAGE_H
+
+#include "mod.h"
+
+#define MAX_KEYS 255
+#define MAX_KEY_VALUE_LENGTH 64
+#define SAVE_DIRECTORY "sav"
+#define SAVE_EXTENSION ".sav"
+
+bool mod_storage_save(const char *key, const char *value);
+const char *mod_storage_load(const char *key);
+
+#endif
diff --git a/src/pc/network/network_utils.c b/src/pc/network/network_utils.c
index 90562dcb8..0dce3266f 100644
--- a/src/pc/network/network_utils.c
+++ b/src/pc/network/network_utils.c
@@ -1,5 +1,6 @@
#include
#include "network_utils.h"
+#include "discord/discord.h"
#include "game/mario_misc.h"
u8 network_global_index_from_local(u8 localIndex) {
@@ -22,6 +23,11 @@ u8 network_local_index_from_global(u8 globalIndex) {
return globalIndex + ((globalIndex < gNetworkPlayerLocal->globalIndex) ? 1 : 0);
}
+char* network_discord_id_from_local_index(u8 localIndex) {
+ if (gNetworkSystem == &gNetworkSystemDiscord) { return gNetworkSystem->get_id_str(localIndex); }
+ return NULL;
+}
+
bool network_is_server(void) {
return gNetworkType == NT_SERVER;
}
diff --git a/src/pc/network/network_utils.h b/src/pc/network/network_utils.h
index de7de04de..f1eabed97 100644
--- a/src/pc/network/network_utils.h
+++ b/src/pc/network/network_utils.h
@@ -7,6 +7,8 @@
u8 network_global_index_from_local(u8 localIndex);
u8 network_local_index_from_global(u8 globalIndex);
+char* network_discord_id_from_local_index(u8 localIndex);
+
bool network_is_server(void);
bool network_is_moderator(void);
diff --git a/src/pc/queue.h b/src/pc/queue.h
new file mode 100644
index 000000000..a82af55c5
--- /dev/null
+++ b/src/pc/queue.h
@@ -0,0 +1,694 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ * $FreeBSD: head/sys/sys/queue.h 251887 2013-06-18 02:57:56Z lstewart $
+ */
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+#include
+
+/*
+ * This file defines four types of data structures: singly-linked lists,
+ * singly-linked tail queues, lists and tail queues.
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction. Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A singly-linked tail queue is headed by a pair of pointers, one to the
+ * head of the list and the other to the tail of the list. The elements are
+ * singly linked for minimum space and pointer manipulation overhead at the
+ * expense of O(n) removal for arbitrary elements. New elements can be added
+ * to the list after an existing element, at the head of the list, or at the
+ * end of the list. Elements being removed from the head of the tail queue
+ * should use the explicit macro for this purpose for optimum efficiency.
+ * A singly-linked tail queue may only be traversed in the forward direction.
+ * Singly-linked tail queues are ideal for applications with large datasets
+ * and few or no removals or for implementing a FIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may be traversed in either direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ *
+ *
+ * SLIST LIST STAILQ TAILQ
+ * _HEAD + + + +
+ * _HEAD_INITIALIZER + + + +
+ * _ENTRY + + + +
+ * _INIT + + + +
+ * _EMPTY + + + +
+ * _FIRST + + + +
+ * _NEXT + + + +
+ * _PREV - + - +
+ * _LAST - - + +
+ * _FOREACH + + + +
+ * _FOREACH_FROM + + + +
+ * _FOREACH_SAFE + + + +
+ * _FOREACH_FROM_SAFE + + + +
+ * _FOREACH_REVERSE - - - +
+ * _FOREACH_REVERSE_FROM - - - +
+ * _FOREACH_REVERSE_SAFE - - - +
+ * _FOREACH_REVERSE_FROM_SAFE - - - +
+ * _INSERT_HEAD + + + +
+ * _INSERT_BEFORE - + - +
+ * _INSERT_AFTER + + + +
+ * _INSERT_TAIL - - + +
+ * _CONCAT - - + +
+ * _REMOVE_AFTER + - + -
+ * _REMOVE_HEAD + - + -
+ * _REMOVE + + + +
+ * _SWAP + + + +
+ *
+ */
+#ifdef QUEUE_MACRO_DEBUG
+/* Store the last 2 places the queue element or head was altered */
+struct qm_trace {
+ unsigned long lastline;
+ unsigned long prevline;
+ const char *lastfile;
+ const char *prevfile;
+};
+
+#define TRACEBUF struct qm_trace trace;
+#define TRACEBUF_INITIALIZER { __FILE__, __LINE__, NULL, 0 } ,
+#define TRASHIT(x) do {(x) = (void *)-1;} while (0)
+#define QMD_SAVELINK(name, link) void **name = (void *)&(link)
+
+#define QMD_TRACE_HEAD(head) do { \
+ (head)->trace.prevline = (head)->trace.lastline; \
+ (head)->trace.prevfile = (head)->trace.lastfile; \
+ (head)->trace.lastline = __LINE__; \
+ (head)->trace.lastfile = __FILE__; \
+} while (0)
+
+#define QMD_TRACE_ELEM(elem) do { \
+ (elem)->trace.prevline = (elem)->trace.lastline; \
+ (elem)->trace.prevfile = (elem)->trace.lastfile; \
+ (elem)->trace.lastline = __LINE__; \
+ (elem)->trace.lastfile = __FILE__; \
+} while (0)
+
+#else
+#define QMD_TRACE_ELEM(elem)
+#define QMD_TRACE_HEAD(head)
+#define QMD_SAVELINK(name, link)
+#define TRACEBUF
+#define TRACEBUF_INITIALIZER
+#define TRASHIT(x)
+#endif /* QUEUE_MACRO_DEBUG */
+
+/*
+ * Singly-linked List declarations.
+ */
+#define SLIST_HEAD(name, type) \
+struct name { \
+ struct type *slh_first; /* first element */ \
+}
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define SLIST_ENTRY(type) \
+struct { \
+ struct type *sle_next; /* next element */ \
+}
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
+
+#define SLIST_FIRST(head) ((head)->slh_first)
+
+#define SLIST_FOREACH(var, head, field) \
+ for ((var) = SLIST_FIRST((head)); \
+ (var); \
+ (var) = SLIST_NEXT((var), field))
+
+#define SLIST_FOREACH_FROM(var, head, field) \
+ for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
+ (var); \
+ (var) = SLIST_NEXT((var), field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SLIST_FIRST((head)); \
+ (var) && ((tvar) = SLIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
+ for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
+ (var) && ((tvar) = SLIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
+ for ((varp) = &SLIST_FIRST((head)); \
+ ((var) = *(varp)) != NULL; \
+ (varp) = &SLIST_NEXT((var), field))
+
+#define SLIST_INIT(head) do { \
+ SLIST_FIRST((head)) = NULL; \
+} while (0)
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
+ SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
+ SLIST_NEXT((slistelm), field) = (elm); \
+} while (0)
+
+#define SLIST_INSERT_HEAD(head, elm, field) do { \
+ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
+ SLIST_FIRST((head)) = (elm); \
+} while (0)
+
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_REMOVE(head, elm, type, field) do { \
+ QMD_SAVELINK(oldnext, (elm)->field.sle_next); \
+ if (SLIST_FIRST((head)) == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } \
+ else { \
+ struct type *curelm = SLIST_FIRST((head)); \
+ while (SLIST_NEXT(curelm, field) != (elm)) \
+ curelm = SLIST_NEXT(curelm, field); \
+ SLIST_REMOVE_AFTER(curelm, field); \
+ } \
+ TRASHIT(*oldnext); \
+} while (0)
+
+#define SLIST_REMOVE_AFTER(elm, field) do { \
+ SLIST_NEXT(elm, field) = \
+ SLIST_NEXT(SLIST_NEXT(elm, field), field); \
+} while (0)
+
+#define SLIST_REMOVE_HEAD(head, field) do { \
+ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
+} while (0)
+
+#define SLIST_SWAP(head1, head2, type) do { \
+ struct type *swap_first = SLIST_FIRST(head1); \
+ SLIST_FIRST(head1) = SLIST_FIRST(head2); \
+ SLIST_FIRST(head2) = swap_first; \
+} while (0)
+
+/*
+ * Singly-linked Tail queue declarations.
+ */
+#define STAILQ_HEAD(name, type) \
+struct name { \
+ struct type *stqh_first;/* first element */ \
+ struct type **stqh_last;/* addr of last next element */ \
+}
+
+#define STAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).stqh_first }
+
+#define STAILQ_ENTRY(type) \
+struct { \
+ struct type *stqe_next; /* next element */ \
+}
+
+/*
+ * Singly-linked Tail queue functions.
+ */
+#define STAILQ_CONCAT(head1, head2) do { \
+ if (!STAILQ_EMPTY((head2))) { \
+ *(head1)->stqh_last = (head2)->stqh_first; \
+ (head1)->stqh_last = (head2)->stqh_last; \
+ STAILQ_INIT((head2)); \
+ } \
+} while (0)
+
+#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
+
+#define STAILQ_FIRST(head) ((head)->stqh_first)
+
+#define STAILQ_FOREACH(var, head, field) \
+ for((var) = STAILQ_FIRST((head)); \
+ (var); \
+ (var) = STAILQ_NEXT((var), field))
+
+#define STAILQ_FOREACH_FROM(var, head, field) \
+ for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
+ (var); \
+ (var) = STAILQ_NEXT((var), field))
+
+#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = STAILQ_FIRST((head)); \
+ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
+ for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
+ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define STAILQ_INIT(head) do { \
+ STAILQ_FIRST((head)) = NULL; \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (0)
+
+#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_NEXT((tqelm), field) = (elm); \
+} while (0)
+
+#define STAILQ_INSERT_HEAD(head, elm, field) do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_FIRST((head)) = (elm); \
+} while (0)
+
+#define STAILQ_INSERT_TAIL(head, elm, field) do { \
+ STAILQ_NEXT((elm), field) = NULL; \
+ *(head)->stqh_last = (elm); \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+} while (0)
+
+#define STAILQ_LAST(head, type, field) \
+ (STAILQ_EMPTY((head)) ? NULL : \
+ __containerof((head)->stqh_last, struct type, field.stqe_next))
+
+#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
+
+#define STAILQ_REMOVE(head, elm, type, field) do { \
+ QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \
+ if (STAILQ_FIRST((head)) == (elm)) { \
+ STAILQ_REMOVE_HEAD((head), field); \
+ } \
+ else { \
+ struct type *curelm = STAILQ_FIRST((head)); \
+ while (STAILQ_NEXT(curelm, field) != (elm)) \
+ curelm = STAILQ_NEXT(curelm, field); \
+ STAILQ_REMOVE_AFTER(head, curelm, field); \
+ } \
+ TRASHIT(*oldnext); \
+} while (0)
+
+#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
+ if ((STAILQ_NEXT(elm, field) = \
+ STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+} while (0)
+
+#define STAILQ_REMOVE_HEAD(head, field) do { \
+ if ((STAILQ_FIRST((head)) = \
+ STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (0)
+
+#define STAILQ_SWAP(head1, head2, type) do { \
+ struct type *swap_first = STAILQ_FIRST(head1); \
+ struct type **swap_last = (head1)->stqh_last; \
+ STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
+ (head1)->stqh_last = (head2)->stqh_last; \
+ STAILQ_FIRST(head2) = swap_first; \
+ (head2)->stqh_last = swap_last; \
+ if (STAILQ_EMPTY(head1)) \
+ (head1)->stqh_last = &STAILQ_FIRST(head1); \
+ if (STAILQ_EMPTY(head2)) \
+ (head2)->stqh_last = &STAILQ_FIRST(head2); \
+} while (0)
+
+
+/*
+ * List declarations.
+ */
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+}
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+
+/*
+ * List functions.
+ */
+
+#if (defined(_KERNEL) && defined(INVARIANTS))
+#define QMD_LIST_CHECK_HEAD(head, field) do { \
+ if (LIST_FIRST((head)) != NULL && \
+ LIST_FIRST((head))->field.le_prev != \
+ &LIST_FIRST((head))) \
+ panic("Bad list head %p first->prev != head", (head)); \
+} while (0)
+
+#define QMD_LIST_CHECK_NEXT(elm, field) do { \
+ if (LIST_NEXT((elm), field) != NULL && \
+ LIST_NEXT((elm), field)->field.le_prev != \
+ &((elm)->field.le_next)) \
+ panic("Bad link elm %p next->prev != elm", (elm)); \
+} while (0)
+
+#define QMD_LIST_CHECK_PREV(elm, field) do { \
+ if (*(elm)->field.le_prev != (elm)) \
+ panic("Bad link elm %p prev->next != elm", (elm)); \
+} while (0)
+#else
+#define QMD_LIST_CHECK_HEAD(head, field)
+#define QMD_LIST_CHECK_NEXT(elm, field)
+#define QMD_LIST_CHECK_PREV(elm, field)
+#endif /* (_KERNEL && INVARIANTS) */
+
+#define LIST_EMPTY(head) ((head)->lh_first == NULL)
+
+#define LIST_FIRST(head) ((head)->lh_first)
+
+#define LIST_FOREACH(var, head, field) \
+ for ((var) = LIST_FIRST((head)); \
+ (var); \
+ (var) = LIST_NEXT((var), field))
+
+#define LIST_FOREACH_FROM(var, head, field) \
+ for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
+ (var); \
+ (var) = LIST_NEXT((var), field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = LIST_FIRST((head)); \
+ (var) && ((tvar) = LIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
+ for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
+ (var) && ((tvar) = LIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define LIST_INIT(head) do { \
+ LIST_FIRST((head)) = NULL; \
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do { \
+ QMD_LIST_CHECK_NEXT(listelm, field); \
+ if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\
+ LIST_NEXT((listelm), field)->field.le_prev = \
+ &LIST_NEXT((elm), field); \
+ LIST_NEXT((listelm), field) = (elm); \
+ (elm)->field.le_prev = &LIST_NEXT((listelm), field); \
+} while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
+ QMD_LIST_CHECK_PREV(listelm, field); \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ LIST_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &LIST_NEXT((elm), field); \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do { \
+ QMD_LIST_CHECK_HEAD((head), field); \
+ if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
+ LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
+ LIST_FIRST((head)) = (elm); \
+ (elm)->field.le_prev = &LIST_FIRST((head)); \
+} while (0)
+
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_PREV(elm, head, type, field) \
+ ((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \
+ __containerof((elm)->field.le_prev, struct type, field.le_next))
+
+#define LIST_REMOVE(elm, field) do { \
+ QMD_SAVELINK(oldnext, (elm)->field.le_next); \
+ QMD_SAVELINK(oldprev, (elm)->field.le_prev); \
+ QMD_LIST_CHECK_NEXT(elm, field); \
+ QMD_LIST_CHECK_PREV(elm, field); \
+ if (LIST_NEXT((elm), field) != NULL) \
+ LIST_NEXT((elm), field)->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = LIST_NEXT((elm), field); \
+ TRASHIT(*oldnext); \
+ TRASHIT(*oldprev); \
+} while (0)
+
+#define LIST_SWAP(head1, head2, type, field) do { \
+ struct type *swap_tmp = LIST_FIRST((head1)); \
+ LIST_FIRST((head1)) = LIST_FIRST((head2)); \
+ LIST_FIRST((head2)) = swap_tmp; \
+ if ((swap_tmp = LIST_FIRST((head1))) != NULL) \
+ swap_tmp->field.le_prev = &LIST_FIRST((head1)); \
+ if ((swap_tmp = LIST_FIRST((head2))) != NULL) \
+ swap_tmp->field.le_prev = &LIST_FIRST((head2)); \
+} while (0)
+
+/*
+ * Tail queue declarations.
+ */
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+ TRACEBUF \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).tqh_first, TRACEBUF_INITIALIZER }
+
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+ TRACEBUF \
+}
+
+/*
+ * Tail queue functions.
+ */
+#if (defined(_KERNEL) && defined(INVARIANTS))
+#define QMD_TAILQ_CHECK_HEAD(head, field) do { \
+ if (!TAILQ_EMPTY(head) && \
+ TAILQ_FIRST((head))->field.tqe_prev != \
+ &TAILQ_FIRST((head))) \
+ panic("Bad tailq head %p first->prev != head", (head)); \
+} while (0)
+
+#define QMD_TAILQ_CHECK_TAIL(head, field) do { \
+ if (*(head)->tqh_last != NULL) \
+ panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \
+} while (0)
+
+#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \
+ if (TAILQ_NEXT((elm), field) != NULL && \
+ TAILQ_NEXT((elm), field)->field.tqe_prev != \
+ &((elm)->field.tqe_next)) \
+ panic("Bad link elm %p next->prev != elm", (elm)); \
+} while (0)
+
+#define QMD_TAILQ_CHECK_PREV(elm, field) do { \
+ if (*(elm)->field.tqe_prev != (elm)) \
+ panic("Bad link elm %p prev->next != elm", (elm)); \
+} while (0)
+#else
+#define QMD_TAILQ_CHECK_HEAD(head, field)
+#define QMD_TAILQ_CHECK_TAIL(head, headname)
+#define QMD_TAILQ_CHECK_NEXT(elm, field)
+#define QMD_TAILQ_CHECK_PREV(elm, field)
+#endif /* (_KERNEL && INVARIANTS) */
+
+#define TAILQ_CONCAT(head1, head2, field) do { \
+ if (!TAILQ_EMPTY(head2)) { \
+ *(head1)->tqh_last = (head2)->tqh_first; \
+ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ TAILQ_INIT((head2)); \
+ QMD_TRACE_HEAD(head1); \
+ QMD_TRACE_HEAD(head2); \
+ } \
+} while (0)
+
+#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+
+#define TAILQ_FOREACH(var, head, field) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var); \
+ (var) = TAILQ_NEXT((var), field))
+
+#define TAILQ_FOREACH_FROM(var, head, field) \
+ for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
+ (var); \
+ (var) = TAILQ_NEXT((var), field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
+ for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
+ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var); \
+ (var) = TAILQ_PREV((var), headname, field))
+
+#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \
+ for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
+ (var); \
+ (var) = TAILQ_PREV((var), headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \
+ for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
+ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_INIT(head) do { \
+ TAILQ_FIRST((head)) = NULL; \
+ (head)->tqh_last = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ QMD_TAILQ_CHECK_NEXT(listelm, field); \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else { \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ } \
+ TAILQ_NEXT((listelm), field) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+} while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ QMD_TAILQ_CHECK_PREV(listelm, field); \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ TAILQ_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ QMD_TAILQ_CHECK_HEAD(head, field); \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
+ TAILQ_FIRST((head))->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ TAILQ_FIRST((head)) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ QMD_TAILQ_CHECK_TAIL(head, field); \
+ TAILQ_NEXT((elm), field) = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \
+ QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \
+ QMD_TAILQ_CHECK_NEXT(elm, field); \
+ QMD_TAILQ_CHECK_PREV(elm, field); \
+ if ((TAILQ_NEXT((elm), field)) != NULL) \
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else { \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ QMD_TRACE_HEAD(head); \
+ } \
+ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
+ TRASHIT(*oldnext); \
+ TRASHIT(*oldprev); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+#define TAILQ_SWAP(head1, head2, type, field) do { \
+ struct type *swap_first = (head1)->tqh_first; \
+ struct type **swap_last = (head1)->tqh_last; \
+ (head1)->tqh_first = (head2)->tqh_first; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ (head2)->tqh_first = swap_first; \
+ (head2)->tqh_last = swap_last; \
+ if ((swap_first = (head1)->tqh_first) != NULL) \
+ swap_first->field.tqe_prev = &(head1)->tqh_first; \
+ else \
+ (head1)->tqh_last = &(head1)->tqh_first; \
+ if ((swap_first = (head2)->tqh_first) != NULL) \
+ swap_first->field.tqe_prev = &(head2)->tqh_first; \
+ else \
+ (head2)->tqh_last = &(head2)->tqh_first; \
+} while (0)
+
+#endif /* !_SYS_QUEUE_H_ */