Allerdings ist der Einsatz nicht ganz einfach, man benötigt wissen in PHP, SQL und natürlich in PureBasic.
Die Routinen sind rein in PureBasic unter Windows geschrieben, sie sollten aber auch unter Linux und MacOs laufen - ist aber ungetestet. Als Web&SQL-Server fungiert eine XAMPP-Installation und damit MySQL und APACHE - aber das läuft eh auf den meisten Webservern, von daher sollte das kein Problem sein.
WICHTIG
Der Code ist zwar getestet, aber ist halt die erste Beta-Version. Es ist bei weiten nicht 100% fehlerfrei!
Zum Konzept
Der Grundgedanke ist, das eine einfach PHP-Seite Daten entgegenimmt und auswertet. Ein Webspace mit PHP sollte heutzutage kein Problem sein, gibts ja schon ab ca. 10 Euro in Jahr (man sollte nur regelmäßig manuell Backups machen

Meine Idee war, weg zu kommen von den üblichen Benutzername & Passwort-System. Gerade nach solchen hacks wie bei PSN stellt sich ja die Frage, ob sowas wirklich überall nötig ist - wenn Sony es nicht schafft seine Daten zu sichern (und mittlerweile wirklich viele andere Firmen auch) - wieso sollte dann mein System so sicher sein.
Die Registierung erfolgt hier wesentlich einfacher, man Teil den System seinen Wunschnamen mit und das System antwortet mit einer ID und einer 20-stelligen Identifizierungsnummer. Diese werden lokal auf der HDD gespeichert (Unter windows am besten unter %appdata%) und damit hat es sich. Sollte da wirklich mal wer in die Datenbank eindringen, hat er höchstens diese Identifizierungsnummer und mehr nicht. Da diese 100% nirgends woanders genutzt wird, wäre es absolut nicht schlimm wenn die wer bekommen würde. Das schlimmste was derjenige dann machen könnte wäre, Levels löschen und Bewertungen abgeben

Man kann auch eine E-Mail Adresse angeben, diese wird benötigt, wenn man seine Zugangsdaten gelöscht hat - man kann sie dann einfach per E-Mail neu anfordern. Der E-Mail-Parameter ist optional.
Damit die Kommunikation nicht so einfach abgefangen werden können, erfolgt diese komplett Verschlüsselt (AES 256-Key). Als Handshake wird der InitializationVector genutzt. Das Spiel sendet 8 Byte und somit die erste Hälfte, die Webseite antwortet mit 8 weiteren Bytes. Da diese leicht abfangbar sind, sollte man diese weiter verrechnen, damit es nicht so einfach wird. In Beispielcode wird hierfür einfach die MD5-Funktion verwendet (sollte man ändern).
Einschränkungen
Der Code unterstützt keinen Proxy-Server. Das dürfte aber eher ein Problem von Firmen sein, und die haben eh was dagegen, das man spielt. Theoretisch ist es auch aktuell möglich, das jemand in gleichen Netzwerk die Session-Daten klauen und die Session übernehmen. Allerdings wozu... da sind keine Wichtigen Daten auf den Server gespeichert...
Die Datenbank
Es werden 3 Tabellen benötigt: User, Level, Rating, Hiscore. Diese Tabellen können einen beliebigen Namen haben, man muss nur die Config-Dateien anpassen. Die User-Datenbank kann man sogar für verschiedene Spiele genutzt werden, damit kann man quasi mit ein und den selben Userdaten einfach für verschiedene Spiele nutzen. Die anderen Tabellen sollten aber alle eindeutig für jedes Spiel exestieren.
Bei meiner Testinstalltion unter XAMPP hab ich eine Datenbank "crillion" erstellt und dort meine Tabellen erzeugt.
Fangen wir mit der Userdatenbank an (der Name in Beispiel cril_user):
Code: Alles auswählen
CREATE TABLE IF NOT EXISTS `cril_user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) CHARACTER SET armscii8 NOT NULL,
`level` int(10) unsigned NOT NULL,
`key` varchar(20) CHARACTER SET armscii8 COLLATE armscii8_bin NOT NULL,
`email` varchar(20) CHARACTER SET armscii8 COLLATE armscii8_bin NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB;
* ID - eine einfach hochzählende Nummer. Identifiziert den Benutzer
* Name - Von Benutzer gewählter Name
* Level - Userlevel. Dieser Wert bestimmt die Rechte des Users. 0:Admin, 1:Levels hochladen,bewerten,Hiscores, 2:nur Bewerten und Hiscores, 3:gebannt.
* Key - der von script erzeugte Identifizierungsnummer
* email - die optionale E-Mail adresse.
Als nächstes die Level-Tabelle (hier cril_level):
Code: Alles auswählen
CREATE TABLE IF NOT EXISTS `cril_level` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`userid` int(10) unsigned NOT NULL,
`name` varchar(20) CHARACTER SET ascii NOT NULL,
`md5` varchar(32) CHARACTER SET armscii8 COLLATE armscii8_bin NOT NULL,
PRIMARY KEY (`id`,`userid`)
) ENGINE=InnoDB;
Die Rating-Tabelle (hier cril_rating):
Code: Alles auswählen
CREATE TABLE IF NOT EXISTS `cril_rating` (
`levelid` int(10) unsigned NOT NULL,
`userid` int(10) unsigned NOT NULL,
`rating` int(11) NOT NULL,
PRIMARY KEY (`levelid`,`userid`)
) ENGINE=InnoDB;
Und zum Schluß die Hiscore-Tabelle (hier cril_hiscore):
Code: Alles auswählen
CREATE TABLE IF NOT EXISTS `cril_hiscore` (
`levelid` int(10) unsigned NOT NULL,
`userid` int(10) unsigned NOT NULL,
`score` int(11) NOT NULL,
PRIMARY KEY (`levelid`,`userid`)
) ENGINE=InnoDB;
Die PHP-Dateien
diese befinden sich in htdocs in XAMPP-Verzeichnis.
Beginnen wir mit der index.php
in prinzip ist es die Config-Datei, die dann die eigentliche Hauptdatei benutzt.
Code: Alles auswählen
<?php
//Dantenbank-daten
$db_host='localhost';
$db_user='root';
$db_pass='';
$db_name='crillion';
$server_name='Crillion'; // Titel bei E-Mail etc.
$server_Version='1.24'; // Serverversion
$server_key="12345678901234561234567890123456"; // Verschlüsselungskey
define('DOCOMMAND', 'do'); // Name des Do-Command
define('USERTABLE','`cril_user`'); // Tabelle für Userdaten
define('LEVELTABLE','`cril_level`'); // Tabelle für Leveldaten
define('RATETABLE','`cril_rating`'); // Tabelle für Bewertungen
define('HISCORETABLE','`cril_hiscore`'); // Tabelle für Bewertungen
define('LEVELPATH','cril_lev/'); // Pfad, wo Level/Upload gespeichert werden
define('ALLOWEDUSERNAME','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'); // Erlaubte Zeichen für Username
define('ALLOWEDLEVEL','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !.,-+*=?@'); // Erlaubte Zeichen für Levelname
define('MAXUSERNAME',20); // maximale Anzahl der Zeichen für einen Namen
define('MAXEMAIL',20); // maximale Anzahl der Zeichen für eine e-mail
define('MAXUPLOAD',64000); //maximale größe eines uploads
define('MAXLEVELNAME',20); //maximale Anzahl der Zeichen für Levelname
define('STARTUSERLEVEL',1); // Neue User bekommen diesen Level. 0:Admin 1:Levelupload+rating 2:rating 3:banned
define('HISCORESORT','DESC'); // sortiert nach DESC (absteigender,max zuerst) oder ASC (aufsteigend,min zuerst)
define('HISCOREMAX',2147483647); // maximal größter Wert für Hiscores
// Damit wird überprüft, ob die Datei, die hochgeladen wurde, gültig ist.
function CheckUpload($file){
$datei = fopen($file,"r");
$check=fgets($datei, 17);
fclose($datei);
return ($check=="CrillionScenario");
};
//hex2bin
function hex2bin($in){
return pack("H*",$in);
}
function CreateIV($parts){
return hex2bin(md5($parts));
};
require("server.php");
?>
Die Funktion CheckUpload($file) überprüft, ob eine Datei gültig ist. In Beispiel werden die ersten 16 Zeichen eingelesen und überprüft ob dort "CrillionScenario" steht. Wenn ja, gibt man True zurück, ansonsten False. Jede von User hochgeladene Datei muss diesen Check überleben, ansonsten wird sie verworfen.
Die Funktion CreateIV($parts) ist für den Handshake wichtig. In $parts ist ein 16-Byte großer InitializationVector in seinen Rohzustand. Hier wird ein eine 32-Stellige MD5-Hexzahl erzeugt und diese wieder in ein 16-Byte-Code zurückgewandelt. Sinn ist halt, das man nicht einfach mit den beiden Handshake-Teilen die Verbindung abhören kann.
Der $server_key sollte auch für jedes Spiel einzigartig sein. Den sollte man vorallen später in PureBasic-Programm gut versteckt unterbringen.
so, als nächstes die "server.php" - ohne kommentar, das ganze ding wird dann in PureBasic-Teil besprochen.
Code: Alles auswählen
<?php
//Eigentliche Hauptdatei
require("common.php");
// Session und Verschlüsselung starten
session_start();
if (isset($_POST['start'])){
session_unset();
$mypart=MakePinC(8);
$iv=CreateIV(base64_decode($_POST['start']).$mypart);
if (strlen($iv)==16){
$_SESSION['iv']=$iv; // hier wird der IV mittels einen md5 "verschlüsselt" - sollte man ändern!
echo base64_encode($mypart)."\n";
echocrypt($server_Version."\n"
.ALLOWEDUSERNAME."\n"
.ALLOWEDLEVEL."\n"
.MAXUSERNAME."\n"
.MAXEMAIL."\n"
.MAXUPLOAD."\n"
.MAXLEVELNAME."\n");
dieok();
} else {
dieerror("4");
}
}
if (isset($_POST['stop'])){
session_destroy();
setcookie(session_name(), '', 0, '/');
dieerror("0");
};
// ab hier verschlüsselt, also überprüfen, ob das überhaupt geht!
if (!isset($_SESSION['iv'])){
session_destroy();
setcookie(session_name(), '', 0, '/');
dieerror("6");//start fehlt
}
// Daten entschlüsseln falls vorhanden
$arr=array();
//var_dump($_SESSION);
if (isset($_POST['crypt'])){
$crypt=$_POST['crypt'];
$crypt=mydecrypt($server_key,$_SESSION['iv'],$crypt);
foreach(explode(';',$crypt) as $value){
$a=explode(':',$value);
if (isset($a[0],$a[1])){
$arr[trim($a[0])] = trim($a[1]);
} else {
$arr[trim($a[0])] ="";
};
};
};
// gibts ein do?
if (!isset($arr[DOCOMMAND])){
dieerror("8");
}
// letzten Fehler zurückgeben
if ($arr[DOCOMMAND]=="lasterror"){
if (isset($_SESSION['error'])){
echocrypt($_SESSION['error']);
dieok();
}
echocrypt("0");
dieok();
}
// Datenbank öffnen
$db = @new mysqli($db_host, $db_user,$db_pass,$db_name);
if (mysqli_connect_errno()) {
dieerror("10"," ".mysqli_connect_error().'('.mysqli_connect_errno().')');
}
// auswerten
switch ($arr[DOCOMMAND]){
case 'test':
echocrypt("OK");
dieok();
// Neuer Benutzer anlegen
case 'adduser':
if (!isset($arr['name'],$arr['email'])){
dieerror("8");
}
$name=mysql_real_escape_string($arr['name']);
$email=mysql_real_escape_string($arr['email']);
$key=makePin(20);
if (strlen($name)>MAXUSERNAME or strlen($email)>MAXUSERNAME or strlen($name)<3){
dieerror("16");
}
if (!is_allowed($name,ALLOWEDUSERNAME)){
dieerror("15");
}
$result= doSQL("SELECT * FROM ".USERTABLE."
WHERE `name` = '$name';" );
if ($result->num_rows==0) {
$result2=doSQL( "INSERT INTO ".USERTABLE." (`name` , `level` , `key` , `email`)
VALUES ('$name',".STARTUSERLEVEL.",'$key','$email');" );
$result2->close();
$_SESSION['user']=array('name'=>$name,'level'=>STARTUSERLEVEL,'key'=>$key,'email'=>$email,'id'=>($result->insert_id));
SendEmail($email,"Welcome.");
echocrypt(($result->insert_id)."\n$key");
$result->close();
dieok();
} else {
$result->close();
dieerror("14"); //Eintrag gibts schon
}
// Benutzer identifizieren
case 'authuser':
if (!isset($arr['id'],$arr['key'])){
dieerror("8");
}
$id=mysql_real_escape_string($arr['id']);
$key=mysql_real_escape_string($arr['key']);
$user=authuser($id,$key);
$_SESSION['user']=$user;
echocrypt($user['name']);
dieok();
// E-Mail ändern
case 'newmail':
if (!isset($arr['email'])){
dieerror("8");
}
if (!isset($_SESSION['user'])){
dieerror("19");
}
$email=mysql_real_escape_string($arr['email']);
if (strlen($email)>MAXUSERNAME){
dieerror("16");
}
if ($email != $_SESSION['user']['email']){
$result = doSQL( "UPDATE ".USERTABLE." SET `email` = '$email'
WHERE `id` = '".$_SESSION ['user']['id']."';" );
if ($result->affected_rows) {
SendMail($_SESSION['user']['email'],"New E-Mail: $email");
SendMail($email,"Welcome");
$_SESSION['user']['email']=$email;
echocrypt($email);
$result->close();
dieok();
}
$result->close();
}
echocrypt("ok");
dieok();
// Key neu anfordern
case 'getkey':
if (!isset($arr['email'])){
dieerror("8");
}
$email=mysql_real_escape_string($arr['email']);
if (strlen($email)>MAXUSERNAME){
dieerror("16");
}
$result= doSQL("SELECT * FROM ".USERTABLE."
WHERE `email` = '$email';" );
if ($result->num_rows) {
$row=($result->fetch_assoc());
if ( SendEMail($email,"Enter as Name: #".$row['id']."\nEnter as EMail: ".$row['key']) ) {
echocrypt("ok");
$result->close();
dieok();
} else {
$result->close();
dieerror("20"); //e-Mail gesendet
}
}
$result->close();
dieerror("12"); //E-Mail gibts nicht
// Datei hochladen
case 'upload':
if (!isset($_SESSION['user'])){
dieerror("19");
}
if (!isset($_FILES['data'])){
dieerror("8");
}
if ($_SESSION['user']['level'] >= 2){
dieerror("17");
};
if ($_FILES['data']['size']>MAXUPLOAD) {
dieerror("21");
};
if ($_FILES['data']['error']) {
dieerror("22"," ".$_FILES['data']['error']);
};
$name=mysql_real_escape_string($_FILES['data']['name']);
if (!is_allowed($name,ALLOWEDLEVEL)){
dieerror("15");
}
if (strlen($name)>MAXLEVELNAME){
dieerror("16");
}
if (CheckUpload($_FILES['data']['tmp_name'])) {
$result= doSQL("INSERT INTO ".LEVELTABLE." (`userid` , `name`, `md5` )
VALUES (".$_SESSION['user']['id'].",'$name','".md5_file($_FILES['data']['tmp_name'])."');");
copy($_FILES['data']['tmp_name'],LEVELPATH.$result->insert_id);
$result->close();
echocrypt("ok");
dieok();
} else {
dieerror("23");
}
break;
// Levelname ändern.
case "renupload":
if (!isset($arr['id'],$arr['name'])){
dieerror("8");
}
if (!isset($_SESSION['user'])){
dieerror("19");
}
if ($_SESSION['user']['level'] >= 2){
dieerror("17");
};
$name=mysql_real_escape_string($arr['name']);
if (!is_allowed($name,ALLOWEDLEVEL)){
dieerror("15");
}
if (strlen($name)>MAXLEVELNAME){
dieerror("16");
}
$levelid=mysql_real_escape_string($arr['id']);
if (!is_numeric($levelid)){
dieerror("9");
}
if($_SESSION['user']['level']==0) {
$where="";
} else {
$where="AND `userid`='".$_SESSION['user']['id']."'";
};
$result = doSQL("UPDATE ".LEVELTABLE." SET `name` = '$name'
WHERE `id` = '$levelid' $where ;");
if ($result->affected_rows) {
echocrypt("ok");
$result->close();
dieok();
};
$result->close();
dieerror("17");
// Datei löschen
case "delupload":
if (!isset($arr['id'])){
dieerror("8");
}
if (!isset($_SESSION['user'])){
dieerror("19");
}
if ($_SESSION['user']['level'] >= 2){
dieerror("17");
};
$levelid=mysql_real_escape_string($arr['id']);
if (!is_numeric($levelid)){
dieerror("9");
}
$levelid=mysql_real_escape_string($arr['id']);
if($_SESSION['user']['level']==0) {
$where="";
} else {
$where="AND `userid`='".$_SESSION['user']['id']."'";
};
$result = doSQL("DELETE FROM ".LEVELTABLE." WHERE `id` = '$levelid' $where ;");
if ($result->affected_rows) {
$result2=doSQL("DELETE FROM ".RATETABLE." WHERE `levelid` = '$levelid' ;");
$result3=doSQL("DELETE FROM ".LEVELTABLE." WHERE `levelid` = '$levelid' ;");
@unlink(LEVELPATH.$levelid);
echocrypt("ok");
$result->close();
$result2->close();
$result3->close();
dieok();
}
$result->close();
dieerror("17");
// Leveliste abfragen
case "listlevel":
if (!isset($arr['start'],$arr['length'])){
dieerror("8");
}
$start=$arr['start'];
$length=$arr['length'];
if (!is_numeric($start) or !is_numeric($length)) {
dieerror("15");
}
$userid="-";
$userlevel="3";
if (isset($_SESSION['user'])){
$userid=$_SESSION['user']['id'];
$userlevel=$_SESSION['user']['level'];
}
$result =dosql("SELECT l.id id, l.name name, u.name 'from', l.userid, avg(r.rating) rate,count(r.rating) count
FROM ".LEVELTABLE." l
INNER JOIN ".USERTABLE." u ON u.id=l.userid
LEFT JOIN ".RATETABLE." r ON l.id=r.levelid
GROUP BY l.id
LIMIT $start,$length ");
$list="";
while ($row = $result->fetch_assoc()) {
$list=$list.$row['id'].";".$row['name'].";".$row['from'].";".$row['rate'].";".$row['count'].";";
if ($userid==$row['userid'] or $userlevel==0){
$list=$list."y;";
} else {
$list=$list."n;";
}
$list=$list."\n";
};
$result->close();
if ($list){
echocrypt($list);
dieok();
}
dieerror("1");
// Level bewerten
case "rate":
if (!isset($arr['rate'],$arr['id'])){
dieerror("8");
}
if (!isset($_SESSION['user'])){
dieerror("19");
}
$rate=intval($arr['rate']);
if ($rate<1 or $rate>5){
dieerror("15");
}
$levelid=$arr['id'];
if (!is_numeric($levelid)){
dieerror("9");
}
$result=doSQL( "INSERT INTO ".RATETABLE." (levelid,userid,rating)
VALUES ('$levelid','".$_SESSION['user']['id']."','$rate')
ON DUPLICATE KEY UPDATE rating = '$rate';");
$result->close();
echocrypt("ok");
dieok();
// Persönlichen Hiscore setzen.
case "sethiscore":
if (!isset($arr['score'],$arr['id'],$arr['md5'])){
dieerror("8");
}
if (!isset($_SESSION['user'])){
dieerror("19");
}
$score=intval($arr['score']);
if ($score<0 or $score>HISCOREMAX){
dieerror("15");
}
$levelid=$arr['id'];
if (!is_numeric($levelid)){
dieerror("9");
}
$md5=$arr['md5'];
//gespeicherten md5-string holen.
$result=doSQL ("SELECT * FROM ".LEVELTABLE."
WHERE ID='$levelid'");
if ($result->num_rows){
$row=$result->fetch_assoc();
$savedmd5=$row['md5'];
};
$result->close();
if ($md5!=$savedmd5){
dieerror("17");
};
//erstmal schaun, ob ein hiscore schon da ist
$result=doSQL ("SELECT * FROM ".HISCORETABLE."
WHERE userid='".$_SESSION['user']['id']."' AND levelid='$levelid';");
if ($result->num_rows){
$row=$result->fetch_assoc();
$oldscore=$row['score'];
} elseif (HISCORESORT=='DESC') {
$oldscore=0;
} else {
$oldscore=HISCOREMAX;
}
$result->close();
if ( (HISCORESORT=='DESC' AND $score>$oldscore) OR (HISCORESORT!='DESC' AND $score<$oldscore)){
$result=doSQL( "INSERT INTO ".HISCORETABLE." (levelid,userid,score)
VALUES ('$levelid','".$_SESSION['user']['id']."','$score')
ON DUPLICATE KEY UPDATE score = '$score';");
$result->close();
echocrypt("ok");
dieok();
}
dieerror("25");
// Meinen Hiscore und Platz suchen
case "getmyscore":
if (!isset($arr['id'])){
dieerror("8");
}
if (!isset($_SESSION['user'])){
dieerror("19");
}
$levelid=$arr['id'];
if (!is_numeric($levelid)){
dieerror("9");
}
$result=doSQL( "SELECT pos,score
FROM (SELECT userid,levelid , score, @rownum:=@rownum+1 pos
FROM ".HISCORETABLE." ht,
(SELECT @rownum:=0) rn
WHERE levelid='$levelid'
ORDER BY score ".HISCORESORT.") t
WHERE userid = '".$_SESSION['user']['id']."'; ");
if ($result->num_rows){
$row=$result->fetch_assoc();
$result->close();
echocrypt($row['pos']."\n".$row['score']);
dieok();
}
$result->close();
dieerror("25");
// Komplette Hiscoreliste hohlen
case "gethiscore":
if (!isset($arr['start'],$arr['length'],$arr['id'])){
dieerror("8");
}
$start=$arr['start'];
$length=$arr['length'];
if (!is_numeric($start) or !is_numeric($length)) {
dieerror("15");
}
$levelid=$arr['id'];
if (!is_numeric($levelid)){
dieerror("9");
}
$result=doSQL( "SELECT u.name name, h.score score
FROM ".HISCORETABLE." h
INNER JOIN ".USERTABLE." u ON u.id=h.userid
WHERE h.levelid='$levelid'
ORDER BY score ".HISCORESORT."
LIMIT $start,$length ");
$list="";
while ($row = $result->fetch_assoc()) {
$list=$list.$row['name'].";".$row['score']."\n";
};
$result->close();
if ($list){
echocrypt($list);
dieok();
}
dieerror("1");
// Level downloaden
case "getlevel":
if (!isset($arr['id'])){
dieerror("8");
}
$levelid=$arr['id'];
if (!is_numeric($levelid)){
dieerror("9");
}
$result=doSQL( "SELECT * FROM ".LEVELTABLE." WHERE id='$levelid';");
if ($result->num_rows){
if (readfile(LEVELPATH.$levelid)=== false) {
header('HTTP/1.0 404 Not Found');
}
} else {
header('HTTP/1.0 404 Not Found');
}
dieok();
default:
dieerror("24",$do);
break;
}
?>