Slick Forums

Discuss the Slick 2D Library
It is currently Fri Apr 18, 2014 11:17 pm

All times are UTC




Post new topic Reply to topic  [ 13 posts ] 
Author Message
 Post subject: Global high score
PostPosted: Fri Jun 27, 2008 2:51 am 
Offline
Oldbie

Joined: Tue Jun 17, 2008 5:11 pm
Posts: 336
I'm starting this thread so that people (tackle) can make a very simple global high score system.

I created a high score system which used Java (obviously), PHP, and Sqlite. It should be very easy to switch scripting languages and dmbs's to your taste.

1. The database
Again I used a sqlite database. What's nice about this is that the whole thing is contained in a file which I can upload to my server (after developing and testing it locally) without additional configuration. I created a single table as follows:

Code:
CREATE TABLE scores (
    id integer primary key,
    name text,
    score integer,
    created date);


Obviously you can add additional data as you like. For instance it might be worthwhile to add a column for which level the player reached.

2. The scripting language
At this point I wrote two PHP scripts which operated on GET requests. The first is titled save.php it expects:
* a name
* a score
* a password
The password is something I made up just to keep people from entering names and scores willy nilly. To be honest it won't stop someone who is determined from cheating, but that's a whole other subject.

The save script takes the GET data and creates a SQL query which it executes. If it is successful the script outputs "success" on failure it outputs "failure". Obviously these values can change as you like as long as you're consistent between the script and game code.
sve.php: http://anotherearlymorning.com/games/as ... /save.phps

The second script is called read.php. It performs a select statement on the score table formats the results as I want them to appear and prints each score on a new line. Again, it isn't important how it is formatted as long as you're consistent on both ends.
read.php: http://anotherearlymorning.com/games/as ... /read.phps

3. The game
With Java I'm going to do access the server in a very simple manner. We can wrap up how to improve this approach later.

Anyway, in a nutshell you create an instance of the URL class and then pull an input stream out of it. However, I'll just let the code do the talking:
HighScore.java: http://anotherearlymorning.com/games/as ... core.javas

4. Conclusion
So, that's how it all works. I think it should be pretty easy to integrate this into your game. Please ask any questions.

This approach is very naive -- for instance it handles errors poorly and has tons of hard coded crap. If you end up writing a proper client-server solution complete with proxies and such please post it here!

Above all though I hope you find this useful![/url][/list]


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jun 27, 2008 11:07 am 
Offline
Regular

Joined: Sat Jun 07, 2008 6:54 pm
Posts: 119
Thanks a bunch! I'll have to get on this as soon as time allows. Seems fairly straightforward.

I'd never heard of sqlite before as well, so that's a bonus to get to know about that. I've always used mysql, even for very small and simple stuff, looks like sqlite might be a better alternative.

I'll probably get back with some questions :)


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jun 27, 2008 6:03 pm 
Offline
Regular

Joined: Sat Jun 07, 2008 6:54 pm
Posts: 119
How would one make a more secure highscore keeping?
Calling out to anyone with any good ideas.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jun 28, 2008 12:55 am 
Offline
Game Developer
User avatar

Joined: Sun May 25, 2008 9:45 am
Posts: 578
Secure is impossible. The best you can do is make it hard to hack. An ideal system would be hard to hack while easy to change to invalidate a hack. Eg, every time you release an update you could change out the highscore code to work in a different way. This way a hacker has to hack every single update. Users of versions older than the website should be prompted to upgrade their version or else they cannot submit high scores. An open source version of this would be great! It would need the meat that actually encodes/decodes the highscores for submission to be easily pluggable.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jun 28, 2008 7:09 pm 
Offline
Regular

Joined: Sat Jun 07, 2008 6:54 pm
Posts: 119
I sent an email to Jens at istarion games (highnoon, for example) about the subject, and he gave me a quite informative reply:

Jens wrote:
...
Online highscore lists can only be done with "security by obscurity".
I'm using several techniques to make it hard to figure out and hack
them:

