diff --git a/r5dev/engine/shared/base_rcon.h b/r5dev/engine/shared/base_rcon.h
index f46ad20e..5ce54733 100644
--- a/r5dev/engine/shared/base_rcon.h
+++ b/r5dev/engine/shared/base_rcon.h
@@ -11,7 +11,7 @@ public:
 	CNetConBase(void)
 	{}
 
-	virtual bool Connect(const char* pHostAdr, const int nHostPort = SOCKET_ERROR);
+	virtual bool Connect(const char* pHostName, const int nHostPort = SOCKET_ERROR);
 	virtual void Disconnect(const char* szReason = nullptr) { NOTE_UNUSED(szReason); };
 
 	virtual bool ProcessBuffer(CConnectedNetConsoleData& data, const char* pRecvBuf, int nRecvLen, const int nMaxLen = SOCKET_ERROR);
diff --git a/r5dev/netconsole/netconsole.cpp b/r5dev/netconsole/netconsole.cpp
index 27c584b2..4d3fb8bc 100644
--- a/r5dev/netconsole/netconsole.cpp
+++ b/r5dev/netconsole/netconsole.cpp
@@ -23,7 +23,7 @@
 //-----------------------------------------------------------------------------
 CNetCon::CNetCon(void)
 	: m_bInitialized(false)
-	, m_bQuitApplication(false)
+	, m_bQuitting(false)
 	, m_bPromptConnect(true)
 	, m_flTickInterval(0.05f)
 {
@@ -44,9 +44,12 @@ CNetCon::~CNetCon(void)
 // Purpose: WSA and NETCON systems init
 // Output : true on success, false otherwise
 //-----------------------------------------------------------------------------
-bool CNetCon::Init(const bool bAnsiColor)
+bool CNetCon::Init(const bool bAnsiColor, const char* pHostName, const int nPort)
 {
+	std::lock_guard<std::mutex> l(m_Mutex);
+
 	g_CoreMsgVCallback = &EngineLoggerSink;
+	TermSetup(bAnsiColor);
 
 	WSAData wsaData;
 	const int nError = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
@@ -57,20 +60,42 @@ bool CNetCon::Init(const bool bAnsiColor)
 		return false;
 	}
 
-	m_bInitialized = true;
+	// Try to connect from given parameters, these are passed in from the
+	// command line. If we fail, return out as this allows the user to
+	// quickly retry again.
+	if (pHostName && nPort != SOCKET_ERROR)
+	{
+		if (!Connect(pHostName, nPort))
+		{
+			return false;
+		}
+	}
 
-	TermSetup(bAnsiColor);
+	m_bInitialized = true;
 	Msg(eDLL_T::NONE, "R5 TCP net console [Version %s]\n", NETCON_VERSION);
 
 	static std::thread frame([this]()
 		{
-			for (;;)
-			{
-				RunFrame();
-			}
+			while (RunFrame())
+			{}
 		});
 	frame.detach();
 
+
+	static std::thread input([this]()
+		{
+			while (!NetConsole()->GetQuitting())
+			{
+				std::string lineInput;
+
+				if (std::getline(std::cin, lineInput))
+				{
+					NetConsole()->RunInput(lineInput);
+				}
+			}
+		});
+	input.detach();
+
 	return true;
 }
 
@@ -80,9 +105,19 @@ bool CNetCon::Init(const bool bAnsiColor)
 //-----------------------------------------------------------------------------
 bool CNetCon::Shutdown(void)
 {
+	if (!m_bInitialized)
+	{
+		// Called twice!
+		Assert(0);
+		return false;
+	}
+
+	m_bInitialized = false;
+
+	std::lock_guard<std::mutex> l(m_Mutex);
 	bool bResult = false;
 
-	m_Socket.CloseAllAcceptedSockets();
+	GetSocketCreator()->CloseAllAcceptedSockets();
 
 	const int nError = ::WSACleanup();
 	if (nError == 0)
@@ -96,11 +131,33 @@ bool CNetCon::Shutdown(void)
 	}
 
 	SpdLog_Shutdown();
-	Console_Shutdown();
-
 	return bResult;
 }
 
