diff --git a/src/Home/Net/Load.CC b/src/Home/Net/Load.CC
index 890456bc..98ad8f58 100755
--- a/src/Home/Net/Load.CC
+++ b/src/Home/Net/Load.CC
@@ -17,9 +17,10 @@ Cd(__DIR__);;
 
 #include "Utilities/BST"
 #include "Protocols/UDP/MakeUDP"
-#include "Protocols/TCP/MakeTCP"
-
 #include "Protocols/DNS"
+
+#include "Protocols/TCP/MakeTCP"
+
 #include "Protocols/DHCP"
 
 #include "Utilities/NetHandler" // needs IPV4, UDP, ICMP
\ No newline at end of file
diff --git a/src/Home/Net/Programs/Gopher.CC b/src/Home/Net/Programs/Gopher.CC
new file mode 100755
index 00000000..9dd12bf8
--- /dev/null
+++ b/src/Home/Net/Programs/Gopher.CC
@@ -0,0 +1,290 @@
+/* Gopher client for TempleOS/Sockets
+   by Declan Hoare 2020
+   ported to ZealOS by TomAwezome in 2021
+   Public Domain - No Warranty */
+
+#define GOPHER_CLIENT_LOADED
+
+#ifndef GOPHER_ASSOCIATIONS
+#define GOPHER_ASSOCIATIONS
+U8* gopher_associations[256];
+#endif
+
+I64 GopherOpen(U8* host, U16 port, U8* selector, U8* query)
+{
+	U8* line;
+	I64 sock;
+	
+	if (host == NULL)
+	{
+		return -1;
+	}
+	
+//	sock = create_connection(host, port);
+	sock = TCPConnectionCreate(host, port);
+	if (sock < 0)
+	{
+		PrintErr("Failed to connect to %s:%d\n", host, port);
+		return sock;
+	}
+	
+	if (query == NULL)
+	{
+		line = StrPrint(NULL, "%s\r\n", selector);
+	}
+	else
+	{
+		line = StrPrint(NULL, "%s\t%s\r\n", selector, query);
+	}
+	
+//	sendString(sock, line, 0);
+	TCPSocketSendString(sock, line);
+	Free(line);
+	
+	return sock;
+}
+
+public I64 GopherDl
+(U8* host, U16 port = 70, U8* selector, U8* query = NULL, U8* dest)
+{
+	CFile* f;
+	U8 buf[BLK_SIZE];
+	I64 data_len = 0, total_len = 0, got, sock;
+	
+	f = FOpen(dest, "w");
+	if (!f)
+	{
+		PrintErr("Failed to open %s for writing\n", dest);
+		return -1;
+	}
+	
+	sock = GopherOpen(host, port, selector, query);
+	if (sock < 0)
+	{
+		return sock;
+	}
+	
+	while (TRUE)
+	{
+//		got = recv(sock, buf + data_len, sizeof(buf) - data_len, 0);
+		got = TCPSocketReceive(sock, buf + data_len, sizeof(buf) - data_len);
+		if (got <= 0)
+		{
+			if (data_len != 0 && !FBlkWrite(f, buf))
+			{
+				break;
+			}
+			f->de.size = total_len;
+			FClose(f);
+			return got;
+		}
+		data_len += got;
+		total_len += got;
+		if (data_len == BLK_SIZE)
+		{
+			if (!FBlkWrite(f, buf))
+			{
+				break;
+			}
+			data_len = 0;
+		}
+	}
+	PrintErr("Write failed, %s may be corrupted\n", dest);
+	FClose(f);
+	return -1;
+}
+
+U0 PrintEscaped(U8* txt, U8* backslashes = "")
+{
+	U8* cur;
+	U8 offending;
+	
+	while (cur = StrFirstOcc(txt, "$$\\\""))
+	{
+		offending = *cur;
+		*cur = 0;
+		if (offending == '$$')
+		{
+			"%s$$$$", txt;
+		}
+		else
+		{
+			"%s%s%c", txt, backslashes, offending;
+		}
+		txt = cur + 1;
+	}
+	"%s", txt;
+}
+
+U8* GopherBasename(U8* selector)
+{
+	U8* lastslash = StrLastOcc(selector, "/");
+	if (lastslash == NULL)
+	{
+		return selector;
+	}
+	else
+	{
+		return lastslash + 1;
+	}
+}
+
+U0 GopherTextView(U8* host, U16 port, U8* selector)
+{
+	U8* basename;
+	U8* tmpname;
+	
+	DirMake("::/Tmp/Gopher");
+	
+	basename = ExtChange(GopherBasename(selector), "TXT");
+	tmpname = StrPrint(NULL, "::/Tmp/Gopher/%s", basename);
+	Free(basename);
+	
+	if (GopherDl(host, port, selector, , tmpname) == 0)
+	{
+		Plain(tmpname);
+	}
+	else
+	{
+		PrintErr("Failed to download %s from %s:%d\n",
+			selector, host, port);
+	}
+	Free(tmpname);
+}
+
+U0 GopherLine(U8* line)
+{
+	U8 type;
+	U8* display;
+	U8* selector;
+	U8* host = NULL;
+	U16 port = 0;
+	
+	if (*line == 0)
+	{
+		"\n";
+		return;
+	}
+	type = *line++;
+	display = line;
+	
+	line = StrFind("\t", line);
+	if (line)
+	{
+		*line = 0;
+		line++;
+	}	
+	selector = line;
+
+	if (line)
+	{
+		line = StrFind("\t", line);
+	}
+	if (line)
+	{
+		*line = 0;
+		line++;
+	}
+	host = line;
+	
+	if (line)
+	{
+		line = StrFind("\t", line);
+	}
+	if (line)
+	{
+		*line = 0;
+		line++;
+		port = Str2I64(line);
+	}
+	
+	switch (type)
+	{
+		case '3':
+			PrintErr("");
+		case 'i':
+			PrintEscaped(display);
+			break;
+		default:
+			"$$MA,\"";
+			PrintEscaped(display, "\\");
+			"\",LM=\"%s(\\\"", gopher_associations[type];
+			PrintEscaped(host, "\\\\\\");
+			"\\\",%d,\\\"", port;
+			PrintEscaped(selector, "\\\\\\");
+			"\\\");\\n\"$$";
+	}
+	"\n";
+}
+
+public I64 GopherMenu
+(U8* host, U16 port = 70, U8* selector = "/", U8* query = NULL)
+{
+	I64 sock, n;
+	U8 buf[256];
+	
+	sock = GopherOpen(host, port, selector, query);
+	if (sock < 0)
+	{
+		return sock;
+	}
+	
+	do 
+	{
+//		n = recvLine(sock, buf, sizeof(buf), 0);
+		n = TCPSocketReceiveLine(sock, buf, sizeof(buf));
+		if (StrCompare(buf, ".") == 0)
+		{
+			break;
+		}
+		GopherLine(buf);
+	}
+	while (n > 0);
+
+//	close(sock);
+	TCPSocketClose(sock);
+	return 0;
+}
+
+class CQueryForm
+{
+	U8 query[65] format "$$DA-P,A=\"Query:%s\"$$";
+};
+U0 GopherQueryPrompt(U8* host, U16 port, U8* selector)
+{
+	CQueryForm form;
+	
+	form.query[0] = 0;
+	if (PopUpForm(&form))
+	{
+		GopherMenu(host, port, selector, form.query);
+	}
+}
+
+class CDlForm
+{
+	U8 name[256] format "$$DA-P,LEN=255,A=\"FileName:%s\"$$";
+};
+U0 GopherDlPrompt(U8* host, U16 port, U8* selector)
+{
+	CDlForm form;
+	U8* basename;
+	
+	basename = GopherBasename(selector);
+	MemCopy(form.name, basename,
+		MinI64(StrLen(basename), sizeof(form.name) - 1));
+	form.name[255] = 0;
+	if (PopUpForm(&form))
+	{
+		GopherDl(host, port, selector, , form.name);
+	}
+}
+
+MemSetI64(gopher_associations, "GopherDlPrompt", 256);
+gopher_associations['0'] = "GopherTextView";
+gopher_associations['1'] = "GopherMenu";
+gopher_associations['7'] = "GopherQueryPrompt";
+/* Include this file from your HomeSys, then add more associations */
+
+"\n\nTry using GopherMenu to connect to a gopher server,"
+"for example:  GopherMenu(\"gopher.floodgap.com\");\n\n";
diff --git a/src/Home/Net/Protocols/TCP/TCP.CC b/src/Home/Net/Protocols/TCP/TCP.CC
index 97a91ec2..ed15fb2d 100755
--- a/src/Home/Net/Protocols/TCP/TCP.CC
+++ b/src/Home/Net/Protocols/TCP/TCP.CC
@@ -1076,7 +1076,10 @@ I64 TCPSocketConnect(CTCPSocket *tcp_socket, CSocketAddressStorage *address)
 	}
 
 	if (tcp_socket->state != TCP_STATE_ESTABLISHED)
