Space Horde: Server Directory

While working on the login server for Space Horde I realized that I would need some way to register

Space Horde: Login Server – Part 2

When last we left off I had created the table we’re going to use to store all user account in

Space Horde: Login Server – Part 1

Before I start diving into the game prototype I felt like looking into the server/web side of things

 

Space Horde: Server Directory

January 23, 2012 in Game Development, Space Horde, Web Development

While working on the login server for Space Horde I realized that I would need some way to register servers with the game, and I would need to be able to retrieve these on the fly.  This would allow me to dynamically change IPs and ports for my primary servers (login, chat) as needed, and would allow me to let other people host game servers while keeping a central repository for them.

To that end I created two database tables to hold this information:

CREATE TABLE servers
(
id int unsigned auto_increment not null,
typeID int unsigned not null,
name text,
description text,
ip varchar(15) not null,
port int not null,
primary key (id),
foreign key (typeID) references serverTypes(typeID)
);

CREATE TABLE serverTypes
(
typeID int unsigned not null,
type text not null,
primary key (typeID)
);

The first table will hold the server information for server type, ip, and port with an optional server name and description.  The latter two will primarily be used for game servers to allow the hosts to unique identify themselves and provide information about their server.

The second table will hold available server types.  The initial rows here are login, chat, and game, but this design allows me the flexibility to add more later if needed without affecting the first table at all.

Next we want another PHP script to act as our API layer over these tables.  It will take a single parameter named ‘type’ and return a JSON encoded string as a result.  If any servers are found for the given type it will return a list of those under the ‘servers’ key.  Otherwise an error of sometype will be returned.

EDIT:  I updated this script to reflect a lot of debugging from tonight’s session.  My original query was basically wrong, and I fixed up how I passed the data to JSON for encoding to let my server fetch and parse it correctly.

<?php

require_once('scripts/functions.php');
require_once('scripts/credentials.php');
require_once('scripts/PDOWrapper.php');

