[D]Packet writer, reader and structures

11/21/2014 03:38 Super Aids#1
As I've decided to kinda write a new source in D, then I wanted to finish the packet structures before starting to write the server. I will be updating this thread with packet structures as I get further in development.

This could be used for people who might be interested in D, the packet structures (Although not offset based.) etc.

All packets will be based on patch 5515-5518 or any patches around there. In the future I might use the conditional compilation to have different versions for offsets. Gotta love D's version keyword.

Note: None of the packets has been tested in terms of server usage as of now, but they should be pretty much legit structures.

Packet writer/reader: (packet.d)
Code:
module packet;

import std.c.string;

version (Windows) {
	private const string newLine = "\r\n";
}
else {
	private const string newLine = "\n";
}

enum PacketType : ushort {
	// Auth Packets ...
	authrequest = 1086,
	authresponse = 1055,
	passwordseed = 1059,
	
	// Game Packets ...
	authmessage = 1052,
	message = 1004
}

/**
*	A data packet class.
*/
class DataPacket {
protected:
	/**
	*	The data packet buffer.
	*/
	ubyte[] b;
	/**
	*	The current read offset.
	*/
	size_t offset = 0;
	
	/**
	*	Creates a new instance of DataPacket.
	*/
	this() { }
public:
	/**
	*	Creates a new instance of DataPacket.
	*/
	this(ushort type, ushort size) {
		assert(size < 1024);
		write!ushort(size);
		write!ushort(type);
	}
	
	/**
	*	Creates a new instance of DataPacket.
	*/
	this(ubyte[] buffer) {
		assert(buffer !is null);
		assert(buffer.length > 4);
		
		b = buffer.dup;
	}
	
	/**
	*	Creates a new instance of DataPacket.
	*/
	this(DataPacket packet) {
		assert(packet !is null);
		assert(packet.plength > 4);
		b = packet.b.dup;
	}
	
	/**
	*	Appends bytes to the buffer.
	*/
	void writeBuffer(ubyte[] buffer) {
		b ~= buffer;
	}
	
	/**
	*	Writes an empty buffer until the specific offset.
	*/
	void writeEmpty(size_t toOffset) {
		while (b.length < toOffset)
			write!ubyte(0);
	}
	
	/**
	*	Writes a value to the buffer.
	*/
	void write(T)(T value) {
		ubyte[] pBuffer = new ubyte[T.sizeof];
		auto ptr = &value;
		memcpy(pBuffer.ptr, ptr, T.sizeof);
		writeBuffer(pBuffer);
	}
	
	/**
	*	Writes a string value to the buffer.
	*/
	void writeString(string value) {
		writeBuffer(cast(ubyte[])value);
	}
	
	/**
	*	Reads a value from the buffer.
	*/
	auto read(T)() {
		ubyte[] pBuffer = b[offset .. (offset + T.sizeof)];
		T val;
		memcpy(&val, pBuffer.ptr, T.sizeof);
		offset += T.sizeof;
		return val;
	}
	
	/**
	*	Reads a buffer value.
	*/
	ubyte[] readBuffer(size_t size) {
		ubyte[] pBuffer = b[offset .. (offset + size)];
		offset += size;
		return pBuffer;
	}
	
	/**
	*	Reads a string value.
	*/
	string readString(size_t size) {
		return cast(string)readBuffer(size);
	}
	
	/**
	*	Skips offsets for reading.
	*/
	void skip(size_t offsets = 1) {
		offset += offsets;
	}
	
	/**
	*	Goes to a specific offset for reading.
	*/
	void go(size_t offset) {
		this.offset = offset;
	}
	
	@property {
		/**
		*	Gets the final buffer.
		*/
		ubyte[] buffer() {
			ushort vlen = vlength;
			while (b.length != vlen) {
				write!ubyte(0);
			}
			return b;
		}
		
		/**
		*	Gets the physical length of the packet.
		*/
		size_t plength() {
			return b.length;
		}
		
		/**
		*	Gets the virtual length of the packet.
		*/
		ushort vlength() {
			size_t oldOffset = offset;
			offset = 0;
			ushort size = read!ushort;
			offset = oldOffset;
			return size;
		}
		
		/**
		*	Gets the packet type.
		*/
		ushort ptype() {
			size_t oldOffset = offset;
			offset = 2;
			ushort p = read!ushort;
			offset = oldOffset;
			return p;
		}
	}
	
	/**
	*	Gets the string of the packet.
	*/
	override string toString() {
		import std.conv : to;
		import std.string : format;
		
		string s = format("Packet: %s P-Size: %s V-Size: %s", ptype, plength, vlength);
		s ~= newLine;
		s ~= "bytes[";
		foreach (v; b) {
			s ~= "0x" ~ to!string(v, 16) ~ ", ";
		}
		s.length -= 2;
		s ~= "]";
		s ~= newLine;
		s ~= "text('";
		foreach (v; b) {
			s ~= (v >= 32 && v <= 126 ? cast(char)v : '.');
		}
		return s ~ "')";
	}
}

/**
*	A string packer class.
*/
class StringPacker {
public:
static:
	/**
	*	Packs the strings into a packet.
	*/
	void pack(DataPacket packet, string[] strings) {
		packet.write!ubyte(cast(ubyte)strings.length);
		foreach (s; strings) {
			if (s) {
				packet.write!ubyte(cast(ubyte)s.length);
				if (s.length)
					packet.writeString(s);
			}
			else
				packet.write!ubyte(0);
		}
	}
	
	/**
	*	Unpacks strings from a packet.
	*/
	string[] unpack(DataPacket packet) {
		string[] strings;
		ubyte stringCount = packet.read!ubyte;
		foreach (i; 0 .. stringCount) {
			ubyte stringSize = packet.read!ubyte;
			if (stringSize > 0) {
				strings ~= packet.readString(stringSize);
			}
			else
				strings  ~= null;
		}
		return strings;
	}
}
Auth packets:

Game packets:

That's all for now :)
11/21/2014 04:25 pro4never#2
D gives me a headache... but gj?
11/21/2014 12:59 Super Aids#3
Quote:
Originally Posted by pro4never View Post
D gives me a headache... but gj?
How does it give you a headache? It's pretty straight forward and has way better capability than C#.
11/21/2014 16:23 .Ocularis#4
I fancied the idea of XML packet structures for versioning. A server, built once, could handle any client it has the structures for.

D is an interesting language.. what made you look into it?

I really like Haskell, but I fear it.. haha, I think I'm not smart enough to use the language. My professor knows the language and he explains it to be very robust for the sort of things I'm interested in.. Much more over C#, which is what I use now.
11/21/2014 16:53 Super Aids#5
Quote:
Originally Posted by .Ocularis View Post
I fancied the idea of XML packet structures for versioning. A server, built once, could handle any client it has the structures for.

D is an interesting language.. what made you look into it?

I really like Haskell, but I fear it.. haha, I think I'm not smart enough to use the language. My professor knows the language and he explains it to be very robust for the sort of things I'm interested in.. Much more over C#, which is what I use now.
The only problem with the xml idea would be performance decrease and you really only want your server to run on specific patches and not a range of patches, because of feature changes between them.

I looked into D, because I wanted to learn a new language that I could use to build efficient thread-safe, concurrent applications such as web servers, so I dug into D with a focus on simple network programming and the structure of a webserver and really liked the language and its many features which most langauges today lack. Then I discovered vibe.d and got really hyped.
vibed.org

I started to use it as my main language and wrote a few interpreters and a simple language that compiled to C then to native, wrote an extension to the sfml library for D to support GUI programming and simplified event handling and interaction between gaming and GUI.
Such as this.
Code:
/**
	MODULE: control.d
	
	DESCRIPTION:
	The control.d module wraps around the base control management.
*/
module dgui.control;

import dsfml.graphics : RenderWindow;
import dsfml.window;

import dgui.space;
import dgui.controlcontainer;

class EventHandler(T1,T2) {
private:
	void function(T1,T2) F;
	void delegate(T1,T2) D;
public:
	this(void function(T1,T2) F) {
		this.F = F;
	}
	this(void delegate(T1,T2) D) {
		this.D = D;
	}
	
	void exec(T1 v1, T2 v2) {
		if (F) F(v1,v2);
		else if (D) D(v1,v2);
	}
}

class Control {
private:
	string m_name;
	size_t m_layer;
	bool m_enabled;
	bool m_visible;
	
	ControlContainer m_parent;
	
	Rectangle m_rect;
protected:
	@property {
		void rect(Rectangle value) {
			m_rect = value;
		}
	}
public:
	this(string name) {
		m_name = name;
		m_layer = 0;
		m_enabled = true;
		m_visible = true;
		m_rect = new Rectangle(0,0,0,0);
	}
	
	@property {
		string name() { return m_name; }
		
		size_t layer() { return m_layer; }
		void layer(size_t value) {
			m_layer = value;
			if (m_parent)
				m_parent.sortLayers();
		}
		
		bool enabled() { return m_enabled; }
		void enabled(bool value) {
			m_enabled = value;
		}
		
		bool visible() { return m_visible; }
		void visible(bool value) {
			m_visible = value;
			if (m_parent)
				m_parent.sortLayers();
		}
		
		ControlContainer parent() { return m_parent; }
		void parent(ControlContainer value) {
			if (m_parent)
				m_parent.removeControl(this);
			m_parent = value;
		}
		
		Rectangle rect() {
			return m_rect;
		}
	}
	
	void draw(RenderWindow window) { }
	void boundPosition(Point p) { }
	void unboundPosition(Point p) { }
	
	void onKeyPressed(Keyboard.Key key) { }
	void onKeyReleased(Keyboard.Key key) { }
	void onMousePressed(Mouse.Button button) { }
	void onMouseReleased(Mouse.Button button) { }
	void onMouseMove(Point point) { }
	
	Point getHorizontalPoint() {
		return new Point(m_rect.x + m_rect.width, m_rect.y);
	}
	
	Point getVerticalPoint() {
		return new Point(m_rect.x, m_rect.y + m_rect.height);
	}
	
	bool intersecting(Rectangle r) {
		return intersect(m_rect, r);
	}
}
Code:
module dgui.controls.label;

import std.conv;

import dsfml.system;
import dsfml.window;
import dsfml.graphics;

import dgui.control;
import dgui.image;
import dgui.space;
import dgui.color;

alias DGUIColor = dgui.color.Color;
alias DSFMLColor = dsfml.graphics.Color;

/**
	A simple wrapper around the text class from dsfml.
	This simplifies around text management, drawing, size and position.
*/
class Label : Control {
private:
	/**
		The text.
	*/
	Text m_text;
	/**
		The font.
	*/
	Font m_font;
	/**
		The character size.
	*/
	uint m_characterSize;
	/**
		The color.
	*/
	DGUIColor m_color;
	
public:
	/**
		Creates a new instance of Label.
	*/
	this(string name, string msg, Font font, uint characterSize = 14) {
		super(name);
		
		m_characterSize = characterSize;
		m_font = font;
		m_color = white;
		text = msg;
	}
	
	@property {
		/**
			Gets the text.
		*/
		string text() {
			return to!string(m_text.getString());
		}
		
		/**
			Sets the text.
		*/
		void text(string value) {
				m_text = new Text(to!dstring(value), m_font, m_characterSize);
				m_text.position = Vector2f(rect.x, rect.y);
				m_text.setColor(DSFMLColor(m_color.r, m_color.g, m_color.b, m_color.a));
				auto size = m_text.getLocalBounds();
				rect.size = new Capacity(cast(uint)size.width, cast(uint)size.height);
		}
		
		/**
			Gets the position.
		*/
		Point position() {
			return rect.position;
		}
		/**
			Sets the position.
		*/
		void position(Point value) {
			rect.position = value;
			m_text.position = Vector2f(value.x, value.y);
		}
		
		/**
			Gets the size.
		*/
		Capacity size() {
			return rect.size;
		}
		
		/**
			Gets the color.
		*/
		DGUIColor color() {
			return m_color;
		}
		
		/**
			Sets the color.
		*/
		void color(DGUIColor color) {
			m_color = color;
			m_text.setColor(DSFMLColor(color.r, color.g, color.b, color.a));
		}
	}
	
