Index: mptrack/Settings.cpp
===================================================================
--- mptrack/Settings.cpp	(revision 24333)
+++ mptrack/Settings.cpp	(working copy)
@@ -140,6 +140,16 @@
 	backend->RemoveSection(section);
 }
 
+bool SettingsContainer::BackendsCanWriteMultipleSettings() const
+{
+	return backend->CanWriteMultipleSettings();
+}
+
+void SettingsContainer::BackendsWriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings)
+{
+	backend->WriteMultipleSettings(settings);
+}
+
 SettingValue SettingsContainer::ReadSetting(const SettingPath &path, const SettingValue &def) const
 {
 	ASSERT(theApp.InGuiThread());
@@ -242,6 +252,20 @@
 {
 	ASSERT(theApp.InGuiThread());
 	ASSERT(!CMainFrame::GetMainFrame() || (CMainFrame::GetMainFrame() && !CMainFrame::GetMainFrame()->InNotifyHandler())); // This is a slow path, use CachedSetting for stuff that is accessed in notify handler.
+	if(BackendsCanWriteMultipleSettings())
+	{
+		std::map<SettingPath, SettingValue> settings;
+		for(auto &[path, value] : map)
+		{
+			if(value.IsDirty())
+			{
+				settings.insert(std::make_pair(path, value.GetRefValue()));
+				value.Clean();
+			}
+		}
+		BackendsWriteMultipleSettings(settings);
+		return;
+	}
 	for(auto &[path, value] : map)
 	{
 		if(value.IsDirty())
@@ -407,7 +431,12 @@
 IniFileSettingsBackend::IniFileSettingsBackend(const mpt::PathString &filename)
 	: filename(filename)
 {
-	return;
+#if defined(UNICODE)
+	if(mpt::osinfo::windows::Version::Current().IsAtLeast(mpt::osinfo::windows::Version::WinXP))
+	{
+		ConvertToUnicode();
+	}
+#endif
 }
 
 IniFileSettingsBackend::~IniFileSettingsBackend()
@@ -493,10 +522,143 @@
 	RemoveSectionRaw(section);
 }
 
+std::set<mpt::winstring> IniFileSettingsBackend::ReadSections() const
+{
+	std::set<mpt::winstring> result;
+	const std::vector<TCHAR> sectionsstr = [&]()
+		{
+			std::vector<TCHAR> buf;
+			buf.resize(1024);
+			while(true)
+			{
+				DWORD bufused = ::GetPrivateProfileSectionNames(buf.data(), mpt::saturate_cast<DWORD>(buf.size()), filename.AsNative().c_str());
+				if(bufused >= (buf.size() - 2))
+				{
+					buf.resize(mpt::exponential_grow(buf.size()));
+					continue;
+				}
+				buf.resize(bufused);
+				break;
+			};
+			return buf;
+		}();
+	const std::vector<mpt::winstring> sections = mpt::split(mpt::winstring(sectionsstr.data(), sectionsstr.size()), mpt::winstring(_T("\0"), 1));
+	for(const auto &section : sections)
+	{
+		result.insert(section);
+	}
+	return result;
+}
 
+std::map<mpt::winstring, mpt::winstring> IniFileSettingsBackend::ReadSection(const mpt::winstring &section) const
+{
+	std::map<mpt::winstring, mpt::winstring> result;
+	const std::vector<TCHAR> keyvalsstr = [&]()
+		{
+			std::vector<TCHAR> buf;
+			buf.resize(1024);
+			while(true)
+			{
+				DWORD bufused = ::GetPrivateProfileSection(mpt::ToWin(section).c_str(), buf.data(), mpt::saturate_cast<DWORD>(buf.size()), filename.AsNative().c_str());
+				if(bufused >= (buf.size() - 2))
+				{
+					buf.resize(mpt::exponential_grow(buf.size()));
+					continue;
+				}
+				buf.resize(bufused);
+				break;
+			};
+			return buf;
+		}();
+	const std::vector<mpt::winstring> keyvals = mpt::split(mpt::winstring(keyvalsstr.data(), keyvalsstr.size()), mpt::winstring(_T("\0"), 1));
+	for(const auto &keyval : keyvals)
+	{
+		const auto equalpos = keyval.find(_T("="));
+		if(equalpos == mpt::winstring::npos)
+		{
+			continue;
+		}
+		if(equalpos == 0)
+		{
+			continue;
+		}
+		result.insert(std::make_pair(keyval.substr(0, equalpos), keyval.substr(equalpos + 1)));
+	}
+	return result;
+}
 
+mpt::winstring IniFileSettingsBackend::FormatValueAsIni(const SettingValue &value)
+{
+	switch(value.GetType())
+	{
+		case SettingTypeBool:
+			return mpt::tfmt::val(value.as<bool>());
+			break;
+		case SettingTypeInt:
+			return mpt::tfmt::val(value.as<int32>());
+			break;
+		case SettingTypeFloat:
+			return mpt::tfmt::val(value.as<double>());
+			break;
+		case SettingTypeString:
+			return mpt::ToWin(value.as<mpt::ustring>());
+			break;
+		case SettingTypeBinary:
+			{
+				std::vector<std::byte> data = value.as<std::vector<std::byte>>();
+				uint8 checksum = 0;
+				for(const std::byte x : data) {
+					checksum += mpt::byte_cast<uint8>(x);
+				}
+				return mpt::ToWin(mpt::encode_hex(mpt::as_span(data)) + mpt::ufmt::HEX0<2>(checksum));
+			}
+			break;
+		case SettingTypeNone:
+		default:
+			return mpt::ustring();
+			break;
+	}
+}
 
+void IniFileSettingsBackend::WriteSection(const mpt::winstring &section, const std::map<mpt::winstring, mpt::winstring> keyvalues)
+{
+	mpt::winstring keyvals;
+	for(const auto &[key, val] : keyvalues)
+	{
+		keyvals.append(key);
+		keyvals.append(_T("="));
+		keyvals.append(val);
+		keyvals.append(mpt::winstring(_T("\0"), 1));
+	}
+	keyvals.append(mpt::winstring(_T("\0"), 1));
+	::WritePrivateProfileSection(section.c_str(), keyvals.c_str(), filename.AsNative().c_str());
+}
 
+void IniFileSettingsBackend::WriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings)
+{
+	std::map<mpt::ustring, std::map<SettingPath, SettingValue>> sectionssettings;
+	for(const auto &[path, value] : settings)
+	{
+		sectionssettings[path.GetRefSection()][path] = value;
+	}
+	for(const auto &[section, sectionsettings] : sectionssettings)
+	{
+		std::map<mpt::winstring, mpt::winstring> workingsectionsettings = ReadSection(mpt::ToWin(section));
+		for(const auto &[path, value] : sectionsettings)
+		{
+			workingsectionsettings[mpt::ToWin(path.GetRefKey())] = FormatValueAsIni(value);
+		}
+		WriteSection(mpt::ToWin(section), workingsectionsettings);
+	}
+}
+
+bool IniFileSettingsBackend::CanWriteMultipleSettings() const
+{
+	return true;
+}
+
+
+
 IniFileSettingsContainer::IniFileSettingsContainer(const mpt::PathString &filename)
 	: IniFileSettingsBackend(filename)
 	, SettingsContainer(this)
Index: mptrack/Settings.h
===================================================================
--- mptrack/Settings.h	(revision 24333)
+++ mptrack/Settings.h	(working copy)
@@ -408,6 +408,8 @@
 	virtual void WriteSetting(const SettingPath &path, const SettingValue &val) = 0;
 	virtual void RemoveSetting(const SettingPath &path) = 0;
 	virtual void RemoveSection(const mpt::ustring &section) = 0;
+	virtual bool CanWriteMultipleSettings() const = 0;
+	virtual void WriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings) = 0;
 protected:
 	virtual ~ISettingsBackend() = default;
 };
@@ -448,6 +450,8 @@
 	void BackendsWriteSetting(const SettingPath &path, const SettingValue &val);
 	void BackendsRemoveSetting(const SettingPath &path);
 	void BackendsRemoveSection(const mpt::ustring &section);
+	bool BackendsCanWriteMultipleSettings() const;
+	void BackendsWriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings);
 	void NotifyListeners(const SettingPath &path);
 	SettingValue ReadSetting(const SettingPath &path, const SettingValue &def) const;
 	bool IsDefaultSetting(const SettingPath &path) const;
@@ -711,6 +715,14 @@
 	virtual void WriteSetting(const SettingPath &path, const SettingValue &val) override;
 	virtual void RemoveSetting(const SettingPath &path) override;
 	virtual void RemoveSection(const mpt::ustring &section) override;
+private:
+	std::set<mpt::winstring> ReadSections() const;
+	std::map<mpt::winstring, mpt::winstring> ReadSection(const mpt::winstring &section) const;
+	static mpt::winstring FormatValueAsIni(const SettingValue &value);
+	void WriteSection(const mpt::winstring &section, const std::map<mpt::winstring, mpt::winstring> keyvalues);
+public:
+	virtual bool CanWriteMultipleSettings() const override;
+	virtual void WriteMultipleSettings(const std::map<SettingPath, SettingValue> &settings) override;
 	const mpt::PathString& GetFilename() const { return filename; }
 };
 
