// Copyright (C) 2022 TiltedPhoques SRL. // For licensing information see LICENSE at the root of this distribution. #include #include #include #include #include #include #include #include #include namespace Console { namespace { template static SI_Error SetIniValue(CSimpleIni& ini, const T* a_pSection, const T* a_pKey, const TVal a_nValue, const T* a_pComment = nullptr) { char szValue[64]{}; std::to_chars(szValue, szValue + sizeof(szValue), a_nValue); // convert to output text T szOutput[256]; CSimpleIni::Converter c(ini.IsUnicode()); c.ConvertFromStore(szValue, std::strlen(szValue) + 1, szOutput, sizeof(szOutput) / sizeof(T)); return ini.AddEntry(a_pSection, a_pKey, szOutput, a_pComment, false, true); } template TVal GetIniValue(CSimpleIni& ini, const T* a_pSection, const T* a_pKey, const TVal a_nDefault, bool& a_pHasMultiple) { const T* pszValue = ini.GetValue(a_pSection, a_pKey, nullptr, &a_pHasMultiple); if (!pszValue || !*pszValue) return a_nDefault; // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII char szValue[64]{}; CSimpleIni::Converter c(ini.IsUnicode()); if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { return a_nDefault; } return ConvertStringValue(szValue, a_nDefault); } std::pair SplitSection(const SettingBase* setting) { std::string nameProperty(setting->name); size_t pos = nameProperty.find_first_of(':'); std::string section = pos == std::string_view::npos ? "general" : nameProperty.substr(0, pos); auto name = &nameProperty[pos + 1]; return {section, name}; } } // namespace void SaveSettingsToIni(ConsoleRegistry& aReg, const std::filesystem::path& aPath) { CSimpleIni ini; SI_Error error{SI_Error::SI_OK}; aReg.ForAllSettings( [&](SettingBase* setting) { auto items = SplitSection(setting); auto& section = items.first; auto& name = items.second; switch (setting->type) { case SettingBase::Type::kBoolean: error = ini.SetBoolValue(section.c_str(), name.c_str(), setting->data.as_boolean); break; case SettingBase::Type::kInt: error = SetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_int32); break; case SettingBase::Type::kUInt: error = SetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_uint32); break; case SettingBase::Type::kInt64: error = SetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_int64); break; case SettingBase::Type::kUInt64: error = SetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_uint64); break; case SettingBase::Type::kFloat: error = SetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_float); break; case SettingBase::Type::kString: error = ini.SetValue(section.c_str(), name.c_str(), setting->c_str()); break; default: BASE_ASSERT(true, "SaveSettingsToIni(): Unknown type index for {}", setting->name); break; } if (error != SI_Error::SI_OK) { BASE_ASSERT(true, "Failed to write ini setting."); } }); std::string buf; error = ini.Save(buf, true); BASE_ASSERT(error == SI_Error::SI_OK, "Saving the ini failed"); // TODO(Vince): eventually we shall burn the ini library TiltedPhoques::SaveFile(aPath, TiltedPhoques::String(buf)); } void LoadSettingsFromIni(ConsoleRegistry& aReg, const std::filesystem::path& aPath) { CSimpleIni ini; { auto buf = TiltedPhoques::LoadFile(aPath); BASE_ASSERT(ini.LoadData(buf.c_str()) == SI_Error::SI_OK, "Failed to load ini data"); } aReg.ForAllSettings( [&](SettingBase* setting) { auto items = SplitSection(setting); auto& section = items.first; auto& name = items.second; bool multiMatch = false; switch (setting->type) { // With scalar types we don't expect the size to change... // However, they should all call StoreValue in the future. case SettingBase::Type::kBoolean: setting->data.as_boolean = ini.GetBoolValue(section.c_str(), name.c_str(), setting->data.as_boolean); break; case SettingBase::Type::kInt: setting->data.as_int32 = GetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_int32, multiMatch); break; case SettingBase::Type::kUInt: setting->data.as_uint32 = GetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_uint32, multiMatch); break; case SettingBase::Type::kInt64: setting->data.as_int64 = GetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_int64, multiMatch); break; case SettingBase::Type::kUInt64: setting->data.as_uint64 = GetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_uint64, multiMatch); break; case SettingBase::Type::kFloat: setting->data.as_float = GetIniValue(ini, section.c_str(), name.c_str(), setting->data.as_float, multiMatch); break; // Strings however are a special case, as it has its own allocator. case SettingBase::Type::kString: { // This is not at all how i want it to be :/ const char* c = ini.GetValue(section.c_str(), name.c_str(), setting->c_str()); static_cast(setting)->StoreValue(*setting, c); break; } default: BASE_ASSERT(true, "LoadSettingsFromIni(): Unknown type index for {}", setting->name); break; } }); } } // namespace Console