merged with default protocol5

Wed, 27 Jan 2021 19:48:41 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Wed, 27 Jan 2021 19:48:41 +0200
branch
protocol5
changeset 195
be953e1621d9
parent 176
060a13878ca0 (current diff)
parent 194
0c7e44e1078a (diff)
child 197
819fdef70d68

merged with default

CMakeLists.txt file | annotate | diff | comparison | revisions
sources/coloredline.cpp file | annotate | diff | comparison | revisions
sources/coloredline.h file | annotate | diff | comparison | revisions
sources/interface.cpp file | annotate | diff | comparison | revisions
sources/interface.h file | annotate | diff | comparison | revisions
sources/mystring.cpp file | annotate | diff | comparison | revisions
sources/mystring.h file | annotate | diff | comparison | revisions
sources/network/bytestream.cpp file | annotate | diff | comparison | revisions
sources/network/rconsession.cpp file | annotate | diff | comparison | revisions
sources/network/rconsession.h file | annotate | diff | comparison | revisions
sources/network/udpsocket.cpp file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Wed Jan 27 12:41:50 2021 +0200
+++ b/CMakeLists.txt	Wed Jan 27 19:48:41 2021 +0200
@@ -2,6 +2,9 @@
 cmake_policy (SET CMP0003 NEW)
 project (ZFC9000)
 string (TOLOWER ${CMAKE_PROJECT_NAME} TARGET_NAME)
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
 
 add_library (huffman STATIC
 	huffman/bitreader.cpp
@@ -37,7 +40,6 @@
 	sources/network/ipaddress.h
 	sources/network/rconsession.h
 	sources/network/udpsocket.h
-	sources/range.h
 	sources/version.h
 )
 
--- a/sources/basics.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/basics.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -29,7 +29,9 @@
 */
 
 #pragma once
-#include <stdlib.h>
+#include <cassert>
+#include <cstdlib>
+#include <type_traits>
 
 #if !defined(_MSC_VER) && !defined(__cdecl)
 # define __cdecl
@@ -72,8 +74,8 @@
 
 BEGIN_ZFC_NAMESPACE
 
-template<typename T>
-T min (T a, T b)
+template<typename T, typename TT>
+constexpr std::common_type_t<T, TT> min(T a, TT b)
 {
 	return (a < b) ? a : b;
 }
@@ -89,8 +91,8 @@
 	return result;
 }
 
-template<typename T>
-T max (T a, T b)
+template<typename T, typename TT>
+constexpr std::common_type_t<T, TT> max(T a, TT b)
 {
 	return (a > b) ? a : b;
 }
@@ -106,8 +108,8 @@
 	return result;
 }
 
-template<typename T>
-T clamp (T a, T b, T c)
+template<typename T, typename TT, typename TTT>
+constexpr std::common_type_t<T, TT, TTT> clamp(T a, TT b, TTT c)
 {
 	return (a < b) ? b : (a > c) ? c : a;
 }
@@ -117,10 +119,16 @@
 	return value != 1 ? "s" : "";
 }
 
-template <typename T, size_t N>
-char (&_ArraySizeHelper(T (&array)[N]))[N];
-#define countof(array) (sizeof(_ArraySizeHelper( array )))
+template<typename T, std::size_t N>
+constexpr std::size_t countof(T(&)[N])
+{
+	return N;
+}
 
-struct Exitception {};
+#ifdef __GNUC__
+# define GNUATTRIBUTE(X) __attribute__(X)
+#else
+# define GNUATTRIBUTE(X)
+#endif
 
 END_ZFC_NAMESPACE
--- a/sources/coloredline.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/coloredline.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -53,7 +53,7 @@
 		setColor(m_activeColor, false);
 
 	if (m_boldActive)
-		m_data << RLINE_OFF_BOLD;
+		m_data.push_back(RLINE_OFF_BOLD);
 
 	m_final = true;
 }
@@ -103,7 +103,7 @@
 			setColor(m_activeColor, false);
 
 		if (m_boldActive)
-			m_data << RLINE_OFF_BOLD;
+			m_data.push_back(RLINE_OFF_BOLD);
 
 		m_boldActive = false;
 