+	{
+		NetErr("TCP SOCKET CONNECT: Failed to establish TCP Socket connection.");
 		return -1;
+	}
 
 	switch (tcp_socket->socket->state)
 	{
@@ -1303,6 +1306,28 @@ I64 TCPSocketReceive(CTCPSocket *tcp_socket, U8 *buffer, I64 length)
 	return read_total;
 }
 
+I64 TCPSocketReceiveLine(CTCPSocket *tcp_socket, U8 *buffer, I64 size)
+{
+	I64 received = 0;
+
+	while (received + 1 < size)
+	{
+		if (TCPSocketReceive(tcp_socket, buffer + received, 1) <= 0)
+		{
+			NetErr("TCP SOCKET RECEIVE LINE: Failed at TCPSocketReceive.");
+			return -1;
+		}
+		if (buffer[received] == '\n')
+			break;
+		else if (buffer[received] != '\r')
+			received++;
+	}
+
+	// Shrine has 'FIXME: safe but incorrect behavior on overflow'
+	buffer[received] = 0; // terminate string
+	return received;
+}
+
 I64 TCPSocketSend(CTCPSocket *tcp_socket, U8 *buffer, I64 length)
 {
 	I64 sent_total = 0;
@@ -1399,6 +1424,62 @@ I64 TCPSocketSendString(CTCPSocket *tcp_socket, U8 *string)
 	return TCPSocketSendAll(tcp_socket, string, StrLen(string));
 }
 
