1. spencercw
  2. gb_emulator

Commits

spencercw  committed 18edb95

#33 Add command line options for acting as a serial client or server.

The options currently don't do anything.

  • Participants
  • Parent commits 2f8897c
  • Branches serial

Comments (0)

Files changed (2)

File gb_emulator/include/gb_emulator/gb_config.h

View file
  • Ignore whitespace
 	 */
 	boost::filesystem::path rom;
 
+	//! The serial server to connect to.
+	/**
+	 * Leave this empty if you don't want to use serial communications or if you want to act as a
+	 * server. If this is not empty \c serialPort must not be zero. This may be a hostname or an IP
+	 * address (IPv4 and IPv6 are supported).
+	 */
+	std::string serialHost;
+
+	//! The port of the remote serial server, or the port to serve on.
+	/**
+	 * Set this to zero if you don't want to use serial communications. To act as a server set this
+	 * value and leave \c serialHost empty. If \c serialHost is not empty this value must not be
+	 * zero.
+	 */
+	uint16_t serialPort;
+
 	//! Default constructor; sets appropriate default values.
 	GbConfig():
 	videoDriver(VIDEO_DEFAULT),
 	audioDriver(AUDIO_DEFAULT),
-	renderer(RENDER_DEFAULT)
+	renderer(RENDER_DEFAULT),
+	serialPort(0)
 	{
 	}
 };

File gb_emulator_main/main.cpp

View file
  • Ignore whitespace
     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
+#include <stdint.h>
 #include <stdlib.h>
 
 #include <iostream>
+#include <sstream>
 #include <string>
 
 #include <argtable2.h>
 
 #include <SDL/SDL_main.h>
 
+#ifdef _WIN32
+#include <WS2tcpip.h>
+#else
+#include <arpa/inet.h>
+#endif
+
 #include <gb_emulator/gb.hpp>
 #include <gb_emulator/gb_config.h>
 
 using std::basic_string;
 using std::clog;
 using std::ios_base;
+using std::istringstream;
 using std::streampos;
 using std::string;
 
 
 static const fs::path BIOS = "bios.bin";
 
+// Default port number for serial communications
+static const uint16_t DEFAULT_SERIAL_PORT = 27218;
+
+bool parsePort(const string &portStr, uint16_t &port)
+{
+	if (portStr.empty() || portStr.length() > 5 ||
+		portStr.find_first_not_of("0123456789") != string::npos)
+	{
+		return false;
+	}
+
+	uint32_t tmpPort;
+	istringstream iss(portStr);
+	iss >> tmpPort;
+	assert(iss);
+	if (!tmpPort || tmpPort > 0xffff)
+	{
+		return false;
+	}
+
+	port = static_cast<uint16_t>(tmpPort);
+	return true;
+}
+
 int main(int argc, char **argv)
 {
 	try
 			"the audio driver to use [wasapi (default), sdl]");
 		arg_str *rendererArg = arg_str0(NULL, "renderer", "<method>",
 			"the render method to use [software (default), hardware (experimental)]");
+		arg_str *clientArg = arg_str0("c", "client", "<host>[:<port>]",
+			"the address and port of the serial server to connect to");
+		arg_str *serverArg = arg_str0("s", "server", "[<port>]",
+			"act as a serial server that another emulator may connect to");
 		arg_lit *help = arg_lit0(NULL, "help", "displays this help message");
 		arg_file *romArg = arg_file0(NULL, NULL, "<rom>", "the ROM file to execute");
 		struct arg_end *end = arg_end(20);
-		void *argTable[] = { vDriverArg, aDriverArg, rendererArg, help, romArg, end };
+		void *argTable[] = { vDriverArg, aDriverArg, rendererArg, clientArg, serverArg, help,
+			romArg, end };
 
 		if (arg_nullcheck(argTable))
 		{
 			return EXIT_FAILURE;
 		}
 
+		// Mark the server port argument as optional
+		serverArg->hdr.flag |= ARG_HASOPTVALUE;
+		serverArg->sval[0] = NULL;
+
 		// Parse the arguments
 		int errors = arg_parse(argc, argv, argTable);
 		if (errors)
 
 		// Handle the arguments
 		GbConfig config;
