all repos — mgba @ 93e5b6da7b69d94bbe2e0783f05897b1adee755b

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