+//-----------------------------------------------------------------------------
+// Purpose: handle close events outside application routines
+//-----------------------------------------------------------------------------
+BOOL WINAPI CNetCon::CloseHandler(DWORD eventCode)
+{
+	switch (eventCode)
+	{
+	case CTRL_C_EVENT:
+	case CTRL_BREAK_EVENT:
+	case CTRL_CLOSE_EVENT:
+	case CTRL_LOGOFF_EVENT:
+	case CTRL_SHUTDOWN_EVENT:
+
+		NetConsole()->SetQuitting(true);
+
+		// Give it time to shutdown properly, value is set to the max possible
+		// of SPI_GETWAITTOKILLSERVICETIMEOUT, which is 20000ms by default.
+		Sleep(20000);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
 //-----------------------------------------------------------------------------
 // Purpose: terminal setup
 //-----------------------------------------------------------------------------
@@ -108,136 +165,165 @@ void CNetCon::TermSetup(const bool bAnsiColor)
 {
 	SpdLog_Init(bAnsiColor);
 	Console_Init(bAnsiColor);
+
+	// Handle ctrl+x or X close events, give the application time to shutdown
+	// properly and flush all logging buffers.
+	SetConsoleCtrlHandler(CloseHandler, true);
+
+	// Write console output to a file, this includes everything from local logs
+	// to messages over the wire. Note that logs emitted locally are prefixed
+	// with timestamps in () while messages over the wire are in [].
+	SpdLog_InstallSupplementalLogger("supplemental_logger_mt", "netconsole.log");
 }
 
 //-----------------------------------------------------------------------------
 // Purpose: gets input IP and port for initialization
 //-----------------------------------------------------------------------------
-void CNetCon::UserInput(void)
+void CNetCon::RunInput(const string& lineInput)
 {
-	if (std::getline(std::cin, m_Input))
+	std::lock_guard<std::mutex> l(m_Mutex);
+
+	if (lineInput.compare("nquit") == 0)
 	{
-		if (m_Input.compare("nquit") == 0)
+		SetQuitting(true);
+		return;
+	}
+
+	if (IsConnected())
+	{
+		CCommand cmd;
+		cmd.Tokenize(lineInput.c_str());
+
+		if (V_strcmp(cmd.Arg(0), "disconnect") == 0)
 		{
-			m_bQuitApplication = true;
+			Disconnect("user closed connection");
 			return;
 		}
 
-		std::lock_guard<std::mutex> l(m_Mutex);
+		vector<char> vecMsg;
 
-		if (IsConnected())
+		const SocketHandle_t hSocket = GetSocket();
+		bool bSend = false;
+
+		if (cmd.ArgC() > 1)
 		{
-			CCommand cmd;
-			cmd.Tokenize(m_Input.c_str());
-
-			if (V_strcmp(cmd.Arg(0), "disconnect") == 0)
+			if (V_strcmp(cmd.Arg(0), "PASS") == 0) // Auth with RCON server.
 			{
-				Disconnect("user closed connection");
-				return;
+				bSend = Serialize(vecMsg, cmd.Arg(1), "",
+					cl_rcon::request_t::SERVERDATA_REQUEST_AUTH);
 			}
-
-			vector<char> vecMsg;
-
-			const SocketHandle_t hSocket = GetSocket();
-			bool bSend = false;
-
-			if (cmd.ArgC() > 1)
+			else // Execute command query.
 			{
-				if (V_strcmp(cmd.Arg(0), "PASS") == 0) // Auth with RCON server.
-				{
-					bSend = Serialize(vecMsg, cmd.Arg(1), "",
-						cl_rcon::request_t::SERVERDATA_REQUEST_AUTH);
-				}
-				else // Execute command query.
-				{
-					bSend = Serialize(vecMsg, cmd.Arg(0), cmd.GetCommandString(),
-						cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
-				}
-			}
-			else if (!m_Input.empty()) // Single arg command query.
-			{
-				bSend = Serialize(vecMsg, m_Input.c_str(), "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
-			}
-
-			if (bSend) // Only send if serialization process was successful.
-			{
-				if (!Send(hSocket, vecMsg.data(), int(vecMsg.size())))
-				{
-					Error(eDLL_T::CLIENT, NO_ERROR, "Failed to send RCON message: (%s)\n", "SOCKET_ERROR");
-				}
+				bSend = Serialize(vecMsg, cmd.Arg(0), cmd.GetCommandString(),
+					cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
 			}
 		}
-		else // Setup connection from input.
+		else if (!lineInput.empty()) // Single arg command query.
 		{
-			CCommand cmd;
-			cmd.Tokenize(m_Input.c_str(), cmd_source_t::kCommandSrcCode, &m_CharacterSet);
+			bSend = Serialize(vecMsg, lineInput.c_str(), "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND);
+		}
 
-			if (cmd.ArgC() > 1)
+		if (bSend) // Only send if serialization process was successful.
+		{
+			if (!Send(hSocket, vecMsg.data(), int(vecMsg.size())))
 			{
-				const char* inAddr = cmd.Arg(0);
-				const char* inPort = cmd.Arg(1);
-
-				if (!*inAddr || !*inPort)
-				{
-					Warning(eDLL_T::CLIENT, "No IP address or port provided\n");
-					m_bPromptConnect = true;
-					return;
-				}
-
-				if (!Connect(inAddr, atoi(inPort)))
-				{
-					m_bPromptConnect = true;
-					return;
-				}
-			}
-			else
-			{
-				if (!Connect(cmd.GetCommandString()))
-				{
-					m_bPromptConnect = true;
-					return;
-				}
+				Error(eDLL_T::CLIENT, NO_ERROR, "Failed to send RCON message: (%s)\n", "SOCKET_ERROR");
 			}
 		}
 	}
-}
+	else // Setup connection from input.
+	{
+		CCommand cmd;
+		cmd.Tokenize(lineInput.c_str(), cmd_source_t::kCommandSrcCode, &m_CharacterSet);
 
-//-----------------------------------------------------------------------------
-// Purpose: clears the input buffer
-//-----------------------------------------------------------------------------
-void CNetCon::ClearInput(void)
-{
-	m_Input.clear();
+		if (cmd.ArgC() > 1)
+		{
+			const char* inAddr = cmd.Arg(0);
+			const char* inPort = cmd.Arg(1);
+
+			if (!*inAddr || !*inPort)
+			{
+				Warning(eDLL_T::CLIENT, "No IP address or port provided\n");
+				SetPrompting(true);
+				return;
+			}
+
+			if (!Connect(inAddr, atoi(inPort)))
+			{
+				SetPrompting(true);
+				return;
+			}
+		}
+		else
+		{
+			if (!Connect(cmd.GetCommandString()))
+			{
+				SetPrompting(true);
+				return;
+			}
+		}
+	}
 }
 
 //-----------------------------------------------------------------------------
 // Purpose: client's main processing loop
 //-----------------------------------------------------------------------------
-void CNetCon::RunFrame(void)
+bool CNetCon::RunFrame(void)
 {
-	if (IsInitialized() && IsConnected())
+	if (IsInitialized())
 	{
-		std::lock_guard<std::mutex> l(m_Mutex);
+		if (IsConnected())
+		{
+			std::lock_guard<std::mutex> l(m_Mutex);
 
-		CConnectedNetConsoleData& pData = m_Socket.GetAcceptedSocketData(0);
-		Recv(pData);
-	}
-	else if (m_bPromptConnect)
-	{
-		Msg(eDLL_T::NONE, "Enter [<IP>]:<PORT> or <IP> <PORT>: ");
-		m_bPromptConnect = false;
+			CConnectedNetConsoleData& pData = GetSocketCreator()->GetAcceptedSocketData(0);
+			Recv(pData);
+		}
+		else if (GetPrompting())
+		{
+			Msg(eDLL_T::NONE, "Enter [<IP>]:<PORT> or <IP> <PORT>: ");
+			SetPrompting(false);
+		}
 	}
 
 	std::this_thread::sleep_for(IntervalToDuration(m_flTickInterval));
+	return true;
 }
 
 //-----------------------------------------------------------------------------
 // Purpose: checks if application should be terminated
 // Output : true for termination, false otherwise
 //-----------------------------------------------------------------------------
-bool CNetCon::ShouldQuit(void) const
+bool CNetCon::GetQuitting(void) const
 {
-	return m_bQuitApplication;
+	return m_bQuitting;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: set whether we should quit
+// input : bQuit
+//-----------------------------------------------------------------------------
+void CNetCon::SetQuitting(const bool bQuit)
+{
+	m_bQuitting = bQuit;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: checks if we should prompt the connect message
+// Output : true for prompting, false otherwise
+//-----------------------------------------------------------------------------
+bool CNetCon::GetPrompting(void) const
+{
+	return m_bPromptConnect;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: set whether we should prompt the connect message
+// input : bPrompt
+//-----------------------------------------------------------------------------
+void CNetCon::SetPrompting(const bool bPrompt)
+{
+	m_bPromptConnect = bPrompt;
 }
 
 //-----------------------------------------------------------------------------
@@ -254,10 +340,10 @@ void CNetCon::Disconnect(const char* szReason)
 		}
 
 		Msg(eDLL_T::CLIENT, "Disconnect: (%s)\n", szReason);
-		m_Socket.CloseAcceptedSocket(0);
+		GetSocketCreator()->CloseAcceptedSocket(0);
 	}
 
-	m_bPromptConnect = true;
+	SetPrompting(true);
 }
 
 //-----------------------------------------------------------------------------
@@ -374,23 +460,25 @@ int main(int argc, char* argv[])
 		}
 	}
 
-	if (!NetConsole()->Init(bEnableColor))
+	const char* pHostName = nullptr;
+	int nPort = SOCKET_ERROR;
+
+	if (argc >= 3) // Get IP and Port from command line.
+	{
+		pHostName = argv[1];
+		nPort = atoi(argv[2]);
+	}
+
+	if (!NetConsole()->Init(bEnableColor, pHostName, nPort))
 	{
 		return EXIT_FAILURE;
 	}
 
-	if (argc >= 3) // Get IP and Port from command line.
+	while (!NetConsole()->GetQuitting())
 	{
-		if (!NetConsole()->Connect(argv[1], atoi(argv[2])))
-		{
-			return EXIT_FAILURE;
-		}
-	}
-
-	while (!NetConsole()->ShouldQuit())
-	{
-		NetConsole()->UserInput();
-		NetConsole()->ClearInput();
+		// Run with a reasonable tick rate so we don't eat all the CPU.
+		std::this_thread::sleep_for(
+			IntervalToDuration(NetConsole()->GetTickInterval()));
 	}
 
 	if (!NetConsole()->Shutdown())
diff --git a/r5dev/netconsole/netconsole.h b/r5dev/netconsole/netconsole.h
index 2a0926a6..0d77ab48 100644
--- a/r5dev/netconsole/netconsole.h
+++ b/r5dev/netconsole/netconsole.h
@@ -17,15 +17,21 @@ public:
 	CNetCon(void);
 	~CNetCon(void);
 
-	bool Init(const bool bAnsiColor);
+	bool Init(const bool bAnsiColor, const char* pHostName = nullptr, const int nPort = SOCKET_ERROR);
 	bool Shutdown(void);
-
 	void TermSetup(const bool bAnsiColor);
-	void UserInput(void);
-	void ClearInput(void);
 
-	void RunFrame(void);
-	bool ShouldQuit(void) const;
+	void RunInput(const string& lineInput);
+	bool RunFrame(void);
+
+	bool GetQuitting(void) const;
+	void SetQuitting(const bool bQuit);
+
+	bool GetPrompting(void) const;
+	void SetPrompting(const bool bPrompt);
+
+	inline float GetTickInterval() const { return m_flTickInterval; }
+	static BOOL WINAPI CloseHandler(DWORD eventCode);
 
 	virtual void Disconnect(const char* szReason = nullptr);
 	virtual bool ProcessMessage(const char* pMsgBuf, const int nMsgLen) override;
@@ -39,13 +45,11 @@ public:
 
 private:
 	bool m_bInitialized;
-	bool m_bQuitApplication;
+	bool m_bQuitting;
 	bool m_bPromptConnect;
 	float m_flTickInterval;
 
 	characterset_t m_CharacterSet;
-
-	std::string m_Input;
 	mutable std::mutex m_Mutex;
 };