+
+		// Video driver
 		if (vDriverArg->count)
 		{
 			if (!strcmp(vDriverArg->sval[0], "d3d11"))
 			}
 		}
 
+		// Audio driver
 		if (aDriverArg->count)
 		{
 			if (!strcmp(aDriverArg->sval[0], "wasapi"))
 			}
 		}
 
+		// Video renderer
 		if (rendererArg->count)
 		{
 			if (!strcmp(rendererArg->sval[0], "software"))
 			}
 		}
 
+		// ROM
 		if (romArg->count)
 		{
 			config.rom = romArg->filename[0];
 		}
 
+		// Serial communications
+		if (clientArg->count && serverArg->count)
+		{
+			clog << "gb_emulator: the --client and --server options are mutually exclusive\n";
+			return EXIT_FAILURE;
+		}
+		if (serverArg->count)
+		{
+			// Parse the port number
+			uint16_t port = DEFAULT_SERIAL_PORT;
+			if (serverArg->sval[0] && !parsePort(serverArg->sval[0], port))
+			{
+				clog << "gb_emulator: '" << serverArg->sval[0] << "' is not a valid port number\n";
+				return EXIT_FAILURE;
+			}
+			config.serialPort = port;
+		}
+		else if (clientArg->count)
+		{
+			// Split out the host and port
+			string hostPort = clientArg->sval[0];
+			size_t offset = hostPort.find(':');
+			if (offset == string::npos)
+			{
+				config.serialHost = hostPort;
+				config.serialPort = DEFAULT_SERIAL_PORT;
+			}
+			else if (hostPort.find(':', offset + 1) != string::npos)
+			{
+				// Multiple colons; could be an IPv6 address. IPv6 addresses are formatted either as
+				// is in their normal form, or surrounded by square brackets if a port number is
+				// also required (e.g., [::1]:1234). Try to parse the string outright; if that fails
+				// check for square brackets followed by colon and port and parse that; if that also
+				// fails print an error message.
+				struct in6_addr ipv6Addr;
+				if (inet_pton(AF_INET6, hostPort.c_str(), &ipv6Addr))
+				{
+					// Parsed ok; it is an IPv6 address with no port
+					config.serialHost = hostPort;
+					config.serialPort = DEFAULT_SERIAL_PORT;
+				}
+				else
+				{
+					// Parse failed; check for [host]:port
+					bool ok = false;
+					if (hostPort[0] == '[')
+					{
+						size_t offset = hostPort.find(']', 1);
+						if (offset != string::npos)
+						{
+							string host = hostPort.substr(1, offset - 1);
+							if (inet_pton(AF_INET6, host.c_str(), &ipv6Addr))
+							{
+								// Address parsed ok; check the port
+								if (offset <= hostPort.size() - 3 && hostPort[offset + 1] == ':')
+								{
+									uint16_t port;
+									if (parsePort(hostPort.substr(offset + 2), port))
+									{
+										// All good
+										ok = true;
+										config.serialHost = host;
+										config.serialPort = port;
+									}
+								}
+							}
+						}
+					}
+					if (!ok)
+					{
+						clog << "gb_emulator: '" << hostPort
+						     << "' is not a valid host:port specification\n";
+						return EXIT_FAILURE;
+					}
+				}
+			}
+			else
+			{
+				// One colon; nice and easy host:port split. Note that this can't be an IPv6 address
+				// because IPv6 needs at least two colons (e.g., ::1).
+				string host = hostPort.substr(0, offset);
+				string portStr = hostPort.substr(offset + 1);
+				if (host.empty())
+				{
+					clog << "gb_emulator: the serial server hostname may not be empty\n";
+					return EXIT_FAILURE;
+				}
+				if (portStr.empty())
+				{
+					clog << "gb_emulator: the serial server port may not be empty\n";
+					return EXIT_FAILURE;
+				}
+
+				uint16_t port;
+				if (!parsePort(portStr, port))
+				{
+					clog << "gb_emulator: '" << portStr << "' is not a valid port number\n";
+					return EXIT_FAILURE;
+				}
+
+				config.serialHost = host;
+				config.serialPort = port;
+			}
+		}
+
 		// Start the emulator
 		Gb gb(config);
 		gb.run();