Source

gb_emulator / gb_emulator_main / main.cpp

Full commit
/*  Copyright © 2011 Chris Spencer <spencercw@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#ifdef _WIN32
// 'argument' : conversion from 'boost::locale::utf::code_point' to 'wchar_t', possible loss of data
#pragma warning(disable: 4244)
#endif

#include <stdint.h>
#include <stdlib.h>

#include <iostream>
#include <sstream>
#include <string>

#include <argtable2.h>

#include <boost/asio/ip/address_v6.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/locale/encoding_utf.hpp>

#include <SDL/SDL_main.h>

#include <gb_emulator/gb.hpp>
#include <gb_emulator/gb_config.h>

namespace bl = boost::locale;
namespace bs = boost::system;
namespace fs = boost::filesystem;
namespace ip = boost::asio::ip;
using std::basic_string;
using std::clog;
using std::ios_base;
using std::istringstream;
using std::streampos;
using std::string;
using std::wstring;

typedef basic_string<uint8_t> ustring;

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
	{
		// Create the command line argument structures
		arg_str *vDriverArg = arg_str0("v", "vdriver", "<driver>",
			"the video driver to use [d3d11 (default), sdl]");
		arg_str *aDriverArg = arg_str0("a", "adriver", "<driver>",
			"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, clientArg, serverArg, help,
			romArg, end };

		if (arg_nullcheck(argTable))
		{
			clog << "Failed to allocate memory for command line arguments.\n";
			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)
		{
			fprintf(stderr, "Usage: ");
			arg_print_syntax(stderr, argTable, "\n");
			arg_print_errors(stderr, end, "gb_emulator");
			return EXIT_FAILURE;
		}

		// Display help if necessary
		if (help->count)
		{
			fprintf(stderr, "Usage: ");
			arg_print_syntax(stderr, argTable, "\n\n");
			arg_print_glossary(stderr, argTable, "  %-25s %s\n");
			return EXIT_FAILURE;
		}

		// Handle the arguments
		GbConfig config;

		// Video driver
		if (vDriverArg->count)
		{
			if (!strcmp(vDriverArg->sval[0], "d3d11"))
			{
				config.videoDriver = GbConfig::VIDEO_D3D11;
			}
			else if (!strcmp(vDriverArg->sval[0], "sdl"))
			{
				config.videoDriver = GbConfig::VIDEO_SDL;
			}
			else
			{
				clog << "gb_emulator: invalid video driver '" << vDriverArg->sval[0] << "'\n";
				return EXIT_FAILURE;
			}
		}

		// Audio driver
		if (aDriverArg->count)
		{
			if (!strcmp(aDriverArg->sval[0], "wasapi"))
			{
				config.audioDriver = GbConfig::AUDIO_WASAPI;
			}
			else if (!strcmp(aDriverArg->sval[0], "sdl"))
			{
				config.audioDriver = GbConfig::AUDIO_SDL;
			}
			else
			{
				clog << "gb_emulator: invalid audio driver '" << aDriverArg->sval[0] << "'\n";
				return EXIT_FAILURE;
			}
		}

		// Video renderer
		if (rendererArg->count)
		{
			if (!strcmp(rendererArg->sval[0], "software"))
			{
				config.renderer = GbConfig::RENDER_SOFTWARE;
			}
			else if (!strcmp(rendererArg->sval[0], "hardware"))
			{
				config.renderer = GbConfig::RENDER_HARDWARE;
			}
			else
			{
				clog << "gb_emulator: invalid renderer '" << rendererArg->sval[0] << "'\n";
				return EXIT_FAILURE;
			}
		}

		// 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.
				bs::error_code ec;
				ip::address_v6::from_string(hostPort, ec);
				if (!ec)
				{
					// 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);
							ip::address_v6::from_string(host, ec);
							if (!ec)
							{
								// 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();

		return EXIT_SUCCESS;
	}
	catch (std::exception &e)
	{
		clog << "An error occurred: " << e.what() << "\n";

#ifdef _WIN32
		wstring message = L"An error occurred: " + bl::conv::utf_to_utf<wchar_t>(e.what());
		MessageBoxW(NULL, message.c_str(), L"Game Boy Emulator", MB_ICONERROR);
#endif

		return EXIT_FAILURE;
	}
}