diff --git a/src/PowderToySDL.cpp b/src/PowderToySDL.cpp
index 78f269678..b416fb6e2 100644
--- a/src/PowderToySDL.cpp
+++ b/src/PowderToySDL.cpp
@@ -2,6 +2,7 @@
 #include "common/tpt-minmax.h"
 
 #include <map>
+#include <optional>
 #include <ctime>
 #include <climits>
 #ifdef WIN
@@ -296,109 +297,6 @@ unsigned int GetTicks()
 	return SDL_GetTicks();
 }
 
-std::map<ByteString, ByteString> readArguments(int argc, char * argv[])
-{
-	std::map<ByteString, ByteString> arguments;
-
-	//Defaults
-	arguments["scale"] = "";
-	arguments["proxy"] = "";
-	arguments["cafile"] = "";
-	arguments["capath"] = "";
-	arguments["nohud"] = "false"; //the nohud, sound, and scripts commands currently do nothing.
-	arguments["sound"] = "false";
-	arguments["kiosk"] = "false";
-	arguments["redirect"] = "false";
-	arguments["nobluescreen"] = "false";
-	arguments["scripts"] = "false";
-	arguments["open"] = "";
-	arguments["ddir"] = "";
-	arguments["ptsave"] = "";
-
-	for (int i=1; i<argc; i++)
-	{
-		if (!strncmp(argv[i], "scale:", 6) && argv[i][6])
-		{
-			arguments["scale"] = &argv[i][6];
-		}
-		else if (!strncmp(argv[i], "proxy:", 6))
-		{
-			if(argv[i][6])
-				arguments["proxy"] = &argv[i][6];
-			else
-				arguments["proxy"] = "false";
-		}
-		else if (!strncmp(argv[i], "cafile:", 7))
-		{
-			if(argv[i][7])
-				arguments["cafile"] = &argv[i][7];
-			else
-				arguments["cafile"] = "false";
-		}
-		else if (!strncmp(argv[i], "capath:", 7))
-		{
-			if(argv[i][7])
-				arguments["capath"] = &argv[i][7];
-			else
-				arguments["capath"] = "false";
-		}
-		else if (!strncmp(argv[i], "nohud", 5))
-		{
-			arguments["nohud"] = "true";
-		}
-		else if (!strncmp(argv[i], "kiosk", 5))
-		{
-			arguments["kiosk"] = "true";
-		}
-		else if (!strncmp(argv[i], "redirect", 8))
-		{
-			arguments["redirect"] = "true";
-		}
-		else if (!strncmp(argv[i], "nobluescreen", 12))
-		{
-			arguments["nobluescreen"] = "true";
-		}
-		else if (!strncmp(argv[i], "sound", 5))
-		{
-			arguments["sound"] = "true";
-		}
-		else if (!strncmp(argv[i], "scripts", 8))
-		{
-			arguments["scripts"] = "true";
-		}
-		else if (!strncmp(argv[i], "file:", 5) && strlen(argv[i]) >= 7)
-		{
-			arguments["open"] = format::URLDecode(argv[i] + 7); // skip "file://"
-		}
-		else if (!strncmp(argv[i], "open", 5) && i+1<argc)
-		{
-			arguments["open"] = argv[i+1];
-			i++;
-		}
-		else if (!strncmp(argv[i], "ddir", 5) && i+1<argc)
-		{
-			arguments["ddir"] = argv[i+1];
-			i++;
-		}
-		else if (!strncmp(argv[i], "ptsave:", 7) && strlen(argv[i]) >= 8)
-		{
-			arguments["ptsave"] = argv[i];
-			break;
-		}
-		else if (!strncmp(argv[i], "ptsave", 7) && i+1<argc)
-		{
-			arguments["ptsave"] = argv[i+1];
-			i++;
-			break;
-		}
-		else if (!strncmp(argv[i], "disable-network", 16))
-		{
-			arguments["disable-network"] = "true";
-		}
-	}
-	return arguments;
-}
-
 int elapsedTime = 0, currentTime = 0, lastTime = 0, currentFrame = 0;
 unsigned int lastTick = 0;
 unsigned int lastFpsUpdate = 0;
