Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Tools

Development::Tools 3rd Party Tools for EQEMu (DB management tools, front ends, etc...)

 
 
Thread Tools Display Modes
Prev Previous Post   Next Post Next
  #1  
Old 06-20-2015, 03:08 PM
Zaela_S
Hill Giant
 
Join Date: Jun 2012
Posts: 216
Default EQNet - Client networking API

Project: https://github.com/Zaela/EQNet
Main include file: https://github.com/Zaela/EQNet/blob/...nclude/eqnet.h


Netcode talk in IRC lately inspired me to start this little project: a C API for connecting to EQEmu servers as a client.

The goal is to provide a relatively small set of functions to handle common client-to-server communications (connecting, moving around, sending chat messages, using skills, etc), with almost all of the messy networking details -- and client version differences -- handled transparently to the user.

For server-to-client communications, a simple event loop is provided to notify the host program of state changes (e.g. login -> char select, char select -> zone), failures (timeout, disconnection, zone unavailable) and, most importantly, incoming data packets.

For the login and char select/world connection states, the user doesn't need to worry about packets at all; the API takes care of the details and simply provides a small set of notifications and a few functions to retrieve structured data (info about characters at char select, for instance).

For the in-zone connection state, packets are inevitable. The ultimate goal is to provide a reduced, streamlined set of opcodes and packet structures that iron out most of the client-version-specific details (hint: most of the work to be done is here). In the meantime, though -- and for some extra flexibility and potential efficiency -- the "native", client-version-specific opcodes and packet data are also made available.

This is all a work in progress (just started a week ago, technically) and the API is far from complete or stable. However, at this point it should at least be possible to log in, go through server select and char select and finally reach a zone and get spammed with all the native packets under the sun as whatever client version you wish (Titanium, SoF, SoD, Underfoot, RoF and RoF2 have all been tested and confirmed to that point on local and remote servers -- although issues are certainly possible). It's possible that someone could find a use for it already -- functions are provided for sending arbitrary packets to the server, could be used for networking unit tests. Some basic work still needs to be done (ping, unexpected disconnection detection and recovery, handling for missed/misordered udp packets) but I've been making fairly steady progress thus far.


Very basic example usage:
Code:
#include "eqnet.h"
#include <cstdio>
#include <cstring>
#include <thread>
#include <chrono>
#ifdef _WIN32
#include <windows.h>
#endif

void HandleReceivedPacket(EQNet* net, EQNet_Event& ev);