	/**
		Draws the label to a sfml RenderWindow.
	*/
	override void draw(RenderWindow window) {
		window.draw(m_text);
	}
	
	/**
		Sets bounding position.
	*/
	override void boundPosition(Point p) {
		position = getBoundPosition(p.x, p.y, rect.x, rect.y);
	}
	
	private bool isIntersecting = false;
	
	EventHandler!(Label,Mouse.Button) mousePressedEvent;
	EventHandler!(Label,Mouse.Button) mouseReleasedEvent;
	EventHandler!(Label,Point) mouseMoveEvent;
	
	/**
		Sets unbounding position.
	*/
	override void unboundPosition(Point p) {
		position = getUnboundPosition(p.x, p.y, rect.x, rect.y);
	}
	
	override void onMousePressed(Mouse.Button button) {
		if (!isIntersecting) return;
		
		if (mousePressedEvent)
			mousePressedEvent.exec(this,button);
	}
	
	override void onMouseReleased(Mouse.Button button) {
		if (!isIntersecting) return;
		
		if (mouseReleasedEvent)
			mouseReleasedEvent.exec(this,button);
	}
	
	override void onMouseMove(Point point) {
		isIntersecting = intersecting(new Rectangle(point, 1, 1));
		if (mouseMoveEvent)
			mouseMoveEvent.exec(this,point);
	}
}
Code:
module main;

// D IMPORTS
import std.conv;

// DGUI Imports
import dgui.window;

import dgui.screen;
class MyScreen : Screen {

import dgui.color;
import dgui.controls.label;
import dgui.control : EventHandler;

import dsfml.window;

Label lblTest;

private:
	void lblTest_mousePressed(Label lbl, Mouse.Button button) {
		lbl.color = green;
	}
	
	void lblTest_mouseReleased(Label lbl, Mouse.Button button) {
		lbl.color = red;
	}
public:
	this(string name) {
		super(name);
	
		string[] fontNames = ["verdana.ttf", "verdanab.ttf", "verdanai.ttf", "verdanaz.ttf"];
		import dsfml.graphics.font;
		import dgui.fonts;
		Font font;
		foreach (fname; fontNames) {
			font = getFont(fname);
			if (font)
				break;
		}
		
		if (font) {
			lblTest = new Label("lblTest", "Hello World!", font);
			lblTest.mousePressedEvent = new EventHandler!(Label,Mouse.Button)(&lblTest_mousePressed);
			lblTest.mouseReleasedEvent = new EventHandler!(Label,Mouse.Button)(&lblTest_mouseReleased);
			addControl(lblTest);
		}
	}
}

/**
	Entry point
*/
void main(string[] args)
{
	auto window = new Window(800,600,"DGUI1");
	
	// INIT GUI
	window.addScreen(new MyScreen("MyScreen"));
	
	window.open();
}
I have been looking at Rust and Go too, but didn't like their syntaxes as much.
11/22/2014 06:45 InsomniacPro#6
Quote:
Originally Posted by pro4never View Post
D gives me a headache... but gj?
Ha.
11/23/2014 12:04 Korvacs#7
Why would there be a performance decrease when using XML packet structures? You just use the xml to build or populate a class you're not reading from them continually just when the server starts...

Also the whole multi patch support isn't very easy to implement there's a whole range of changes beyond just packet structures that you would need to account for, I dropped the concept in the end as the amount of changes would basically require you to build a base and then use plugins for handling the different changes in logic. It would be an exceptional amount of work, basically.