+CTCPSocket *TCPConnectionCreate(U8 *hostname, U16 port)
+{
+	CSocketAddressIPV4	 socket_addr;
+	CAddressInfo		*current;
+	CAddressInfo		*info = NULL;
+	I64					 error = DNSAddressInfoGet(hostname, NULL, &info);
+	CTCPSocket			*result;
+
+	if (error < 0)
+	{
+		NetErr("TCP CONNECTION CREATE: Failed at DNSAddressInfoGet.");
+		AddressInfoFree(info);
+		return NULL;
+	}
+
+	socket_addr.family = AF_INET;
+	socket_addr.port = EndianU16(port);
+	socket_addr.address.address = INADDR_ANY;
+
+	current = info;
+	while (current)
+	{
+
+		if (current->family == AF_INET && (current->socket_type == 0 || current->socket_type == SOCKET_STREAM))
+		{ // TODO: IPV6 support !
+			socket_addr.address.address = EndianU32(current->address(CSocketAddressIPV4 *)->address.address); // ?? why are bits flipped here
+			AddressInfoFree(info);
+
+			result = TCPSocket(AF_INET);
+			if (!result)
+			{
+				NetErr("TCP CONNECTION CREATE: Failed to create new TCP Socket.");
+				return NULL;
+			}
+			result->timeout = TCP_TIMEOUT;
+			error = TCPSocketConnect(result, &socket_addr);
+			if (error < 0)
+			{
+				NetErr("TCP CONNECTION CREATE: Failed to connect TCP Socket to address.");
+				TCPSocketClose(result);
+				return error;
+			}
+
+			return result;
+		}
+
+		current = current->next;
+	}
+
+	NetErr("TCP CONNECTION CREATE: Failed to find a suitable address.");
+
+	AddressInfoFree(info);
+	return NULL;
+
+}
+
 U0 TCPTreeNodeRep(CTCPTreeNode *node)
 {
 	CTCPTreeQueue		*queue = node->queue->next;
diff --git a/src/System/Utils/ConversionScript.CC b/src/System/Utils/ConversionScript.CC
index d62962aa..b6f2adae 100755
--- a/src/System/Utils/ConversionScript.CC
+++ b/src/System/Utils/ConversionScript.CC
@@ -39,6 +39,7 @@ U0 Cvt(U8 *ff_mask="*", U8 *fu_flags="+r+l-i+S")
 	Find("LstMatch",		ff_mask, fu_flags, "ListMatch");
 	Find("DefineLstLoad",	ff_mask, fu_flags, "DefineListLoad");
 	Find("ExtDft",			ff_mask, fu_flags, "ExtDefault");
+	Find("ExtChg",			ff_mask, fu_flags, "ExtChange");
 	Find("RegDft",			ff_mask, fu_flags, "RegDefault");
 	Find("\"HC\"",			ff_mask, fu_flags, "\"CC\"");
 	Find("CDrv",			ff_mask, fu_flags, "CDrive");
@@ -60,6 +61,7 @@ U0 Cvt(U8 *ff_mask="*", U8 *fu_flags="+r+l-i+S")
 	Find("fp_draw_ms",		ff_mask, fu_flags, "fp_draw_mouse");
 	Find("DrawStdMs",		ff_mask, fu_flags, "DrawStdMouse");
 	Find("WIG_TASK_DFT",	ff_mask, fu_flags, "WIG_TASK_DEFAULT");
+	Find("DirMk",			ff_mask, fu_flags, "DirMake");
 
 
 	"\n$$BK,1$$$$LTRED$$Might want to go over these$$FG$$$$BK,0$$\n";