int main(int argc, const char** argv)
{
	const char* username = argv[1];
	const char* password = argv[2];
	const char* partialServerName = argv[3];
	const char* characterName = argv[4];

#ifdef _WIN32
	SetConsoleTitle("EQNet Example");
#endif

	// EQNet_Init must be called exactly once before creating any EQNet objects
	if (!EQNet_Init())
	{
		fprintf(stderr, "EQNet_Init failed.\n");
		return 1;
	}

	// The EQNet object is an opaque pointer representing the overall state of one distinct
	// "connection" or "session". One EQNet object will last you through login, char select,
	// zone, and back again. EQNet objects are independent -- you can have multiple running
	// in a single thread or in separate threads without worrying about interfering with 
	// each other.
	EQNet* net = EQNet_Create();

	// Set our desired client version to masquerade as
	EQNet_SetClientVersion(net, EQNET_CLIENT_ReignOfFear);

	// Jumpstart the event loop by attempting to log in to server select
	EQNet_LoginToServerSelect(net, username, password);

	const EQNet_Character* selectedChar = nullptr;


	// Main loop
	for (;;)
	{
		// Event loop!
		EQNet_Event ev;

		// A lot of magic happens in the EQNet_Poll call. It must be called regularly to keep our
		// network I/O queues flowing. If you are in-zone or at character select and will be doing 
		// some time-consuming processing that won't allow you to return to your event loop for a 
		// while (multiple seconds), call EQNet_KeepAlive periodically let the server know you're
		// still there

		while (EQNet_Poll(net, &ev))
		{
			// Event handlers
			switch (ev.type)
			{
			case EQNET_EVENT_FatalError:
				fprintf(stderr, "EQNet encountered a fatal error: %s\n", EQNet_GetErrorMessage(net));
				goto CLOSE;

			case EQNET_EVENT_Timeout:
				if (selectedChar)
				{
					// If we get a timeout trying to enter a zone, it may be booting up
					// try connecting again (auto-reconnect attempt not yet implemented)
					EQNet_WorldToZone(net, selectedChar);
					selectedChar = nullptr;
					break;
				}
				fprintf(stderr, "Connection timed out, aborting\n");
				goto CLOSE;

			case EQNET_EVENT_BadCredentials:
				fprintf(stderr, "Invalid username/password\n");
				goto CLOSE;

			case EQNET_EVENT_AtServerSelect:
			{
				fprintf(stdout, "Reached Server Select\n");

				// We can view the available servers now
				int numServers = 0;
				const EQNet_Server* servers = EQNet_GetServerList(net, &numServers);

				// Find our desired server
				for (int i = 0; i < numServers; ++i)
				{
					if (strstr(servers[i].name, partialServerName) == nullptr)
						continue;

					// Found it!
					if (!EQNet_ServerIsUp(net, &servers[i]))
					{
						fprintf(stderr, "Server '%s' is %s, aborting\n", servers[i].name,
							EQNet_ServerIsLocked(net, &servers[i]) ? "LOCKED" : "DOWN");
						goto CLOSE;
					}

					// Send request to log in to this server
					fprintf(stdout, "Attempting to connect to %s\n", servers[i].name);
					EQNet_LoginToWorld(net, &servers[i]);
					break;
				}

				break;
			}

			case EQNET_EVENT_WorldConnectFailed:
				fprintf(stderr, "World server refused our connection, aborting\n");
				goto CLOSE;

			case EQNET_EVENT_LoginToWorld:
				fprintf(stdout, "Connecting to world server . . .\n");
				break;

			case EQNET_EVENT_AtCharacterSelect:
			{
				fprintf(stdout, "Reached Character Select\n");

				// We can view our available characters now
				int numChars = 0;
				const EQNet_Character* chars = EQNet_GetCharacterList(net, &numChars);

				// Find our desired character
				for (int i = 0; i < numChars; ++i)
				{
					if (strcmp(characterName, chars[i].name) != 0)
						continue;

					// Found them! Send request to zone them in
					EQNet_WorldToZone(net, &chars[i]);
					selectedChar = &chars[i];
					break;
				}

				break;
			}

			case EQNET_EVENT_ZoneUnavailable:
				fprintf(stderr, "Zone unavailable, aborting\n");
				goto CLOSE;

			case EQNET_EVENT_Zoning:
				fprintf(stdout, "Connecting to zone . . .\n");
				break;

			case EQNET_EVENT_AtZone:
				fprintf(stdout, "Connection to zone complete\n");
				break;

			case EQNET_EVENT_Packet:
				// Note that some packets (e.g. MessageOfTheDay) are received before we officially leave character select
#ifndef NO_OPCODE_SPAM
				fprintf(stdout, "Received packet: EQNet opcode 0x%0.4x, len %u; native opcode 0x%0.4x, len %u\n",
					ev.packet.opcode, ev.packet.len, ev.nativePacket.opcode, ev.nativePacket.len);
#endif

				HandleReceivedPacket(net, ev);
				break;
			} // switch
		}

		// Don't hog the CPU
		std::this_thread::sleep_for(std::chrono::milliseconds(20));
	}

CLOSE:
	// Release our EQNet object
	EQNet_Destroy(net);

	EQNet_Close(); // Close the library
	return 0;
}

void HandleReceivedPacket(EQNet* net, EQNet_Event& ev)
{
	switch (ev.packet.opcode)
	{
	case EQNET_OP_MessageOfTheDay:
		fprintf(stdout, "Message of the Day: %s\n", (const char*)ev.packet.data);
		break;

	case EQNET_OP_ChatMessage:
	{
		EQNetPacket_ChatMessage* msg = (EQNetPacket_ChatMessage*)ev.packet.data;
		fprintf(stdout, "%s: %s\n", msg->senderName, msg->msg);
		break;
	}

	case EQNET_OP_PlayerSpawn:
		fprintf(stdout, "You spawned!\n");
		EQNet_SendChatMessage(net, "I spawned!!");
		break;
	}
}
No real documentation yet.

Anyway, hope someone will find this interesting. If I find time to hop back on the "make my own client" bandwagon again I'll definitely put this to use, at least ;p
Reply With Quote
 

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 01:14 PM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3