@@ -731,14 +629,53 @@ int main(int argc, char * argv[])
 
 	Platform::originalCwd = Platform::GetCwd();
 
-	std::map<ByteString, ByteString> arguments = readArguments(argc, argv);
+	using Argument = std::optional<ByteString>;
+	std::map<ByteString, Argument> arguments;
 
-	if (arguments["ddir"].length())
+	for (auto i = 1; i < argc; ++i)
+	{
+		auto str = ByteString(argv[i]);
+		if (str.BeginsWith("file://"))
+		{
+			arguments.insert({ "open", format::URLDecode(str.substr(7 /* length of the "file://" prefix */)) });
+		}
+		else if (str.BeginsWith("ptsave:"))
+		{
+			arguments.insert({ "ptsave", str });
+		}
+		else if (auto split = str.SplitBy(':'))
+		{
+			arguments.insert({ split.Before(), split.After() });
+		}
+		else if (auto split = str.SplitBy('='))
+		{
+			arguments.insert({ split.Before(), split.After() });
+		}
+		else if (str == "open" || str == "ptsave" || str == "ddir")
+		{
+			if (i + 1 < argc)
+			{
+				arguments.insert({ str, argv[i + 1] });
+				i += 1;
+			}
+			else
+			{
+				std::cerr << "no value provided for command line parameter " << str << std::endl;
+			}
+		}
+		else
+		{
+			arguments.insert({ str, "" }); // so .has_value() is true
+		}
+	}
+
+	auto ddirArg = arguments["ddir"];
+	if (ddirArg.has_value())
 	{
 #ifdef WIN
-		int failure = _chdir(arguments["ddir"].c_str());
+		int failure = _chdir(ddirArg.value().c_str());
 #else
-		int failure = chdir(arguments["ddir"].c_str());
+		int failure = chdir(ddirArg.value().c_str());
 #endif
 		if (!failure)
 			Platform::sharedCwd = Platform::GetCwd();
@@ -787,14 +724,27 @@ int main(int argc, char * argv[])
 	momentumScroll = Client::Ref().GetPrefBool("MomentumScroll", true);
 	showAvatars = Client::Ref().GetPrefBool("ShowAvatars", true);
 
+	auto true_string = [](ByteString str) {
+		str = str.ToLower();
+		return str == "true" ||
+		       str == "t" ||
+		       str == "on" ||
+		       str == "yes" ||
+		       str == "y" ||
+		       str == ""; // standalone "redirect" or "disable-bluescreen" or similar arguments
+	};
+	auto true_arg = [&true_string](Argument arg) {
+		return arg.has_value() && true_string(arg.value());
+	};
 
-	if(arguments["kiosk"] == "true")
+	auto kioskArg = arguments["kiosk"];
+	if (kioskArg.has_value())
 	{
-		fullscreen = true;
+		fullscreen = true_string(kioskArg.value());
 		Client::Ref().SetPref("Fullscreen", fullscreen);
 	}
 
-	if(arguments["redirect"] == "true")
+	if (true_arg(arguments["redirect"]))
 	{
 		FILE *new_stdout = freopen("stdout.log", "w", stdout);
 		FILE *new_stderr = freopen("stderr.log", "w", stderr);
@@ -804,26 +754,33 @@ int main(int argc, char * argv[])
 		}
 	}
 
-	if(arguments["scale"].length())
+	auto scaleArg = arguments["scale"];
+	if (scaleArg.has_value())
 	{
-		scale = arguments["scale"].ToNumber<int>();
-		Client::Ref().SetPref("Scale", scale);
+		try
+		{
+			scale = scaleArg.value().ToNumber<int>();
+			Client::Ref().SetPref("Scale", scale);
+		}
+		catch (const std::runtime_error &e)
+		{
+			std::cerr << "failed to set scale: " << e.what() << std::endl;
+		}
 	}
 