- The class file containing the score submitting code is obfuscated
with Proguard (http://proguard.sourceforge.net/) so it isn't
human-readible anymore when decompiled with a java decompiler.
- Before doing anything, it checks the MD5-sums of some critical
classes, so it can detect if code has been modified for cheating. (Of
course the game has to store the valid MD5-sums somewhere, and a
hacker could modify the code and also update the stored MD5-sums, so
this doesn't really stop anyone but makes it a little bit harder.)
- Then it builds a String containing the score, name and other
relevant stuff, obfuscates this String by crazily shifting and
rotating characters around and adding a simple checksum.
I do not use real encryption like DES, because the crypt key would
have to be stored anywhere in the code and when using Java's built-in
encryption classes, it would be easy to detect for a hacker. So
writing some weird custom code which just obfuscates the score string
is much more effectiv because much harder to figure out.
- The score is then sent to a php-script on my website via http, which
does exactly the opposite shifting and rotating operations and checks
the checksum.

All this is just obfuscation, not real encryption, but a hacker needs
a lot of time at hand to figure out all the steps and reverse them,
and I think no one will do so much work just to hack scores of a small
java game. :-)

Greetings,
Jens



Perhaps someone can benefit from this.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Jun 29, 2008 12:23 am 
Offline
User avatar

Joined: Tue May 13, 2008 11:33 pm
Posts: 32
You do know you can just download 3rd party drivers for the DBMS of you choice and do it purely through java? Nice php but you can cut that out.

Code:
import java.sql.*;

public class MysqlConnect{
  public static void main(String[] args) {
    System.out.println("MySQL Connect Example.");
    Connection conn = null;
    String url = "jdbc:mysql://localhost:3306/";
    String dbName = "jdbctutorial";
    String driver = "com.mysql.jdbc.Driver";
    String userName = "root";
    String password = "root";
    try {
      Class.forName(driver).newInstance();
      conn = DriverManager.getConnection(url+dbName,userName,password);
      System.out.println("Connected to the database");
      conn.close();
      System.out.println("Disconnected from database");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}


Top
 Profile  
 
 Post subject:
PostPosted: Sun Jun 29, 2008 5:52 am 
Offline
Game Developer
User avatar

Joined: Sun May 25, 2008 9:45 am
Posts: 578
Does Jens care you exposed vaguely how his high score works? :p Probably not?

Ru5tyNZ, with most hosting you can only connect to the database from localhost. You could use a servlet it your hosting supports that, but not all do. Nearly all hosting supports PHP.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Jun 29, 2008 6:18 am 
Offline
User avatar

Joined: Tue May 13, 2008 11:33 pm
Posts: 32
NateS wrote:
Does Jens care you exposed vaguely how his high score works? :p Probably not?

Ru5tyNZ, with most hosting you can only connect to the database from localhost. You could use a servlet it your hosting supports that, but not all do. Nearly all hosting supports PHP.


Interesting I didnt know that. Was just exploring an alternative :)

I've only used it once after going through kev's space invaders tutorial.

Image


Top
 Profile  
 
 Post subject:
PostPosted: Sun Jun 29, 2008 11:39 am 
Offline
Regular

Joined: Sat Jun 07, 2008 6:54 pm
Posts: 119
NateS wrote:
Does Jens care you exposed vaguely how his high score works? :p Probably not?


Hmm I didn't think of it that way... I did tell him I posted it here but he has not replied yet. I'll wait a bit and see if he replies, otherwise I should perhaps remove it.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Jun 29, 2008 3:02 pm 
Offline
Site Admin

Joined: Mon Nov 13, 2006 3:26 am
Posts: 122
I used generally the same method above for BeetleMania.

Talking about obfuscation, don't forget Strings are stored literally, so you need to make sure you mask that, otherwise your higschore URL is clearly visible in the class file. BeetleMania used a normal GET request, here's how I hid the request strings:
Code:
data = new int[][] {
   // GET /beetlemania/query.php?x=
   {71,69,84,32,47,98,101,101,116,108,101,109,97,110,105,97,47,113,117,101,114,121,46,112,104,112,63,120,61},
   // &y=
   {38,121,61},
   // &z=
   {38,122,61},
   //  HTTP/1.1
   {32,72,84,84,80,47,49,46,49},
   // From: beetlemania@javaunlimited.net
   {70,114,111,109,58,32,98,101,101,116,108,101,109,97,110,105,97,64,106,97,
118,97,117,110,108,105,109,105,116,101,100,46,110,101,116},
   // Referer: BeetleMania
   {82,101,102,101,114,101,114,58,32,66,101,101,116,108,101,77,97,110,105,97},
   // User-Agent: BeetleMania
   {85,115,101,114,45,65,103,101,110,116,58,32,66,101,101,116,108,101,77,97,110,105,97},
   // Host: javaunlimited.net
   {72,111,115,116,58,32,106,97,118,97,117,110,108,105,109,105,116,101,100,46,110,101,116},
   // Connection: close
   {67,111,110,110,101,99,116,105,111,110,58,32,99,108,111,115,101},
   // http://javaunlimited.net/beetlemania/scores.php?txt=1
   {104,116,116,112,58,47,47,106,97,118,97,117,110,108,105,109,105,116,101,100,46,110,101,
116,47,98,101,101,116,108,101,109,97,110,105,97,47,115,99,111,114,101,115,46,112,104,112,63,116,120,116,61,49
};


Just very simple ASCII encoding.. you can take that a step futher and use your own mapping

Obviously, for sending the score across the tubes, it needs to be encrypted. I wrote my own encryption.. not very advanced but hard enough to throw off most hackers.

See these encodings:
Code:
XlJm27@Ib1#HY1*FI1uE^1EAU1 = 14257
W:I41-HC1KGP2]E]1RDW1eCT2ZBf1hAN2 = 10337
WGI21lH_2BFC2-Dv2gB+1gA[2 = 11480
XWJr23}F|1PE}1FDl1OC^1nBH1 = 11838
W<IW1dHg2xF62mDY2-C@1kB^1GAD1 = 11488
WwIN2jGU1nFM1IE(1VCX1oB>1|AI1 = 14188
XiJ:25vH*1jGh1CFp1:Ei1aDm1vBP1 = 13050
W-IA1SHc1qGl2qF$2.EC1MDm2vCd2cAp1 = 10846
X`Jo26zIS1.Hy1TG,1-Eq1pDp1 = 13784
VqGQ3[F{1pE+3MDN2)Cm2)B02AA52 = 14250


So even if a hacker is sniffing packets, it would still be difficult to crack the encryption, because there isn't much consistency. In fact, this particular encyption strategy can generate different values for the same number.

Here's the code for sending it across the net to the php script:
Code:
Socket sock = new Socket("woogley.net",80);
PrintStream out = new PrintStream(sock.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
String s = d(0)+scr+d(1)+name+d(2)+num+d(3)+"\r\n";
out.print(s);
out.print(d(4)+"\r\n");
out.print(d(5)+"\r\n");
out.print(d(6)+"\r\n");
out.print(d(7)+"\r\n");
out.print(d(8)+"\r\n\r\n");


That d() method is just decoding the ASCII values above to actual characters. This way none of the request (URLs, etc) is visible inside the class file.

So as you can see, the 'security' in these things is just giving hoops for hackers to jump. The idea is to wear the hacker out, because you can't truly secure this.

Even with all this obfuscation.. if a hacker is able to figure out which variable is holding the score, he can just change all variables to be "public" and then do stuff like:
Code:
BeetleMania game = new BeetleMania();
game.score = 250000;


Finding the variable isn't as hard as you think, either. All you need is a decent debugger. I suppose this is where the checksum thing comes in handy, but I personally don't know how to do that.

Before I go, here's the PHP script on the backend:

Code:
<?php
function decode($s) {
   $x = 0;
   $num = 0;
   $pow = 0;
   $mul = 0;
   $base = 90-ord($s{$x++});
   $tmp = "";
   while ($x < strlen($s)) {
      if (!is_digit($s{x})) {
         $x++;
         $pow = ord($s{$x})-65;
         $x+=2;
         $tmp = "";
         while ($x < strlen($s) && is_digit($s{$x})) $tmp.=$s{$x++};
         $mul = $tmp;
         $num+=pow($base,$pow)*$mul;
      }
   }
   return floor($num);
}
function is_digit($num) {
   return ($num == '0' || $num == '1' || $num == '2' || $num == '3' || $num == '4' || $num == '5' || $num == '6' || $num == '7' || $num == '8' || $num == '9');
}
function is_name_valid($s) {
   if (strlen($s) > 16) {
      return false;
   }
   for ($j = 0;$j < strlen($s);$j++) {
      $c = $s{$j};
      $x = ord($c);
      if (!(($x >= 65 && $x <= 90) || ($x >= 48 && $x <= 57) || $c === '_' || $c === '-' || $c === '.' || $c === ':')) {
         return false;
      }
   }
   return true;
}
if (!isset($_GET["x"]) || !isset($_GET["y"]) || !is_name_valid($_GET["y"]) || !isset($_GET["z"]) || (decode($_GET["x"]) != $_GET["z"]) || $_SERVER["HTTP_CONNECTION"] !== "close" || $_SERVER["HTTP_HOST"] !== "javaunlimited.net" || $_SERVER["HTTP_REFERER"] !== "BeetleMania" || $_SERVER["HTTP_USER_AGENT"] !== "BeetleMania") {
   header("Location: http://javaunlimited.net/");
   exit;
}
else {
   $name = $_GET["y"];
   while (strlen($name) < 16) $name.=" ";
   $score = decode($_GET["x"]);
   $link = @mysql_connect("localhost","username","password");
   if (@mysql_select_db("beetlemania")) {
      @mysql_query("INSERT INTO `scores` (`name`,`score`,`code`,`ip`) VALUES(\"{$name}\",{$score},\"{$_GET['x']}\",\"{$_SERVER['REMOTE_ADDR']}\")");
   }
   @mysql_close($link);
   print $_SERVER["REMOTE_ADDR"];
}
?>


I left the decryption code in there purposefully, just in case you're curious as to how the ecnryption is working.. who knows, it may give you ideas

Also note the tiny bit of extra security added by checking HTTP headers to weed out people trying to use a browser to submit the score


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 01, 2008 8:42 am 
Offline
Slick Zombie

Joined: Wed Apr 02, 2008 1:32 pm
Posts: 1325
Location: Italy
what do you think about a central server for global high score?

Imagine just a server where, with a html form, i "register" my game. (with spam/security check, of course).

Then, into game, coder just only set to the central server username/password/score/id of the game, and .. we have a central server for global high score :D

What do you think? Someone will use this?

So.. we can do some statistics of number of players, audience of Slick, and so on :D


Top
 Profile  
 
 Post subject:
PostPosted: Thu Sep 17, 2009 7:17 pm 
Offline
Regular
User avatar

Joined: Mon Mar 05, 2007 11:05 pm
Posts: 111
Location: Burträsk, Norrland, Sweden
Man! I've been searching with light n' torch all evenin' for this kind of information! Good thing Slick folk got everything covered! Just wanted to add a thanx for the good nfo here!


Top
 Profile  
 
 Post subject:
PostPosted: Thu Sep 17, 2009 10:52 pm 
Offline
User avatar

Joined: Wed Mar 11, 2009 7:37 pm
Posts: 52
Location: Germany
To complete this thread I'd like to contribute two methods I wrote today! :wink:

Code:
   /**
    * Calculate MD5 checksum for a File
    *
    * @param ref The InputStream of the file for which the checksum should be calculated
    * @return The resulting MD5 checksum as String
    * @throws Exception if the checksum could not be calculated
    */
   public static String getMD5Checksum(InputStream input) throws Exception {
      MessageDigest msgdigest = MessageDigest.getInstance("MD5");
      byte[]        buffer    = new byte[8192];
      int           read      = 0;
             
      while((read = input.read(buffer)) > 0) {
         msgdigest.update(buffer, 0, read);
      }

      byte[] md5bytes  = msgdigest.digest();
      String md5string = new BigInteger(1, md5bytes).toString(16);

      return md5string;
   }
   



   /**
    * Calculate MD5 checksum for a String.
    *
    * @param string The String for which the checksum should be calculated
    * @return The resulting MD5 checksum as String
    * @throws Exception if the checksum could not be calculated
    */
   public static String getMD5Checksum(String string) throws Exception {
      MessageDigest msgdigest = MessageDigest.getInstance("MD5");
      StringBuffer  md5hex    = new StringBuffer();
      char[]         chars    = string.toCharArray();
       byte[]         bytes    = new byte[chars.length];

       for (int i = 0; i < chars.length; i++) {
           bytes[i] = (byte)chars[i];
       }

       byte[] md5bytes = msgdigest.digest(bytes);
      
       for (int i = 0; i < md5bytes.length; i++) {
           int val = ((int)md5bytes[i]) & 0xff;

           if (val < 16) {
               md5hex.append("0");
           }

           md5hex.append(Integer.toHexString(val));
       }

       String md5string = md5hex.toString();
      
       return md5string;
   }


With these you could easily compare the actual checksum of any class with the valid one stored somewhere in an other class to determine if someone changed the source. 8)
(Of course you could also check your configfiles, maps, sprites, skins, ... 8) 8) )

Example:
Code:
      String tocheck  = "de/funnysystems/library/tests/MD5UtilTest.class";
      String validmd5 = "b1ff26328dfac163192f1393040a9b26";
      
      System.out.println("Doing securitycheck...");
      try {
         InputStream input = MD5UtilTest.class.getClassLoader().getResourceAsStream(tocheck);
         String classmd5 = getMD5Checksum(input);
         
         if(validmd5.equals(classmd5)) {
            System.out.println("Securitycheck passed!");
         } else {
            System.out.println("!! Hacking attemp discovered !!");
            System.exit(0);
         }
      } catch (Exception e) {
         e.printStackTrace();
      }


I hope this helps somebody.

_________________
Srsly, DaRealSheep

- There will never be a problem that could defeat sunrise or hope. -


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 13 posts ] 

All times are UTC


Who is online

Users browsing this forum: Jon and 0 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group