all repos — mgba @ 4a7dc8bff6fcbf2225634d05a41a18b68bd6b051

mGBA Game Boy Advance Emulator

src/platform/example/client-server/server.c (view raw)

  1// This source file is placed into the public domain.
  2#include "core/core.h"
  3#include "feature/commandline.h"
  4#include "util/socket.h"
  5
  6#define DEFAULT_PORT 13721
  7
  8static bool _mExampleRun(const struct mArguments* args, Socket client);
  9static void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args);
 10
 11static int _logLevel = 0;
 12
 13int main(int argc, char** argv) {
 14	bool didFail = false;
 15
 16	// Arguments from the command line are parsed by the parseArguments function.
 17	// The NULL here shows that we don't give it any arguments beyond the default ones.
 18	struct mArguments args = {};
 19	bool parsed = parseArguments(&args, argc, argv, NULL);
 20	// Parsing can succeed without finding a filename, but we need one.
 21	if (!args.fname) {
 22		parsed = false;
 23	}
 24	if (!parsed || args.showHelp) {
 25		// If parsing failed, or the user passed --help, show usage.
 26		usage(argv[0], NULL);
 27		didFail = !parsed;
 28		goto cleanup;
 29	}
 30
 31	if (args.showVersion) {
 32		// If the user passed --version, show version.
 33		version(argv[0]);
 34		goto cleanup;
 35	}
 36
 37	// Set up a logger. The default logger prints everything to STDOUT, which is not usually desirable.
 38	struct mLogger logger = { .log = _log };
 39	mLogSetDefaultLogger(&logger);
 40
 41	// Initialize the socket layer and listen on the default port for this protocol.
 42	SocketSubsystemInit();
 43	Socket sock = SocketOpenTCP(DEFAULT_PORT, NULL);
 44	if (SOCKET_FAILED(sock) || SOCKET_FAILED(SocketListen(sock, 0))) {
 45		SocketSubsystemDeinit();
 46		didFail = true;
 47		goto cleanup;
 48	}
 49
 50	// We only grab one client.
 51	Socket client = SocketAccept(sock, NULL);
 52	if (SOCKET_FAILED(client)) {
 53		SocketClose(sock);
 54		SocketSubsystemDeinit();
 55		didFail = true;
 56		goto cleanup;		
 57	}
 58
 59	// Run the server
 60	didFail = _mExampleRun(&args, client);
 61
 62	// Clean up the sockets.
 63	SocketClose(client);
 64	SocketClose(sock);
 65	SocketSubsystemDeinit();
 66
 67	cleanup:
 68	freeArguments(&args);
 69
 70	return didFail;
 71}
 72
 73bool _mExampleRun(const struct mArguments* args, Socket client) {
 74	// First, we need to find the mCore that's appropriate for this type of file.
 75	// If one doesn't exist, it returns NULL and we can't continue.
 76	struct mCore* core = mCoreFind(args->fname);
 77	if (!core) {
 78		return false;
 79	}
 80
 81	// Initialize the received core.
 82	core->init(core);
 83
 84	// Get the dimensions required for this core and send them to the client.
 85	unsigned width, height;
 86	core->desiredVideoDimensions(core, &width, &height);
 87	ssize_t bufferSize = width * height * BYTES_PER_PIXEL;
 88	uint32_t sendNO;
 89	sendNO = htonl(width);
 90	SocketSend(client, &sendNO, sizeof(sendNO));
 91	sendNO = htonl(height);
 92	SocketSend(client, &sendNO, sizeof(sendNO));
 93	sendNO = htonl(BYTES_PER_PIXEL);
 94	SocketSend(client, &sendNO, sizeof(sendNO));
 95
 96	// Create a video buffer and tell the core to use it.
 97	// If a core isn't told to use a video buffer, it won't render any graphics.
 98	// This may be useful in situations where everything except for displayed
 99	// output is desired.
100	void* videoOutputBuffer = malloc(bufferSize);
101	core->setVideoBuffer(core, videoOutputBuffer, width);
102
103	// Tell the core to actually load the file.
104	mCoreLoadFile(core, args->fname);
105
106	// Initialize the configuration system and load any saved settings for
107	// this frontend. The second argument to mCoreConfigInit should either be
108	// the name of the frontend, or NULL if you're not loading any saved
109	// settings from disk.
110	mCoreConfigInit(&core->config, "client-server");
111	mCoreConfigLoad(&core->config);
112
113	// Take any settings overrides from the command line and make sure they get
114	// loaded into the config system, as well as manually overriding the
115	// "idleOptimization" setting to ensure cores that can detect idle loops
116	// will attempt the detection.
117	applyArguments(args, NULL, &core->config);
118	mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "detect");
119
120	// Tell the core to apply the configuration in the associated config object.
121	mCoreLoadConfig(core);
122
123	// Set our logging level to be the logLevel in the configuration object.
124	mCoreConfigGetIntValue(&core->config, "logLevel", &_logLevel);
125
126	// Reset the core. This is needed before it can run.
127	core->reset(core);
128
129	uint16_t inputNO;
130	while (SocketRecv(client, &inputNO, sizeof(inputNO)) == sizeof(inputNO)) {
131		// After receiving the keys from the client, tell the core that these are
132		// the keys for the current input.
133		core->setKeys(core, ntohs(inputNO));
134
135		// Emulate a single frame.
136		core->runFrame(core);
137
138		// Send back the video buffer.
139		if (SocketSend(client, videoOutputBuffer, bufferSize) != bufferSize) {
140			break;
141		}
142	}
143
144	// Deinitialization associated with the core.
145	mCoreConfigDeinit(&core->config);
146	core->deinit(core);
147
148	return true;
149}
150
151void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) {
152	// We don't need the logging object, so we call UNUSED to ensure there's no warning.
153	UNUSED(log);
154	// The level parameter is a bitmask that we can easily filter.
155	if (level & _logLevel) {
156		// Categories are registered at runtime, but the name can be found
157		// through a simple lookup.
158		printf("%s: ", mLogCategoryName(category));
159
160		// We get a format string and a varargs context from the core, so we
161		// need to use the v* version of printf.
162		vprintf(format, args);
163
164		// The format strings do NOT include a newline, so we need to
165		// append it ourself.
166		putchar('\n');
167	}
168}