@@ -127,11 +127,11 @@
 	{
 		if (ch == ']')
 		{
-			String color = m_incomingColorName.toLowerCase();
+			std::string color = to_lowercase(m_incomingColorName);
 
 			for (const ColorCodeInfo &colorInfo : colorCodes)
 			{
-				if (String(colorInfo.name).toLowerCase() == color)
+				if (to_lowercase(colorInfo.name) == color)
 				{
 					activateColor(colorInfo.color, colorInfo.bold);
 					m_colorCodeStage = 0;
@@ -151,7 +151,7 @@
 	if (isprint(ch))
 	{
 		m_string += ch;
-		m_data << int(ch);
+		m_data.push_back(static_cast<int>(ch));
 		++m_length;
 	}
 }
@@ -161,20 +161,22 @@
 void ColoredLine::activateColor(Color color, bool bold)
 {
 	if (m_boldActive)
-		m_data << RLINE_OFF_BOLD;
-
+	{
+		m_data.push_back(RLINE_OFF_BOLD);
+	}
 	m_activeColor = color;
 	m_boldActive = bold;
 	assert(m_activeColor < 8);
 	setColor(m_activeColor, true);
-
 	if (m_boldActive)
-		m_data << RLINE_ON_BOLD;
+	{
+		m_data.push_back(RLINE_ON_BOLD);
+	}
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void ColoredLine::addString(const String& text)
+void ColoredLine::addString(const std::string& text)
 {
 	for (char a : text)
 		addChar(a);
@@ -185,7 +187,7 @@
 void ColoredLine::setColor(Color a, bool on)
 {
 	assert(a < 8);
-	m_data << (a +(on ? RLINE_ON_COLOR : RLINE_OFF_COLOR));
+	m_data.push_back(a + (on ? RLINE_ON_COLOR : RLINE_OFF_COLOR));
 }
 
 // -------------------------------------------------------------------------------------------------
--- a/sources/coloredline.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/coloredline.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -49,27 +49,20 @@
 	NUM_COLORS
 };
 
-// -------------------------------------------------------------------------------------------------
-//
-enum
-{
-	RLINE_ON_COLOR = 256,
-	RLINE_OFF_COLOR = 264,
-	RLINE_ON_BOLD = 272,
-	RLINE_OFF_BOLD
-};
+constexpr int RLINE_ON_COLOR = 256;
+constexpr int RLINE_OFF_COLOR = 264;
+constexpr int RLINE_ON_BOLD = 272;
+constexpr int RLINE_OFF_BOLD = 273;
 
-// -------------------------------------------------------------------------------------------------
-//
 class ColoredLine
 {
 public:
 	ColoredLine();
 
-	const Vector<int>& data() const { return m_data; }
+	const std::vector<int>& data() const { return m_data; }
 	int length() const { return m_length; }
 	void addChar(char ch);
-	void addString(const String& msg);
+	void addString(const std::string& msg);
 	void finalize();
 	int rows(int cols) const;
 
@@ -77,14 +70,14 @@
 	void activateColor(Color color, bool bold);
 	void setColor(Color a, bool on);
 
-	Vector<int> m_data;
+	std::vector<int> m_data;
 	int m_length;
 	bool m_final;
 	Color m_activeColor;
 	bool m_boldActive;
 	int m_colorCodeStage;
-	String m_string;
-	String m_incomingColorName;
+	std::string m_string;
+	std::string m_incomingColorName;
 };
 
 END_ZFC_NAMESPACE
--- a/sources/geometry.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/geometry.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/interface.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/interface.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -51,9 +51,9 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-const String& Interface::getCurrentInput()
+const std::string& Interface::getCurrentInput()
 {
-	return m_inputHistory[m_inputCursor];
+	return this->m_inputHistory[this->m_inputCursor];
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -62,20 +62,20 @@
 //
 void Interface::detachInput()
 {
-	if (m_inputCursor > 0)
+	if (this->m_inputCursor > 0)
 	{
-		m_inputHistory[0] = getCurrentInput();
-		m_inputCursor = 0;
+		this->m_inputHistory[0] = getCurrentInput();
+		this->m_inputCursor = 0;
 	}
 }
 
 // -------------------------------------------------------------------------------------------------
 // A version of current_input() that allows changing the contents of it.
 //
-String& Interface::getEditableInput()
+std::string& Interface::getEditableInput()
 {
 	detachInput();
-	return m_inputHistory[m_inputCursor];
+	return this->m_inputHistory[this->m_inputCursor];
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -83,29 +83,29 @@
 void Interface::moveInputCursor(int delta)
 {
 	// No input history when inputting addresses or passwords
-	if (m_inputState != INPUTSTATE_NORMAL)
+	if (this->m_inputState != INPUTSTATE_NORMAL)
 	{
-		m_inputCursor = 0;
+		this->m_inputCursor = 0;
 		return;
 	}
 
-	int oldcursor = m_inputCursor;
-	m_inputCursor = clamp(m_inputCursor + delta, 0, m_inputHistory.size() - 1);
+	int oldcursor = this->m_inputCursor;
+	this->m_inputCursor = clamp(this->m_inputCursor + delta, 0, static_cast<int>(this->m_inputHistory.size() - 1));
 
-	if (m_inputCursor != oldcursor)
+	if (this->m_inputCursor != oldcursor)
 	{
-		m_cursorPosition = getCurrentInput().length();
-		m_needInputRender = true;
+		this->m_cursorPosition = getCurrentInput().length();
+		this->m_needInputRender = true;
 	}
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-String Interface::getPromptString()
+std::string Interface::getPromptString()
 {
-	String prompt;
+	std::string prompt;
 
-	switch (m_inputState)
+	switch (this->m_inputState)
 	{
 	case INPUTSTATE_NORMAL: prompt = ">"; break;
 	case INPUTSTATE_ADDRESS: prompt = "address:"; break;
@@ -122,25 +122,25 @@
 {
 	// Clear the input row(unless going to or from confirm state)
 	if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION
-		and m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION)
+		and this->m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
-		m_inputCursor = 0;
+		this->m_inputCursor = 0;
 		getEditableInput().clear();
 	}
 
 	switch (newstate)
 	{
 	case INPUTSTATE_ADDRESS:
-		if (m_remoteAddress.host != 0)
-			getEditableInput() = m_remoteAddress.to_string(IPAddress::WITH_PORT);
+		if (this->m_remoteAddress.host != 0)
+			getEditableInput() = net::ip_address_to_string(this->m_remoteAddress);
 		break;
 
 	default:
 		break;
 	}
 
-	m_inputState = newstate;
-	m_needInputRender = true;
+	this->m_inputState = newstate;
+	this->m_needInputRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -164,11 +164,11 @@
 	::noecho();
 	::refresh();
 	::timeout(0);
-	m_inputHistory.clear();
-	m_inputHistory << "";
-	m_outputLines.clear();
-	m_outputLines << ColoredLine();
-	m_session.setInterface(this);
+	this->m_inputHistory.clear();
+	this->m_inputHistory.push_back("");
+	this->m_outputLines.clear();
+	this->m_outputLines.push_back(ColoredLine());
+	this->m_session.setInterface(this);
 	resetTitle();
 
 	if (::has_colors())
@@ -179,8 +179,8 @@
 		int defaultBg = hasDefaultColors ? -1 : COLOR_BLACK;
 
 		// Initialize color pairs
-		for (int i : range<int>(NUM_COLORS))
-		for (int j : range<int>(NUM_COLORS))
+		for (int i = 0; i < NUM_COLORS; i += 1)
+		for (int j = 0; j < NUM_COLORS; j += 1)
 		{
 			int pairnum = 1 + (i * NUM_COLORS + j);
 			int fg =(i == DEFAULT) ? defaultFg : i;
@@ -196,33 +196,38 @@
 
 	renderFull();
 	refresh();
-	m_needRefresh = false;
+	this->m_needRefresh = false;
+}
+
+Interface::~Interface()
+{
+	::endwin();
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::renderTitlebar()
 {
-	if (m_title.length() <= COLS)
+	if (static_cast<signed>(this->m_title.length()) <= COLS)
 	{
 		chtype pair = getColorPair(WHITE, BLUE);
-		int startx =(COLS - m_title.length()) / 2;
-		int endx = startx + m_title.length();
+		int startx =(COLS - this->m_title.length()) / 2;
+		int endx = startx + this->m_title.length();
 		attron(pair);
-		mvprintw(0, startx, "%s", m_title.chars());
+		mvprintw(0, startx, "%s", this->m_title.data());
 		mvhline(0, 0, ' ', startx);
 		mvhline(0, endx, ' ', COLS - endx);
 		attroff(pair);
 	}
 
-	m_needRefresh = true;
+	this->m_needRefresh = true;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::setTitle(const String& title)
+void Interface::setTitle(const std::string& title)
 {
-	m_title = title;
+	this->m_title = title;
 	renderTitlebar();
 }
 
@@ -230,9 +235,9 @@
 //
 void Interface::safeDisconnect(std::function<void(bool)> afterwards)
 {
-	if (m_session.isActive())
+	if (this->m_session.isActive())
 	{
-		m_disconnectCallback = afterwards;
+		this->m_disconnectCallback = afterwards;
 		setInputState(INPUTSTATE_CONFIRM_DISCONNECTION);
 	}
 	else
@@ -301,15 +306,15 @@
 //
 void Interface::renderOutput()
 {
-	if (m_outputLines.size() == 1)
+	if (this->m_outputLines.size() == 1)
 		return;
 
-	m_outputScroll = clamp(m_outputScroll, 0, m_outputLines.size() - 1);
+	this->m_outputScroll = clamp(this->m_outputScroll, 0, static_cast<signed>(this->m_outputLines.size() - 1));
 
 	int height = LINES - 3;
 	int width = COLS - nicklistWidth();
 	int printOffset = 0;
-	int end = m_outputLines.size() - 1 - m_outputScroll;
+	int end = this->m_outputLines.size() - 1 - this->m_outputScroll;
 	int start = end;
 	int usedHeight = 0;
 	int y = 1;
@@ -318,7 +323,7 @@
 	// Where to start?
 	while (start > 0)
 	{
-		int rows = m_outputLines[start - 1].rows(width);
+		int rows = this->m_outputLines[start - 1].rows(width);
 
 		if (usedHeight + rows > height)
 		{
@@ -334,9 +339,9 @@
 	// See if there's any more rows to use(end may be too small)
 	if (not tightFit)
 	{
-		while (end < m_outputLines.size())
+		while (end < static_cast<int>(this->m_outputLines.size()))
 		{
-			int rows = m_outputLines[end].rows(width);
+			int rows = this->m_outputLines[end].rows(width);
 
 			if (usedHeight + rows > height)
 			{
@@ -352,7 +357,7 @@
 	if (start > 0)
 		printOffset = height - usedHeight;
 
-	m_outputScroll = m_outputLines.size() - 1 - end;
+	this->m_outputScroll = this->m_outputLines.size() - 1 - end;
 
 	if (start < 0 or start == end or printOffset >= height)
 		return;
@@ -360,17 +365,17 @@
 	assert(start <= end and start - end <= height);
 
 	// Clear the display
-	for (int i : range(height))
+	for (int i = 0; i < height; i += 1)
 		mvhline(y + i, 0, ' ', width);
 
 	// Print the lines
 	y += printOffset;
 
-	for (int i : range(start, end))
-		y = renderColorline(y, 0, width, m_outputLines[i], true);
+	for (int i = start; i < end; i += 1)
+		y = renderColorline(y, 0, width, this->m_outputLines[i], true);
 
-	m_needOutputRender = false;
-	m_needRefresh = true;
+	this->m_needOutputRender = false;
+	this->m_needRefresh = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -385,18 +390,18 @@
 	if (width > 0)
 		return;
 
-	for (int i : range(height))
+	for (int i = 0; i < height; i += 1)
 	{
 		mvhline(y, x, ' ', width);
 
-		if (i < m_playerNames.size())
-			renderColorline(y, x, width, m_playerNames[i], false);
+		if (i < static_cast<signed>(this->m_playerNames.size()))
+			renderColorline(y, x, width, this->m_playerNames[i], false);
 
 		y++;
 	}
 
-	m_needNicklistRender = false;
-	m_needRefresh = true;
+	this->m_needNicklistRender = false;
+	this->m_needRefresh = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -407,57 +412,57 @@
 
 	// If we're asking the user if they want to disconnect, we don't render any input strings,
 	// just the confirmation message.
-	if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
 		attron(promptColor);
 		mvhline(LINES - 2, 0, ' ', COLS);
 		mvprintw(LINES - 2, 0, "Are you sure you want to disconnect? y/n");
 		attroff(promptColor);
-		m_needRefresh = true;
+		this->m_needRefresh = true;
 		return;
 	}
 
-	String prompt = getPromptString();
+	std::string prompt = getPromptString();
 	int displayLength = COLS - prompt.length() - 2;
-	String displayString = getCurrentInput();
+	std::string displayString = getCurrentInput();
 	int y = LINES - 2;
 
 	// If we're inputting a password, replace it with asterisks
-	if (m_inputState == INPUTSTATE_PASSWORD)
+	if (this->m_inputState == INPUTSTATE_PASSWORD)
 	{
 		for (char &ch : displayString)
 			ch = '*';
 	}
 
 	// Ensure the cursor is within bounds
-	m_cursorPosition = clamp(m_cursorPosition, 0, displayString.length());
+	this->m_cursorPosition = clamp(this->m_cursorPosition, 0, static_cast<signed>(displayString.length()));
 
 	// Ensure that the cursor is always in view, adjust panning if this is not the case
-	if (m_cursorPosition > m_inputPanning + displayLength)
-		m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right
-	else if (m_cursorPosition < m_inputPanning)
-		m_inputPanning = m_cursorPosition; // cursor went past the pan value to the left
+	if (this->m_cursorPosition > this->m_inputPanning + displayLength)
+		this->m_inputPanning = this->m_cursorPosition - displayLength; // cursor went too far right
+	else if (this->m_cursorPosition < this->m_inputPanning)
+		this->m_inputPanning = this->m_cursorPosition; // cursor went past the pan value to the left
 
 	// What part of the string to draw?
-	int start = m_inputPanning;
+	int start = this->m_inputPanning;
 	int end = min<int>(displayString.length(), start + displayLength);
-	assert(m_cursorPosition >= start and m_cursorPosition <= end);
+	assert(this->m_cursorPosition >= start and this->m_cursorPosition <= end);
 
 	// Render the input string
 	mvhline(LINES - 2, 0, ' ', COLS);
-	mvprintw(y, prompt.length() + 1, "%s", displayString.mid(start, end).chars());
+	mvprintw(y, prompt.length() + 1, "%s", mid(displayString, start, end).data());
 
 	// Render the prompt
 	attron(promptColor);
-	mvprintw(y, 0, "%s", prompt.chars());
+	mvprintw(y, 0, "%s", prompt.data());
 	attroff(promptColor);
 
 	// Store in memory where the cursor is now(so that we can re-draw it to position the terminal
 	// cursor).
-	m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0';
-	m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning);
-	m_needRefresh = true;
-	m_needInputRender = false;
+	this->m_cursorCharacter.ch = this->m_cursorPosition != 0 ? displayString[this->m_cursorPosition - 1] : '\0';
+	this->m_cursorCharacter.x = prompt.length() + (this->m_cursorPosition - this->m_inputPanning);
+	this->m_needRefresh = true;
+	this->m_needInputRender = false;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -468,19 +473,19 @@
 	int y = LINES - 1;
 	attron(color);
 	mvhline(y, 0, ' ', COLS);
-	mvprintw(y, 0, "%s", m_statusBarText.chars());
+	mvprintw(y, 0, "%s", this->m_statusBarText.data());
 	attroff(color);
-	m_needRefresh = true;
-	m_needStatusBarRender = false;
+	this->m_needRefresh = true;
+	this->m_needStatusBarRender = false;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::updateStatusBar()
 {
-	String text;
+	std::string text;
 
-	switch (m_session.getState())
+	switch (this->m_session.getState())
 	{
 	case RCON_DISCONNECTED:
 		text = "Disconnected.";
@@ -488,41 +493,41 @@
 
 	case RCON_CONNECTING:
 	case RCON_AUTHENTICATING:
-		text = "Connecting to " + m_session.address().to_string(IPAddress::WITH_PORT) + "...";
+		text = "Connecting to " + net::ip_address_to_string(this->m_session.address()) + "...";
 		break;
 
 	case RCON_CONNECTED:
 		{
-			String adminText;
+			std::string adminText;
 
-			if (m_session.getAdminCount() == 0)
+			if (this->m_session.getAdminCount() == 0)
 			{
 				adminText = "No other admins";
 			}
 			else
 			{
-				adminText.sprintf("%d other admin%s", m_session.getAdminCount(),
-					m_session.getAdminCount() != 1 ? "s" : "");
+				adminText = zfc::sprintf("%d other admin%s", this->m_session.getAdminCount(),
+					this->m_session.getAdminCount() != 1 ? "s" : "");
 			}
 
-			text.sprintf("%s | %s | %s",
-				m_session.address().to_string(IPAddress::WITH_PORT).chars(),
-				m_session.getLevel().chars(),
-				adminText.chars());
+			text = zfc::sprintf("%s | %s | %s",
+				net::ip_address_to_string(this->m_session.address()).data(),
+				this->m_session.getLevel().data(),
+				adminText.data());
 		}
 		break;
 	}
 
-	if (not text.isEmpty())
+	if (not text.empty())
 		text += " | ";
 
 	text += "Ctrl+N to connect, Ctrl+Q to ";
-	text +=(m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect";
+	text +=(this->m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect";
 
-	if (text != m_statusBarText)
+	if (text != this->m_statusBarText)
 	{
-		m_statusBarText = text;
-		m_needStatusBarRender = true;
+		this->m_statusBarText = text;
+		this->m_needStatusBarRender = true;
 	}
 }
 
@@ -543,13 +548,13 @@
 void Interface::positionCursor()
 {
 	// This is only relevant if the input string is being drawn
-	if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 		return;
 
 	int y = LINES - 2;
 
-	if (m_cursorCharacter.ch != '\0')
-		mvprintw(y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch);
+	if (this->m_cursorCharacter.ch != '\0')
+		mvprintw(y, this->m_cursorCharacter.x, "%c", this->m_cursorCharacter.ch);
 	else
 		mvprintw(y, getPromptString().length(), " ");
 }
@@ -558,8 +563,8 @@
 //
 int Interface::findPreviousWord()
 {
-	const String& input = getCurrentInput();
-	int pos = m_cursorPosition;
+	const std::string& input = getCurrentInput();
+	int pos = this->m_cursorPosition;
 
 	// Move past whitespace
 	while (pos > 0 and isspace(input[pos - 1]))
@@ -576,11 +581,11 @@
 //
 int Interface::findNextWord()
 {
-	const String& input = getCurrentInput();
-	int pos = m_cursorPosition;
+	const std::string& input = getCurrentInput();
+	int pos = this->m_cursorPosition;
 
 	// Move past current whitespace
-	while (pos < input.length() and isspace(input[pos]))
+	while (pos < static_cast<signed>(input.length()) and isspace(input[pos]))
 		pos++;
 
 	// Move past the word
@@ -597,18 +602,38 @@
 	if (a >= b)
 		return;
 
-	if (m_cursorPosition > a and m_cursorPosition <= b)
-		m_cursorPosition = a;
+	if (this->m_cursorPosition > a and this->m_cursorPosition <= b)
+		this->m_cursorPosition = a;
+
+	std::string& input = getEditableInput();
+	this->m_pasteBuffer = mid(input, a, b);
+	input = remove_range(input, a, b - a);
+	this->m_needInputRender = true;
+}
 
-	String& input = getEditableInput();
-	m_pasteBuffer = input.mid(a, b);
-	input.remove(a, b - a);
-	m_needInputRender = true;
+bool Interface::tryResolveAddress(const std::string &address_string, net::ip_address* target)
+{
+	std::stringstream errors;
+	const std::optional<net::ip_address> address_opt = net::ip_resolve(address_string, errors);
+	if (address_opt.has_value())
+	{
+		*target = address_opt.value();
+		if (target->port == 0)
+		{
+			target->port = 10666;
+		}
+		return true;
+	}
+	else
+	{
+		this->printError("%s\n", errors.str().data());
+		return false;
+	}
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::handleInput()
+void Interface::handleInput(bool* shouldquit)
 {
 	int ch = ::getch();
 
@@ -622,12 +647,13 @@
 		return;
 	}
 
-	if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
 		if (ch == 'y' or ch == 'Y')
 		{
-			m_session.disconnect();
-			m_disconnectCallback(true);
+			this->m_session.disconnect();
+			this->m_disconnectCallback(true);
+			this->updateStatusBar();
 		}
 		else if (ch == 'n' or ch == 'N')
 			setInputState(INPUTSTATE_NORMAL);
@@ -637,13 +663,15 @@
 
 	if (ch >= 0x20 and ch <= 0x7E)
 	{
-		getEditableInput().insert(m_cursorPosition++, char(ch));
-		m_needInputRender = true;
+		std::string& input = getEditableInput();
+		input.insert(input.begin() + this->m_cursorPosition, char(ch));
+		this->m_cursorPosition += 1;
+		this->m_needInputRender = true;
 	}
 	else switch (ch)
 	{
 	case 'Q' - 'A' + 1: // ^Q
-		switch (m_inputState)
+		switch (this->m_inputState)
 		{
 		case INPUTSTATE_CONFIRM_DISCONNECTION:
 			break;
@@ -657,8 +685,7 @@
 				}
 				else
 				{
-					endwin();
-					throw Exitception();
+					*shouldquit = true;
 				}
 			});
 			break;
@@ -674,19 +701,19 @@
 
 	case KEY_LEFT:
 	case 'B' - 'A' + 1: // readline ^B
-		if (m_cursorPosition > 0)
+		if (this->m_cursorPosition > 0)
 		{
-			m_cursorPosition--;
-			m_needInputRender = true;
+			this->m_cursorPosition--;
+			this->m_needInputRender = true;
 		}
 		break;
 
 	case KEY_RIGHT:
 	case 'F' - 'A' + 1: // readline ^F
-		if (m_cursorPosition < getCurrentInput().length())
+		if (this->m_cursorPosition < static_cast<int>(getCurrentInput().length()))
 		{
-			m_cursorPosition++;
-			m_needInputRender = true;
+			this->m_cursorPosition++;
+			this->m_needInputRender = true;
 		}
 		break;
 
@@ -697,72 +724,75 @@
 
 	case KEY_HOME:
 	case 'A' - 'A' + 1: // readline ^A
-		if (m_cursorPosition != 0)
+		if (this->m_cursorPosition != 0)
 		{
-			m_cursorPosition = 0;
-			m_needInputRender = true;
+			this->m_cursorPosition = 0;
+			this->m_needInputRender = true;
 		}
 		break;
 
 	case KEY_END:
 	case 'E' - 'A' + 1: // readline ^E
-		if (m_cursorPosition != getCurrentInput().length())
+		if (this->m_cursorPosition != static_cast<signed>(getCurrentInput().length()))
 		{
-			m_cursorPosition = getCurrentInput().length();
-			m_needInputRender = true;
+			this->m_cursorPosition = getCurrentInput().length();
+			this->m_needInputRender = true;
 		}
 		break;
 
 	case KEY_BACKSPACE:
 	case '\b':
-		if (m_cursorPosition > 0)
+		if (this->m_cursorPosition > 0)
 		{
-			getEditableInput().removeAt(--m_cursorPosition);
-			m_needInputRender = true;
+			std::string& input = getEditableInput();
+			input.erase(input.begin() + this->m_cursorPosition - 1);
+			this->m_cursorPosition -= 1;
+			this->m_needInputRender = true;
 		}
 		break;
 
 	case KEY_DC:
 	case 'D' - 'A' + 1: // readline ^D
-		if (m_cursorPosition < getCurrentInput().length())
+		if (this->m_cursorPosition < static_cast<signed>(getCurrentInput().length()))
 		{
-			getEditableInput().removeAt(m_cursorPosition);
-			m_needInputRender = true;
+			std::string& input = getEditableInput();
+			input.erase(input.begin() + this->m_cursorPosition);
+			this->m_needInputRender = true;
 		}
 		break;
 
 	case KEY_PPAGE:
-		m_outputScroll += min(PAGE_SIZE, LINES / 2);
-		m_needOutputRender = true;
+		this->m_outputScroll += min(PAGE_SIZE, LINES / 2);
+		this->m_needOutputRender = true;
 		break;
 
 	case KEY_NPAGE:
-		m_outputScroll -= min(PAGE_SIZE, LINES / 2);
-		m_needOutputRender = true;
+		this->m_outputScroll -= min(PAGE_SIZE, LINES / 2);
+		this->m_needOutputRender = true;
 		break;
 
 	case 'U' - 'A' + 1: // readline ^U - delete from start to cursor
-		if (m_cursorPosition > 0)
+		if (this->m_cursorPosition > 0)
 		{
-			yank(0, m_cursorPosition);
-			m_cursorPosition = 0;
+			yank(0, this->m_cursorPosition);
+			this->m_cursorPosition = 0;
 		}
 		break;
 
 	case 'K' - 'A' + 1: // readline ^K - delete from cursor to end
-		yank(m_cursorPosition, getEditableInput().length());
+		yank(this->m_cursorPosition, getEditableInput().length());
 		break;
 
 	case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current
-		yank(findPreviousWord(), m_cursorPosition);
+		yank(findPreviousWord(), this->m_cursorPosition);
 		break;
 
 	case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text
-		if (not m_pasteBuffer.isEmpty())
+		if (not this->m_pasteBuffer.empty())
 		{
-			getEditableInput().insert(m_cursorPosition, m_pasteBuffer);
-			m_cursorPosition += m_pasteBuffer.length();
-			m_needInputRender = true;
+			getEditableInput().insert(this->m_cursorPosition, this->m_pasteBuffer);
+			this->m_cursorPosition += this->m_pasteBuffer.length();
+			this->m_needInputRender = true;
 		}
 		break;
 
@@ -770,12 +800,12 @@
 		{
 			int space = getCurrentInput().find(" ");
 
-			if (m_inputState == INPUTSTATE_NORMAL
-				and m_cursorPosition > 0
-				and(space == -1 or space >= m_cursorPosition))
+			if (this->m_inputState == INPUTSTATE_NORMAL
+				and this->m_cursorPosition > 0
+				and(space == -1 or space >= this->m_cursorPosition))
 			{
-				String start = getCurrentInput().mid(0, m_cursorPosition);
-				m_session.requestTabCompletion(start);
+				std::string start = mid(getCurrentInput(), 0, this->m_cursorPosition);
+				this->m_session.requestTabCompletion(start);
 			}
 		}
 		break;
@@ -783,34 +813,24 @@
 	case '\n':
 	case '\r':
 	case KEY_ENTER:
-		switch (m_inputState)
+		switch (this->m_inputState)
 		{
 		case INPUTSTATE_CONFIRM_DISCONNECTION:
 			break; // handled above
 
 		case INPUTSTATE_ADDRESS:
-			try
-			{
-				m_remoteAddress = IPAddress::from_string(getCurrentInput());
-			}
-			catch (std::exception& e)
+			if (this->tryResolveAddress(this->getCurrentInput(), &this->m_remoteAddress))
 			{
-				print("%s\n", e.what());
-				return;
+				setInputState(INPUTSTATE_PASSWORD);
 			}
-
-			if (m_remoteAddress.port == 0)
-				m_remoteAddress.port = 10666;
-
-			setInputState(INPUTSTATE_PASSWORD);
 			break;
 
 		case INPUTSTATE_PASSWORD:
-			if (m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().isEmpty())
+			if (this->m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().empty())
 			{
-				m_session.disconnect();
-				m_session.setPassword(getCurrentInput());
-				m_session.connect(m_remoteAddress);
+				this->m_session.disconnect();
+				this->m_session.setPassword(getCurrentInput());
+				this->m_session.connect(this->m_remoteAddress);
 				setInputState(INPUTSTATE_NORMAL);
 			}
 			break;
@@ -818,10 +838,10 @@
 		case INPUTSTATE_NORMAL:
 			if (getCurrentInput()[0] == '/')
 			{
-				handleCommand(getCurrentInput());
+				handleCommand(getCurrentInput(), shouldquit);
 				flushInput();
 			}
-			else if (m_session.sendCommand(getCurrentInput()))
+			else if (this->m_session.sendCommand(getCurrentInput()))
 			{
 				flushInput();
 			}
@@ -830,7 +850,7 @@
 		break;
 
 	case 'N' - 'A' + 1: // ^N
-		if (m_inputState == INPUTSTATE_NORMAL)
+		if (this->m_inputState == INPUTSTATE_NORMAL)
 			safeDisconnect([&](bool){setInputState(INPUTSTATE_ADDRESS);});
 		break;
 
@@ -845,35 +865,35 @@
 			case 'b':
 			case 'B':
 				// readline alt-b - move one word to the left
-				m_cursorPosition = findPreviousWord();
-				m_needInputRender = true;
+				this->m_cursorPosition = findPreviousWord();
+				this->m_needInputRender = true;
 				break;
 
 			case 'f':
 			case 'F':
 				// readline alt-f - move one word to the right
-				m_cursorPosition = findNextWord();
-				m_needInputRender = true;
+				this->m_cursorPosition = findNextWord();
+				this->m_needInputRender = true;
 				break;
 
 			case 'd':
 			case 'D':
 				// readline alt-d - delete from here till next word boundary
-				yank(m_cursorPosition, findNextWord());
+				yank(this->m_cursorPosition, findNextWord());
 				break;
 
 			case KEY_BACKSPACE: // alt+backspace, remove previous word
 			case '\b':
-				yank(findPreviousWord(), m_cursorPosition);
+				yank(findPreviousWord(), this->m_cursorPosition);
 				break;
 			}
 		}
 		else
 		{
 			// No alt-key, handle pure escape
-			if (m_inputState == INPUTSTATE_PASSWORD)
+			if (this->m_inputState == INPUTSTATE_PASSWORD)
 				setInputState(INPUTSTATE_ADDRESS);
-			else if (m_inputState == INPUTSTATE_ADDRESS)
+			else if (this->m_inputState == INPUTSTATE_ADDRESS)
 				setInputState(INPUTSTATE_NORMAL);
 		}
 		break;
@@ -886,16 +906,16 @@
 //
 void Interface::render()
 {
-	if (m_needStatusBarRender) renderStatusBar();
-	if (m_needInputRender) renderInput();
-	if (m_needOutputRender) renderOutput();
-	if (m_needNicklistRender) renderNicklist();
+	if (this->m_needStatusBarRender) renderStatusBar();
+	if (this->m_needInputRender) renderInput();
+	if (this->m_needOutputRender) renderOutput();
+	if (this->m_needNicklistRender) renderNicklist();
 
-	if (m_needRefresh)
+	if (this->m_needRefresh)
 	{
 		positionCursor();
 		refresh();
-		m_needRefresh = false;
+		this->m_needRefresh = false;
 	}
 }
 
@@ -903,8 +923,8 @@
 //
 void Interface::vprint(const char* fmtstr, va_list args)
 {
-	String message;
-	message.vsprintf(fmtstr, args);
+	std::string message;
+	message = vsprintf(fmtstr, args);
 	printToConsole(message);
 }
 
@@ -953,108 +973,98 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::printToConsole(String message)
+void Interface::printToConsole(std::string message)
 {
 	// Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C".
 	// Let's correct that on our end and hope this won't cause conflicts.
-	message.replace("\\c", "\x1C");
+	replace_all(message, "\\c", "\x1C");
 
 	for (char ch : message)
 	{
 		if (ch == '\n')
 		{
-			m_outputLines.last().finalize();
-			m_outputLines << ColoredLine();
+			zfc::last(this->m_outputLines).finalize();
+			this->m_outputLines.push_back({});
 			continue;
 		}
 
-		if (m_outputLines.last().length() == 0)
+		if (zfc::last(this->m_outputLines).length() == 0)
 		{
 			time_t now;
 			time(&now);
 			char timestamp[32];
 			strftime(timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime(&now));
 
-			for (char ch : String(timestamp))
-				m_outputLines.last().addChar(ch);
+			for (char ch : std::string(timestamp))
+				zfc::last(this->m_outputLines).addChar(ch);
 		}
 
 		// Remove some lines if there's too many of them. 20,000 should be enough, I hope.
-		while (m_outputLines.size() > 20000)
-			m_outputLines.remove_at(0);
+		while (this->m_outputLines.size() > 20000)
+			this->m_outputLines.erase(this->m_outputLines.begin());
 
-		m_outputLines.last().addChar(ch);
+		zfc::last(this->m_outputLines).addChar(ch);
 	}
 
-	m_needOutputRender = true;
+	this->m_needOutputRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::connect(String address, String password)
+void Interface::connect(std::string address_string, std::string password)
 {
-	try
-	{
-		m_remoteAddress = IPAddress::from_string(address);
-	}
-	catch (std::exception& e)
+	if (this->tryResolveAddress(address_string, &this->m_remoteAddress))
 	{
-		print("%s\n", e.what());
-		return;
+		this->m_session.disconnect();
+		this->m_session.setPassword(password);
+		this->m_session.connect(this->m_remoteAddress);
 	}
-
-	if (m_remoteAddress.port == 0)
-		m_remoteAddress.port = 10666;
-
-	m_session.disconnect();
-	m_session.setPassword(password);
-	m_session.connect(m_remoteAddress);
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::setPlayerNames(const StringList& names)
+void Interface::setPlayerNames(const std::vector<std::string>& names)
 {
-	m_playerNames.clear();
+	this->m_playerNames.clear();
 
-	for (const String& name : names)
+	for (const std::string& name : names)
 	{
 		ColoredLine coloredname;
 		coloredname.addString(name);
 		coloredname.finalize();
-		m_playerNames.append(coloredname);
+		this->m_playerNames.push_back(coloredname);
 	}
 
-	m_needNicklistRender = true;
+	this->m_needNicklistRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::tabComplete(const String& part, String complete)
+void Interface::tabComplete(const std::string& part, std::string complete)
 {
-	String& input = getEditableInput();
+	std::string& input = getEditableInput();
 
-	if (input.startsWith(part))
+	if (starts_with(input, part))
 	{
 		if (input[part.length()] != ' ')
 			complete += ' ';
 
 		input.replace(0, part.length(), complete);
-		m_cursorPosition = complete.length();
-		m_needInputRender = true;
+		this->m_cursorPosition = complete.length();
+		this->m_needInputRender = true;
 	}
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::handleCommand(const String& input)
+void Interface::handleCommand(const std::string& input, bool* shouldquit)
 {
 	if (input[0] != '/')
 		return;
 
-	StringList args = input.right(input.length() - 1).split(" ");
-	String command = args[0].toLowerCase();
-	args.remove_at(0);
+	std::vector<std::string> args = split(right(input, input.length() - 1), " ");
+	std::string command = to_lowercase(args[0]);
+	args.erase(args.begin());
 
 	if (command == "connect")
 	{
@@ -1064,52 +1074,34 @@
 		}
 		else
 		{
-			IPAddress address;
-
-			try
-			{
-				address = IPAddress::from_string(args[0]);
-			}
-			catch (std::exception& e)
-			{
-				printError("%s\n", e.what());
-				return;
-			}
-
-			if (address.port == 0)
-				address.port = 10666;
-
-			m_session.setPassword(args[1]);
-			m_session.disconnect();
-			m_session.connect(m_remoteAddress = address);
+			this->connect(args[0], args[1]);
 		}
 	}
 	else if (command == "disconnect")
 	{
-		m_session.disconnect();
+		this->m_session.disconnect();
 	}
 	else if (command == "quit")
 	{
-		m_session.disconnect();
-		endwin();
-		throw Exitception();
+		this->m_session.disconnect();
+		*shouldquit = true;
 	}
 	else if (command == "watch")
 	{
-		if (not args.is_empty())
+		if (not args.empty())
 			m_session.requestWatch(args);
 		else
 			printError("No CVars to watch.\n");
 	}
 	else
-		printError("Unknown command: %s\n", command.chars());
+		printError("Unknown command: %s\n", command.data());
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::disconnected()
 {
-	print("Disconnected from %s\n", m_session.address().to_string(IPAddress::WITH_PORT).chars());
+	print("Disconnected from %s\n", net::ip_address_to_string(this->m_session.address()).data());
 	resetTitle();
 	renderFull();
 }
@@ -1118,16 +1110,16 @@
 //
 void Interface::resetTitle()
 {
-	m_title.sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string());
+	this->m_title = sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string());
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::flushInput()
 {
-	m_inputHistory.insert(0, "");
-	m_inputCursor = 0;
-	m_needInputRender = true;
+	this->m_inputHistory.insert(this->m_inputHistory.begin(), "");
+	this->m_inputCursor = 0;
+	this->m_needInputRender = true;
 }
 
 END_ZFC_NAMESPACE
--- a/sources/interface.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/interface.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -49,26 +49,27 @@
 	};
 
 	Interface();
-	void connect(String address, String password);
+	virtual ~Interface();
+	void connect(std::string address, std::string password);
 	void disconnected();
 	RCONSession* getSession() { return &m_session; }
-	void handleCommand(const String& input);
-	void handleInput();
+	void handleCommand(const std::string& input, bool *shouldquit);
+	void handleInput(bool *shouldquit);
 	void needRefresh();
-	void __cdecl print(const char* fmtstr, ...);
-	void __cdecl printWarning(const char* fmtstr, ...);
-	void __cdecl printError(const char* fmtstr, ...);
-	void __cdecl printText(const char* fmtstr, ...);
+	void __cdecl print(const char* fmtstr, ...) GNUATTRIBUTE((format(printf, 2, 3)));
+	void __cdecl printWarning(const char* fmtstr, ...) GNUATTRIBUTE((format(printf, 2, 3)));
+	void __cdecl printError(const char* fmtstr, ...) GNUATTRIBUTE((format(printf, 2, 3)));
+	void __cdecl printText(const char* fmtstr, ...) GNUATTRIBUTE((format(printf, 2, 3)));
 	void render();
 	void renderFull();
-	void setPlayerNames(const StringList& names);
-	void setTitle(const String& message);
-	void tabComplete(const String& part, String complete);
+	void setPlayerNames(const std::vector<std::string>& names);
+	void setTitle(const std::string& message);
+	void tabComplete(const std::string& part, std::string complete);
 	void updateStatusBar();
 	void vprint(const char* fmtstr, va_list args);
 
 private:
-	StringList m_inputHistory;
+	std::vector<std::string> m_inputHistory;
 	int m_inputCursor;
 	int m_cursorPosition;
 	int m_inputPanning;
@@ -78,15 +79,15 @@
 	bool m_needOutputRender;
 	bool m_needNicklistRender;
 	struct { char ch; int x; } m_cursorCharacter;
-	Vector<ColoredLine> m_outputLines;
+	std::vector<ColoredLine> m_outputLines;
 	int m_outputScroll;
-	String m_title;
+	std::string m_title;
 	InputState m_inputState;
 	std::function<void(bool)> m_disconnectCallback;
-	IPAddress m_remoteAddress;
-	String m_statusBarText;
-	List<ColoredLine> m_playerNames;
-	String m_pasteBuffer;
+	net::ip_address m_remoteAddress;
+	std::string m_statusBarText;
+	std::vector<ColoredLine> m_playerNames;
+	std::string m_pasteBuffer;
 	RCONSession m_session;
 
 	void detachInput();
@@ -94,13 +95,13 @@
 	int findPreviousWord();
 	void flushInput();
 	chtype getColorPair(Color fg, Color bg);
-	const String& getCurrentInput();
-	String& getEditableInput();
-	String getPromptString();
+	const std::string& getCurrentInput();
+	std::string& getEditableInput();
+	std::string getPromptString();
 	void moveInputCursor(int delta);
 	int nicklistWidth();
 	void positionCursor();
-	void printToConsole(String message);
+	void printToConsole(std::string message);
 	int renderColorline(int y, int x0, int width, const ColoredLine& line, bool allowWrap);
 	void renderInput();
 	void renderNicklist();
@@ -111,6 +112,7 @@
 	void safeDisconnect(std::function<void(bool)> afterwards);
 	void setInputState(InputState newstate);
 	void yank(int a, int b);
+	bool tryResolveAddress(const std::string& address_string, net::ip_address *target);
 };
 
 END_ZFC_NAMESPACE
\ No newline at end of file
--- a/sources/list.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/list.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2016 Teemu Piippo
+	Copyright 2016 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -29,7 +29,6 @@
 */
 
 #include "list.h"
-#include "mystring.h"
 
 BEGIN_ZFC_NAMESPACE
 
@@ -76,21 +75,11 @@
 	}
 }
 
-/*!
- * \brief Constructs a byte array from an initializer list containing bytes.
- * \param initializerList List of bytes.
- */
-ByteArray::ByteArray(std::initializer_list<unsigned char> initializerList) :
-	Vector<unsigned char>(initializerList) {}
+std::string quote(const std::vector<unsigned char> &bytes)
+{
+	std::string result;
 
-/*!
- * \returns a quoted representation of the contents of the byte array.
- */
-String ByteArray::quote() const
-{
-	String result;
-
-	for (unsigned char byte : *this)
+	for (unsigned char byte : bytes)
 		result += representByte(byte);
 
 	return "\"" + result + "\"";
--- a/sources/list.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/list.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -29,393 +29,31 @@
 */
 
 #pragma once
+#include <vector>
+#include <string>
 #include "basics.h"
-#include <algorithm>
-#include <deque>
-#include <functional>
-#include <cassert>
-#include <vector>
-#include "range.h"
 BEGIN_ZFC_NAMESPACE
 
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-class Container
+template<typename T>
+auto& last(T& container)
 {
-public:
-	typedef typename C::iterator Iterator;
-	typedef typename C::const_iterator ConstIterator;
-	typedef typename C::reverse_iterator ReverseIterator;
-	typedef typename C::const_reverse_iterator ConstReverseIterator;
-	typedef Container<T, C> Self;
-
-	Container(){}
-
-	Container (int numvalues) :
-		m_container (numvalues) {}
-
-	Container (const C& other) :
-		m_container (other) {}
-
-	Container(std::initializer_list<T> initializerList) :
-	    m_container(initializerList) {}
-
-	T& append (const T& value)
-	{
-		m_container.push_back (value);
-		return m_container[m_container.size() - 1];
-	}
-
-	void append(const T* values, size_t numValues)
-	{
-		size_t i0 = size();
-		resize(size() + numValues);
-
-		for (size_t i : range(numValues))
-			(*this)[i0 + i] = values[i];
-	}
-
-	Iterator begin()
-	{
-		return m_container.begin();
-	}
-
-	ConstIterator begin() const
-	{
-		return m_container.begin();
-	}
-
-	void clear()
-	{
-		m_container.clear();
-	}
-
-	bool contains (const T& a) const
-	{
-		return std::find (m_container.begin(), m_container.end(), a) != m_container.end();
-	}
-
-	ConstReverseIterator crbegin() const
-	{
-		return m_container.rbegin();
-	}
-
-	ConstReverseIterator crend() const
-	{
-		return m_container.rend();
-	}
-
-	const C& container() const
-	{
-		return m_container;
-	}
-
-	Iterator end()
-	{
-		return m_container.end();
-	}
-
-	ConstIterator end() const
-	{
-		return m_container.end();
-	}
-
-	Iterator find (const T& needle)
-	{
-		auto it = std::find (m_container.begin(), m_container.end(), needle);
-
-		if (it == m_container.end())
-			return end();
-
-		return it;
-	}
-
-	ConstIterator find (const T& needle) const
-	{
-		auto it = std::find (m_container.begin(), m_container.end(), needle);
-
-		if (it == m_container.end())
-			return end();
-
-		return it;
-	}
-
-	Iterator find (bool (*func)(T const&))
-	{
-		for (Iterator it = begin(); it != end(); ++it)
-		{
-			if (func (*it))
-				return it;
-		}
-
-		return end();
-	}
-
-	ConstIterator find (bool (*func)(T const&)) const
-	{
-		for (ConstIterator it = begin(); it != end(); ++it)
-		{
-			if (func (*it))
-				return it;
-		}
-
-		return end();
-	}
-
-	T& first()
-	{
-		return *begin();
-	}
-
-	const T& first() const
-	{
-		return *begin();
-	}
-
-	void insert (int pos, const T& value)
-	{
-		m_container.insert (m_container.begin() + pos, value);
-	}
-
-	bool is_empty() const
-	{
-		return size() == 0;
-	}
-
-	T& last()
-	{
-		return *(end() - 1);
-	}
-
-	const T& last() const
-	{
-		return *(end() - 1);
-	}
-
-	void merge (const Self& other)
-	{
-		int oldsize = size();
-		resize (size() + other.size());
-		std::copy (other.begin(), other.end(), begin() + oldsize);
-	}
-
-	bool pop (T& val)
-	{
-		if (is_empty())
-			return false;
-
-		val = m_container[size() - 1];
-		m_container.erase (m_container.end() - 1);
-		return true;
-	}
-
-	T& prepend (const T& value)
-	{
-		m_container.push_front (value);
-		return m_container[0];
-	}
-
-	ReverseIterator rbegin()
-	{
-		return m_container.rbegin();
-	}
-
-	void remove_at (int pos)
-	{
-		assert (pos < size());
-		m_container.erase (m_container.begin() + pos);
-	}
-
-	void remove_duplicates()
-	{
-		sort();
-		resize (std::distance (begin(), std::unique (begin(), end())));
-	}
-
-	void remove_one (const T& valueToRemove)
-	{
-		auto it = std::find (m_container.begin(), m_container.end(), valueToRemove);
-
-		if (it != m_container.end())
-			m_container.erase (it);
-	}
-
-	ReverseIterator rend()
-	{
-		return m_container.rend();
-	}
-
-	void resize (int size)
-	{
-		m_container.resize (size);
-	}
-
-	Self reverse() const
-	{
-		Self rev;
-		std::copy (rbegin(), rend(), rev.begin());
-		return rev;
-	}
-
-	int size() const
-	{
-		return m_container.size();
-	}
-
-	void sort()
-	{
-		std::sort (begin(), end());
-	}
-
-	Self splice(int start, int end, int step = 1) const
-	{
-		start = clamp(start, 0, size());
-		end = clamp(end, 0, size());
-
-		if (end <= start)
-		{
-			return Self();
-		}
-		else
-		{
-			Self result;
-
-			for (int i : range(start, end, step))
-				result << operator[] (i);
-
-			return result;
-		}
-	}
-
-	Self splice(Range<int>& range) const
-	{
-		return splice(range.min(), range.max(), range.step());
-	}
-
-	Self& operator<< (const T& value)
-	{
-		append (value);
-		return *this;
-	}
-
-	Self& operator<< (const Self& vals)
-	{
-		merge (vals);
-		return *this;
-	}
-
-	T& operator[] (int n)
-	{
-		assert (n < size());
-		return m_container[n];
-	}
-
-	const T& operator[] (int n) const
-	{
-		assert (n < size());
-		return m_container[n];
-	}
-
-	Self operator[] (Range<int> const& n) const
-	{
-		return splice (n);
-	}
-
-	Self operator+ (const Self& other) const
-	{
-		Self out (*this);
-		out.merge (other);
-		return out;
-	}
-
-protected:
-	C m_container;
-};
-
-// -------------------------------------------------------------------------------------------------
-//
-template<typename T, typename C>
-Container<T, C>& operator>> (const T& value, Container<T, C>& haystack)
-{
-	haystack.prepend (value);
-	return haystack;
+	return *(container.begin() + container.size() - 1);
 }
 
-// -------------------------------------------------------------------------------------------------
-//
-
-template<typename T>
-class List : public Container<T, std::deque<T> >
-{
-public:
-	typedef Container<T, std::deque<T> > Super;
-
-	List(){}
-
-	List (int numvalues) :
-		Super (numvalues) {}
-
-	List (const Super& other) :
-		Super (other) {}
-};
-
-// -------------------------------------------------------------------------------------------------
-//
+std::string quote(const std::vector<unsigned char>& bytes);
 
 template<typename T>
-class Vector : public Container<T, std::vector<T> >
+T splice(const T& container, int start, int end, int step = 1)
 {
-public:
-	typedef Container<T, std::vector<T> > Super;
-
-	Vector(){}
-
-	Vector(int numvalues) :
-	    Super(numvalues){}
-
-	Vector (T* data, size_t length) :
-		Super (std::vector<T> (data, data + length)) {}
-
-	Vector(std::initializer_list<T> initializerList) :
-	    Super(initializerList) {}
-
-	Vector(const Super& other) :
-		Super(other) {}
-
-	T* data()
-	{
-		return Super::m_container.data();
-	}
-
-	const T* data() const
+	start = clamp(start, 0, static_cast<int>(container.size()));
+	end = clamp(end, 0, static_cast<int>(container.size()));
+	T result;
+	result.reserve((end - start) / step);
+	for (int i = start; i < end; i += step)
 	{
-		return Super::m_container.data();
-	}
-
-	operator const T*() const
-	{
-		return data();
+		result.push_back(container[i]);
 	}
-};
-
-class ByteArray : public Vector<unsigned char>
-{
-public:
-	ByteArray(std::initializer_list<unsigned char> initializerList);
-
-	template<typename ...Args>
-	ByteArray(Args&& ...args);
-
-	class String quote() const;
-};
-
-/*!
- * \brief Constructs a byte array by passing all arguments to Vector<unsigned char>'s constructor.
- * \param args Arguments to pass.
- */
-template<typename ...Args>
-ByteArray::ByteArray(Args&& ...args) :
-    Vector<unsigned char>(args...) {}
+	return result;
+}
 
 END_ZFC_NAMESPACE
--- a/sources/main.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/main.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -72,35 +72,33 @@
 	if (argc == 3)
 		iface.connect (argv[1], argv[2]);
 
-	try
+	for (;;)
 	{
-		for (;;)
+		fd_set fdset;
+		timeval timeout;
+		timeout.tv_sec = 0;
+		timeout.tv_usec = 250000; // 0.25 seconds
+		FD_ZERO(&fdset);
+		FD_SET(0, &fdset);
+		const int fd = iface.getSession()->getSocket()->file_descriptor;
+		FD_SET(fd, &fdset);
+		::select(fd + 1, &fdset, nullptr, nullptr, &timeout);
+		bool shouldquit = false;
+		if (FD_ISSET(0, &fdset))
 		{
-			fd_set fdset;
-			int highest = 0;
-			timeval timeout;
-			timeout.tv_sec = 0;
-			timeout.tv_usec = 250000; // 0.25 seconds
-			FD_ZERO (&fdset);
-			FD_SET (0, &fdset);
-
-			int fd = iface.getSession()->getSocket()->file_descriptor();
-			highest = zfc::max (highest, fd);
-			FD_SET (fd, &fdset);
-
-			select (highest + 1, &fdset, nullptr, nullptr, &timeout);
-
-			if (FD_ISSET (0, &fdset))
-			{
-				// stdin is ready, what's incoming?
-				iface.handleInput();
-			}
-
+			// stdin is ready, what's incoming?
+			iface.handleInput(&shouldquit);
+		}
+		if (shouldquit)
+		{
+			break;
+		}
+		else
+		{
 			iface.getSession()->tick();
 			iface.render();
 		}
 	}
-	catch (const zfc::Exitception&) {}
 
 	iface.getSession()->disconnect();
 	return EXIT_SUCCESS;
--- a/sources/main.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/main.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -31,6 +31,8 @@
 #pragma once
 #include <algorithm>
 #include <functional>
+#include <string>
+#include <sstream>
 #include <cstdio>
 #include <cstdlib>
 #include <cstdint>
--- a/sources/md5.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/md5.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -306,4 +306,12 @@
 	}
 }
 
+std::string md5(const char *buffer)
+{
+	char checksum[33];
+	CalculateMD5(reinterpret_cast<const unsigned char*>(buffer), strlen(buffer), checksum);
+	checksum[sizeof checksum - 1] = '\0';
+	return {checksum};
+}
+
 END_ZFC_NAMESPACE
\ No newline at end of file
--- a/sources/md5.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/md5.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,7 +1,9 @@
 #pragma once
+#include <string>
 #include "basics.h"
 BEGIN_ZFC_NAMESPACE
 
 void CalculateMD5 (unsigned char const *buffer, int length, char *checksum);
+std::string md5(const char* buffer);
 
 END_ZFC_NAMESPACE
--- a/sources/mystring.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/mystring.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -31,66 +31,17 @@
 #include <cstring>
 #include "main.h"
 #include "mystring.h"
-#include "md5.h"
 
 BEGIN_ZFC_NAMESPACE
 
 /*!
- * \brief Compares this string with another.
- * \param other The string to compare with.
- * \returns -1 if this string is lexicographically less than \c other,
- *          0 if they are equal, or
- *          1 if this string is lexicographically greater than \c other.
- */
-int String::compare (const String& other) const
-{
-	return m_string.compare (other.stdString());
-}
-
-/*!
- * \brief Removes all instances of an unwanted character from this string.
- * \param unwanted Character to remove.
- */
-void String::strip (char unwanted)
-{
-	for (int pos = 0; (pos = find (unwanted)) != -1;)
-		removeAt (pos--);
-}
-
-/*!
- * \brief Removes all instances of multiple characters from this string.
- * \param unwanted Characters to remove.
- */
-void String::strip (const List<char>& unwanted)
-{
-	for (char character : unwanted)
-		strip(character);
-}
-
-/*!
- * \returns an upper-case version of this string.
- */
-String String::toUpperCase() const
-{
-	String result (m_string);
-
-	for (char &ch : result)
-	{
-		if (islower(ch))
-			ch -= 'a' - 'A';
-	}
-
-	return result;
-}
-
-/*!
  * \returns a lower-case version of this string.
  */
-String String::toLowerCase() const
+std::string to_lowercase(const std::string& string)
 {
-	String result (m_string);
+	std::string result = string;
 
-	for (char &ch : result)
+	for (char& ch : result)
 	{
 		if (isupper(ch))
 			ch += 'a' - 'A';
@@ -100,74 +51,35 @@
 }
 
 /*!
- * \brief Splits this string using the provided delimeter.
- * \param delimeter Delimeter to use for splitting.
- * \returns a string list containing the split strings.
+ * \brief Joins the elements of this string list into one longer string.
+ * \param delimeter The delimeter to place between the element strings.
+ * \returns the catenated string.
  */
-StringList String::split (char delimeter) const
-{
-	String delimeterString;
-	delimeterString += delimeter;
-	return split (delimeterString);
-}
-
-/*!
- * \brief Splits this string using the provided delimeter.
- * \param delimeter Delimeter to use for splitting.
- * \returns a string list containing the split strings.
- */
-StringList String::split (const String& delimeter) const
+std::string join_string_list(const std::vector<std::string>& strings, const std::string& delimeter)
 {
-	StringList result;
-	int a = 0;
-	int b;
-
-	// Find all separators and store the text left to them.
-	while ((b = find (delimeter, a)) != -1)
-	{
-		String sub = mid (a, b);
+	std::string result;
 
-		if (sub.length() > 0)
-			result << sub;
+	for (const std::string &item : strings)
+	{
+		if (not result.empty())
+			result += delimeter;
 
-		a = b + delimeter.length();
+		result += item;
 	}
 
-	// Add the string at the right of the last separator
-	if (a < (int) length())
-		result.append (mid (a, length()));
-
 	return result;
 }
 
 /*!
- * \brief Replaces all instances of \c text with \c replacement.
- * \param text Text to replace away.
- * \param replacement Text to replace \c text with.
+ * \brief Modifies the given index so that if it is negative, it is translated into a positive index starting from the
+ *        end of the string. For example, an index of -1 will be modified to point to the last character in the string,
+ *        -2 to the second last, etc.
+ * \param index Index to translate.
  */
-void String::replace (const char* text, const char* replacement)
+inline void modifyIndex(const std::string& str, int& index)
 {
-	int position;
-
-	while ((position = find (text)) != -1)
-		m_string = m_string.replace (position, strlen (text), replacement);
-}
-
-/*!
- * \param character Character to count.
- * \returns the amount of \c character found in the string.
- */
-int String::count (char character) const
-{
-	int result = 0;
-
-	for (char ch : *this)
-	{
-		if (ch == character)
-			result++;
-	}
-
-	return result;
+	if (index < 0)
+		index = str.length() - index;
 }
 
 /*!
@@ -175,204 +87,29 @@
  * \param b Ending index of the range.
  * \returns a sub-string containing all characters from \c a to \c b, not including the character at \c b.
  */
-String String::mid (int rangeBegin, int rangeEnd) const
+std::string mid(const std::string& str, int rangeBegin, int rangeEnd)
 {
-	modifyIndex(rangeBegin);
-	modifyIndex(rangeEnd);
+	modifyIndex(str, rangeBegin);
+	modifyIndex(str, rangeEnd);
 	rangeBegin = max(rangeBegin, 0);
-	rangeEnd = min(rangeEnd, length());
+	rangeEnd = min(rangeEnd, static_cast<signed>(str.length()));
 
 	if (rangeEnd <= rangeBegin)
 		return "";
 	else
-		return m_string.substr(rangeBegin, rangeEnd - rangeBegin);
+		return str.substr(rangeBegin, rangeEnd - rangeBegin);
 }
 
 /*!
  * \param length Amount of characters to return.
  * \returns the \c length right-most characters of the string.
  */
-String String::right(int length) const
-{
-	if (length >= this->length())
-		return *this;
-	else
-		return String(chars() + this->length() - length);
-}
-
-/*!
- * \brief Finds the first instance of a sub-string.
- * \param subString Sub-string to search within this string.
- * \param startingPosition Position to start looking for the sub-string from.
- * \returns the position the first instance of sub-string found, or -1 if not found.
- */
-int String::find (const char* subString, int startingPosition) const
-{
-	int position = m_string.find (subString, startingPosition);
-
-	if (position == int (std::string::npos))
-		return -1;
-	else
-		return position;
-}
-
-/*!
- * \brief Finds the first instance of a character.
- * \param character Character to search within this string.
- * \param startingPosition Position to start looking for the character from.
- * \returns the position of the first instance of the provided character found, or -1 if not found.
- */
-int String::find (char character, int startingPosition) const
-{
-	int position = m_string.find (character, startingPosition);
-
-	if (position == int (std::string::npos))
-		return -1;
-	else
-		return position;
-}
-
-/*!
- * \brief Finds the last instance of a sub-string.
- * \param subString Sub-string to search within this string.
- * \param startingPosition Position to start looking for the sub-string from.
- * \returns the position the last instance of sub-string found, or -1 if not found.
- */
-int String::findLast (const char* subString, int startingPosition) const
-{
-	modifyIndex(startingPosition);
-
-	for (; startingPosition > 0; startingPosition--)
-	{
-		if (strncmp (chars() + startingPosition, subString, strlen (subString)) == 0)
-			return startingPosition;
-	}
-
-	return -1;
-}
-
-/*!
- * \brief Converts this string to an integer.
- * \param ok An pointer to a boolean to store whether or not the conversion was successful.
- *           If \c ok is \c NULL, the success state is not stored.
- * \param base The base to interpret this string with.
- * \returns the resulting integer.
- */
-long String::toInt (bool* ok, int base) const
-{
-	errno = 0;
-	char* endPointer;
-	long result = strtol (chars(), &endPointer, base);
-
-	if (ok != nullptr)
-		*ok = (errno == 0 and *endPointer == '\0');
-
-	return result;
-}
-
-/*!
- * \brief Converts this string to a floating-point number.
- * \param ok An pointer to a boolean to store whether or not the conversion was successful.
- *           If \c ok is \c NULL, the success state is not stored.
- * \returns the resulting floating-point number.
- */
-float String::toFloat (bool* ok) const
+std::string right(const std::string& str, int length)
 {
-	return static_cast<float>(toDouble(ok));
-}
-
-/*!
- * \brief Converts this string to a double-precision floating-point number.
- * \param ok An pointer to a boolean to store whether or not the conversion was successful.
- *           If \c ok is \c NULL, the success state is not stored.
- * \returns the resulting floating-point number.
- */
-double String::toDouble (bool* ok) const
-{
-	errno = 0;
-	char* endptr;
-	double i = strtod (chars(), &endptr);
-
-	if (ok != nullptr)
-		*ok = (errno == 0 and *endptr == '\0');
-
-	return i;
-}
-
-/*!
- * \brief Catenates this string with another string.
- * \param text String to catenate to the end of this string.
- * \returns the resulting string.
- */
-String String::operator+ (const String& text) const
-{
-	String newString = *this;
-	newString.append (text);
-	return newString;
-}
-
-/*!
- * \brief Catenates this string with another string.
- * \param text String to catenate to the end of this string.
- * \returns the resulting string.
- */
-String String::operator+ (const char* text) const
-{
-	String newString = *this;
-	newString.append (text);
-	return newString;
-}
-
-/*!
- * \returns whether or not this string represents a number.
- */
-bool String::isNumeric() const
-{
-	char* endPointer;
-	strtol (chars(), &endPointer, 10);
-	return (endPointer != nullptr) and (*endPointer != '\0');
-}
-
-/*!
- * \param other Sub-string to find from the end of this string.
- * \return whether or not this string ends with the provided sub-string.
- */
-bool String::endsWith (const String& other) const
-{
-	if (length() < other.length())
-	{
-		return false;
-	}
+	if (length >= static_cast<signed>(str.length()))
+		return str;
 	else
-	{
-		const int offset = length() - other.length();
-		return strncmp (chars() + offset, other.chars(), other.length()) == 0;
-	}
-}
-
-/*!
- * \param other Sub-string to find from the beginning of this string.
- * \returns whether or not this string begins with the provided sub-string.
- */
-bool String::startsWith (const String& other) const
-{
-	if (length() < other.length())
-		return false;
-	else
-		return strncmp (chars(), other.chars(), other.length()) == 0;
-}
-
-/*!
- * \brief Formats this string using \c printf -like syntax.
- * \param formatString Template string to use with formatting.
- * \param ... Variadic arguments to use with formatting.
- */
-void __cdecl String::sprintf (const char* formatString, ...)
-{
-	va_list args;
-	va_start (args, formatString);
-	this->vsprintf (formatString, args);
-	va_end (args);
+		return std::string{str.data() + str.length() - length};
 }
 
 /*!
@@ -380,8 +117,10 @@
  * \param formatString Template string to use with formatting.
  * \param args Variadic arguments to use with formatting.
  */
-void String::vsprintf (const char* formatString, va_list args)
+std::string vsprintf(const char* formatString, va_list args)
 {
+	std::string result;
+
 	// Copy the argument list so that we have something to provide to vsnprintf in case we have to call it again.
 	va_list argsCopy;
 	va_copy(argsCopy, args);
@@ -393,192 +132,118 @@
 	if (length < sizeof buffer)
 	{
 		// vsnprintf succeeded in fitting the formatted string into the buffer, so we're done.
-		m_string = buffer;
+		result = buffer;
 	}
 	else
 	{
 		// vsnprintf needs more space, so we have to allocate a new buffer and try again.
-		Vector<char> newBuffer(length + 1);
+		std::vector<char> newBuffer(length + 1);
 		vsnprintf(newBuffer.data(), length + 1, formatString, argsCopy);
-		m_string = newBuffer;
+		result = newBuffer.data();
+	}
+	
+	return result;
+}
+
+/*!
+ * \brief Formats this string using \c printf -like syntax.
+ * \param formatString Template string to use with formatting.
+ * \param ... Variadic arguments to use with formatting.
+ */
+std::string __cdecl sprintf(const char* formatString, ...)
+{
+	va_list args;
+	va_start (args, formatString);
+	std::string result = vsprintf(formatString, args);
+	va_end (args);
+	return result;
+}
+
+std::string remove_range(const std::string &string, int start, int end)
+{
+	std::string result;
+	result.reserve(string.length() - (end - start));
+	std::copy(string.begin(), string.begin() + start, std::back_inserter(result));
+	std::copy(string.begin() + end, string.end(), std::back_inserter(result));
+	return result;
+}
+
+
+/*!
+ * \param other Sub-string to find from the beginning of this string.
+ * \returns whether or not this string begins with the provided sub-string.
+ */
+bool starts_with(const std::string& str, const std::string& other)
+{
+	if (str.length() < other.length())
+		return false;
+	else
+		return std::strncmp(str.data(), other.data(), other.length()) == 0;
+}
+
+/*!
+ * \brief Replaces all instances of \c text with \c replacement.
+ * \param text Text to replace away.
+ * \param replacement Text to replace \c text with.
+ */
+void replace_all(std::string& str, const char* text, const char* replacement)
+{
+	int position;
+	while ((position = str.find(text)) != -1)
+	{
+		str.replace(position, std::strlen(text), replacement);
 	}
 }
 
 /*!
- * \brief Joins the elements of this string list into one longer string.
- * \param delimeter The delimeter to place between the element strings.
- * \returns the catenated string.
+ * \brief Splits this string using the provided delimeter.
+ * \param delimeter Delimeter to use for splitting.
+ * \returns a string list containing the split strings.
  */
-String StringList::join (const String& delimeter)
+std::vector<std::string> split(const std::string& string, const std::string& delimeter)
 {
-	String result;
+	std::vector<std::string> result;
+	int a = 0;
+	int b;
 
-	for (const String &item : container())
+	// Find all separators and store the text left to them.
+	while ((b = string.find(delimeter, a)) != -1)
 	{
-		if (result.isEmpty() == false)
-			result += delimeter;
+		std::string sub = mid(string, a, b);
+
+		if (sub.length() > 0)
+			result.push_back(sub);
 
-		result += item;
+		a = b + delimeter.length();
 	}
 
+	// Add the string at the right of the last separator
+	if (a < static_cast<int>(string.length()))
+		result.push_back(mid(string, a, string.length()));
+
 	return result;
 }
 
 /*!
- * \brief Tries to match this string against a mask pattern. In the pattern, '?' refers to one character, and '*' to
- *        any number of characters.
- * \param pattern The masking pattern to use for matching.
- * \returns whether or not this string matches the provided pattern.
+ * \brief Converts this string to an integer.
+ * \param ok An pointer to a boolean to store whether or not the conversion was successful.
+ *           If \c ok is \c NULL, the success state is not stored.
+ * \param base The base to interpret this string with.
+ * \returns the resulting integer.
  */
-bool String::maskAgainst (const String& pattern) const
-{
-	// Elevate to uppercase for case-insensitive matching
-	String pattern_upper = pattern.toUpperCase();
-	String this_upper = toUpperCase();
-	const char* maskstring = pattern_upper.chars();
-	const char* mptr = &maskstring[0];
-
-	for (const char* sptr = this_upper.chars(); *sptr != '\0'; sptr++)
-	{
-		if (*mptr == '?')
-		{
-			if (*(sptr + 1) == '\0')
-			{
-				// ? demands that there's a character here and there wasn't.
-				// Therefore, mask matching fails
-				return false;
-			}
-		}
-		else if (*mptr == '*')
-		{
-			char end = *(++mptr);
-
-			// If '*' is the final character of the message, all of the remaining
-			// string matches against the '*'. We don't need to bother checking
-			// the string any further.
-			if (end == '\0')
-				return true;
-
-			// Skip to the end character
-			while (*sptr != end and *sptr != '\0')
-				sptr++;
-
-			// String ended while the mask still had stuff
-			if (*sptr == '\0')
-				return false;
-		}
-		else if (*sptr != *mptr)
-			return false;
-
-		mptr++;
-	}
-
-	return true;
-}
-
-/*!
- * \brief Converts a short integer into a string.
- * \param value The value to convert.
- * \returns the resulting string.
- */
-String String::fromNumber (short int value)
-{
-	char buffer[32];
-	::sprintf (buffer, "%d", value);
-	return String (buffer);
-}
-
-/*!
- * \brief Converts an integer into a string.
- * \param value The value to convert.
- * \returns the resulting string.
- */
-String String::fromNumber (int value)
+std::optional<long> to_int(const char* str, int base)
 {
-	char buffer[32];
-	::sprintf (buffer, "%d", value);
-	return String (buffer);
-}
-
-/*!
- * \brief Converts a long integer into a string.
- * \param value The value to convert.
- * \returns the resulting string.
- */
-String String::fromNumber (long int value)
-{
-	char buffer[32];
-	::sprintf (buffer, "%ld", value);
-	return String (buffer);
-}
-
-/*!
- * \brief Converts an unsigned short integer into a string.
- * \param value The value to convert.
- * \returns the resulting string.
- */
-String String::fromNumber (unsigned short int value)
-{
-	char buffer[32];
-	::sprintf (buffer, "%u", value);
-	return String (buffer);
-}
-
-/*!
- * \brief Converts an unsigned integer into a string.
- * \param value The value to convert.
- * \returns the resulting string.
- */
-String String::fromNumber (unsigned int value)
-{
-	char buffer[32];
-	::sprintf (buffer, "%u", value);
-	return String (buffer);
-}
-
-/*!
- * \brief Converts an unsigned long integer into a string.
- * \param value The value to convert.
- * \returns the resulting string.
- */
-String String::fromNumber (unsigned long int value)
-{
-	char buffer[32];
-	::sprintf (buffer, "%lu", value);
-	return String (buffer);
-}
-
-/*!
- * \brief Converts a double-precision floating point number into a string, using the "%f" format specifier.
- * \param value The value to convert.
- * \returns the resulting string.
- */
-String String::fromNumber (double value)
-{
-	char buffer[64];
-	::sprintf (buffer, "%f", value);
-	return String (buffer);
-}
-
-/*!
- * \brief Constructs a string from a vector of bytes. The bytes do not have to be null-terminated.
- * \param bytes Bytes to use for construction
- * \returns the resulting string.
- */
-String String::fromBytes(const ByteArray& bytes)
-{
-	return String(reinterpret_cast<const Vector<char>&>(bytes));
-}
-
-/*!
- * \returns the MD5-checksum of this string.
- */
-String String::md5() const
-{
-	char checksum[33];
-	CalculateMD5 (reinterpret_cast<const unsigned char*> (chars()), length(), checksum);
-	checksum[sizeof checksum - 1] = '\0';
-	return String (checksum);
+	errno = 0;
+	char* endPointer;
+	long result = strtol(str, &endPointer, base);
+	if (errno == 0 and *endPointer == '\0')
+	{
+		return result;
+	}
+	else
+	{
+		return {};
+	}
 }
 
 /*!
@@ -586,32 +251,26 @@
  *        something else than whitespace.
  * \param filter The filtering function to use.
  */
-void String::normalize (int (*filter)(int))
+void normalize(std::string& string, int (*filter)(int))
 {
 	int a = 0;
-	int b = length() - 1;
-
-	while ((*filter) (m_string[a]) and a != b)
+	int b = string.length() - 1;
+	while ((*filter)(string[a]) and a != b)
+	{
 		++a;
-
-	while ((*filter) (m_string[b]) and a != b)
+	}
+	while ((*filter)(string[b]) and a != b)
+	{
 		--b;
-
+	}
 	if (a == b)
-		m_string = "";
-	else if (a != 0 or b != length() - 1)
-		m_string = m_string.substr (a, b - a + 1);
-}
-
-/*!
- * \returns a version of this string without leading or trailing whitespace. Alternatively a custom filter can be used
- *          to strip something else than whitespace.
- */
-String String::normalized (int (*filter)(int)) const
-{
-	String result = *this;
-	result.normalize(filter);
-	return result;
+	{
+		string = "";
+	}
+	else if (a != 0 or b != static_cast<signed>(string.length() - 1))
+	{
+		string = string.substr (a, b - a + 1);
+	}
 }
 
 END_ZFC_NAMESPACE
--- a/sources/mystring.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/mystring.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -37,573 +37,21 @@
 
 BEGIN_ZFC_NAMESPACE
 
-
-class String
-{
-public:
-	typedef std::string::iterator Iterator;
-	typedef std::string::const_iterator ConstIterator;
-
-	String();
-	String(char a);
-	String(const char* data);
-	String(const std::string& data);
-	String(const Vector<char>& data);
-
-	void                        append(const char* text);
-	void                        append(char character);
-	void                        append(const String& text);
-	ConstIterator               begin() const;
-	Iterator                    begin();
-	int                         compare(const String &other) const;
-	int                         count(char character) const;
-	const char*                 chars() const;
-	void                        clear();
-	ConstIterator               end() const;
-	Iterator                    end();
-	bool                        endsWith(const String &other) const;
-	int                         find(const char* subString, int startingPosition = 0) const;
-	int                         find(char character, int startingPosition = 0) const;
-	int                         indexDifference(int a, int b);
-	void                        insert(int position, char character);
-	void                        insert(int position, const char* string);
-	bool                        isEmpty() const;
-	bool                        isNumeric() const;
-	void                        modifyIndex(int &a) const;
-	int                         findLast(const char* subString, int startingPosition = -1) const;
-	int                         length() const;
-	bool                        maskAgainst(const String &pattern) const;
-	String                      md5() const;
-	String                      mid(int rangeBegin, int rangeEnd) const;
-	void                        normalize(int(*filter)(int) = &isspace);
-	String                      normalized(int(*filter)(int) = &isspace) const;
-	void                        prepend(String text);
-	void                        remove(int position, int length);
-	void                        removeAt(int position);
-	void                        removeFromEnd(int length);
-	void                        removeFromStart(int length);
-	void                        replace(const char* text, const char* replacement);
-	void                        replace(int position, int amount, const String &text);
-	String                      right(int length) const;
-	void                        shrinkToFit();
-	class StringList            split(const String &delimeter) const;
-	class StringList            split(char delimeter) const;
-	void __cdecl                sprintf(const char* fmtstr, ...);
-	bool                        startsWith(const String &other) const;
-	const std::string&          stdString() const;
-	void                        strip(char unwanted);
-	void                        strip(const List<char> &unwanted);
-	const unsigned char*        toBytes() const;
-	double                      toDouble(bool* ok = nullptr) const;
-	float                       toFloat(bool* ok = nullptr) const;
-	long                        toInt(bool* ok = nullptr, int base = 10) const;
-	String                      toLowerCase() const;
-	String                      toUpperCase() const;
-	void                        vsprintf(const char* fmtstr, va_list args);
-
-	static String               fromNumber(short int a);
-	static String               fromNumber(int a);
-	static String               fromNumber(long int a);
-	static String               fromNumber(unsigned short int a);
-	static String               fromNumber(unsigned int a);
-	static String               fromNumber(unsigned long int a);
-	static String               fromNumber(double a);
-	static String               fromBytes(const ByteArray& bytes);
-
-	String                      operator+(const String& data) const;
-	String                      operator+(const char* data) const;
-	String                      operator+(int num) const;
-	String&                     operator+=(const String& data);
-	String&                     operator+=(const char* data);
-	String&                     operator+=(int num);
-	String&                     operator+=(char data);
-	char&                       operator[](int i);
-	char                        operator[](int i) const;
-	bool                        operator==(const String& other) const;
-	bool                        operator==(const char* other) const;
-	bool                        operator!=(const String& other) const;
-	bool                        operator!=(const char* other) const;
-	bool                        operator>(const String& other) const;
-	bool                        operator<(const String& other) const;
-	bool                        operator>=(const String& other) const;
-	bool                        operator<=(const String& other) const;
-	                            operator const char*() const;
-	                            operator const std::string&() const;
-
-private:
-	std::string m_string;
-};
-
-
-class StringList : public List<String>
-{
-public:
-	StringList();
-	StringList(int numvalues);
-	StringList(const List<String>& other);
-	String join(const String& delim);
-};
-
-
-inline bool operator==(const char* a, const String& b);
-inline String operator+(const char* a, const String& b);
-
-// --------------------------------------------------------------------------------------------------------------------
-
-/*!
- * \brief Constructs an empty string.
- */
-inline String::String() {}
-
-/*!
- * \brief Constructs a string from a single character.
- * \param character Character to create a string out of.
- */
-inline String::String(char character)
-{
-	char buffer[2] = { character, '\0' };
-	m_string = buffer;
-}
-
-/*!
- * \brief Constructs a string from a char-array.
- * \param string char-array to convert.
- */
-inline String::String(const char* string) :
-	m_string(string) {}
-
-/*!
- * \brief Constructs a string out of a \c std::string .
- * \param string \c std::string to base the construction on.
- */
-inline String::String(const std::string& string) :
-	m_string(string) {}
-
-/*!
- * \brief Constructs a string out of a vector of characters. The vector does not have to be null-terminated.
- * \param charVector Vector of characters to construct the string out of.
- */
-inline String::String(const Vector<char>& charVector) :
-	m_string(charVector.data(), charVector.size()) {}
-
-/*!
- * \returns a constant iterator to the beginning of the string.
- */
-inline String::ConstIterator String::begin() const
-{
-	return m_string.cbegin();
-}
-
-/*!
- * \returns the string's contents as a char-array.
- */
-inline const char* String::chars() const
-{
-	return m_string.c_str();
-}
-
-/*!
- * \returns the string's constant end-iterator.
- */
-inline String::ConstIterator String::end() const
-{
-	return m_string.end();
-}
-
-/*!
- * \returns whether or not the string is empty.
- */
-inline bool String::isEmpty() const
-{
-	return m_string[0] == '\0';
-}
-
-/*!
- * \returns the length of the string.
- */
-inline int String::length() const
-{
-	return m_string.length();
-}
-
-/*!
- * \returns the underlying \c std::string .
- */
-inline const std::string& String::stdString() const
-{
-	return m_string;
-}
-
-/*!
- * \brief Adds text from a char-array to the end of the string.
- * \param text Text to append.
- */
-inline void String::append(const char* text)
-{
-	m_string.append(text);
-}
-
-/*!
- * \brief Adds text to the end of the string.
- * \param text Text to append.
- */
-inline void String::append(char character)
-{
-	m_string.push_back(character);
-}
-
-/*!
- * \brief Adds text from another string to the end of this string.
- * \param text Text to append.
- */
-inline void String::append(const String& text)
-{
-	m_string.append(text.chars());
-}
-
-/*!
- * \returns a mutable iterator to the beginning of the string.
- */
-inline String::Iterator String::begin()
-{
-	return m_string.begin();
-}
-
-/*!
- * \brief Clears the string.
- */
-inline void String::clear()
-{
-	m_string.clear();
-}
-
-/*!
- * \returns the string's mutable end-iterator.
- */
-inline String::Iterator String::end()
-{
-	return m_string.end();
-}
-
-/*!
- * \brief Compares two string indices, supporting negatives as offsets from the end of string.
- * \param a First index to compare
- * \param b Second index to compare
- * \returns the difference of two indices.
- */
-inline int String::indexDifference(int a, int b)
-{
-	modifyIndex(a);
-	modifyIndex(b);
-	return b - a;
-}
-
-/*!
- * \brief Inserts a character into the string.
- * \param position Position in the string where to insert the character into.
- * \param character Character to insert into the string.
- */
-inline void String::insert(int position, char character)
-{
-	m_string.insert(m_string.begin() + position, character);
-}
-
-/*!
- * \brief Inserts a substring into the string.
- * \param position Position in the string where to insert the substring.
- * \param string Substring to insert.
- */
-inline void String::insert(int position, const char* string)
-{
-	m_string.insert(position, string);
-}
+using String = std::string;
+using StringList = std::vector<std::string>;
+using namespace std::string_literals;
 
-/*!
- * \brief Modifies the given index so that if it is negative, it is translated into a positive index starting from the
- *        end of the string. For example, an index of -1 will be modified to point to the last character in the string,
- *        -2 to the second last, etc.
- * \param index Index to translate.
- */
-inline void String::modifyIndex(int& index) const
-{
-	if (index < 0)
-		index = length() - index;
-}
-
-/*!
- * \brief Prepends the given text to the beginning of the string.
- * \param text Text to prepend.
- */
-inline void String::prepend(String text)
-{
-	m_string = (text + m_string).stdString();
-}
-
-/*!
- * \brief Removes a range of text from the string.
- * \param position Position where to start removing text.
- * \param length Amount of characters to remove.
- */
-inline void String::remove(int position, int length)
-{
-	m_string.replace(position, length, "");
-}
-
-/*!
- * \brief Removes a single character from the string.
- * \param position Position of the character to remove string from.
- */
-inline void String::removeAt(int position)
-{
-	m_string.erase(m_string.begin() + position);
-}
-
-/*!
- * \brief Removes a number of characters from the end of the string.
- * \param length Amount of characters to remove.
- */
-inline void String::removeFromEnd(int length)
-{
-	remove(this->length() - length, length);
-}
-
-/*!
- * \brief Removes a number of characters from the beginning of the string.
- * \param length Amount of characters to remove.
- */
-inline void String::removeFromStart(int length)
-{
-	remove(0, length);
-}
-
-/*!
- * \brief Replaces a range of text in the string with another.
- * \param position Position where to start replacing text.
- * \param amount Amount of characters to replace.
- * \param text Replacement string.
- */
-inline void String::replace(int position, int amount, const String& text)
-{
-	m_string.replace(position, amount, text.chars());
-}
-
-/*!
- * \brief Shrinks the string so that it does not allocate more characters than necessary.
- */
-inline void String::shrinkToFit()
-{
-	m_string.shrink_to_fit();
-}
-
-/*!
- * \brief Converts a number into a string, and returns a new string with the number appended to the end of the string.
- * \param number Number to convert and append.
- * \returns the resulting string.
- */
-inline String String::operator+(int number) const
-{
-	return *this + String::fromNumber(number);
-}
-
-/*!
- * \brief Appends text into the string.
- * \param text Text to append.
- * \returns a reference to this string.
- */
-inline String& String::operator+=(const String& text)
-{
-	append(text);
-	return *this;
-}
-
-/*!
- * \brief Appends text into the string.
- * \param text Text to append.
- * \returns a reference to this string.
- */
-inline String& String::operator+=(const char* text)
-{
-	append(text);
-	return *this;
-}
-
-/*!
- * \brief Converts a number into a string, and appends it into this string.
- * \param number The number to append.
- * \returns a refence to this string.
- */
-inline String& String::operator+=(int number)
-{
-	return operator+=(String::fromNumber(number));
-}
-
-/*!
- * \brief Appends a character into this string.
- * \param character The character to append.
- * \return a reference to this string.
- */
-inline String& String::operator+=(char character)
-{
-	append(character);
-	return *this;
-}
-
-/*!
- * \param index Index referring to a character of this string.
- * \returns an editable reference to the character pointed by the given index.
- */
-inline char& String::operator[](int index)
-{
-	return m_string[index];
-}
-
-/*!
- * \param index Index referring to a character of this string.
- * \returns an const reference to the character pointed by the given index.
- */
-inline char String::operator[](int index) const
-{
-	return m_string[index];
-}
-
-/*!
- * \param other String to compare with.
- * \returns whether or not this string is the same as the other string.
- */
-inline bool String::operator==(const String& other) const
-{
-	return stdString() == other.stdString();
-}
-
-/*!
- * \param other String to compare with.
- * \returns whether or not this string is the same as the other string.
- */
-inline bool String::operator==(const char* other) const
-{
-	return m_string == other;
-}
-
-/*!
- * \param other String to compare with.
- * \returns whether or not this string is different than the other string.
- */
-inline bool String::operator!=(const String& other) const
-{
-	return stdString() != other.stdString();
-}
-
-/*!
- * \param other String to compare with.
- * \returns whether or not this string is different than the other string.
- */
-inline bool String::operator!=(const char* other) const
-{
-	return m_string != other;
-}
-
-/*!
- * \param other String to compare with.
- * \return whether or not this string is lexicographically greater than the other string.
- */
-inline bool String::operator>(const String& other) const
-{
-	return stdString() > other.stdString();
-}
-
-/*!
- * \param other String to compare with.
- * \return whether or not this string is lexicographically lesser than the other string.
- */
-inline bool String::operator<(const String& other) const
-{
-	return stdString() < other.stdString();
-}
-
-/*!
- * \param other String to compare with.
- * \return whether or not this string is lexicographically at least as great as the other string.
- */
-inline bool String::operator>=(const String& other) const
-{
-	return stdString() >= other.stdString();
-}
-
-/*!
- * \param other String to compare with.
- * \return whether or not this string is lexicographically at most as great as the other string.
- */
-inline bool String::operator<=(const String& other) const
-{
-	return stdString() <= other.stdString();
-}
-
-/*!
- * \returns a char-array representation of this string.
- */
-inline String::operator const char*() const
-{
-	return chars();
-}
-
-/*!
- * \returns the underlying \c std::string of this string.
- */
-inline String::operator const std::string&() const
-{
-	return stdString();
-}
-
-/*!
- * \returns the underlying char-array representation of this string, casted to unsigned chars.
- */
-inline const unsigned char* String::toBytes() const
-{
-	return reinterpret_cast<const unsigned char*>(chars());
-}
-
-/*!
- * \brief Constructs an empty string list.
- */
-inline StringList::StringList() {}
-
-/*!
- * \brief Constructs a string list containing \c numvalues empty strings.
- * \param numvalues Amount of empty strings to fill.
- */
-inline StringList::StringList(int numvalues) :
-	List<String>(numvalues) {}
-
-/*!
- * \brief Constructs a string list from another list of strings.
- * \param other The list of strings to use for construction.
- */
-inline StringList::StringList(const List<String>& other) :
-	List<String>(other) {}
-
-/*!
- * \brief An \c operator== implementation that allows a char-array to be at the left side of a string comparison
- *        with a \c String.
- * \param one A char-array representation of a string to compare.
- * \param other A string to compare.
- * \returns whether or not the two parameters are equal.
- */
-inline bool operator==(const char* one, const String& other)
-{
-	return other == one;
-}
-
-/*!
- * \brief An \c operator+ implementation that allows a char-array to be at the left side of a string catenation
- *        with a \c String.
- * \param one A char-array representation of a string to catenate.
- * \param other A string to catenate.
- * \returns the catenated string.
- */
-inline String operator+(const char* one, const String& other)
-{
-	return String(one) + other;
-}
-
+std::string to_lowercase(const std::string& string);
+std::string join_string_list(const std::vector<std::string>& strings, const std::string& delim);
+std::string mid(const std::string& str, int rangeBegin, int rangeEnd);
+std::string right(const std::string& str, int length);
+std::string vsprintf(const char* formatString, va_list args);
+std::string __cdecl sprintf(const char* formatString, ...) GNUATTRIBUTE((format(printf, 1, 2)));
+std::string remove_range(const std::string& string, int start, int end);
+void replace_all(std::string& str, const char* text, const char* replacement);
+bool starts_with(const std::string& str, const std::string& other);
+std::vector<std::string> split(const std::string& string, const std::string& delimeter);
+std::optional<long> to_int(const char* str, int base = 10);
+void normalize(std::string& string, int (*filter)(int) = std::isspace);
 
 END_ZFC_NAMESPACE
--- a/sources/network/bytestream.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/network/bytestream.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -36,7 +36,7 @@
  * \brief Constructs a byte cursor. The cursor is placed to the beginning of the stream.
  * \param data
  */
-Bytestream::Bytestream(ByteArray& data) :
+Bytestream::Bytestream(std::vector<unsigned char>& data) :
     m_data(data),
     m_position(0) {}
 
@@ -49,8 +49,8 @@
 	if (bytesLeft() < bytes)
 	{
 		int bytesPast = bytes - bytesLeft();
-		String message;
-		message.sprintf("attempted to read %d byte%s past the end of bytestream", bytesPast, plural(bytesPast));
+		std::string message;
+		message = sprintf("attempted to read %d byte%s past the end of bytestream", bytesPast, plural(bytesPast));
 		throw IOError (message);
 	}
 }
@@ -66,7 +66,7 @@
 /*!
  * \returns an iterator to the current data position.
  */
-ByteArray::Iterator Bytestream::getCurrentIterator()
+std::vector<unsigned char>::iterator Bytestream::getCurrentIterator()
 {
 	return m_data.begin() + m_position;
 }
@@ -90,7 +90,7 @@
 	ensureReadSpace (2);
 	int16_t result = 0;
 
-	for (int i : range(2))
+	for (int i : {0, 1})
 		result |= read() << (i * 8);
 
 	return result;
@@ -104,8 +104,8 @@
 {
 	ensureReadSpace (4);
 	int32_t result = 0;
-
-	for (int i : range(4))
+	
+	for (int i = 0; i < 4; i += 1)
 		result |= read() << (i * 8);
 
 	return result;
@@ -127,9 +127,9 @@
  * \brief Reads in characters until a null terminator is encountered.
  * \returns the read string.
  */
-String Bytestream::readString()
+std::string Bytestream::readString()
 {
-	String result;
+	std::string result;
 
 	for (char byte; (byte = readByte()) != '\0';)
 	{
@@ -145,10 +145,10 @@
  * \param length Amount of bytes to read.
  * \returns the read buffer.
  */
-ByteArray Bytestream::readBuffer(int length)
+std::vector<unsigned char> Bytestream::readBuffer(int length)
 {
 	ensureReadSpace(length);
-	ByteArray result(length);
+	std::vector<unsigned char> result(length);
 	memcpy(result.data(), m_data.data() + m_position, length);
 	m_position += length;
 	return result;
@@ -160,7 +160,7 @@
  */
 void Bytestream::writeByte(int8_t value)
 {
-	m_data.append(value);
+	m_data.push_back(value);
 }
 
 /*!
@@ -169,8 +169,9 @@
  */
 void Bytestream::writeShort(int16_t value)
 {
-	for (int i : range(2))
-		m_data.append((value >> (i * 8)) & 0xFF);
+	m_data.reserve(m_data.size() + 2);
+	for (int i : {0, 1})
+		m_data.push_back((value >> (i * 8)) & 0xFF);
 }
 
 /*!
@@ -179,8 +180,9 @@
  */
 void Bytestream::writeLong(int32_t value)
 {
-	for (int i : range(4))
-		m_data.append((value >> (i * 8)) & 0xFF);
+	m_data.reserve(m_data.size() + 4);
+	for (int i = 0; i < 4; i += 1)
+		m_data.push_back((value >> (i * 8)) & 0xFF);
 }
 
 /*!
@@ -199,10 +201,13 @@
  * \brief Writes the given string to the end of the data.
  * \param text String to write.
  */
-void Bytestream::writeString(const String& string)
+void Bytestream::writeString(const std::string& string)
 {
-	m_data.append(string.toBytes(), string.length());
-	m_data.append(0);
+	const int oldSize = m_data.size();
+	m_data.reserve(m_data.size() + string.length() + 1);
+	m_data.resize(m_data.size() + string.length());
+	std::copy(string.begin(), string.end(), m_data.begin() + oldSize);
+	m_data.push_back(0);
 }
 
 /*!
--- a/sources/network/bytestream.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/network/bytestream.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -30,11 +30,10 @@
 
 #pragma once
 #include <stdexcept>
+#include <string>
 #include "../main.h"
 BEGIN_ZFC_NAMESPACE
 
-class String;
-
 enum
 {
 	MAX_NETWORK_STRING = 0x800
@@ -42,45 +41,45 @@
 
 class IOError : public std::exception
 {
-	String m_message;
+	std::string m_message;
 
 public:
-	IOError(String message) :
+	IOError(std::string message) :
 		m_message(message) {}
 
 	const char* what() const throw()
 	{
-		return m_message;
+		return m_message.data();
 	}
 };
 
 class Bytestream
 {
 public:
-	Bytestream(ByteArray& data);
+	Bytestream(std::vector<unsigned char>& data);
 
 	int bytesLeft() const;
-	ByteArray::Iterator getCurrentIterator();
+	std::vector<unsigned char>::iterator getCurrentIterator();
 	int position() const;
-	ByteArray readBuffer(int length);
+	std::vector<unsigned char> readBuffer(int length);
 	int8_t readByte();
 	int32_t readLong();
 	int16_t readShort();
-	String readString();
+	std::string readString();
 	float readFloat();
 	void rewind();
 	void seek(int position);
 	void write(const unsigned char* val, unsigned int length);
-	void writeBuffer(const ByteArray& other);
+	void writeBuffer(const std::vector<unsigned char>& other);
 	void writeByte(int8_t value);
 	void writeDouble(double val);
 	void writeFloat(float value);
 	void writeLong(int32_t value);
 	void writeShort(int16_t value);
-	void writeString(const String& string);
+	void writeString(const std::string& string);
 
 private:
-	ByteArray& m_data;
+	std::vector<unsigned char>& m_data;
 	int m_position;
 
 	int8_t read();
--- a/sources/network/ipaddress.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/network/ipaddress.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -46,145 +46,143 @@
 typedef struct addrinfo AddrInfo;
 #endif
 
-// -----------------------------------------------------------------------------
-//
-IPAddress::IPAddress() :
-	host (0),
-	port (0) {}
-
-// -----------------------------------------------------------------------------
-//
-IPAddress::IPAddress (unsigned long host, unsigned short port) :
-	host (host),
-	port (port) {}
+net::octet_t net::ip_octet(const net::ip_address& address, unsigned char n)
+{
+	assert(n < 4);
+	return (address.host >> ((3 - n) * 8)) & 0xFF;
+}
 
-// -----------------------------------------------------------------------------
-//
-IPAddress::IPAddress (const IPAddress& other) :
-	host (other.host),
-	port (other.port) {}
+void net::ip_set_octet(net::ip_address* address, unsigned char n, net::octet_t octet)
+{
+	// TODO: make a big-endian version
+	assert(n < 4);
+	address->host &= ~(0xFF << (3 - n) * 8);
+	address->host |= octet << ((3 - n) * 8);
+}
 
-// -----------------------------------------------------------------------------
-//
-String IPAddress::to_string (WithPort withport) const
+std::string net::ip_address_to_string(const net::ip_address& address)
 {
-	String val;
-
-	if (withport == WITH_PORT)
-		val.sprintf ("%u.%u.%u.%u:%u", octet (0), octet (1), octet (2), octet (3), port);
-	else
-		val.sprintf ("%u.%u.%u.%u", octet (0), octet (1), octet (2), octet (3));
-
+	const auto octet = [&](unsigned char n) { return ip_octet(address, n); };
+	std::string val = sprintf ("%u.%u.%u.%u:%u", octet(0), octet(1), octet(2), octet(3), address.port);
 	return val;
 }
 
-// -----------------------------------------------------------------------------
-//
-unsigned char IPAddress::octet (int n) const
+int net::ip_compare(const net::ip_address &one, const net::ip_address &other)
 {
-	return (host >> ((3 - n) * 8)) & 0xFF;
-}
-
-// -----------------------------------------------------------------------------
-//
-void IPAddress::set_octet (int n, unsigned char oct)
-{
-	// TODO: make a big-endian version
-	host &= ~(0xFF << (3 - n) * 8);
-	host |= oct << ((3 - n) * 8);
+	if (one.host != other.host)
+	{
+		return one.host - other.host;
+	}
+	return one.port - other.port;
 }
 
-// -----------------------------------------------------------------------------
-//
-bool IPAddress::compare (const IPAddress& other) const
+bool net::ip_address::operator<(const ip_address& other) const
 {
-	for (int i : range(4))
-	{
-		if (octet (i) != other.octet (i))
-			return false;
-	}
+	return ip_compare(*this, other) < 0;
+}
 
-	if (port != 0
-		and other.port != 0
-		and port != other.port)
-	{
-		return false;
-	}
-
-	return true;
+bool net::ip_address::operator==(const net::ip_address &other) const
+{
+	return this->host == other.host and this->port == other.port;
 }
 
 // -----------------------------------------------------------------------------
 //
-bool IPAddress::operator< (const IPAddress& other) const
-{
-	for (int i : range(4))
-	{
-		if (octet (i) != other.octet (i))
-			return octet (i) < other.octet (i);
-	}
-
-	return port < other.port;
-}
-
-// -----------------------------------------------------------------------------
-//
-sockaddr_in IPAddress::to_sockaddr_in() const
+sockaddr_in net::ip_address_to_sockaddr_in(const net::ip_address& address)
 {
 	sockaddr_in claddr;
 	memset (&claddr, 0, sizeof claddr);
-	claddr.sin_addr.s_addr = htonl (host);
-	claddr.sin_port = htons (port);
+	claddr.sin_addr.s_addr = htonl(address.host);
+	claddr.sin_port = htons(address.port);
 	claddr.sin_family = AF_INET;
 	return claddr;
 }
 
-// -----------------------------------------------------------------------------
-//
-IPAddress IPAddress::from_string (String input)
+std::optional<unsigned short> net::ip_parse_port(const char* port_string, std::ostream& errorStream)
+{
+	std::optional<long> opt = to_int(port_string);
+	if (opt.has_value())
+	{
+		if (*opt >= 0 and *opt < 65536)
+		{
+			return static_cast<unsigned short>(opt.value());
+		}
+		else
+		{
+			return {};
+			errorStream << "port is not in range"s;
+		}
+	}
+	else
+	{
+		return {};
+		errorStream << "could not parse port"s;
+	}
+}
+
+std::optional<net::ip_address> net::ip_resolve_hostname(const std::string& node, std::ostream& errorStream)
 {
-	unsigned int parts[4];
-	int colonpos = input.find (":");
-	String addressString = colonpos == -1 ? input : input.mid (0, colonpos);
-	IPAddress value;
+	AddrInfo hints;
+	AddrInfo* lookup;
+	memset(&hints, 0, sizeof hints);
+	hints.ai_family = AF_INET;
+	std::optional<net::ip_address> result = {};
+	if (::getaddrinfo(node.data(), nullptr, &hints, &lookup) != 0)
+	{
+		errorStream << "unknown host "s + node;
+	}
+	else
+	{
+		assert (lookup != nullptr);
+		sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(lookup[0].ai_addr);
+		result = net::ip_address{ntohl(addr->sin_addr.s_addr), ntohs(addr->sin_port)};
+		::freeaddrinfo(lookup);
+	}
+	return result;
+}
 
+std::optional<net::ip_address> net::ip_resolve(const std::string& input_string, std::ostream& errorStream)
+{
+	int colonpos = input_string.find(":");
+	std::string addressString = colonpos == -1 ? input_string : mid(input_string, 0, colonpos);
+	std::optional<net::ip_address> value;
 	// Try scanf the IPv4 host first
-	if (sscanf (addressString, "%u.%u.%u.%u", &parts[0], &parts[1], &parts[2], &parts[3]))
+	int parts[4];
+	if (std::sscanf(addressString.data(), "%d.%d.%d.%d", &parts[0], &parts[1], &parts[2], &parts[3]))
 	{
-		for (int i : range(4))
-			value.set_octet (i, parts[i]);
+		value = net::ip_address{};
+		for (unsigned char i = 0; i < 4; i += 1)
+		{
+			if (parts[i] >= 0 and parts[i] < 256)
+			{
+				ip_set_octet(&*value, i, parts[i]);
+			}
+			else
+			{
+				value.reset();
+				errorStream << "IP address value out of range";
+				break;
+			}
+		}
 	}
 	else
 	{
 		// Possibly a hostname, try resolve it
-		value = IPAddress::resolve (addressString);
+		value = ip_resolve_hostname(addressString, errorStream);
 	}
-
-	if (colonpos != -1)
-		value.port = (unsigned short) input.mid (colonpos + 1, -1).toInt();
-
+	if (value.has_value() and colonpos != -1)
+	{
+		std::optional<unsigned short> port_opt = ip_parse_port(&input_string.data()[colonpos + 1], errorStream);
+		if (port_opt.has_value())
+		{
+			value->port = *port_opt;
+		}
+		else
+		{
+			value.reset();
+		}
+	}
 	return value;
 }
 
-// -----------------------------------------------------------------------------
-//
-IPAddress IPAddress::resolve (String node)
-{
-	AddrInfo hints;
-	AddrInfo* lookup;
-	memset (&hints, 0, sizeof hints);
-	hints.ai_family = AF_INET;
-
-	if (getaddrinfo (node, nullptr, &hints, &lookup) != 0)
-		throw StringParseError ("unknown host " + node);
-
-	IPAddress result;
-	assert (lookup != nullptr);
-	sockaddr_in* addr = reinterpret_cast<sockaddr_in*> (lookup[0].ai_addr);
-	result.host = ntohl (addr->sin_addr.s_addr);
-	result.port = ntohs (addr->sin_port);
-	freeaddrinfo (lookup);
-	return result;
-}
-
 END_ZFC_NAMESPACE
\ No newline at end of file
--- a/sources/network/ipaddress.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/network/ipaddress.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -29,6 +29,7 @@
 */
 
 #pragma once
+#include <ostream>
 #include "../main.h"
 
 struct sockaddr;
@@ -36,52 +37,27 @@
 
 BEGIN_ZFC_NAMESPACE
 
-struct IPAddress
+namespace net
 {
-	enum
-	{
-		LOCALHOST = 0x7f000001
-	};
-
-	enum WithPort
-	{
-		WITH_PORT,
-		NO_PORT
-	};
-
-	class StringParseError : public std::exception
+	using octet_t = std::uint8_t;
+	using port_t = std::uint16_t;
+	using host_t = std::uint32_t;
+	struct ip_address
 	{
-		String m_message;
-
-	public:
-		StringParseError (String message) :
-			m_message (message) {}
-
-		const char* what() const throw()
-		{
-			return m_message.chars();
-		}
+		net::host_t host = 0;
+		net::port_t port = 0;
+		bool operator<(const ip_address& other) const;
+		bool operator==(const ip_address& other) const;
 	};
-
-	unsigned long host;
-	unsigned short port;
-
-	IPAddress();
-	IPAddress (unsigned long host, unsigned short port);
-	IPAddress (const IPAddress& other);
-
-	bool compare (const IPAddress& other) const;
-	unsigned char octet (int n) const;
-	void set_octet (int n, unsigned char oct);
-	String to_string (WithPort withport = NO_PORT) const;
-	sockaddr_in to_sockaddr_in() const;
-	bool operator< (const IPAddress& other) const;
-	bool operator== (const IPAddress& other) const { return compare (other); }
-	bool operator!= (const IPAddress& other) const { return not compare (other); }
-	unsigned char operator[] (int n) const { return octet (n); }
-
-	static IPAddress from_string (String input);
-	static IPAddress resolve (String node);
-};
+	constexpr ip_address localhost = {0x7f000001, 0};
+	int ip_compare(const ip_address& one, const ip_address& other);
+	net::octet_t ip_octet(const ip_address& address, unsigned char n);
+	void ip_set_octet(ip_address* address, unsigned char n, net::octet_t octet);
+	std::optional<unsigned short> ip_parse_port(const char* port_string, std::ostream& errorStream);
+	std::optional<ip_address> ip_resolve_hostname(const std::string& node, std::ostream& errorStream);
+	std::optional<ip_address> ip_resolve(const std::string& input_string, std::ostream &errorStream);
+	sockaddr_in ip_address_to_sockaddr_in(const ip_address& address);
+	std::string ip_address_to_string(const ip_address& address);
+}
 
 END_ZFC_NAMESPACE
--- a/sources/network/rconsession.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/network/rconsession.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -31,6 +31,7 @@
 #include <time.h>
 #include "rconsession.h"
 #include "../interface.h"
+#include "../md5.h"
 BEGIN_ZFC_NAMESPACE
 
 // -------------------------------------------------------------------------------------------------
@@ -41,10 +42,10 @@
 	m_adminCount(0),
 	m_interface(nullptr)
 {
-	if (not m_socket.set_blocking(false))
+	std::stringstream errors;
+	if (not m_socket.set_blocking(false, errors))
 	{
-		fprintf(stderr, "unable to set socket as non-blocking: %s\n",
-			m_socket.error_string().chars());
+		fprintf(stderr, "unable to set socket as non-blocking: %s\n", errors.str().data());
 		exit(EXIT_FAILURE);
 	}
 }
@@ -55,7 +56,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-void RCONSession::connect(IPAddress address)
+void RCONSession::connect(net::ip_address address)
 {
 	m_address = address;
 	m_state = RCON_CONNECTING;
@@ -79,9 +80,15 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-void RCONSession::send(const ByteArray& packet)
+bool RCONSession::send(const std::vector<unsigned char>& packet)
 {
-	m_socket.send(m_address, packet);
+	std::stringstream errors;
+	const bool result = m_socket.send(m_address, packet, errors);
+	if (not result)
+	{
+		this->m_interface->printError("Network error: %s\n", errors.str().data());
+	}
+	return result;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -112,8 +119,14 @@
 	}
 
 	// Check for new packets in our socket
-	for (Datagram datagram; m_socket.read(datagram);)
+	std::stringstream errors;
+	for (net::Datagram datagram; m_socket.read(datagram, errors);)
 	{
+		if (errors.tellp() > 0)
+		{
+			m_interface->printError("Network error: %s\n", errors.str().data());
+			errors = {};
+		}
 		// Only process packets that originate from the game server.
 		if (datagram.address == m_address)
 			handlePacket(datagram.message);
@@ -122,7 +135,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-void RCONSession::handlePacket(ByteArray& message)
+void RCONSession::handlePacket(std::vector<unsigned char>& message)
 {
 	Bytestream stream(message);
 
@@ -157,9 +170,9 @@
 
 			case SVRC_MESSAGE:
 				{
-					String message = stream.readString();
-					message.normalize();
-					m_interface->printText("%s\n", message.chars());
+					std::string message = stream.readString();
+					normalize(message);
+					m_interface->printText("%s\n", message.data());
 				}
 				break;
 
@@ -177,9 +190,9 @@
 
 				for (int i = stream.readByte(); i > 0; --i)
 				{
-					String message = stream.readString();
-					message.normalize();
-					m_interface->printText("--- %s\n", message.chars());
+					std::string message = stream.readString();
+					normalize(message);
+					m_interface->printText("--- %s\n", message.data());
 				}
 
 				m_interface->print("End of previous messages.\n");
@@ -197,55 +210,55 @@
 				{
 					unsigned int numCompletions = stream.readShort();
 					m_interface->print("%d completions for '%s'.\n",
-						int(numCompletions), m_lastTabComplete.chars());
+						int(numCompletions), m_lastTabComplete.data());
 				}
 				break;
 
 			case SVRC_TABCOMPLETE:
 				{
-					StringList completes;
+					std::vector<std::string> completes;
 					completes.resize(stream.readByte());
 
-					for (String& completion : completes)
+					for (std::string& completion : completes)
 						completion = stream.readString();
 
 					if (completes.size() == 1)
 					{
 						m_interface->tabComplete(m_lastTabComplete, completes[0]);
 					}
-					else if (not completes.is_empty())
+					else if (completes.size() > 0)
 					{
-						m_interface->print("Completions for '%s':\n", m_lastTabComplete.chars());
+						m_interface->print("Completions for '%s':\n", m_lastTabComplete.data());
 
-						for (int i : range(0, completes.size(), 8))
+						for (std::size_t i = 0; i < completes.size(); i += 8)
 						{
-							Range<int> spliceRange(i, min(i + 8, completes.size()));
-							StringList splice(completes.splice(spliceRange));
-							m_interface->print("- %s\n", splice.join(", ").chars());
+							const int end = min(i + 8, completes.size());
+							std::vector<std::string> splices = splice(completes, i, end);
+							m_interface->print("- %s\n", join_string_list(splices, ", ").data());
 						}
 					}
 				}
 				break;
 
 			case SVRC_WATCHINGCVAR:
-				m_interface->print ("You are now watching %s\n", stream.readString().chars());
-				m_interface->print ("Its value is: %s\n", stream.readString().chars());
+				m_interface->print ("You are now watching %s\n", stream.readString().data());
+				m_interface->print ("Its value is: %s\n", stream.readString().data());
 				break;
 
 			case SVRC_ALREADYWATCHINGCVAR:
-				m_interface->print ("You are already watching %s\n", stream.readString().chars());
+				m_interface->print ("You are already watching %s\n", stream.readString().data());
 				break;
 
 			case SVRC_WATCHCVARNOTFOUND:
-				m_interface->print ("CVar %s not found\n", stream.readString().chars());
+				m_interface->print ("CVar %s not found\n", stream.readString().data());
 				break;
 
 			case SVRC_CVARCHANGED:
 				{
 					String name = stream.readString();
 					String value = stream.readString();
-					m_interface->print ("The value of CVar %s", name.chars());
-					m_interface->print (" is now %s\n", value.chars());
+					m_interface->print ("The value of CVar %s", name.data());
+					m_interface->print (" is now %s\n", value.data());
 
 					// If sv_hostname changes, update the titlebar
 					if (name == "sv_hostname")
@@ -257,7 +270,7 @@
 				break;
 
 			case SVRC_YOUREDISCONNECTED:
-				m_interface->print ("You have been disconnected: %s\n", stream.readString().chars());
+				m_interface->print ("You have been disconnected: %s\n", stream.readString().data());
 				m_interface->disconnected();
 				break;
 			}
@@ -266,7 +279,7 @@
 	catch (std::exception& e)
 	{
 		m_interface->printWarning("Couldn't process packet: %s\n", e.what());
-		m_interface->printWarning("Packet contents was: %s\n", message.quote().chars());
+		m_interface->printWarning("Packet contents was: %s\n", quote(message).data());
 		m_interface->printWarning("Stream position in payload was: %d\n", stream.position());
 	}
 }
@@ -279,10 +292,10 @@
 	{
 	case SVRCU_PLAYERDATA:
 		{
-			StringList players;
+			std::vector<std::string> players;
 
 			for (int i = packet.readByte(); i > 0; --i)
-				players.append(packet.readString());
+				players.push_back(packet.readString());
 
 			m_interface->setPlayerNames(players);
 		}
@@ -306,7 +319,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-UDPSocket* RCONSession::getSocket()
+net::UDPSocket* RCONSession::getSocket()
 {
 	return &m_socket;
 }
@@ -315,7 +328,7 @@
 //
 void RCONSession::sendHello()
 {
-	m_interface->print("Connecting to %s...\n", m_address.to_string(IPAddress::WITH_PORT).chars());
+	m_interface->print("Connecting to %s...\n", net::ip_address_to_string(m_address).data());
 	send({CLRC_BEGINCONNECTION, RCON_PROTOCOL_VERSION});
 	bumpLastPing();
 }
@@ -325,17 +338,17 @@
 void RCONSession::sendPassword()
 {
 	m_interface->print("Authenticating...\n");
-	ByteArray message;
+	std::vector<unsigned char> message;
 	Bytestream stream(message);
 	stream.writeByte(CLRC_PASSWORD);
-	stream.writeString((m_salt + m_password).md5());
+	stream.writeString(md5((m_salt + m_password).data()));
 	send(message);
 	bumpLastPing();
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void RCONSession::setPassword(const String& password)
+void RCONSession::setPassword(const std::string& password)
 {
 	m_password = password;
 }
@@ -359,12 +372,12 @@
 // -------------------------------------------------------------------------------------------------
 // Returns true if the message was successfully sent.
 //
-bool RCONSession::sendCommand(const String& commandString)
+bool RCONSession::sendCommand(const std::string& commandString)
 {
-	if (m_state != RCON_CONNECTED or commandString.isEmpty())
+	if (m_state != RCON_CONNECTED or commandString.empty())
 		return false;
 
-	ByteArray message;
+	std::vector<unsigned char> message;
 	Bytestream stream(message);
 	stream.writeByte(CLRC_COMMAND);
 	stream.writeString(commandString);
@@ -382,7 +395,7 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-const IPAddress& RCONSession::address() const
+const net::ip_address& RCONSession::address() const
 {
 	return m_address;
 }
@@ -396,18 +409,18 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-const String& RCONSession::getLevel() const
+const std::string& RCONSession::getLevel() const
 {
 	return m_level;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void RCONSession::requestTabCompletion(const String& part)
+void RCONSession::requestTabCompletion(const std::string& part)
 {
 	if (m_serverProtocol >= 4)
 	{
-		ByteArray message;
+		std::vector<unsigned char> message;
 		Bytestream stream(message);
 		stream.writeByte(CLRC_TABCOMPLETE);
 		stream.writeString(part);
@@ -417,7 +430,7 @@
 	}
 	else
 	{
-		m_interface->print("This server does not support tab-completion\n", m_serverProtocol);
+		m_interface->print("This server does not support tab-completion\n");
 	}
 }
 
@@ -432,22 +445,22 @@
 //
 void RCONSession::requestWatch(const String& cvar)
 {
-	StringList cvars;
-	cvars.append(cvar);
+	const std::vector<std::string> cvars{{cvar}};
 	requestWatch(cvars);
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void RCONSession::requestWatch(const StringList& cvars)
+void RCONSession::requestWatch(const std::vector<std::string>& cvars)
 {
-	ByteArray message;
+	std::vector<unsigned char> message;
 	Bytestream stream(message);
 	stream.writeByte(CLRC_WATCHCVAR);
-
-	for (const String& cvar : cvars)
-		stream.writeString(cvar.normalized());
-
+	for (String cvar : cvars)
+	{
+		normalize(cvar);
+		stream.writeString(cvar);
+	}
 	stream.writeString("");
 	send(message);
 }
--- a/sources/network/rconsession.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/network/rconsession.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -101,40 +101,40 @@
 	RCONSession();
 	~RCONSession();
 
-	const IPAddress&            address() const;
+	const net::ip_address&            address() const;
 	void                        bumpLastPing();
-	void                        connect(IPAddress address);
+	void                        connect(net::ip_address address);
 	void                        disconnect();
 	int                         getAdminCount() const;
-	const String&               getLevel() const;
-	UDPSocket*                  getSocket();
+	const std::string&               getLevel() const;
+	net::UDPSocket*                  getSocket();
 	RCONSessionState            getState() const;
-	void                        handlePacket(ByteArray& message);
+	void                        handlePacket(std::vector<unsigned char>& message);
 	bool                        isActive() const;
 	void                        processServerUpdates(Bytestream& packet);
-	void                        requestTabCompletion(const String& part);
+	void                        requestTabCompletion(const std::string& part);
 	void                        requestWatch (const String& cvar);
 	void                        requestWatch (const StringList& cvars);
-	void                        send(const ByteArray& packet);
-	bool                        sendCommand(const String& commandString);
+	bool send(const std::vector<unsigned char>& packet);
+	bool                        sendCommand(const std::string& commandString);
 	void                        sendHello();
 	void                        sendPassword();
 	void                        setInterface(class Interface* interface);
-	void                        setPassword(const String& password);
+	void                        setPassword(const std::string& password);
 	void                        tick();
 
 private:
 	RCONSessionState m_state;
-	IPAddress m_address;
-	UDPSocket m_socket;
+	net::ip_address m_address;
+	net::UDPSocket m_socket;
 	time_t m_lastPing;
-	String m_password;
-	String m_salt;
+	std::string m_password;
+	std::string m_salt;
 	int m_serverProtocol;
-	String m_hostname;
+	std::string m_hostname;
 	int m_adminCount;
-	String m_level;
-	String m_lastTabComplete;
+	std::string m_level;
+	std::string m_lastTabComplete;
 	class Interface* m_interface;
 };
 
--- a/sources/network/udpsocket.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/network/udpsocket.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -47,115 +47,119 @@
 
 BEGIN_ZFC_NAMESPACE
 
-char UDPSocket::HuffmanBuffer[131072];
+static char HuffmanBuffer[131072];
 
-// -----------------------------------------------------------------------------
-//
-UDPSocket::UDPSocket() :
-	m_socket (socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) {}
+net::UDPSocket::UDPSocket() :
+	file_descriptor{::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)} {}
 
-// -----------------------------------------------------------------------------
-//
-UDPSocket::~UDPSocket()
+net::UDPSocket::~UDPSocket()
 {
 #ifdef _WIN32
-	closesocket (m_socket);
+	::closesocket(m_socket);
 #else
-	close (m_socket);
+	::close(this->file_descriptor);
 #endif
 }
 
-// -------------------------------------------------------------------------------------------------
-//
-bool UDPSocket::set_blocking (bool a)
+bool net::UDPSocket::set_blocking(bool a, std::ostream& errors)
 {
 #ifndef _WIN32
-	int flags = fcntl (m_socket, F_GETFL, 0);
-	int newflags = a ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
-
-	if (flags < 0 || fcntl (m_socket, F_SETFL, newflags) != 0)
+	int flags = ::fcntl(this->file_descriptor, F_GETFL, 0);
+	int newflags = a ?(flags & ~O_NONBLOCK) :(flags | O_NONBLOCK);
+	if (flags < 0 || ::fcntl(this->file_descriptor, F_SETFL, newflags) != 0)
 	{
-		m_error = "Unable to set socket as non-blocking";
+		errors << "Unable to set the UDP socket as non-blocking";
 		return false;
 	}
-
-	return true;
+	else
+	{
+		return true;
+	}
 #else
 	unsigned long mode = a ? 0 : 1;
 
-	if (ioctlsocket (m_socket, FIONBIO, &mode) != 0)
+	if (::ioctlsocket(m_socket, FIONBIO, &mode) != 0)
 	{
-		m_error = strerror (errno);
+		errors << strerror(errno);
 		return false;
 	}
-
-	return true;
+	else
+	{
+		return true;
+	}
 #endif
 }
 
-// -------------------------------------------------------------------------------------------------
-//
-bool UDPSocket::bind (unsigned short port)
+bool net::UDPSocket::bind(const net::port_t port, std::ostream& errors)
 {
 	sockaddr_in svaddr;
-	memset (&svaddr, 0, sizeof svaddr);
+	std::memset(&svaddr, 0, sizeof svaddr);
 	svaddr.sin_family = AF_INET;
-	svaddr.sin_port = htons (port);
-	svaddr.sin_addr.s_addr = htonl (INADDR_ANY);
-
-	if (::bind (m_socket, reinterpret_cast<sockaddr*> (&svaddr), sizeof svaddr) == -1)
+	svaddr.sin_port = htons(port);
+	svaddr.sin_addr.s_addr = htonl(INADDR_ANY);
+	if (::bind(this->file_descriptor, reinterpret_cast<sockaddr*>(&svaddr), sizeof svaddr) == -1)
 	{
-		m_error = String ("Couldn't bind to port ") + String::fromNumber (port);
+		errors << "Couldn't bind to port "s + std::to_string(port);
 		return false;
 	}
-
-	return true;
+	else
+	{
+		return true;
+	}
 }
 
-// -------------------------------------------------------------------------------------------------
-//
-bool UDPSocket::read (Datagram& datagram)
+bool net::UDPSocket::read(Datagram& datagram, std::ostream& errors)
 {
 	sockaddr_in claddr;
 	socklen_t socklen = sizeof claddr;
-	int length = ::recvfrom (m_socket, HuffmanBuffer, sizeof HuffmanBuffer, 0,
-		reinterpret_cast<sockaddr*> (&claddr), &socklen);
-
+	const int length = ::recvfrom(
+		this->file_descriptor,
+		zfc::HuffmanBuffer,
+		sizeof zfc::HuffmanBuffer,
+		0,
+		reinterpret_cast<sockaddr*>(&claddr),
+		&socklen
+	);
 	if (length == -1)
 	{
 		if (errno != EWOULDBLOCK)
-			m_error = String ("recvfrom error: ") + strerror (errno);
-
+		{
+			errors << std::string("recvfrom error: ") + std::strerror(errno);
+		}
 		return false;
 	}
-
 	unsigned char decodedPacket[MAX_DATAGRAM_LENGTH];
 	int decodedLength = sizeof decodedPacket;
-	HUFFMAN_Decode (reinterpret_cast<unsigned char*> (HuffmanBuffer),
+	::HUFFMAN_Decode(reinterpret_cast<unsigned char*>(HuffmanBuffer),
 		decodedPacket, length, &decodedLength);
-	datagram.address.host = ntohl (claddr.sin_addr.s_addr);
-	datagram.address.port = ntohs (claddr.sin_port);
-	datagram.message = ByteArray(decodedPacket, decodedLength);
+	datagram.address.host = ntohl(claddr.sin_addr.s_addr);
+	datagram.address.port = ntohs(claddr.sin_port);
+	datagram.message = std::vector<unsigned char>{&decodedPacket[0], &decodedPacket[decodedLength]};
 	return true;
 }
 
-// -------------------------------------------------------------------------------------------------
-//
-bool UDPSocket::send (const IPAddress& address, const ByteArray& data)
+bool net::UDPSocket::send(const net::ip_address& address, const std::vector<unsigned char>& data, std::ostream& errors)
 {
 	int encodedlength = sizeof HuffmanBuffer;
-	HUFFMAN_Encode (data.data(), reinterpret_cast<unsigned char*> (HuffmanBuffer), data.size(), &encodedlength);
-	sockaddr_in claddr = address.to_sockaddr_in();
-	int res = ::sendto (m_socket, HuffmanBuffer, encodedlength, 0,
-		reinterpret_cast<sockaddr*> (&claddr), sizeof claddr);
-
-	if (res == -1)
+	::HUFFMAN_Encode(data.data(), reinterpret_cast<unsigned char*>(HuffmanBuffer), data.size(), &encodedlength);
+	sockaddr_in claddr = net::ip_address_to_sockaddr_in(address);
+	const int send_result = ::sendto(
+		this->file_descriptor,
+		HuffmanBuffer,
+		encodedlength,
+		0,
+		reinterpret_cast<sockaddr*>(&claddr),
+		sizeof claddr
+	);
+	if (send_result == -1)
 	{
-		m_error = String ("Unable to launch packet: ") + strerror (errno);
+		errors << "Unable to launch packet: "s + std::strerror(errno);
 		return false;
 	}
-
-	return true;
+	else
+	{
+		return true;
+	}
 }
 
 END_ZFC_NAMESPACE
\ No newline at end of file
--- a/sources/network/udpsocket.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/network/udpsocket.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -34,35 +34,31 @@
 #include "bytestream.h"
 BEGIN_ZFC_NAMESPACE
 
-enum { MAX_DATAGRAM_LENGTH = 5120 };
+namespace net
+{
+	constexpr int MAX_DATAGRAM_LENGTH = 5120;
+	struct Datagram;
+	class UDPSocket;
+}
 
-struct Datagram
+struct net::Datagram
 {
-	ByteArray message;
-	IPAddress address;
+	std::vector<unsigned char> message;
+	net::ip_address address;
 };
 
 // -------------------------------------------------------------------------------------------------
 //
-class UDPSocket
+class net::UDPSocket
 {
 public:
 	UDPSocket();
 	virtual ~UDPSocket();
-
-	bool bind (unsigned short port);
-	bool read (Datagram& datagram);
-	bool send (const IPAddress& address, const ByteArray& data);
-	bool set_blocking (bool a);
-	const String& error_string() const { return m_error; }
-	int file_descriptor() const { return m_socket; }
-
-private:
-	static char HuffmanBuffer[131072];
-
-	IPAddress m_addr;
-	String m_error;
-	int m_socket;
+	[[nodiscard]] bool bind(port_t port, std::ostream &errors);
+	[[nodiscard]] bool read(Datagram& datagram, std::ostream& errors);
+	[[nodiscard]] bool send(const ip_address& address, const std::vector<unsigned char>& data, std::ostream &errors);
+	[[nodiscard]] bool set_blocking(bool a, std::ostream &errors);
+	const int file_descriptor;
 };
 
 END_ZFC_NAMESPACE
\ No newline at end of file
--- a/sources/range.h	Wed Jan 27 12:41:50 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
-/*
-	Copyright 2014 - 2016 Teemu Piippo
-	All rights reserved.
-
-	Redistribution and use in source and binary forms, with or without
-	modification, are permitted provided that the following conditions
-	are met:
-
-	1. Redistributions of source code must retain the above copyright
-	   notice, this list of conditions and the following disclaimer.
-	2. Redistributions in binary form must reproduce the above copyright
-	   notice, this list of conditions and the following disclaimer in the
-	   documentation and/or other materials provided with the distribution.
-	3. Neither the name of the copyright holder nor the names of its
-	   contributors may be used to endorse or promote products derived from
-	   this software without specific prior written permission.
-
-	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
-	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-	PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
-	OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-#pragma once
-#include <algorithm>
-#include <memory>
-#include "basics.h"
-BEGIN_ZFC_NAMESPACE
-
-//
-// -------------------------------------------------------------------------------------------------
-//
-
-template<typename T>
-class Range
-{
-	T m_a;
-	T m_b;
-	T m_step;
-
-public:
-	struct Iterator
-	{
-		T value;
-		T step;
-
-		Iterator (T value, T step) :
-			value (value),
-			step (step) {}
-
-		T& operator*()
-		{
-			return value;
-		}
-
-		bool operator== (const Iterator& other) const
-		{
-			return value == other.value;
-		}
-
-		bool operator!= (const Iterator& other) const
-		{
-			return value < other.value;
-		}
-
-		Iterator& operator++()
-		{
-			value += step; return *this;
-		}
-	};
-
-	Range (T a, T b, T step = 1) :
-		m_a (a),
-		m_b (b),
-		m_step (step)
-	{
-		check_bounds();
-	}
-
-	Range() :
-		m_a (T()),
-		m_b (T()) {}
-
-	Iterator begin() const
-	{
-		return Iterator(min(), m_step);
-	}
-
-	Iterator end() const
-	{
-		return Iterator(max(), m_step);
-	}
-
-	T min() const
-	{
-		return m_a;
-	}
-
-	T max() const
-	{
-		return m_b;
-	}
-
-	T step() const
-	{
-		return m_step;
-	}
-
-	void check_bounds()
-	{
-		if (m_b < m_a)
-			std::swap (m_a, m_b);
-	}
-
-	bool contains (T c) const
-	{
-		return c >= m_a
-		   and c <= m_b;
-	}
-
-	bool contains_exclusively (T c) const
-	{
-		return c > m_a
-		   and c < m_b;
-	}
-
-	bool overlaps (Range<T> const& other) const
-	{
-		return contains (other.m_a)
-			or contains (other.m_b);
-	}
-
-	bool operator== (Range<T> const& other) const
-	{
-		return m_a == other.m_a
-		   and m_b == other.m_b;
-	}
-
-	bool operator!= (Range<T> const& other) const
-	{
-		return not operator== (other);
-	}
-};
-
-template<typename T>
-Range<T> range(T a, T b, T step = 1)
-{
-	return Range<T>(a, b, step);
-}
-
-template<typename T>
-Range<T> range(T b)
-{
-	return Range<T>(T(), b);
-}
-
-END_ZFC_NAMESPACE
--- a/sources/version.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/version.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
--- a/sources/version.h	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/version.h	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without

mercurial