if(array_key_exists('type', $_POST) && $type = trim(strip_tags($_POST['type'])))
{
	try
	{
		$jsonData = array('Error' => 'No servers found.');
		$pdo = new PDOWrapper('mysql', DB_HOST, DB_NAME, DB_USER, DB_PASS, array(PDO::ATTR_PERSISTENT => true));
		if($pdo->query('SELECT Name, Description, IP, Port
			FROM servers JOIN serverTypes
			ON servers.typeID=serverTypes.typeID
			WHERE serverTypes.type=:type', array(':type' => $type)))
		{
			if($result = $pdo->lastStatement->fetchAll(PDO::FETCH_ASSOC))
				$jsonData = array('Servers' => $result);
			else
				$jsonData = array('Error' => 'No servers of type ' . $type . ' available.');
		}
		echo json_encode($jsonData);
	}
	catch(PDOException $e)
	{
		echo json_encode(array('Error' => 'PDOException: ' . $e->getMessage()));
	}
}
else
{
	echo json_encode(array('Error' => 'Malformed request.'));
}

?>

That’s all there is to that.  Now I can dynamically add servers to my tables and they can be found by anyone accessing this API.  Once I get to game server code I’ll implement the ability for the server to automatically register itself once it goes online, and to remove itself once it shuts down.  Then the player will be able to see a dynamic list of servers to choose from upon login.

Space Horde: Login Server – Part 2

January 17, 2012 in Game Development, Space Horde, Web Development

When last we left off I had created the table we’re going to use to store all user account information.

Now we’re going to build up a registration form and a couple of PHP scripts to enable us to register new accounts.  So, breaking down the things that I want:

  1. A registration page with a form.
  2. Client and server-side validation of the form.
  3. A script to check for existing usernames.
  4. A script to insert a new row into the database upon submission of a valid form.

Form

The form is pretty straightforward.  We want to get the desired username for the player, an email address so that we can use it for password issues, and the password for the account.  The ID, salt, and joined timestamp columns in our accounts table will be taken care of by the ‘register.php’ script action later.

<form id="registerForm" action="register.php" method="post">
	<fieldset>
		<legend>Please complete all fields to register</legend>
		<p>
			<label for="username">Username</label>
			<input id="username" name="username" type="text" maxlength="16"/>
		</p>
		<p>
			<label for="email">Email</label>
			<input id="email" name="email" type="text"/>
		</p>
		<p>
			<label for="password">Password</label>
			<input id="password" name="password" type="password"/>
		</p>
		<p>
			<label for="verifyPassword">Verify Password</label>
			<input id="verifyPassword" name="verifyPassword" type="password"/>
		</p>
		<p>
			<input class="submit" type="submit" value="Submit"/>
		</p>
	</fieldset>
</form>

Validation

I was 30 minutes or so into getting a hand of JavaScript and writing my own validation scripts when I realized how much of an idiot I was and that I should check for existing solutions.  I knew about jQuery so I checked there first, and within minutes I was knee-deep into the jQuery.validate.js API documentation.  Turns out this is perfect for my needs, and after an hour or so of tinkering around I had a good set of rules in place for our form:

  • The username must be 3 to 16 characters in length.  No numbers or punctuation.  The username must be unique.
  • The email address must be valid.  I may look into actual verification of this later, but for now I’ll just trust the user.
  • The password must be at least 6 characters in length, and must be verified a second time.
$(document).ready(function() {
	jQuery.validator.addMethod("validUsername", function(value, element) {
		return this.optional(element) || /^[a-zA-Z]{3,16}$/i.test(value);
	}, "Your username must be from 3 to 16 characters in length.  No numbers or punctuation are allowed.");
	$("#registerForm").validate({
		success: function(label) {
			label.addClass("valid").text("Ok");
		},
		onkeyup: false,
		rules: {
			username: {
				required: true,
				validUsername: true,
				remote: {
					url: "username.php",
					type: "post",
				},
			},
			email: {
				required: true,
				email: true,
			},
			password: {
				required: true,
				minlength: 6,
			},
			verifyPassword: {
				required: true,
				equalTo: "#password",
			},
		},
		messages: {
			username: {
				required: "A username is required.",
				minlength: jQuery.format("At least {0} characters are required."),
				remote: "This username is already in use.",
			},
			email: {
				required: "A valid email is required for password recovery.",
			},
			password: {
				required: "A password is required.",
				minlength: jQuery.format("At least {0} characters are required."),
			},
			verifyPassword: {
				required: "The password must be verified.",
				equalTo: "Please enter the same password as above.",
			},
		},
	});
});

Verify Username

In the above validation rules you may have noticed the ‘remote:’ key under username.  What this does is send out an AJAX request to the specified script (usernames.php) via POST with the username as the default param.  Then, depending on the return value of true/false will determine if the field is validated.  All that the script does is connect to my database for Space Horde via PHP’s PDO methods, run a SELECT statement to see if any rows are returned with the given username, and return true or false based on that.

<?php

require_once('../credentials.php');

if(array_key_exists('username', $_POST))
{
	$username = trim(strip_tags($_POST['username']));
	try
	{
		$dbh = new PDO('mysql:host=' . $myDBHost . ';dbname=' . $myDBName, $myDBUser, $myDBPass, array(PDO::ATTR_PERSISTENT => true));
		$stmt = $dbh->prepare('SELECT username FROM accounts WHERE username=:username');
		$stmt->bindValue(':username', $username);
		if($stmt->execute() && $stmt->fetch())
			echo 'false';
		else
			echo 'true';
	}
	catch(PDOException $e)
	{
		die($e->getMessage());
	}
}
else
{
	die('Invalid POST data.');
}

$dbh = null;

?>

Create Account

Once our form data is validated and submitted it will all be sent to our final script, register.php, via the POST method.  There we pick it up and do a few extra things.  First, we do not want to store the user’s password as raw text as anyone who gets a hold of the database will immediately have access to the passwords.  Instead, we’ll store the password as a hashed version of the hashed password + a hashed random salt.  A hashed hash plus a hash.  Ya, I know.

$salt = hash("sha256", mt_rand());
$passwordHash = hash("sha256", $password);
$passwordAndSaltHash = hash("sha256", $password . $salt);

We use PHP’s Mersenne Twister implementation to grab a good random number and then hash that into a 64 character hex string via the SHA256 algo with the hash function. This will be our unique salt value for this user. We then hash the raw password in the same way giving us another 64 character hex string. Finally we concatenate the password and salt hashes together and and hash that into a final hex string. This combination is what we’ll actually store as the user’s password, along with the salt value.

The idea here is that we never want to know the user’s password once it has been saved in our database.  Verification of the user will happen by sending them the stored salt value on login which the client will use to generate a hash in the same way we created ‘$passwordAndSaltHash’ above.  That is sent to the server and compared to the store value.  If they match, the user is authenticated.

The rest of our script generates a date stamp, runs the same basic validation checks on the posted data to ensure the data is correct (in the case that JavaScript is disabled client-side), and then runs a SQL INSERT query to create the new row.  Then we’re done!  We can register users. :)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Space Horde - Account Registration</title>
</head>
<body>
<?php

require_once("../credentials.php");

if(array_key_exists("username", $_POST) && array_key_exists("email", $_POST) && array_key_exists("password", $_POST))
{
	$username = trim(strip_tags($_POST["username"]));
	$email = trim(strip_tags($_POST["email"]));
	if(strlen($email) == 0)
		die("A valid email address is required for password recovery.");
	$password = $_POST["password"];
	if(strlen($password) < 6)
		die("Your password must be at least 6 characters long.");
	$salt = hash("sha256", mt_rand());
	$passwordHash = hash("sha256", $password);
	$passwordAndSaltHash = hash("sha256", $password . $salt);
	$date = date("Y-m-d");
	try
	{
		$dbh = new PDO("mysql:host=" . $myDBHost . ";dbname=" . $myDBName, $myDBUser, $myDBPass, array(PDO::ATTR_PERSISTENT => true));
		if(!validateUsername($username))
			die("Your username must be from 3 to 16 characters long.  No numbers or punctuation are allowed.");
		if(!uniqueUsername($dbh, $username))
			die("The username " . $username . " is already in use.");
		$stmt = $dbh->prepare("INSERT INTO accounts (username, email, password, salt, joined) VALUES (:username, :email, :password, :salt, :joined)");
		$stmt->bindValue(":username", $username);
		$stmt->bindValue(":email", $email);
		$stmt->bindValue(":password", $passwordAndSaltHash);
		$stmt->bindValue(":salt", $salt);
		$stmt->bindValue(":joined", $date);
		if($stmt->execute())
			echo "Thank you for registering, " . $username . "!";
		else
		{
			echo "Unable to register your account.";
		}
	}
	catch(PDOException $e)
	{
		die($e->getMessage());
	}

	$dbh = null;
}

function uniqueUsername($dbh, $username)
{
	try
	{
		$stmt = $dbh->prepare('SELECT username FROM accounts WHERE username=:username');
		$stmt->bindValue(':username', $username);
		if($stmt->execute() && $stmt->fetch())
			return false;
		else
			return true;
		$dbh = null;
	}
	catch(PDOException $e)
	{
		die($e->getMessage());
	}
}

function validateUsername($username)
{
	return preg_match("/^\b[a-zA-Z]{3,16}\b/", $username) == 1;
}

?>
</body>
</html>

It isn’t exactly pretty, but it’s functional.  Try it here.  Next time we’ll look at authenticating a user from a C# application.

Space Horde: Login Server – Part 1

January 16, 2012 in Game Development, Space Horde, Web Development

Before I start diving into the game prototype I felt like looking into the server/web side of things to get a feel for what I’d need to get that up and running.  As I’ve never really touched this stuff before (MySQL, PHP) I went to Google and found the w3schools.com tutorials for both PHP and SQL.  These were extremely handy and I was quickly up and running with some basic knowledge in hand.

Next was research into security concerns as I wanted to be sure that any account information registered with me for this game will be as safe as I can offer.  Google turns up a lot of information on server/client security, but nothing particularly game centric.  I extracted what information I could from the various web focused sources I found and came up with some key points:

  • Never send the password as plain text from the client.  Never send the password from the server at all.
  • Do not store user passwords in your database as plain text.  Always hash them.
  • Do not use md5 to hash passwords as it is known to be insecure.  Using sha256 or sha512 is the current preference.
  • Salt your passwords.

With that information in hand I turned to designing my first iteration of the accounts database to hold my user information.  I figured at a base I would want:

  1. A unique integer ID for each user.
  2. A username.
  3. An email address (for password recovery and game related mailings).
  4. A password field.
  5. A salt value.
  6. A date joined time stamp.

I then opened up phpMyAdmin on my webhost, created a database for Space Horde, and created a table called ‘accounts’.

CREATE TABLE accounts
(
id INT UNSIGNED NOT NULL,
username VARCHAR(16) unique NOT NULL,
email TEXT NOT NULL,
password CHAR(64) NOT NULL,
salt CHAR(64) NOT NULL,
joined DATE NOT NULL,
PRIMARY KEY (id)
);

Next time we’ll start on the PHP scripts needed to interact with this database.

New Project: Space Horde

January 16, 2012 in Space Horde

The project I’ve decided to work on is going to be called Space Horde. It will be a 2D top-down space shooter that plays as a version of Horde from Gears of War. Space. Horde. The game will be PC release, for free, and support a large number of networked players which the game will scale to. Think Subspace/Continuum + Horde.  That’s the idea, anyway.

 

I think this is feasible enough as I have the knowledge in-hand to create a shooter like this.  The only new thing will be networking which I have been eager to tackle anyway.

 

More details about the actual game play will come to light as I blog up what I’m working on, but I wanted to put this out there so I didn’t have to bundle  the project introduction with something else.

Hex Colors in XNA

January 13, 2012 in C#, XNA

This was one of my more popular posts on the previous iteration of this blog, so I’m going to re-post the code here for interested parties.  If you want to use ARGB hex strings in your XNA application or game here’s some code to do so:

/// <summary>
/// Creates an ARGB hex string representation of the <see cref="Color"/> value.
/// </summary>
/// <param name="color">The <see cref="Color"/> value to parse.</param>
/// <param name="includeHash">Determines whether to include the hash mark (#) character in the string.</param>
/// <returns>A hex string representation of the specified <see cref="Color"/> value.</returns>
public static string ToHex(this Color color, bool includeHash)
{
	string[] argb = {
         	color.A.ToString("X2"),
		color.R.ToString("X2"),
		color.G.ToString("X2"),
		color.B.ToString("X2"),
	};
	return (includeHash ? "#" : string.Empty) + string.Join(string.Empty, argb);
}

/// Creates a <see cref="Color"/> value from an ARGB or RGB hex string.  The string may
/// begin with or without the hash mark (#) character.
/// </summary>
/// <param name="hexString">The ARGB hex string to parse.</param>
/// <returns>
/// A <see cref="Color"/> value as defined by the ARGB or RGB hex string.
/// </returns>
/// <exception cref="InvalidOperationException">Thrown if the string is not a valid ARGB or RGB hex value.</exception>
public static Color ToColor(this string hexString)
{
	if (hexString.StartsWith("#"))
		hexString = hexString.Substring(1);
	uint hex = uint.Parse(hexString, System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture);
	Color color = Color.White;
	if (hexString.Length == 8)
	{
		color.A = (byte)(hex >> 24);
		color.R = (byte)(hex >> 16);
		color.G = (byte)(hex >> 8);
		color.B = (byte)(hex);
	}
	else if (hexString.Length == 6)
	{
		color.R = (byte)(hex >> 16);
		color.G = (byte)(hex >> 8);
		color.B = (byte)(hex);
	}
	else
	{
		throw new InvalidOperationException("Invald hex representation of an ARGB or RGB color value.");
	}
	return color;
}

Resolute

January 13, 2012 in Game Development

It’s late and I need to get to bed soon, but I wanted to go ahead and get this moving.  I’m making a New Year’s resolution to commit to releasing a game this year.  On XBLIG or PC, free or paid, it doesn’t matter.  I just really, really need to get something out into the hands of the public this year.

In line with that I’ll be doing my best to keep up with this blog more by posting more code, ideas, game theory stuff, interesting links I run across, etc, etc.

Anyway, here’s to a new year and a fresh start.  Wish me luck.

  • Facebook
  • Twitter
  • YouTube