-	auto clientConfig = [](ByteString cmdlineValue, ByteString configName, ByteString defaultValue) {
+	auto clientConfig = [](Argument arg, ByteString name, ByteString defaultValue) {
 		ByteString value;
-		if (cmdlineValue.length())
+		if (arg.has_value())
 		{
-			value = cmdlineValue;
-			if (value == "false")
+			if (value == "")
 			{
 				value = defaultValue;
 			}
-			Client::Ref().SetPref(configName, value);
+			Client::Ref().SetPref(name, value);
 		}
 		else
 		{
-			value = Client::Ref().GetPrefByteString(configName, defaultValue);
+			value = Client::Ref().GetPrefByteString(name, defaultValue);
 		}
 		return value;
 	};
@@ -831,9 +788,7 @@ int main(int argc, char * argv[])
 	ByteString cafileString = clientConfig(arguments["cafile"], "CAFile", "");
 	ByteString capathString = clientConfig(arguments["capath"], "CAPath", "");
 
-	bool disableNetwork = false;
-	if (arguments.find("disable-network") != arguments.end())
-		disableNetwork = true;
+	bool disableNetwork = true_arg(arguments["disable-network"]);
 
 	Client::Ref().Initialise(proxyString, cafileString, capathString, disableNetwork);
 
@@ -871,11 +826,7 @@ int main(int argc, char * argv[])
 	engine->SetFastQuit(Client::Ref().GetPrefBool("FastQuit", true));
 
 #if !defined(DEBUG)
-	bool enableBluescreen = true;
-	if(arguments["nobluescreen"] == "true")
-	{
-		enableBluescreen = false;
-	}
+	bool enableBluescreen = !true_arg(arguments["disable-bluescreen"]);
 	if (enableBluescreen)
 	{
 		//Get ready to catch any dodgy errors
@@ -899,23 +850,24 @@ int main(int argc, char * argv[])
 		gameController = new GameController();
 		engine->ShowWindow(gameController->GetView());
 
-		if(arguments["open"].length())
+		auto openArg = arguments["open"];
+		if (openArg.has_value())
 		{
 #ifdef DEBUG
-			std::cout << "Loading " << arguments["open"] << std::endl;
+			std::cout << "Loading " << openArg.value() << std::endl;
 #endif
-			if (Platform::FileExists(arguments["open"]))
+			if (Platform::FileExists(openArg.value()))
 			{
 				try
 				{
 					std::vector<char> gameSaveData;
-					if (!Platform::ReadFile(gameSaveData, arguments["open"]))
+					if (!Platform::ReadFile(gameSaveData, openArg.value()))
 					{
 						new ErrorMessage("Error", "Could not read file");
 					}
 					else
 					{
-						SaveFile * newFile = new SaveFile(arguments["open"]);
+						SaveFile * newFile = new SaveFile(openArg.value());
 						GameSave * newSave = new GameSave(std::move(gameSaveData));
 						newFile->SetGameSave(newSave);
 						gameController->LoadSaveFile(newFile);
@@ -934,18 +886,18 @@ int main(int argc, char * argv[])
 			}
 		}
 
-		if (arguments["ptsave"].length())
+		auto ptsaveArg = arguments["ptsave"];
+		if (ptsaveArg.has_value())
 		{
 			engine->g->fillrect((engine->GetWidth()/2)-101, (engine->GetHeight()/2)-26, 202, 52, 0, 0, 0, 210);
 			engine->g->drawrect((engine->GetWidth()/2)-100, (engine->GetHeight()/2)-25, 200, 50, 255, 255, 255, 180);
 			engine->g->drawtext((engine->GetWidth()/2)-(Graphics::textwidth("Loading save...")/2), (engine->GetHeight()/2)-5, "Loading save...", style::Colour::InformationTitle.Red, style::Colour::InformationTitle.Green, style::Colour::InformationTitle.Blue, 255);
 
 			blit(engine->g->vid);
-			ByteString ptsaveArg = arguments["ptsave"];
 			try
 			{
 				ByteString saveIdPart;
-				if (ByteString::Split split = arguments["ptsave"].SplitBy(':'))
+				if (ByteString::Split split = ptsaveArg.value().SplitBy(':'))
 				{
 					if (split.Before() != "ptsave")
 						throw std::runtime_error("Not a ptsave link");