diff --git a/macos/Dependencies/dependencies.make b/macos/Dependencies/dependencies.make index 3a19ee0c..0ffd1e2c 100644 --- a/macos/Dependencies/dependencies.make +++ b/macos/Dependencies/dependencies.make @@ -95,6 +95,22 @@ $(DOWNLOADS)/sigcxx/Makefile: $(DOWNLOADS)/sigcxx/autogen.sh $(DOWNLOADS)/sigcxx/autogen.sh: $(CLONE) $(GITLAB)/mkxp-z/libsigcplusplus -b libsigc++-2-10 $(DOWNLOADS)/sigcxx + +# uchardet +uchardet: init_dirs $(LIBDIR)/libuchardet.a + +$(LIBDIR)/libuchardet.a: $(DOWNLOADS)/uchardet/cmakebuild/Makefile + cd $(DOWNLOADS)/uchardet/cmakebuild; \ + make -j$(NPROC); make install + +$(DOWNLOADS)/uchardet/cmakebuild/Makefile: $(DOWNLOADS)/uchardet/CMakeLists.txt + cd $(DOWNLOADS)/uchardet; \ + mkdir cmakebuild; cd cmakebuild; \ + $(CMAKE) -DBUILD_SHARED_LIBS=no + +$(DOWNLOADS)/uchardet/CMakeLists.txt: + $(CLONE) $(GITHUB)/freedesktop/uchardet $(DOWNLOADS)/uchardet + # Pixman pixman: init_dirs libpng $(LIBDIR)/libpixman-1.a @@ -310,5 +326,5 @@ clean-downloads: clean-compiled: -rm -rf build-$(SDK)-$(ARCH) -deps-core: libvorbis sigcxx pixman libpng libjpeg physfs sdl2 sdl2image sdlsound sdl2ttf openal openssl +deps-core: libvorbis sigcxx pixman libpng libjpeg physfs uchardet sdl2 sdl2image sdlsound sdl2ttf openal openssl everything: deps-core ruby diff --git a/macos/mkxp-z.xcodeproj/project.pbxproj b/macos/mkxp-z.xcodeproj/project.pbxproj index 03c48e0a..48b37dc7 100644 --- a/macos/mkxp-z.xcodeproj/project.pbxproj +++ b/macos/mkxp-z.xcodeproj/project.pbxproj @@ -101,6 +101,14 @@ 3B10EE092568E96A00372D13 /* binding-mri.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3B10EDF02568E96A00372D13 /* binding-mri.cpp */; }; 3B10EE0B2568E96A00372D13 /* module_rpg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3B10EDF32568E96A00372D13 /* module_rpg.cpp */; }; 3B10EE0C2568E96A00372D13 /* viewport-binding.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3B10EDF42568E96A00372D13 /* viewport-binding.cpp */; }; + 3B1BC0E1266F7C2600794D22 /* iniconfig.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3B1BC0E0266F7C0C00794D22 /* iniconfig.cpp */; }; + 3B1BC0E2266F7C2700794D22 /* iniconfig.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3B1BC0E0266F7C0C00794D22 /* iniconfig.cpp */; }; + 3B1BC0E3266F7C2700794D22 /* iniconfig.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3B1BC0E0266F7C0C00794D22 /* iniconfig.cpp */; }; + 3B1BC0E4266F7C2800794D22 /* iniconfig.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3B1BC0E0266F7C0C00794D22 /* iniconfig.cpp */; }; + 3B1BC0E8266F91E100794D22 /* libuchardet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B1BC0E7266F91E100794D22 /* libuchardet.a */; }; + 3B1BC0EA266F91FE00794D22 /* libuchardet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B1BC0E9266F91FE00794D22 /* libuchardet.a */; }; + 3B1BC0EC266F924B00794D22 /* libuchardet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B1BC0EB266F924B00794D22 /* libuchardet.a */; }; + 3B1BC0ED266F924B00794D22 /* libuchardet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B1BC0EB266F924B00794D22 /* libuchardet.a */; }; 3B1C230725A142620075EF5D /* libruby.3.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B1C230625A142620075EF5D /* libruby.3.0.dylib */; }; 3B1C230825A1426C0075EF5D /* libruby.3.0.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B1C230625A142620075EF5D /* libruby.3.0.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 3B1C230B25A144A10075EF5D /* libruby.3.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B1C230A25A144A10075EF5D /* libruby.3.0.dylib */; }; @@ -917,6 +925,12 @@ 3B10EDF32568E96A00372D13 /* module_rpg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = module_rpg.cpp; sourceTree = ""; }; 3B10EDF42568E96A00372D13 /* viewport-binding.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "viewport-binding.cpp"; sourceTree = ""; }; 3B10EE1F2569348E00372D13 /* json5pp.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = json5pp.hpp; sourceTree = ""; }; + 3B1BC0DF266F7C0C00794D22 /* iniconfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iniconfig.h; sourceTree = ""; }; + 3B1BC0E0266F7C0C00794D22 /* iniconfig.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = iniconfig.cpp; sourceTree = ""; }; + 3B1BC0E6266F8E8700794D22 /* dependencies.make */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; name = dependencies.make; path = Dependencies/dependencies.make; sourceTree = ""; }; + 3B1BC0E7266F91E100794D22 /* libuchardet.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libuchardet.a; path = "Dependencies/build-macosx-arm64/lib/libuchardet.a"; sourceTree = ""; }; + 3B1BC0E9266F91FE00794D22 /* libuchardet.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libuchardet.a; path = "Dependencies/build-macosx-x86_64/lib/libuchardet.a"; sourceTree = ""; }; + 3B1BC0EB266F924B00794D22 /* libuchardet.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libuchardet.a; path = "Dependencies/build-macosx-x86_64/lib/libuchardet.a"; sourceTree = ""; }; 3B1C230625A142620075EF5D /* libruby.3.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libruby.3.0.dylib; path = "Dependencies/build-macosx-arm64/lib/libruby.3.0.dylib"; sourceTree = ""; }; 3B1C230A25A144A10075EF5D /* libruby.3.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libruby.3.0.dylib; path = "Dependencies/build-macosx-x86_64/lib/libruby.3.0.dylib"; sourceTree = ""; }; 3B1C230D25A144BF0075EF5D /* libruby.3.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libruby.3.0.dylib; path = "Dependencies/build-macosx-universal/lib/libruby.3.0.dylib"; sourceTree = ""; }; @@ -1072,6 +1086,7 @@ 3B1C23C725A19C600075EF5D /* libcrypto.a in Frameworks */, 3B1C23C825A19C600075EF5D /* libopenal.a in Frameworks */, 3B97F77725E6182100A569B5 /* libSDL2_sound.a in Frameworks */, + 3B1BC0ED266F924B00794D22 /* libuchardet.a in Frameworks */, 3B1C23C925A19C600075EF5D /* libpixman-1.a in Frameworks */, 3B5E1F1225A881FB0086FFDC /* libGLESv2.dylib in Frameworks */, 3B1C23CA25A19C600075EF5D /* AppKit.framework in Frameworks */, @@ -1118,6 +1133,7 @@ files = ( 3BC65D622584EED10063AFF1 /* libSDL2_image.a in Frameworks */, 3BC65D5A2584EED10063AFF1 /* libvorbis.a in Frameworks */, + 3B1BC0E8266F91E100794D22 /* libuchardet.a in Frameworks */, 3B522DD2259BFF0B003301C4 /* libssl.a in Frameworks */, 3B5E1F0D25A881FB0086FFDC /* libEGL.dylib in Frameworks */, 3BC65D282584EDC60063AFF1 /* IOKit.framework in Frameworks */, @@ -1161,6 +1177,7 @@ files = ( 3BC65E2C2584F4290063AFF1 /* libvorbisenc.a in Frameworks */, 3BC65E2D2584F4290063AFF1 /* libogg.a in Frameworks */, + 3B1BC0EA266F91FE00794D22 /* libuchardet.a in Frameworks */, 3B522DCC259BFEE0003301C4 /* libssl.a in Frameworks */, 3B5E1F0B25A881FB0086FFDC /* libEGL.dylib in Frameworks */, 3BC65E222584F4290063AFF1 /* libSDL2_ttf.a in Frameworks */, @@ -1209,6 +1226,7 @@ 3B522DD8259BFF2D003301C4 /* libcrypto.a in Frameworks */, 3B5A8445256A0F6300BAF2E5 /* libopenal.a in Frameworks */, 3B97F77625E6182100A569B5 /* libSDL2_sound.a in Frameworks */, + 3B1BC0EC266F924B00794D22 /* libuchardet.a in Frameworks */, 3BE08107256879FE0006849F /* libpixman-1.a in Frameworks */, 3B5E1F1025A881FB0086FFDC /* libGLESv2.dylib in Frameworks */, 3B1C233025A16CB20075EF5D /* AppKit.framework in Frameworks */, @@ -1338,6 +1356,8 @@ 3B10ED412568E95D00372D13 /* exception.h */, 3B10ED422568E95D00372D13 /* debugwriter.h */, 3B10EE1F2569348E00372D13 /* json5pp.hpp */, + 3B1BC0DF266F7C0C00794D22 /* iniconfig.h */, + 3B1BC0E0266F7C0C00794D22 /* iniconfig.cpp */, 3B5A842B2569E8BA00BAF2E5 /* mINI.h */, ); path = util; @@ -1522,6 +1542,14 @@ path = ../binding; sourceTree = ""; }; + 3B1BC0E5266F8E2A00794D22 /* Makefiles */ = { + isa = PBXGroup; + children = ( + 3B1BC0E6266F8E8700794D22 /* dependencies.make */, + ); + name = Makefiles; + sourceTree = ""; + }; 3B1C231D25A15F8F0075EF5D /* mkxp */ = { isa = PBXGroup; children = ( @@ -1638,6 +1666,7 @@ 3BC65D492584EE690063AFF1 /* ARM64 */ = { isa = PBXGroup; children = ( + 3B1BC0E7266F91E100794D22 /* libuchardet.a */, 3BC65D4F2584EED10063AFF1 /* libfreetype.a */, 3BC65D512584EED10063AFF1 /* libogg.a */, 3BC65D562584EED10063AFF1 /* libopenal.a */, @@ -1666,6 +1695,7 @@ children = ( 3B5E1EF425A880D50086FFDC /* OpenGL ES */, 3BC65D7D2584F3780063AFF1 /* libfreetype.a */, + 3B1BC0E9266F91FE00794D22 /* libuchardet.a */, 3B522DA1259BAA13003301C4 /* libfluidsynth.dylib */, 3B1C230D25A144BF0075EF5D /* libruby.3.0.dylib */, 3BC65D822584F3780063AFF1 /* libogg.a */, @@ -1728,6 +1758,7 @@ 3BDB22EB25644FBF00C4A63D = { isa = PBXGroup; children = ( + 3B1BC0E5266F8E2A00794D22 /* Makefiles */, 3BD2B7272565B343003DAD8A /* README.md */, 3BDB23E22564546E00C4A63D /* icon.icns */, 3BDB2409256470AE00C4A63D /* Player */, @@ -1787,6 +1818,7 @@ isa = PBXGroup; children = ( 3BE080FC256879FE0006849F /* libfreetype.a */, + 3B1BC0EB266F924B00794D22 /* libuchardet.a */, 3B5A8444256A0F6300BAF2E5 /* libopenal.a */, 3BE080FD256879FE0006849F /* libogg.a */, 3BE081452568A5C60006849F /* libpng.a */, @@ -2107,6 +2139,7 @@ 3B3F7D2D25B1A73A00EA5F1C /* SettingsMenuController.mm in Sources */, 3B1C23BF25A19C600075EF5D /* filesystemImplApple.mm in Sources */, 3B1C23C025A19C600075EF5D /* fake-api.cpp in Sources */, + 3B1BC0E4266F7C2800794D22 /* iniconfig.cpp in Sources */, 3B1C23C125A19C600075EF5D /* sharedstate.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2190,6 +2223,7 @@ 3BC65D122584EDC60063AFF1 /* systemImplApple.mm in Sources */, 3BC65D132584EDC60063AFF1 /* graphics.cpp in Sources */, 3BC65D142584EDC60063AFF1 /* font.cpp in Sources */, + 3B1BC0E3266F7C2700794D22 /* iniconfig.cpp in Sources */, 3BC65D172584EDC60063AFF1 /* filesystemImplApple.mm in Sources */, 3BC65D182584EDC60063AFF1 /* fake-api.cpp in Sources */, 3BC65D192584EDC60063AFF1 /* sharedstate.cpp in Sources */, @@ -2268,6 +2302,7 @@ 3BC65DD32584F3AD0063AFF1 /* systemImplApple.mm in Sources */, 3BC65DD42584F3AD0063AFF1 /* graphics.cpp in Sources */, 3BC65DD52584F3AD0063AFF1 /* font.cpp in Sources */, + 3B1BC0E1266F7C2600794D22 /* iniconfig.cpp in Sources */, 3BC65DD82584F3AD0063AFF1 /* filesystemImplApple.mm in Sources */, 3BC65DD92584F3AD0063AFF1 /* fake-api.cpp in Sources */, 3BC65DDA2584F3AD0063AFF1 /* sharedstate.cpp in Sources */, @@ -2346,6 +2381,7 @@ 3B5A8464256A46B200BAF2E5 /* systemImplApple.mm in Sources */, 3B10EDC12568E95E00372D13 /* graphics.cpp in Sources */, 3B10EDC02568E95E00372D13 /* font.cpp in Sources */, + 3B1BC0E2266F7C2700794D22 /* iniconfig.cpp in Sources */, 3B5A840D2569BE7C00BAF2E5 /* filesystemImplApple.mm in Sources */, 3B10EDB12568E95E00372D13 /* fake-api.cpp in Sources */, 3B10EDAC2568E95E00372D13 /* sharedstate.cpp in Sources */, @@ -3025,6 +3061,7 @@ "$(inherited)", "$(PROJECT_DIR)/Dependencies/Frameworks/ANGLE", "$(PROJECT_DIR)/Dependencies/build-macosx-universal/lib", + "$(PROJECT_DIR)/Dependencies/build-macosx-x86_64/lib", ); MARKETING_VERSION = 2.2.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; @@ -3107,6 +3144,7 @@ "$(inherited)", "$(PROJECT_DIR)/Dependencies/Frameworks/ANGLE", "$(PROJECT_DIR)/Dependencies/build-macosx-universal/lib", + "$(PROJECT_DIR)/Dependencies/build-macosx-x86_64/lib", ); MARKETING_VERSION = 2.2.1; MTL_ENABLE_DEBUG_INFO = NO; @@ -3310,6 +3348,7 @@ "$(DEPENDENCY_SEARCH_PATH)/include/ruby-$(MRI_VERSION)/$(BUILD_ARCH)-darwin", "$(DEPENDENCY_SEARCH_PATH)/include/AL", "$(DEPENDENCY_SEARCH_PATH)/include/openssl", + "$(DEPENDENCY_SEARCH_PATH)/include/uchardet", "$(PROJECT_DIR)/Dependencies/Frameworks/ANGLE", ); LIBRARY_SEARCH_PATHS = ( @@ -3379,6 +3418,7 @@ "$(DEPENDENCY_SEARCH_PATH)/include/ruby-$(MRI_VERSION)/$(BUILD_ARCH)-darwin", "$(DEPENDENCY_SEARCH_PATH)/include/AL", "$(DEPENDENCY_SEARCH_PATH)/include/openssl", + "$(DEPENDENCY_SEARCH_PATH)/include/uchardet", "$(PROJECT_DIR)/Dependencies/Frameworks/ANGLE", ); LIBRARY_SEARCH_PATHS = ( diff --git a/src/config.cpp b/src/config.cpp index e97e1dd1..08f22d30 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -20,8 +20,94 @@ #include "util/json5pp.hpp" #include "util/mINI.h" +#include "util/iniconfig.h" + +#if defined(MKXPZ_BUILD_XCODE) || defined(MKXPZ_INI_ENCODING) +#include +#include +#include +#endif + namespace json = json5pp; -namespace ini = mINI; + +/* http://stackoverflow.com/a/1031773 */ +static bool validUtf8(const char *string) +{ + const uint8_t *bytes = (uint8_t*) string; + + while(*bytes) + { + if( (/* ASCII + * use bytes[0] <= 0x7F to allow ASCII control characters */ + bytes[0] == 0x09 || + bytes[0] == 0x0A || + bytes[0] == 0x0D || + (0x20 <= bytes[0] && bytes[0] <= 0x7E) + ) + ) { + bytes += 1; + continue; + } + + if( (/* non-overlong 2-byte */ + (0xC2 <= bytes[0] && bytes[0] <= 0xDF) && + (0x80 <= bytes[1] && bytes[1] <= 0xBF) + ) + ) { + bytes += 2; + continue; + } + + if( (/* excluding overlongs */ + bytes[0] == 0xE0 && + (0xA0 <= bytes[1] && bytes[1] <= 0xBF) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) + ) || + (/* straight 3-byte */ + ((0xE1 <= bytes[0] && bytes[0] <= 0xEC) || + bytes[0] == 0xEE || + bytes[0] == 0xEF) && + (0x80 <= bytes[1] && bytes[1] <= 0xBF) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) + ) || + (/* excluding surrogates */ + bytes[0] == 0xED && + (0x80 <= bytes[1] && bytes[1] <= 0x9F) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) + ) + ) { + bytes += 3; + continue; + } + + if( (/* planes 1-3 */ + bytes[0] == 0xF0 && + (0x90 <= bytes[1] && bytes[1] <= 0xBF) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) && + (0x80 <= bytes[3] && bytes[3] <= 0xBF) + ) || + (/* planes 4-15 */ + (0xF1 <= bytes[0] && bytes[0] <= 0xF3) && + (0x80 <= bytes[1] && bytes[1] <= 0xBF) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) && + (0x80 <= bytes[3] && bytes[3] <= 0xBF) + ) || + (/* plane 16 */ + bytes[0] == 0xF4 && + (0x80 <= bytes[1] && bytes[1] <= 0x8F) && + (0x80 <= bytes[2] && bytes[2] <= 0xBF) && + (0x80 <= bytes[3] && bytes[3] <= 0xBF) + ) + ) { + bytes += 4; + continue; + } + + return false; + } + + return true; +} std::string prefPath(const char *org, const char *app) { char *path = SDL_GetPrefPath(org, app); @@ -255,40 +341,82 @@ void Config::readGameINI() { } std::string iniFileName(execName + ".ini"); + SDLRWStream iniFile(iniFileName.c_str(), "r"); - if (mkxp_fs::fileExists(iniFileName.c_str())) { - ini::INIStructure iniStruct; - - if (!ini::INIFile(iniFileName).read(iniStruct)) { - Debug() << "Failed to read INI file" << iniFileName; + if (iniFile) + { + INIConfiguration ic; + if (ic.load(iniFile.stream())) + { + GUARD(game.title = ic.getStringProperty("Game", "Title");); + GUARD(game.scripts = ic.getStringProperty("Game", "Scripts");); + + strReplace(game.scripts, '\\', '/'); + + if (game.title.empty()) { + Debug() << iniFileName + ": Could not find Game.Title"; + } + + if (game.scripts.empty()) + Debug() << iniFileName + ": Could not find Game.Scripts"; } - else if (!iniStruct.has("Game")){ - Debug() << "INI is missing [Game] section"; - } - - game.title = iniStruct["Game"]["Title"]; - game.scripts = iniStruct["Game"]["Scripts"]; } + else + Debug() << "Could not read" << iniFileName; - if (game.title.empty()) { - Debug() << "INI is missing Game.Title property"; + bool convSuccess = false; + + // Attempt to convert from other encodings to UTF-8 + if (!validUtf8(game.title.c_str())) + { +#if defined(MKXPZ_BUILD_XCODE) || defined(MKXPZ_INI_ENCODING) + uchardet_t ud = uchardet_new(); + uchardet_handle_data(ud, game.title.c_str(), game.title.length()); + uchardet_data_end(ud); + const char *charset = uchardet_get_charset(ud); + + Debug() << iniFileName << ": Assuming encoding is" << charset << "..."; + iconv_t cd = iconv_open("UTF-8", charset); + + uchardet_delete(ud); + + size_t inLen = game.title.size(); + size_t outLen = inLen * 4; + std::string buf(outLen, '\0'); + char *inPtr = const_cast(game.title.c_str()); + char *outPtr = const_cast(buf.c_str()); + + errno = 0; + size_t result = iconv(cd, &inPtr, &inLen, &outPtr, &outLen); + + iconv_close(cd); + + if (result != (size_t)-1 && errno == 0) + { + buf.resize(buf.size()-outLen); + game.title = buf; + convSuccess = true; + } + else { + Debug() << iniFileName << ": failed to convert game title to UTF-8"; + } +#else + Debug() << iniFileName << ": Game title isn't valid UTF-8" +#endif + } + else + convSuccess = true; + + if (game.title.empty() || !convSuccess) game.title = "mkxp-z"; - } - if (game.scripts.empty()) - Debug() << "INI is missing Game.Scripts property"; - - if (dataPathOrg.empty()) { + if (dataPathOrg.empty()) dataPathOrg = "."; - } - if (dataPathApp.empty()) { + if (dataPathApp.empty()) dataPathApp = game.title; - } - if (!dataPathApp.empty()) { - customDataPath = prefPath(dataPathOrg.c_str(), dataPathApp.c_str()); - } + customDataPath = prefPath(dataPathOrg.c_str(), dataPathApp.c_str()); commonDataPath = prefPath(".", "mkxp-z"); diff --git a/src/util/iniconfig.cpp b/src/util/iniconfig.cpp new file mode 100644 index 00000000..f3c0485b --- /dev/null +++ b/src/util/iniconfig.cpp @@ -0,0 +1,114 @@ +#include "iniconfig.h" + +#include + +std::string toLowerCase(const std::string& str) +{ + std::string lower = str; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + return lower; +} + +std::string trim(const std::string& str, const std::string& chars = "\t\n\v\f\r ") +{ + std::string trimmed = str; + trimmed.erase(trimmed.find_last_not_of(chars) + 1); + trimmed.erase(0, trimmed.find_first_not_of(chars)); + return trimmed; +} + +INIConfiguration::Section::Section (const std::string& sname) : m_Name (sname), m_PropertyMap() +{ +} + +bool INIConfiguration::Section::getStringProperty (const std::string& name, std::string& outPropStr) const +{ + try + { + outPropStr = m_PropertyMap.at(toLowerCase(name)).m_Value; + return true; + } + catch (std::out_of_range& oorexcept) + { + return false; + } +} + +bool INIConfiguration::load (std::istream& is) +{ + if (!is.good()) + { + return false; + } + + std::string currSectionName; + + std::string line; + std::getline (is, line); + + while (!is.eof() && !is.bad()) + { + if (line[0] == '[') + { + currSectionName = line.substr (1, line.find_last_of (']') - 1); + } + else if (line[0] != '#' && line.length() > 2) + { + int crloc = line.length() - 1; + + if (crloc >= 0 && line[crloc] == '\r') //check for Windows-style newline + line.resize (crloc); //and correct + + size_t equalsPos = line.find_first_of ("="); + + if (equalsPos != std::string::npos) + { + std::string key = line.substr (0, equalsPos); + std::string val = line.substr (equalsPos + 1); + + addProperty (currSectionName, key , val); + } + } + + std::getline (is, line); + } + + if (is.bad()) + { + return false; + } + + return true; +} + + +std::string INIConfiguration::getStringProperty(const std::string& sname, const std::string& name, const std::string& def) const +{ + auto sectionIt = m_SectionMap.find(toLowerCase(sname)); + + if (sectionIt != m_SectionMap.end()) + { + std::string prop; + + if(sectionIt->second.getStringProperty(name, prop)) + { + return prop; + } + } + + return def; +} + +void INIConfiguration::addProperty (const std::string& sname, const std::string& name, const std::string& val) +{ + if (m_SectionMap.find (toLowerCase(sname)) == m_SectionMap.end()) + { + m_SectionMap.emplace (toLowerCase(sname), Section (sname)); + } + + Section::Property p; + p.m_Name = trim(name); + p.m_Value = trim(val); + + m_SectionMap.at (toLowerCase(sname)).m_PropertyMap[toLowerCase(p.m_Name)] = p; +} diff --git a/src/util/iniconfig.h b/src/util/iniconfig.h new file mode 100644 index 00000000..2907df52 --- /dev/null +++ b/src/util/iniconfig.h @@ -0,0 +1,46 @@ +#ifndef INICONFIG_H +#define INICONFIG_H + +#include +#include + +class INIConfiguration +{ + class Section + { + friend class INIConfiguration; + + struct Property + { + std::string m_Name; + std::string m_Value; + }; + + typedef std::map property_map; + public: + Section (const Section& s) = default; + Section (Section&& s) = default; + + bool getStringProperty (const std::string& name, std::string& outPropStr) const; + + private: + explicit Section (const std::string& name); + + std::string m_Name; + property_map m_PropertyMap; + }; + + typedef std::map section_map; +public: + bool load (std::istream& inStream); + + std::string getStringProperty(const std::string& sname, const std::string& name, const std::string& def = "") const; + +protected: + void addProperty (const std::string& sname, const std::string& name, const std::string& val); + +private: + section_map m_SectionMap; +}; + +#endif // INICONFIG_H