diff --git a/autogen/common.py b/autogen/common.py index 42bfe334..0a7ec9dc 100644 --- a/autogen/common.py +++ b/autogen/common.py @@ -198,6 +198,9 @@ def translate_type_to_lua(ptype): if ptype == 'float': return '`number`', None + + if ptype == 'double': + return '`number`', None if ptype == 'bool': return '`boolean`', None diff --git a/autogen/convert_constants.py b/autogen/convert_constants.py index 6053cb57..ebe19add 100644 --- a/autogen/convert_constants.py +++ b/autogen/convert_constants.py @@ -42,7 +42,8 @@ in_files = [ "include/geo_commands.h", "include/level_commands.h", "src/audio/external.h", - "src/game/envfx_snow.h" + "src/game/envfx_snow.h", + "src/pc/mods/mod_storage.cpp.h" ] exclude_constants = { @@ -53,7 +54,8 @@ exclude_constants = { include_constants = { 'include/geo_commands.h': ['BACKGROUND'], 'include/level_commands.h': [ "WARP_CHECKPOINT", "WARP_NO_CHECKPOINT" ], - 'src/audio/external.h': [ "SEQ_PLAYER" ] + 'src/audio/external.h': [ "SEQ_PLAYER" ], + 'src/pc/mods/mod_storage.cpp.h': [ "MAX_KEYS", "MAX_KEY_VALUE_LENGTH" ] } pretend_find = [ diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py index 21063559..d8e381ba 100644 --- a/autogen/convert_functions.py +++ b/autogen/convert_functions.py @@ -5,7 +5,7 @@ from common import * rejects = "" integer_types = ["u8", "u16", "u32", "u64", "s8", "s16", "s32", "s64", "int"] -number_types = ["f32", "float"] +number_types = ["f32", "float", "f64", "double"] param_override_build = {} out_filename = 'src/pc/lua/smlua_functions_autogen.c' out_filename_docs = 'docs/lua/functions%s.md' @@ -56,7 +56,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/mods/mod_storage.c.h", "src/pc/utils/misc.h", "src/game/level_update.h", "src/game/area.h", diff --git a/autogen/lua_definitions/constants.lua b/autogen/lua_definitions/constants.lua index 977270a2..2faab968 100644 --- a/autogen/lua_definitions/constants.lua +++ b/autogen/lua_definitions/constants.lua @@ -4775,6 +4775,12 @@ MARIO_HAND_HOLDING_WING_CAP = 4 --- @type MarioHandGSCId MARIO_HAND_RIGHT_OPEN = 5 +--- @type integer +MAX_KEYS = 512 + +--- @type integer +MAX_KEY_VALUE_LENGTH = 64 + --- @type integer PACKET_LENGTH = 3000 diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua index 009c825c..36007e76 100644 --- a/autogen/lua_definitions/functions.lua +++ b/autogen/lua_definitions/functions.lua @@ -5667,12 +5667,29 @@ function update_all_mario_stars() -- ... end +--- @return boolean +function mod_storage_clear() + -- ... +end + --- @param key string --- @return string function mod_storage_load(key) -- ... end +--- @param key string +--- @return boolean +function mod_storage_load_bool(key) + -- ... +end + +--- @param key string +--- @return number +function mod_storage_load_number(key) + -- ... +end + --- @param key string --- @param value string --- @return boolean @@ -5680,6 +5697,20 @@ function mod_storage_save(key, value) -- ... end +--- @param key string +--- @param value boolean +--- @return boolean +function mod_storage_save_bool(key, value) + -- ... +end + +--- @param key string +--- @param value number +--- @return boolean +function mod_storage_save_number(key, value) + -- ... +end + --- @param courseNum integer --- @param actNum integer --- @param levelNum integer diff --git a/docs/lua/constants.md b/docs/lua/constants.md index b2d334ff..f1a0a4d4 100644 --- a/docs/lua/constants.md +++ b/docs/lua/constants.md @@ -35,6 +35,7 @@ - [enum MarioEyesGSCId](#enum-MarioEyesGSCId) - [enum MarioGrabPosGSCId](#enum-MarioGrabPosGSCId) - [enum MarioHandGSCId](#enum-MarioHandGSCId) +- [mod_storage.cpp.h](#mod_storagecpph) - [network.h](#networkh) - [enum NetworkSystemType](#enum-NetworkSystemType) - [enum PlayerInteractions](#enum-PlayerInteractions) @@ -1692,6 +1693,14 @@
+## [mod_storage.cpp.h](#mod_storage.cpp.h) +- MAX_KEYS +- MAX_KEY_VALUE_LENGTH + +[:arrow_up_small:](#) + +
+ ## [network.h](#network.h) - PACKET_LENGTH - SYNC_DISTANCE_INFINITE diff --git a/docs/lua/functions-3.md b/docs/lua/functions-3.md index f33d3006..df4f501f 100644 --- a/docs/lua/functions-3.md +++ b/docs/lua/functions-3.md @@ -8348,11 +8348,29 @@
--- -# functions from mod_storage.h +# functions from mod_storage.c.h
+## [mod_storage_clear](#mod_storage_clear) + +### Lua Example +`local booleanValue = mod_storage_clear()` + +### Parameters +- None + +### Returns +- `boolean` + +### C Prototype +`bool mod_storage_clear(void);` + +[:arrow_up_small:](#) + +
+ ## [mod_storage_load](#mod_storage_load) ### Lua Example @@ -8373,6 +8391,46 @@
+## [mod_storage_load_bool](#mod_storage_load_bool) + +### Lua Example +`local booleanValue = mod_storage_load_bool(key)` + +### Parameters +| Field | Type | +| ----- | ---- | +| key | `string` | + +### Returns +- `boolean` + +### C Prototype +`bool mod_storage_load_bool(const char *key);` + +[:arrow_up_small:](#) + +
+ +## [mod_storage_load_number](#mod_storage_load_number) + +### Lua Example +`local numberValue = mod_storage_load_number(key)` + +### Parameters +| Field | Type | +| ----- | ---- | +| key | `string` | + +### Returns +- `number` + +### C Prototype +`double mod_storage_load_number(const char *key);` + +[:arrow_up_small:](#) + +
+ ## [mod_storage_save](#mod_storage_save) ### Lua Example @@ -8394,6 +8452,48 @@
+## [mod_storage_save_bool](#mod_storage_save_bool) + +### Lua Example +`local booleanValue = mod_storage_save_bool(key, value)` + +### Parameters +| Field | Type | +| ----- | ---- | +| key | `string` | +| value | `boolean` | + +### Returns +- `boolean` + +### C Prototype +`bool mod_storage_save_bool(const char *key, bool value);` + +[:arrow_up_small:](#) + +
+ +## [mod_storage_save_number](#mod_storage_save_number) + +### Lua Example +`local booleanValue = mod_storage_save_number(key, value)` + +### Parameters +| Field | Type | +| ----- | ---- | +| key | `string` | +| value | `number` | + +### Returns +- `boolean` + +### C Prototype +`bool mod_storage_save_number(const char *key, double value);` + +[:arrow_up_small:](#) + +
+ --- # functions from network_player.h diff --git a/docs/lua/functions.md b/docs/lua/functions.md index e80e7129..c1fc7d08 100644 --- a/docs/lua/functions.md +++ b/docs/lua/functions.md @@ -1096,9 +1096,14 @@
-- mod_storage.h +- mod_storage.c.h + - [mod_storage_clear](functions-3.md#mod_storage_clear) - [mod_storage_load](functions-3.md#mod_storage_load) + - [mod_storage_load_bool](functions-3.md#mod_storage_load_bool) + - [mod_storage_load_number](functions-3.md#mod_storage_load_number) - [mod_storage_save](functions-3.md#mod_storage_save) + - [mod_storage_save_bool](functions-3.md#mod_storage_save_bool) + - [mod_storage_save_number](functions-3.md#mod_storage_save_number)
diff --git a/src/pc/lua/smlua.c b/src/pc/lua/smlua.c index 97a3aae6..81b36729 100644 --- a/src/pc/lua/smlua.c +++ b/src/pc/lua/smlua.c @@ -249,7 +249,7 @@ void smlua_init(void) { luaL_requiref(L, "debug", luaopen_debug, 1); luaL_requiref(L, "io", luaopen_io, 1); luaL_requiref(L, "os", luaopen_os, 1); - luaL_requiref(L, "package ", luaopen_package, 1); + luaL_requiref(L, "package", luaopen_package, 1); #endif luaL_requiref(L, "math", luaopen_math, 1); luaL_requiref(L, "string", luaopen_string, 1); diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c index 7dee941e..70fe6058 100644 --- a/src/pc/lua/smlua_constants_autogen.c +++ b/src/pc/lua/smlua_constants_autogen.c @@ -1,8 +1,6 @@ char gSmluaConstants[] = "" "math.randomseed(get_time())\n" -"\n" "_CObjectPool = {}\n" -"\n" "_CObject = {\n" " __index = function (t,k)\n" " return _get_field(t['_lot'], t['_pointer'], k, t)\n" @@ -17,12 +15,10 @@ char gSmluaConstants[] = "" " return a['_pointer'] == b['_pointer'] and a['_lot'] == b['_lot'] and a['_pointer'] ~= nil and a['_lot'] ~= nil\n" " end\n" "}\n" -"\n" "function _NewCObject(lot, pointer)\n" " if _CObjectPool[lot] == nil then\n" " _CObjectPool[lot] = {}\n" " end\n" -"\n" " if _CObjectPool[lot][pointer] == nil then\n" " local obj = {}\n" " rawset(obj, '_pointer', pointer)\n" @@ -31,12 +27,9 @@ char gSmluaConstants[] = "" " _CObjectPool[lot][pointer] = obj\n" " return obj\n" " end\n" -"\n" " return _CObjectPool[lot][pointer]\n" "end\n" -"\n" "local _CPointerPool = {}\n" -"\n" "_CPointer = {\n" " __index = function (t,k)\n" " return nil\n" @@ -50,12 +43,10 @@ char gSmluaConstants[] = "" " return a['_pointer'] == b['_pointer'] and a['_pointer'] ~= nil and a['_lvt'] ~= nil\n" " end\n" "}\n" -"\n" "function _NewCPointer(lvt, pointer)\n" " if _CPointerPool[lvt] == nil then\n" " _CPointerPool[lvt] = {}\n" " end\n" -"\n" " if _CPointerPool[lvt][pointer] == nil then\n" " local obj = {}\n" " rawset(obj, '_pointer', pointer)\n" @@ -64,10 +55,8 @@ char gSmluaConstants[] = "" " _CPointerPool[lvt][pointer] = obj\n" " return obj\n" " end\n" -"\n" " return _CPointerPool[lvt][pointer]\n" "end\n" -"\n" "_SyncTable = {\n" " __index = function (t,k)\n" " local _table = rawget(t, '_table')\n" @@ -79,7 +68,6 @@ char gSmluaConstants[] = "" " _set_sync_table_field(t, k, v)\n" " end\n" "}\n" -"\n" "_ReadOnlyTable = {\n" " __index = function (t,k)\n" " local _table = rawget(t, '_table')\n" @@ -88,7 +76,6 @@ char gSmluaConstants[] = "" " __newindex = function (t,k,v)\n" " end\n" "}\n" -"\n" "--- @param dest Vec3f\n" "--- @param src Vec3f\n" "--- @return Vec3f\n" @@ -98,7 +85,6 @@ char gSmluaConstants[] = "" " dest.z = src.z\n" " return dest\n" "end\n" -"\n" "--- @param dest Vec3f\n" "--- @param x number\n" "--- @param y number\n" @@ -110,7 +96,6 @@ char gSmluaConstants[] = "" " dest.z = z\n" " return dest\n" "end\n" -"\n" "--- @param dest Vec3f\n" "--- @param a Vec3f\n" "--- @return Vec3f\n" @@ -120,7 +105,6 @@ char gSmluaConstants[] = "" " dest.z = dest.z + a.z\n" " return dest\n" "end\n" -"\n" "--- @param dest Vec3f\n" "--- @param a Vec3f\n" "--- @param b Vec3f\n" @@ -131,7 +115,6 @@ char gSmluaConstants[] = "" " dest.z = a.z + b.z\n" " return dest\n" "end\n" -"\n" "--- @param dest Vec3f\n" "--- @param a number\n" "--- @return Vec3f\n" @@ -141,7 +124,6 @@ char gSmluaConstants[] = "" " dest.z = dest.z * a\n" " return dest\n" "end\n" -"\n" "--- @param dest Vec3f\n" "--- @return Vec3f\n" "function vec3f_normalize(dest)\n" @@ -149,28 +131,23 @@ char gSmluaConstants[] = "" " if divisor == 0 then\n" " return dest\n" " end\n" -"\n" " local invsqrt = 1.0 / divisor\n" " dest.x = dest.x * invsqrt\n" " dest.y = dest.y * invsqrt\n" " dest.z = dest.z * invsqrt\n" -"\n" " return dest\n" "end\n" -"\n" "--- @param a Vec3f\n" "--- @return number\n" "function vec3f_length(a)\n" " return math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z)\n" "end\n" -"\n" "--- @param a Vec3f\n" "--- @param b Vec3f\n" "--- @return number\n" "function vec3f_dot(a, b)\n" " return a.x * b.x + a.y * b.y + a.z * b.z\n" "end\n" -"\n" "--- @param vec Vec3f\n" "--- @param onto Vec3f\n" "--- @return Vec3f\n" @@ -182,7 +159,6 @@ char gSmluaConstants[] = "" " vec3f_mul(out, numerator / denominator)\n" " return out\n" "end\n" -"\n" "--- @param v1 Vec3f\n" "--- @param v2 Vec3f\n" "--- @return number\n" @@ -192,7 +168,6 @@ char gSmluaConstants[] = "" " dz = v1.z - v2.z\n" " return math.sqrt(dx * dx + dy * dy + dz * dz)\n" "end\n" -"\n" "--- @param dest Vec3s\n" "--- @param src Vec3s\n" "--- @return Vec3s\n" @@ -202,7 +177,6 @@ char gSmluaConstants[] = "" " dest.z = src.z\n" " return dest\n" "end\n" -"\n" "--- @param dest Vec3s\n" "--- @param x number\n" "--- @param y number\n" @@ -214,7 +188,6 @@ char gSmluaConstants[] = "" " dest.z = z\n" " return dest\n" "end\n" -"\n" "--- @param dest Vec3s\n" "--- @param a Vec3s\n" "--- @return Vec3s\n" @@ -224,7 +197,6 @@ char gSmluaConstants[] = "" " dest.z = dest.z + a.z\n" " return dest\n" "end\n" -"\n" "--- @param dest Vec3s\n" "--- @param a Vec3s\n" "--- @param b Vec3s\n" @@ -235,7 +207,6 @@ char gSmluaConstants[] = "" " dest.z = a.z + b.z\n" " return dest\n" "end\n" -"\n" "--- @param dest Vec3s\n" "--- @param a number\n" "--- @return Vec3s\n" @@ -245,7 +216,6 @@ char gSmluaConstants[] = "" " dest.z = dest.z * a\n" " return dest\n" "end\n" -"\n" "--- @param v1 Vec3s\n" "--- @param v2 Vec3s\n" "--- @return number\n" @@ -255,7 +225,6 @@ char gSmluaConstants[] = "" " dz = v1.z - v2.z\n" " return math.sqrt(dx * dx + dy * dy + dz * dz)\n" "end\n" -"\n" "--- @param current number\n" "--- @param target number\n" "--- @param inc number\n" @@ -275,7 +244,6 @@ char gSmluaConstants[] = "" " end\n" " return current;\n" "end\n" -"\n" "--- @param current number\n" "--- @param target number\n" "--- @param inc number\n" @@ -293,7 +261,6 @@ char gSmluaConstants[] = "" " current = target\n" " end\n" " end\n" -"\n" " -- keep within 32 bits\n" " if current > 2147483647 then\n" " current = -2147483648 + (current - 2147483647)\n" @@ -302,7 +269,6 @@ char gSmluaConstants[] = "" " end\n" " return current;\n" "end\n" -"\n" "--- @param bank number\n" "--- @param soundID number\n" "--- @param priority number\n" @@ -312,11 +278,9 @@ char gSmluaConstants[] = "" " if flags == nil then flags = 0 end\n" " return (bank << 28) | (soundID << 16) | (priority << 8) | flags | SOUND_STATUS_WAITING\n" "end\n" -"\n" "-------------\n" "-- courses --\n" "-------------\n" -"\n" "--- @type integer\n" "COURSE_NONE = 0\n" "--- @type integer\n" @@ -1831,6 +1795,8 @@ char gSmluaConstants[] = "" "GRAB_POS_LIGHT_OBJ = 1\n" "GRAB_POS_HEAVY_OBJ = 2\n" "GRAB_POS_BOWSER = 3\n" +"MAX_KEYS = 512\n" +"MAX_KEY_VALUE_LENGTH = 64\n" "SYNC_DISTANCE_INFINITE = 0\n" "PACKET_LENGTH = 3000\n" "NS_SOCKET = 0\n" diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c index 2a86df73..f31d657b 100644 --- a/src/pc/lua/smlua_functions_autogen.c +++ b/src/pc/lua/smlua_functions_autogen.c @@ -35,7 +35,7 @@ #include "src/game/object_list_processor.h" #include "src/game/behavior_actions.h" #include "src/game/mario_misc.h" -#include "src/pc/mods/mod_storage.h" +#include "src/pc/mods/mod_storage.c.h" #include "src/pc/utils/misc.h" #include "src/game/level_update.h" #include "src/game/area.h" @@ -19353,9 +19353,24 @@ int smlua_func_update_all_mario_stars(UNUSED lua_State* L) { return 1; } - /////////////////// - // mod_storage.h // -/////////////////// + ///////////////////// + // mod_storage.c.h // +///////////////////// + +int smlua_func_mod_storage_clear(UNUSED lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top != 0) { + LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_storage_clear", 0, top); + return 0; + } + + + lua_pushboolean(L, mod_storage_clear()); + + return 1; +} int smlua_func_mod_storage_load(lua_State* L) { if (L == NULL) { return 0; } @@ -19374,6 +19389,40 @@ int smlua_func_mod_storage_load(lua_State* L) { return 1; } +int smlua_func_mod_storage_load_bool(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top != 1) { + LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_storage_load_bool", 1, top); + return 0; + } + + const char* key = smlua_to_string(L, 1); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "mod_storage_load_bool"); return 0; } + + lua_pushboolean(L, mod_storage_load_bool(key)); + + return 1; +} + +int smlua_func_mod_storage_load_number(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top != 1) { + LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_storage_load_number", 1, top); + return 0; + } + + const char* key = smlua_to_string(L, 1); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "mod_storage_load_number"); return 0; } + + lua_pushnumber(L, mod_storage_load_number(key)); + + return 1; +} + int smlua_func_mod_storage_save(lua_State* L) { if (L == NULL) { return 0; } @@ -19393,6 +19442,44 @@ int smlua_func_mod_storage_save(lua_State* L) { return 1; } +int smlua_func_mod_storage_save_bool(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top != 2) { + LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_storage_save_bool", 2, top); + return 0; + } + + const char* key = smlua_to_string(L, 1); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "mod_storage_save_bool"); return 0; } + bool value = smlua_to_boolean(L, 2); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "mod_storage_save_bool"); return 0; } + + lua_pushboolean(L, mod_storage_save_bool(key, value)); + + return 1; +} + +int smlua_func_mod_storage_save_number(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top != 2) { + LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_storage_save_number", 2, top); + return 0; + } + + const char* key = smlua_to_string(L, 1); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "mod_storage_save_number"); return 0; } + double value = smlua_to_number(L, 2); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "mod_storage_save_number"); return 0; } + + lua_pushboolean(L, mod_storage_save_number(key, value)); + + return 1; +} + ////////////////////// // network_player.h // ////////////////////// @@ -31523,9 +31610,14 @@ void smlua_bind_functions_autogen(void) { // misc.h smlua_bind_function(L, "update_all_mario_stars", smlua_func_update_all_mario_stars); - // mod_storage.h + // mod_storage.c.h + smlua_bind_function(L, "mod_storage_clear", smlua_func_mod_storage_clear); smlua_bind_function(L, "mod_storage_load", smlua_func_mod_storage_load); + smlua_bind_function(L, "mod_storage_load_bool", smlua_func_mod_storage_load_bool); + smlua_bind_function(L, "mod_storage_load_number", smlua_func_mod_storage_load_number); smlua_bind_function(L, "mod_storage_save", smlua_func_mod_storage_save); + smlua_bind_function(L, "mod_storage_save_bool", smlua_func_mod_storage_save_bool); + smlua_bind_function(L, "mod_storage_save_number", smlua_func_mod_storage_save_number); // network_player.h smlua_bind_function(L, "get_network_player_from_area", smlua_func_get_network_player_from_area); diff --git a/src/pc/mini.h b/src/pc/mini.h new file mode 100644 index 00000000..b5f327b6 --- /dev/null +++ b/src/pc/mini.h @@ -0,0 +1,789 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2018 Danijel Durakovic + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/////////////////////////////////////////////////////////////////////////////// +// +// /mINI/ v0.9.14 +// An INI file reader and writer for the modern age. +// +/////////////////////////////////////////////////////////////////////////////// +// +// A tiny utility library for manipulating INI files with a straightforward +// API and a minimal footprint. It conforms to the (somewhat) standard INI +// format - sections and keys are case insensitive and all leading and +// trailing whitespace is ignored. Comments are lines that begin with a +// semicolon. Trailing comments are allowed on section lines. +// +// Files are read on demand, upon which data is kept in memory and the file +// is closed. This utility supports lazy writing, which only writes changes +// and updates to a file and preserves custom formatting and comments. A lazy +// write invoked by a write() call will read the output file, find what +// changes have been made and update the file accordingly. If you only need to +// generate files, use generate() instead. Section and key order is preserved +// on read, write and insert. +// +/////////////////////////////////////////////////////////////////////////////// +// +// /* BASIC USAGE EXAMPLE: */ +// +// /* read from file */ +// mINI::INIFile file("myfile.ini"); +// mINI::INIStructure ini; +// file.read(ini); +// +// /* read value; gets a reference to actual value in the structure. +// if key or section don't exist, a new empty value will be created */ +// std::string& value = ini["section"]["key"]; +// +// /* read value safely; gets a copy of value in the structure. +// does not alter the structure */ +// std::string value = ini.get("section").get("key"); +// +// /* set or update values */ +// ini["section"]["key"] = "value"; +// +// /* set multiple values */ +// ini["section2"].set({ +// {"key1", "value1"}, +// {"key2", "value2"} +// }); +// +// /* write updates back to file, preserving comments and formatting */ +// file.write(ini); +// +// /* or generate a file (overwrites the original) */ +// file.generate(ini); +// +/////////////////////////////////////////////////////////////////////////////// +// +// Long live the INI file!!! +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef MINI_INI_H_ +#define MINI_INI_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mINI +{ + namespace INIStringUtil + { + const char* const whitespaceDelimiters = " \t\n\r\f\v"; + inline void trim(std::string& str) + { + str.erase(str.find_last_not_of(whitespaceDelimiters) + 1); + str.erase(0, str.find_first_not_of(whitespaceDelimiters)); + } +#ifndef MINI_CASE_SENSITIVE + inline void toLower(std::string& str) + { + std::transform(str.begin(), str.end(), str.begin(), [](const char c) { + return static_cast(std::tolower(c)); + }); + } +#endif + inline void replace(std::string& str, std::string const& a, std::string const& b) + { + if (!a.empty()) + { + std::size_t pos = 0; + while ((pos = str.find(a, pos)) != std::string::npos) + { + str.replace(pos, a.size(), b); + pos += b.size(); + } + } + } +#ifdef _WIN32 + const char* const endl = "\r\n"; +#else + const char* const endl = "\n"; +#endif + } + + template + class INIMap + { + private: + using T_DataIndexMap = std::unordered_map; + using T_DataItem = std::pair; + using T_DataContainer = std::vector; + using T_MultiArgs = typename std::vector>; + + T_DataIndexMap dataIndexMap; + T_DataContainer data; + + inline std::size_t setEmpty(std::string& key) + { + std::size_t index = data.size(); + dataIndexMap[key] = index; + data.emplace_back(key, T()); + return index; + } + + public: + using const_iterator = typename T_DataContainer::const_iterator; + + INIMap() { } + + INIMap(INIMap const& other) + { + std::size_t data_size = other.data.size(); + for (std::size_t i = 0; i < data_size; ++i) + { + auto const& key = other.data[i].first; + auto const& obj = other.data[i].second; + data.emplace_back(key, obj); + } + dataIndexMap = T_DataIndexMap(other.dataIndexMap); + } + + T& operator[](std::string key) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + bool hasIt = (it != dataIndexMap.end()); + std::size_t index = (hasIt) ? it->second : setEmpty(key); + return data[index].second; + } + T get(std::string key) const + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it == dataIndexMap.end()) + { + return T(); + } + return T(data[it->second].second); + } + bool has(std::string key) const + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + return (dataIndexMap.count(key) == 1); + } + void set(std::string key, T obj) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it != dataIndexMap.end()) + { + data[it->second].second = obj; + } + else + { + dataIndexMap[key] = data.size(); + data.emplace_back(key, obj); + } + } + void set(T_MultiArgs const& multiArgs) + { + for (auto const& it : multiArgs) + { + auto const& key = it.first; + auto const& obj = it.second; + set(key, obj); + } + } + bool remove(std::string key) + { + INIStringUtil::trim(key); +#ifndef MINI_CASE_SENSITIVE + INIStringUtil::toLower(key); +#endif + auto it = dataIndexMap.find(key); + if (it != dataIndexMap.end()) + { + std::size_t index = it->second; + data.erase(data.begin() + index); + dataIndexMap.erase(it); + for (auto& it2 : dataIndexMap) + { + auto& vi = it2.second; + if (vi > index) + { + vi--; + } + } + return true; + } + return false; + } + void clear() + { + data.clear(); + dataIndexMap.clear(); + } + std::size_t size() const + { + return data.size(); + } + const_iterator begin() const { return data.begin(); } + const_iterator end() const { return data.end(); } + }; + + using INIStructure = INIMap>; + + namespace INIParser + { + using T_ParseValues = std::pair; + + enum class PDataType : char + { + PDATA_NONE, + PDATA_COMMENT, + PDATA_SECTION, + PDATA_KEYVALUE, + PDATA_UNKNOWN + }; + + inline PDataType parseLine(std::string line, T_ParseValues& parseData) + { + parseData.first.clear(); + parseData.second.clear(); + INIStringUtil::trim(line); + if (line.empty()) + { + return PDataType::PDATA_NONE; + } + char firstCharacter = line[0]; + if (firstCharacter == ';') + { + return PDataType::PDATA_COMMENT; + } + if (firstCharacter == '[') + { + auto commentAt = line.find_first_of(';'); + if (commentAt != std::string::npos) + { + line = line.substr(0, commentAt); + } + auto closingBracketAt = line.find_last_of(']'); + if (closingBracketAt != std::string::npos) + { + auto section = line.substr(1, closingBracketAt - 1); + INIStringUtil::trim(section); + parseData.first = section; + return PDataType::PDATA_SECTION; + } + } + auto lineNorm = line; + INIStringUtil::replace(lineNorm, "\\=", " "); + auto equalsAt = lineNorm.find_first_of('='); + if (equalsAt != std::string::npos) + { + auto key = line.substr(0, equalsAt); + INIStringUtil::trim(key); + INIStringUtil::replace(key, "\\=", "="); + auto value = line.substr(equalsAt + 1); + INIStringUtil::trim(value); + parseData.first = key; + parseData.second = value; + return PDataType::PDATA_KEYVALUE; + } + return PDataType::PDATA_UNKNOWN; + } + } + + class INIReader + { + public: + using T_LineData = std::vector; + using T_LineDataPtr = std::shared_ptr; + + bool isBOM = false; + + private: + std::ifstream fileReadStream; + T_LineDataPtr lineData; + + T_LineData readFile() + { + fileReadStream.seekg(0, std::ios::end); + const std::size_t fileSize = static_cast(fileReadStream.tellg()); + fileReadStream.seekg(0, std::ios::beg); + if (fileSize >= 3) { + const char header[3] = { + static_cast(fileReadStream.get()), + static_cast(fileReadStream.get()), + static_cast(fileReadStream.get()) + }; + isBOM = ( + header[0] == static_cast(0xEF) && + header[1] == static_cast(0xBB) && + header[2] == static_cast(0xBF) + ); + } + else { + isBOM = false; + } + std::string fileContents; + fileContents.resize(fileSize); + fileReadStream.seekg(isBOM ? 3 : 0, std::ios::beg); + fileReadStream.read(&fileContents[0], fileSize); + fileReadStream.close(); + T_LineData output; + if (fileSize == 0) + { + return output; + } + std::string buffer; + buffer.reserve(50); + for (std::size_t i = 0; i < fileSize; ++i) + { + char& c = fileContents[i]; + if (c == '\n') + { + output.emplace_back(buffer); + buffer.clear(); + continue; + } + if (c != '\0' && c != '\r') + { + buffer += c; + } + } + output.emplace_back(buffer); + return output; + } + + public: + INIReader(std::string const& filename, bool keepLineData = false) + { + fileReadStream.open(filename, std::ios::in | std::ios::binary); + if (keepLineData) + { + lineData = std::make_shared(); + } + } + ~INIReader() { } + + bool operator>>(INIStructure& data) + { + if (!fileReadStream.is_open()) + { + return false; + } + T_LineData fileLines = readFile(); + std::string section; + bool inSection = false; + INIParser::T_ParseValues parseData; + for (auto const& line : fileLines) + { + auto parseResult = INIParser::parseLine(line, parseData); + if (parseResult == INIParser::PDataType::PDATA_SECTION) + { + inSection = true; + data[section = parseData.first]; + } + else if (inSection && parseResult == INIParser::PDataType::PDATA_KEYVALUE) + { + auto const& key = parseData.first; + auto const& value = parseData.second; + data[section][key] = value; + } + if (lineData && parseResult != INIParser::PDataType::PDATA_UNKNOWN) + { + if (parseResult == INIParser::PDataType::PDATA_KEYVALUE && !inSection) + { + continue; + } + lineData->emplace_back(line); + } + } + return true; + } + T_LineDataPtr getLines() + { + return lineData; + } + }; + + class INIGenerator + { + private: + std::ofstream fileWriteStream; + + public: + bool prettyPrint = false; + + INIGenerator(std::string const& filename) + { + fileWriteStream.open(filename, std::ios::out | std::ios::binary); + } + ~INIGenerator() { } + + bool operator<<(INIStructure const& data) + { + if (!fileWriteStream.is_open()) + { + return false; + } + if (!data.size()) + { + return true; + } + auto it = data.begin(); + for (;;) + { + auto const& section = it->first; + auto const& collection = it->second; + fileWriteStream + << "[" + << section + << "]"; + if (collection.size()) + { + fileWriteStream << INIStringUtil::endl; + auto it2 = collection.begin(); + for (;;) + { + auto key = it2->first; + INIStringUtil::replace(key, "=", "\\="); + auto value = it2->second; + INIStringUtil::trim(value); + fileWriteStream + << key + << ((prettyPrint) ? " = " : "=") + << value; + if (++it2 == collection.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + } + } + if (++it == data.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + if (prettyPrint) + { + fileWriteStream << INIStringUtil::endl; + } + } + return true; + } + }; + + class INIWriter + { + private: + using T_LineData = std::vector; + using T_LineDataPtr = std::shared_ptr; + + std::string filename; + + T_LineData getLazyOutput(T_LineDataPtr const& lineData, INIStructure& data, INIStructure& original) + { + T_LineData output; + INIParser::T_ParseValues parseData; + std::string sectionCurrent; + bool parsingSection = false; + bool continueToNextSection = false; + bool discardNextEmpty = false; + bool writeNewKeys = false; + std::size_t lastKeyLine = 0; + for (auto line = lineData->begin(); line != lineData->end(); ++line) + { + if (!writeNewKeys) + { + auto parseResult = INIParser::parseLine(*line, parseData); + if (parseResult == INIParser::PDataType::PDATA_SECTION) + { + if (parsingSection) + { + writeNewKeys = true; + parsingSection = false; + --line; + continue; + } + sectionCurrent = parseData.first; + if (data.has(sectionCurrent)) + { + parsingSection = true; + continueToNextSection = false; + discardNextEmpty = false; + output.emplace_back(*line); + lastKeyLine = output.size(); + } + else + { + continueToNextSection = true; + discardNextEmpty = true; + continue; + } + } + else if (parseResult == INIParser::PDataType::PDATA_KEYVALUE) + { + if (continueToNextSection) + { + continue; + } + if (data.has(sectionCurrent)) + { + auto& collection = data[sectionCurrent]; + auto const& key = parseData.first; + auto const& value = parseData.second; + if (collection.has(key)) + { + auto outputValue = collection[key]; + if (value == outputValue) + { + output.emplace_back(*line); + } + else + { + INIStringUtil::trim(outputValue); + auto lineNorm = *line; + INIStringUtil::replace(lineNorm, "\\=", " "); + auto equalsAt = lineNorm.find_first_of('='); + auto valueAt = lineNorm.find_first_not_of( + INIStringUtil::whitespaceDelimiters, + equalsAt + 1 + ); + std::string outputLine = line->substr(0, valueAt); + if (prettyPrint && equalsAt + 1 == valueAt) + { + outputLine += " "; + } + outputLine += outputValue; + output.emplace_back(outputLine); + } + lastKeyLine = output.size(); + } + } + } + else + { + if (discardNextEmpty && line->empty()) + { + discardNextEmpty = false; + } + else if (parseResult != INIParser::PDataType::PDATA_UNKNOWN) + { + output.emplace_back(*line); + } + } + } + if (writeNewKeys || std::next(line) == lineData->end()) + { + T_LineData linesToAdd; + if (data.has(sectionCurrent) && original.has(sectionCurrent)) + { + auto const& collection = data[sectionCurrent]; + auto const& collectionOriginal = original[sectionCurrent]; + for (auto const& it : collection) + { + auto key = it.first; + if (collectionOriginal.has(key)) + { + continue; + } + auto value = it.second; + INIStringUtil::replace(key, "=", "\\="); + INIStringUtil::trim(value); + linesToAdd.emplace_back( + key + ((prettyPrint) ? " = " : "=") + value + ); + } + } + if (!linesToAdd.empty()) + { + output.insert( + output.begin() + lastKeyLine, + linesToAdd.begin(), + linesToAdd.end() + ); + } + if (writeNewKeys) + { + writeNewKeys = false; + --line; + } + } + } + for (auto const& it : data) + { + auto const& section = it.first; + if (original.has(section)) + { + continue; + } + if (prettyPrint && output.size() > 0 && !output.back().empty()) + { + output.emplace_back(); + } + output.emplace_back("[" + section + "]"); + auto const& collection = it.second; + for (auto const& it2 : collection) + { + auto key = it2.first; + auto value = it2.second; + INIStringUtil::replace(key, "=", "\\="); + INIStringUtil::trim(value); + output.emplace_back( + key + ((prettyPrint) ? " = " : "=") + value + ); + } + } + return output; + } + + public: + bool prettyPrint = false; + + INIWriter(std::string const& filename) + : filename(filename) + { + } + ~INIWriter() { } + + bool operator<<(INIStructure& data) + { + struct stat buf; + bool fileExists = (stat(filename.c_str(), &buf) == 0); + if (!fileExists) + { + INIGenerator generator(filename); + generator.prettyPrint = prettyPrint; + return generator << data; + } + INIStructure originalData; + T_LineDataPtr lineData; + bool readSuccess = false; + bool fileIsBOM = false; + { + INIReader reader(filename, true); + if ((readSuccess = reader >> originalData)) + { + lineData = reader.getLines(); + fileIsBOM = reader.isBOM; + } + } + if (!readSuccess) + { + return false; + } + T_LineData output = getLazyOutput(lineData, data, originalData); + std::ofstream fileWriteStream(filename, std::ios::out | std::ios::binary); + if (fileWriteStream.is_open()) + { + if (fileIsBOM) { + const char utf8_BOM[3] = { + static_cast(0xEF), + static_cast(0xBB), + static_cast(0xBF) + }; + fileWriteStream.write(utf8_BOM, 3); + } + if (output.size()) + { + auto line = output.begin(); + for (;;) + { + fileWriteStream << *line; + if (++line == output.end()) + { + break; + } + fileWriteStream << INIStringUtil::endl; + } + } + return true; + } + return false; + } + }; + + class INIFile + { + private: + std::string filename; + + public: + INIFile(std::string const& filename) + : filename(filename) + { } + + ~INIFile() { } + + bool read(INIStructure& data) const + { + if (data.size()) + { + data.clear(); + } + if (filename.empty()) + { + return false; + } + INIReader reader(filename); + return reader >> data; + } + bool generate(INIStructure const& data, bool pretty = false) const + { + if (filename.empty()) + { + return false; + } + INIGenerator generator(filename); + generator.prettyPrint = pretty; + return generator << data; + } + bool write(INIStructure& data, bool pretty = false) const + { + if (filename.empty()) + { + return false; + } + INIWriter writer(filename); + writer.prettyPrint = pretty; + return writer << data; + } + }; +} + +#endif // MINI_INI_H_ diff --git a/src/pc/mods/mod_storage.c b/src/pc/mods/mod_storage.c deleted file mode 100644 index ab9b75c4..00000000 --- a/src/pc/mods/mod_storage.c +++ /dev/null @@ -1,165 +0,0 @@ -#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 == '-') { - 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; -} - -void 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.c.h b/src/pc/mods/mod_storage.c.h new file mode 100644 index 00000000..b5e9a16f --- /dev/null +++ b/src/pc/mods/mod_storage.c.h @@ -0,0 +1,14 @@ +#ifndef MOD_STORAGE_C_H +#define MOD_STORAGE_C_H + +bool mod_storage_save(const char *key, const char *value); +bool mod_storage_save_number(const char *key, double value); +bool mod_storage_save_bool(const char *key, bool value); + +const char *mod_storage_load(const char *key); +double mod_storage_load_number(const char *key); +bool mod_storage_load_bool(const char *key); + +bool mod_storage_clear(void); + +#endif diff --git a/src/pc/mods/mod_storage.cpp b/src/pc/mods/mod_storage.cpp new file mode 100644 index 00000000..5c02b5c0 --- /dev/null +++ b/src/pc/mods/mod_storage.cpp @@ -0,0 +1,166 @@ +#include "mod_storage.cpp.h" + +#include +#include +#include +#include "pc/mini.h" + +extern "C" { +#include "pc/platform.h" +#include "pc/mods/mod.h" +#include "pc/lua/smlua.h" +#include "pc/mods/mods_utils.h" +#include "pc/fs/fs.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 == '-') { + buffer++; + continue; + } + return false; + } + return true; +} + +void 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, (char *)".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) { + return false; + } + if (!Char_Valid((char *)key) || !Char_Valid((char *)value)) { + return false; + } + + char filename[SYS_MAX_PATH] = {0}; + 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) { + return false; + } + if (!fs_sys_dir_exists(savPath)) { fs_sys_mkdir(savPath); } + + mINI::INIFile file(filename); + mINI::INIStructure ini; + file.read(ini); + + if (ini["storage"].size() + 1 > MAX_KEYS) { + return false; + } + + ini["storage"][key] = value; + + file.write(ini); + + file.generate(ini); + + return true; +} + +bool Mod_Storage_Save_Number(const char* key, double value) { + return Mod_Storage_Save(key, std::to_string(value).c_str()); +} + +bool Mod_Storage_Save_Bool(const char* key, bool value) { + return Mod_Storage_Save(key, value ? "true" : "false"); +} + +const char *Mod_Storage_Load(const char *key) { + if (strlen(key) > MAX_KEY_VALUE_LENGTH) { + return NULL; + } + if (!Char_Valid((char *)key)) { + return NULL; + } + + char filename[SYS_MAX_PATH] = {0}; + Mod_Storage_Get_Filename(filename); + + if (!path_exists(filename)) { + return NULL; + } + + mINI::INIFile file(filename); + mINI::INIStructure ini; + file.read(ini); + + return const_cast(ini["storage"][key].c_str()); +} + +double Mod_Storage_Load_Number(const char *key) { + const char *value = Mod_Storage_Load(key); + if (value == NULL) { return 0; } + + return std::strtod(value, nullptr); +} + +bool Mod_Storage_Load_Bool(const char *key) { + const char *value = Mod_Storage_Load(key); + if (value == NULL) { return false; } + + return !strcmp(value, "true"); +} + +bool Mod_Storage_Clear(void) { + char filename[SYS_MAX_PATH] = {0}; + Mod_Storage_Get_Filename(filename); + + if (!path_exists(filename)) { + return false; + } + + mINI::INIFile file(filename); + mINI::INIStructure ini; + file.read(ini); + + if (ini["storage"].size() == 0) { + return false; + } + + ini["storage"].clear(); + + file.write(ini); + + file.generate(ini); + + return true; +} \ No newline at end of file diff --git a/src/pc/mods/mod_storage.cpp.h b/src/pc/mods/mod_storage.cpp.h new file mode 100644 index 00000000..a2a8523c --- /dev/null +++ b/src/pc/mods/mod_storage.cpp.h @@ -0,0 +1,21 @@ +#ifndef MOD_STORAGE_C_H +#define MOD_STORAGE_C_H +#ifdef __cplusplus + +#define MAX_KEYS 512 +#define MAX_KEY_VALUE_LENGTH 64 +#define SAVE_DIRECTORY "sav" +#define SAVE_EXTENSION ".sav" + +bool Mod_Storage_Save(const char *key, const char *value); +bool Mod_Storage_Save_Number(const char* key, double value); +bool Mod_Storage_Save_Bool(const char* key, bool value); + +const char *Mod_Storage_Load(const char *key); +double Mod_Storage_Load_Number(const char *key); +bool Mod_Storage_Load_Bool(const char *key); + +bool Mod_Storage_Clear(void); + +#endif +#endif \ No newline at end of file diff --git a/src/pc/mods/mod_storage.h b/src/pc/mods/mod_storage.h deleted file mode 100644 index feddb7f9..00000000 --- a/src/pc/mods/mod_storage.h +++ /dev/null @@ -1,14 +0,0 @@ -#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/mods/mod_storage_c.cpp b/src/pc/mods/mod_storage_c.cpp new file mode 100644 index 00000000..58f62d00 --- /dev/null +++ b/src/pc/mods/mod_storage_c.cpp @@ -0,0 +1,34 @@ +#include "mod_storage.cpp.h" +extern "C" { + +bool mod_storage_save(const char *key, const char *value) { + return Mod_Storage_Save(key, value); +} + +bool mod_storage_save_number(const char *key, double value) { + return Mod_Storage_Save_Number(key, value); +} + +bool mod_storage_save_bool(const char *key, bool value) { + return Mod_Storage_Save_Bool(key, value); +} + + +const char *mod_storage_load(const char *key) { + return Mod_Storage_Load(key); +} + +double mod_storage_load_number(const char *key) { + return Mod_Storage_Load_Number(key); +} + +bool mod_storage_load_bool(const char *key) { + return Mod_Storage_Load_Bool(key); +} + + +bool mod_storage_clear(void) { + return Mod_Storage_Clear(); +} + +} \ No newline at end of file