Index: cpp/common/nonstd_span.h
===================================================================
--- cpp/common/nonstd_span.h	(revision 1179)
+++ cpp/common/nonstd_span.h	(revision 1179)
@@ -0,0 +1,42 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 2019-2022  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
+#ifndef _NONSTD_SPAN_H_
+#define _NONSTD_SPAN_H_
+
+// Remove this and use std::span when available (c++20)
+
+template<class ElementType> class span
+{
+public:
+	constexpr span() noexcept
+	{
+		data_ = NULL; size_ = 0;
+	}
+	constexpr span(ElementType* first, size_t count) noexcept
+	{
+		data_ = first; size_ = count;
+	}
+	constexpr span(ElementType* first, ElementType* end) noexcept
+	{
+		data_ = first; size_ = end - first;
+	}
+	template<size_t N> constexpr span(ElementType(&arr)[N]) noexcept
+	{
+		data_ = arr; size_ = N;
+	}
+
+	size_t size() const { return size_; }
+	size_t size_bytes() const { return size_ * sizeof(ElementType); }
+	ElementType& operator[](size_t idx) const { return data_[idx]; }
+
+	ElementType* begin() const { return data_; }
+	ElementType* end() const { return data_ + size_; }
+
+protected:
+	ElementType* data_;
+	size_t size_;
+};
+
+#endif
Index: cpp/frams/util/sstringutils.cpp
===================================================================
--- cpp/frams/util/sstringutils.cpp	(revision 1175)
+++ cpp/frams/util/sstringutils.cpp	(revision 1179)
@@ -1,4 +1,4 @@
 // This file is a part of Framsticks SDK.  http://www.framsticks.com/
-// Copyright (C) 1999-2021  Maciej Komosinski and Szymon Ulatowski.
+// Copyright (C) 1999-2022  Maciej Komosinski and Szymon Ulatowski.
 // See LICENSE.txt for details.
 
@@ -8,4 +8,5 @@
 #include <common/nonstd.h>
 #include <common/Convert.h>
+#include <common/nonstd_span.h>
 
 int loadSString(const char* filename, SString& s, const char* framsgmodule, const char* error, bool remove_cr)
@@ -30,5 +31,5 @@
 	while (!f->Veof())
 	{
-		len = f->Vread(buf, 1, sizeof(buf));
+		len = (int)f->Vread(buf, 1, sizeof(buf));
 		s.append(buf, len);
 	}
@@ -49,5 +50,5 @@
 		if (r == NULL) break;
 		ret = true;
-		int d = strlen(r);
+		int d = (int)strlen(r);
 		if (d > 0)
 		{
@@ -416,2 +417,98 @@
 	}
 }
+
+static span<char> findEof(span<char> field, const char* pos) // 'eof' includes any number of leading '\'s and is only detected when there is nothing else in the line
+{
+	const char* eof = strstr(pos, "eof");
+	if (!eof)
+		return span<char>();
+
+	if (!(((eof + 3) == field.end()) || (eof[3] == '\n')))
+		return span<char>();
+
+	const char* begin = eof - 1;
+	for (; begin >= field.begin(); begin--)
+	{
+		if (*begin == '\n')
+			break;
+		if (*begin != '\\')
+			return span<char>();
+	}
+
+	return span<char>((char*)(begin + 1), (eof + 3) - (begin + 1));
+}
+
+bool sstringQuoteEOF(SString& target)
+{
+	span<char> field((char*)target.c_str(), target.size());
+	const char* pos = field.begin();
+
+	bool changed = false;
+
+	SString tmp;
+
+	while (true)
+	{
+		auto eof = findEof(field, pos);
+		if (eof.size())
+		{
+			if (!changed)
+			{
+				tmp.reserve(target.length() + 10);
+				changed = true;
+			}
+			tmp.append(pos, eof.begin() - pos);
+			tmp += "\\";
+			tmp.append(eof.begin(), (int)eof.size());
+			pos = eof.end();
+		}
+		else
+		{
+			if (!changed)
+				return false;
+			tmp.append(pos, field.end() - pos);
+			break;
+		}
+	}
+
+	target = tmp;
+	return true;
+}
+
+bool sstringUnquoteEOF(SString& target)
+{
+	span<char> field((char*)target.c_str(), target.size());
+	const char* pos = field.begin();
+
+	bool changed = false;
+
+	SString tmp;
+
+	while (true)
+	{
+		auto eof = findEof(field, pos);
+		if (eof.size() > 3)
+		{
+			if (!changed)
+			{
+				tmp.reserve(target.length());
+				changed = true;
+			}
+			tmp.append(pos, eof.begin() - pos);
+			tmp.append(eof.begin() + 1, (int)eof.size() - 1);
+			pos = eof.end();
+		}
+		else
+		{
+			if (eof.size() == 3) //size()==3 means bare 'eof', which cannot be unquoted (and means something went wrong somewhere else)
+				logPrintf("String", "unquoteEof", LOG_WARN, "Unexpected bare \"eof\" line");
+			if (!changed)
+				return false;
+			tmp.append(pos, field.end() - pos);
+			break;
+		}
+	}
+
+	target = tmp;
+	return true;
+}
Index: cpp/frams/util/sstringutils.h
===================================================================
--- cpp/frams/util/sstringutils.h	(revision 1175)
+++ cpp/frams/util/sstringutils.h	(revision 1179)
@@ -1,4 +1,4 @@
 // This file is a part of Framsticks SDK.  http://www.framsticks.com/
-// Copyright (C) 1999-2021  Maciej Komosinski and Szymon Ulatowski.
+// Copyright (C) 1999-2022  Maciej Komosinski and Szymon Ulatowski.
 // See LICENSE.txt for details.
 
@@ -25,4 +25,6 @@
 bool sstringURLEncode(SString& target);
 bool sstringURLDecode(SString &target);
+bool sstringQuoteEOF(SString& target);
+bool sstringUnquoteEOF(SString& target);
 
 int strFindField(const SString& txt, const SString& name, int &end);
