diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/ICEServerSet.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/ICEServerSet.java
new file mode 100644
index 0000000..675e892
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/ICEServerSet.java
@@ -0,0 +1,32 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+public class ICEServerSet {
+
+	public static enum RelayType {
+		STUN, TURN;
+	}
+
+	public static class RelayServer {
+		
+		public final RelayType type;
+		public final String address;
+		public final String username;
+		public final String password;
+		
+		protected RelayServer(RelayType type, String address, String username, String password) {
+			this.type = type;
+			this.address = address;
+			this.username = username;
+			this.password = password;
+		}
+		
+		protected RelayServer(RelayType type, String address) {
+			this.type = type;
+			this.address = address;
+			this.username = null;
+			this.password = null;
+		}
+		
+	}
+	
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket.java
new file mode 100644
index 0000000..22f6f8d
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket.java
@@ -0,0 +1,144 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class IPacket {
+
+	private static final Map<Integer,Class<? extends IPacket>> definedPacketClasses = new HashMap();
+	private static final Map<Class<? extends IPacket>,Integer> definedPacketIds = new HashMap();
+	
+	private static void register(int id, Class<? extends IPacket> clazz) {
+		definedPacketClasses.put(id, clazz);
+		definedPacketIds.put(clazz, id);
+	}
+	
+	static {
+		register(0x00, IPacket00Handshake.class);
+		register(0x01, IPacket01ICEServers.class);
+		register(0x02, IPacket02NewClient.class);
+		register(0x03, IPacket03ICECandidate.class);
+		register(0x04, IPacket04Description.class);
+		register(0x05, IPacket05ClientSuccess.class);
+		register(0x06, IPacket06ClientFailure.class);
+		register(0x07, IPacket07LocalWorlds.class);
+		register(0x69, IPacket69Pong.class);
+		register(0xFE, IPacketFEDisconnectClient.class);
+		register(0xFF, IPacketFFErrorCode.class);
+	}
+	
+	public static IPacket readPacket(DataInputStream input) throws IOException {
+		int i = input.read();
+		try {
+			Class<? extends IPacket> clazz = definedPacketClasses.get(i);
+			if(clazz == null) {
+				throw new IOException("Unknown packet type: " + i);
+			}
+			IPacket pkt = clazz.newInstance();
+			pkt.read(input);
+			return pkt;
+		} catch (InstantiationException | IllegalAccessException e) {
+			throw new IOException("Unknown packet type: " + i);
+		}
+	}
+	
+	public static byte[] writePacket(IPacket packet) throws IOException {
+		Integer i = definedPacketIds.get(packet.getClass());
+		if(i != null) {
+			int len = packet.packetLength();
+			ByteArrayOutputStream bao = len == -1 ? new ByteArrayOutputStream() :
+				new ByteArrayOutputStream(len + 1);
+			bao.write(i);
+			packet.write(new DataOutputStream(bao));
+			byte[] ret = bao.toByteArray();
+			if(len != -1 && ret.length != len + 1) {
+				System.err.println("writePacket buffer for packet " + packet.getClass().getSimpleName() + " " +
+						(len + 1 < ret.length ? "overflowed" : "underflowed") + " by " + (len + 1 < ret.length ?
+								ret.length - len - 1 : len + 1 - ret.length) + " bytes");
+			}
+			return ret;
+		}else {
+			throw new IOException("Unknown packet type: " + packet.getClass().getSimpleName());
+		}
+	}
+
+	public void read(DataInputStream input) throws IOException {
+	}
+
+	public void write(DataOutputStream output) throws IOException {
+	}
+	
+	public int packetLength() {
+		return -1;
+	}
+	
+	public static String readASCII(InputStream is, int len) throws IOException {
+		char[] ret = new char[len];
+		for(int i = 0; i < len; ++i) {
+			int j = is.read();
+			if(j < 0) {
+				return null;
+			}
+			ret[i] = (char)is.read();
+		}
+		return new String(ret);
+	}
+	
+	public static void writeASCII(OutputStream is, String txt) throws IOException {
+		for(int i = 0, l = txt.length(); i < l; ++i) {
+			is.write((int)txt.charAt(i));
+		}
+	}
+	
+	public static String readASCII8(InputStream is) throws IOException {
+		int i = is.read();
+		if(i < 0) {
+			return null;
+		}else {
+			return readASCII(is, i);
+		}
+	}
+	
+	public static void writeASCII8(OutputStream is, String txt) throws IOException {
+		if(txt == null) {
+			is.write(0);
+		}else {
+			int l = txt.length();
+			is.write(l);
+			for(int i = 0; i < l; ++i) {
+				is.write((int)txt.charAt(i));
+			}
+		}
+	}
+
+	public static String readASCII16(InputStream is) throws IOException {
+		int hi = is.read();
+		int lo = is.read();
+		if(hi < 0 || lo < 0) {
+			return null;
+		}else {
+			return readASCII(is, (hi << 8) | lo);
+		}
+	}
+	
+	public static void writeASCII16(OutputStream is, String txt) throws IOException {
+		if(txt == null) {
+			is.write(0);
+			is.write(0);
+		}else {
+			int l = txt.length();
+			is.write((l >> 8) & 0xFF);
+			is.write(l & 0xFF);
+			for(int i = 0; i < l; ++i) {
+				is.write((int)txt.charAt(i));
+			}
+		}
+	}
+	
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket00Handshake.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket00Handshake.java
new file mode 100644
index 0000000..3702881
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket00Handshake.java
@@ -0,0 +1,41 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class IPacket00Handshake extends IPacket {
+
+	public int connectionType = 0;
+	public int connectionVersion = 1;
+	public String connectionCode = null;
+	
+	public IPacket00Handshake() {
+	}
+	
+	public IPacket00Handshake(int connectionType, int connectionVersion,
+			String connectionCode) {
+		this.connectionType = connectionType;
+		this.connectionVersion = connectionVersion;
+		this.connectionCode = connectionCode;
+	}
+	
+	@Override
+	public void read(DataInputStream input) throws IOException {
+		connectionType = input.read();
+		connectionVersion = input.read();
+		connectionCode = IPacket.readASCII8(input);
+	}
+	
+	@Override
+	public void write(DataOutputStream output) throws IOException {
+		output.write(connectionType);
+		IPacket.writeASCII8(output, connectionCode);
+	}
+	
+	@Override
+	public int packetLength() {
+		return 1 + 1 + (connectionCode != null ? 1 + connectionCode.length() : 0);
+	}
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket01ICEServers.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket01ICEServers.java
new file mode 100644
index 0000000..6fbb5c1
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket01ICEServers.java
@@ -0,0 +1,40 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class IPacket01ICEServers extends IPacket {
+	
+	public final Collection<ICEServerSet.RelayServer> servers;
+	
+	public IPacket01ICEServers() {
+		servers = new ArrayList();
+	}
+	
+	public void read(DataInputStream input) throws IOException {
+		servers.clear();
+		int l = input.readUnsignedShort();
+		for(int i = 0; i < l; ++i) {
+			char type = (char)input.read();
+			if(type == 'S') {
+				servers.add(new ICEServerSet.RelayServer(
+						ICEServerSet.RelayType.STUN,
+						readASCII16(input),
+						null, null
+				));
+			}else if(type == 'T') {
+				servers.add(new ICEServerSet.RelayServer(
+						ICEServerSet.RelayType.TURN,
+						readASCII16(input),
+						readASCII8(input),
+						readASCII8(input)
+				));
+			}else {
+				throw new IOException("Unknown/Unsupported Relay Type: '" + type + "'");
+			}
+		}
+	}
+	
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket02NewClient.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket02NewClient.java
new file mode 100644
index 0000000..4fa2763
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket02NewClient.java
@@ -0,0 +1,17 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+public class IPacket02NewClient extends IPacket {
+	
+	public String clientId;
+	
+	public IPacket02NewClient(String clientId) {
+	}
+	
+	public void read(DataInputStream input) throws IOException {
+		clientId = readASCII8(input);
+	}
+	
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket03ICECandidate.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket03ICECandidate.java
new file mode 100644
index 0000000..239a27f
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket03ICECandidate.java
@@ -0,0 +1,34 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class IPacket03ICECandidate extends IPacket {
+
+	public String peerId;
+	public String candidate;
+	
+	public IPacket03ICECandidate(String peerId, String desc) {
+		this.peerId = peerId;
+		this.candidate = desc;
+	}
+	
+	public IPacket03ICECandidate() {
+	}
+	
+	public void read(DataInputStream input) throws IOException {
+		peerId = readASCII8(input);
+		candidate = readASCII16(input);
+	}
+
+	public void write(DataOutputStream output) throws IOException {
+		writeASCII8(output, peerId);
+		writeASCII16(output, candidate);
+	}
+	
+	public int packetLength() {
+		return 1 + peerId.length() + 2 + candidate.length();
+	}
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket04Description.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket04Description.java
new file mode 100644
index 0000000..8693c7b
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket04Description.java
@@ -0,0 +1,34 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class IPacket04Description extends IPacket {
+
+	public String peerId;
+	public String description;
+	
+	public IPacket04Description(String peerId, String desc) {
+		this.peerId = peerId;
+		this.description = desc;
+	}
+	
+	public IPacket04Description() {
+	}
+	
+	public void read(DataInputStream input) throws IOException {
+		peerId = readASCII8(input);
+		description = readASCII16(input);
+	}
+
+	public void write(DataOutputStream output) throws IOException {
+		writeASCII8(output, peerId);
+		writeASCII16(output, description);
+	}
+	
+	public int packetLength() {
+		return 1 + peerId.length() + 2 + description.length();
+	}
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket05ClientSuccess.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket05ClientSuccess.java
new file mode 100644
index 0000000..a215a1a
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket05ClientSuccess.java
@@ -0,0 +1,30 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class IPacket05ClientSuccess extends IPacket {
+	
+	public String clientId;
+	
+	public IPacket05ClientSuccess() {
+	}
+	
+	public IPacket05ClientSuccess(String clientId) {
+		this.clientId = clientId;
+	}
+
+	public void read(DataInputStream input) throws IOException {
+		clientId = readASCII8(input);
+	}
+
+	public void write(DataOutputStream output) throws IOException {
+		writeASCII8(output, clientId);
+	}
+	
+	public int packetLength() {
+		return 1 + clientId.length();
+	}
+	
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket06ClientFailure.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket06ClientFailure.java
new file mode 100644
index 0000000..fceae93
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket06ClientFailure.java
@@ -0,0 +1,30 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class IPacket06ClientFailure extends IPacket {
+	
+	public String clientId;
+	
+	public IPacket06ClientFailure() {
+	}
+	
+	public IPacket06ClientFailure(String clientId) {
+		this.clientId = clientId;
+	}
+	
+	public void read(DataInputStream input) throws IOException {
+		clientId = readASCII8(input);
+	}
+
+	public void write(DataOutputStream output) throws IOException {
+		writeASCII8(output, clientId);
+	}
+	
+	public int packetLength() {
+		return 1 + clientId.length();
+	}
+	
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket07LocalWorlds.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket07LocalWorlds.java
new file mode 100644
index 0000000..25f0e4a
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket07LocalWorlds.java
@@ -0,0 +1,35 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class IPacket07LocalWorlds extends IPacket {
+	
+	public static class LocalWorld {
+		
+		public final String worldName;
+		public final String worldCode;
+		
+		public LocalWorld(String worldName, String worldCode) {
+			this.worldName = worldName;
+			this.worldCode = worldCode;
+		}
+		
+	}
+	
+	public final List<LocalWorld> worldsList;
+	
+	public IPacket07LocalWorlds() {
+		this.worldsList = new ArrayList();
+	}
+
+	public void read(DataInputStream input) throws IOException {
+		int l = input.read();
+		for(int i = 0; i < l; ++i) {
+			worldsList.add(new LocalWorld(readASCII8(input), readASCII8(input)));
+		}
+	}
+	
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket69Pong.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket69Pong.java
new file mode 100644
index 0000000..bdb2cd7
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket69Pong.java
@@ -0,0 +1,27 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+public class IPacket69Pong extends IPacket {
+
+	public int protcolVersion;
+	public String comment;
+	public String brand;
+	
+	public IPacket69Pong(int protcolVersion, String comment, String brand) {
+		if(comment.length() > 255) {
+			comment = comment.substring(0, 256);
+		}
+		this.protcolVersion = protcolVersion;
+		this.comment = comment;
+		this.brand = brand;
+	}
+	
+	public void read(DataInputStream output) throws IOException {
+		protcolVersion = output.read();
+		comment = readASCII8(output);
+		brand = readASCII8(output);
+	}
+	
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFEDisconnectClient.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFEDisconnectClient.java
new file mode 100644
index 0000000..34d6163
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFEDisconnectClient.java
@@ -0,0 +1,51 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class IPacketFEDisconnectClient extends IPacket {
+
+	public static final int TYPE_FINISHED_SUCCESS = 0x00;
+	public static final int TYPE_FINISHED_FAILED = 0x01;
+	public static final int TYPE_TIMEOUT = 0x02;
+	public static final int TYPE_INVALID_OPERATION = 0x03;
+	public static final int TYPE_INTERNAL_ERROR = 0x04;
+	public static final int TYPE_UNKNOWN = 0xFF;
+	
+	public String clientId;
+	public int code;
+	public String reason;
+	
+	public IPacketFEDisconnectClient() {
+	}
+	
+	public IPacketFEDisconnectClient(String clientId, int code, String reason) {
+		this.clientId = clientId;
+		this.code = code;
+		this.reason = reason;
+	}
+	
+	public void read(DataInputStream input) throws IOException {
+		clientId = readASCII8(input);
+		code = input.read();
+		reason = readASCII16(input);
+	}
+
+	public void write(DataOutputStream output) throws IOException {
+		writeASCII8(output, clientId);
+		output.write(code);
+		writeASCII16(output, reason);
+	}
+	
+	public int packetLength() {
+		return -1;
+	}
+
+	public static final ByteBuffer ratelimitPacketTooMany = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x00 });
+	public static final ByteBuffer ratelimitPacketBlock = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x01 });
+	public static final ByteBuffer ratelimitPacketBlockLock = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x02 });
+	public static final ByteBuffer ratelimitPacketLocked = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x03 });
+	
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFFErrorCode.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFFErrorCode.java
new file mode 100644
index 0000000..8effe08
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFFErrorCode.java
@@ -0,0 +1,46 @@
+package net.lax1dude.eaglercraft.sp.relay.pkt;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class IPacketFFErrorCode extends IPacket {
+
+	public static final int TYPE_INTERNAL_ERROR = 0x00;
+	public static final int TYPE_PROTOCOL_VERSION = 0x01;
+	public static final int TYPE_INVALID_PACKET = 0x02;
+	public static final int TYPE_ILLEGAL_OPERATION = 0x03;
+	public static final int TYPE_CODE_LENGTH = 0x04;
+	public static final int TYPE_INCORRECT_CODE = 0x05;
+	public static final int TYPE_SERVER_DISCONNECTED = 0x06;
+	public static final int TYPE_UNKNOWN_CLIENT = 0x07;
+	
+	public int code;
+	public String desc;
+	
+	public IPacketFFErrorCode() {
+	}
+	
+	public IPacketFFErrorCode(int code, String desc) {
+		this.code = code;
+		this.desc = desc;
+	}
+	
+	@Override
+	public void read(DataInputStream input) throws IOException {
+		code = input.read();
+		desc = readASCII16(input);
+	}
+
+	@Override
+	public void write(DataOutputStream input) throws IOException {
+		input.write(code);
+		writeASCII16(input, desc);
+	}
+
+	@Override
+	public int packetLength() {
+		return 1 + 2 + desc.length();
+	}
+
+}