Created
December 13, 2019 00:18
-
-
Save philronan/557c71dfc1038edead0d6062fc23c8b4 to your computer and use it in GitHub Desktop.
Unity scorekeeper with secret token mechanism to make it harder to falsify scores
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* NOTE: I created this script to make it harder to falsify scores in Unity | |
* WebGL games. It's designed to sit between the objects that modify scores | |
* and the object(s) that display them to the user. If an object needs to | |
* modify the score, then (a) add this object to the scoreSetters[] aray, | |
* and (b) add the method `public void SetSharedSecret(ulong secret)` to | |
* receive and store a 64-bit secret value (guaranteed to be non-zero). For | |
* example: | |
* | |
* public class MyGameObject : MonoBehaviour | |
* { | |
* private ulong sharedSecret; | |
* [SerializeField] ScoreKeeper scoreKeeper; | |
* | |
* // Secret can only be set once: if non-zero, then it has been set. | |
* private void Awake() | |
* { | |
* sharedSecret = 0; | |
* } | |
* | |
* private void HaveSomePoints(int howMany) | |
* { | |
* scoreKeeper.AddToScore(howMany, sharedSecret); | |
* } | |
* | |
* public void SetSharedSecret(ulong secretValue) | |
* { | |
* if (sharedSecret == 0) | |
* { | |
* sharedSecret = secretValue; | |
* } | |
* } | |
* } | |
* | |
* This approach may in fact be overkill. It looks like the Javascript | |
* interface to Unity's object model via unityInstance.SendMessage() only | |
* supports calls with a single parameter. Any public method that has two | |
* or more parameters will be inaccessible from the Javascript console in | |
* the client's browser. | |
* | |
**/ | |
using UnityEngine; | |
public class ScoreKeeper : MonoBehaviour | |
{ | |
private int score; | |
private int hiScore; | |
private ulong sharedSecret; | |
private bool scoringDisabled; | |
[SerializeField] Scoreboard scoreboard; | |
[SerializeField] MonoBehaviour[] scoreSetters; | |
// Start is called before the first frame update | |
void Start() | |
{ | |
score = 0; | |
hiScore = 0; | |
GenerateNewSecret(); | |
scoringDisabled = false; | |
} | |
// Create a new 64-bit shared secret value when the game starts | |
// Random.Range doesn't support uint, so we need to call 3 times. | |
// I'm going to assume that Unity's Random class seeds itself with | |
// sufficient entropy. (This isn't a perfectly secure solution in | |
// any case.) | |
void GenerateNewSecret() | |
{ | |
do | |
{ | |
sharedSecret = (ulong)Random.Range(0, 0x100000) << 44; | |
sharedSecret += (ulong)Random.Range(0, 0x100000) << 24; | |
sharedSecret += (ulong)Random.Range(0, 0x1000000); | |
} while (sharedSecret == 0); | |
// Pass this value to all the attached objects | |
for (int i=0; i<scoreSetters.Length; i++) | |
{ | |
scoreSetters[i].SendMessage("SetSharedSecret", sharedSecret); | |
} | |
} | |
public void AddToScore(int points, ulong secret) | |
{ | |
if (secret != sharedSecret) | |
{ | |
InvalidateScoring(); | |
return; | |
} | |
if (scoringDisabled) | |
{ | |
return; | |
} | |
score += points; | |
scoreboard.UpdateScoreDisplay(score); | |
if (score > hiScore) | |
{ | |
hiScore = score; | |
scoreboard.UpdateHighScoreDisplay(hiScore); | |
} | |
} | |
public void ResetScore() | |
{ | |
score = 0; | |
scoreboard.UpdateScoreDisplay(score); | |
} | |
private void InvalidateScoring() | |
{ | |
ResetScore(); | |
scoringDisabled = true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment