(I’m not sure if I’m posting this in the right section, so if it needs to be moved somewhere else, feel free.)
A game I’m working on has a set of randomly generated levels using a set random number seed, so that the same chain of levels is generated for all players, and this chain of levels can theoretically go on infinitely. As such, I wanted to include leaderboards in Steam for things such as the furthest levels players have reached. However, it occurred to me that since it’s not an online game and everything is run locally, it would be relatively easy for someone to hack the game and cheat their way into wherever position they’d like, potentially making the leaderboards useless and evaporate one of the key driving points for playing the game.
I was wondering if anyone knew of any method by which a game can be checked for people cheating. I realize that this is a rather broad request, seeing as there can be a lot of factors involved when dealing with gameplay variables. But any kind of starting point would be helpful, and the game isn’t all that complex to begin with so it might be able to squeak by with minimal things checked/monitored or however it is approached. Any ideas would be welcome, since I have no experience with cheat prevention, especially in an offline game.
A server is the only practical way to accomplish this. All other methods only buy you time till the cheat has been developed and even then we’re talking less than a day for someone who knows what they’re doing plus however long it takes for them to make everyone else aware of it which can be less than a day too.
Squeaking by with a minimal approach is only going to work if the people you attract don’t feel the desire to cheat because as soon as you attract someone that cheats no matter the game it’s going to happen. Complexity won’t make a meaningful difference in the time it takes them.
I looked into this same issue when I added leaderboards for daily challenges and time trials of my game’s levels. There were two basic insights:
If someone really wants to, they’ll figure it out. If you search for “unity anti-cheat tools”, you’ll find various tools/assets you might consider, but they’ll all pretty much just deter a lazy person. They won’t guarantee that someone won’t be able to cheat. And players often get annoyed by the feeling of having anti-cheat stuff running when they play, so you might not even want that. So, in the end, the “best” approach is often just to look at your leaderboards occasionally, and if you see an obviously cheated entry, go into Steam’s admin site and delete it.
You can make it harder to cheat if players provide more than just a score/time on a leaderboard. My game’s a FPS-style game, and along with the leaderboard entry, every player uploads a file containing their position every 0.1 seconds during their run. I use this to display a ghost of the #1 player in the game that players race against. But it’s also a pretty good barrier to cheating, since creating that data is a lot more complex than just uploading a score. And if someone ever uploads a cheated run without a corroborating ghost file, it’ll be very obvious it’s cheated.
Hmmm… This is a FPS-style game as well, so maybe this approach could work. I don’t even necessarily have to have these saved runs be visible to the players; it could be something I would just be able to review myself, as an extension of what you were saying about occasionally reviewing the top entries. I assume from the size such a replay would have (especially if a typical run takes a while), the file size could get large; I’d have to review what Steam allows. This is very helpful, thank you!
The basic approach is that upon uploading a leaderboard entry, you can attach data to the entry using a combination of FileWriteAsync, FileShare, and AttachLeaderboardUGC. The docs say that the file size limit for each individual upload is 100 MB. So, these files can get pretty big. (100MB is a lot of data. My files tend to be in the range of 100 KB to 1 MB for the longest runs.)
It should be possible to use this for run data then. The question is, do I (the developer) have a way of accessing this run data, or is it private to the player? I’m not entirely clear on how Steam cloud works for things like this. If it’s private, then I may have to find some other way to do this.
You can access it via the API. In fact, it’s essentially public, even though it’s linked to a given Steam user’s account on their specific leaderboard entry.
Using the example I gave above, when a player completes one of my leaderboard levels, I use the steamworks API to create a leaderboard entry. I then upload the run data as a separate file, linked to the player, and then associate that file to the leaderboard entry. From that point on, if a random player tries that level, the API will give them the run data of the top leaderboard entry’s run. So, anyone trying that level will be able to receive that data.
It’s a little bit complicated to set up, mostly because the steamworks stuff is so callback-heavy, but it’s not that bad.
I’m trying to create an actual save file now to store all the data into, but I’m getting conflicting information about how to do this. Originally I was thinking about just saving it to a binary, but when I look up info about BinaryFormatter I keep finding things that say it’s obsolete. Is there a better way to save a file containing all this data? Right now I have it set up so that it needs to write:
An Opening class, for start-of-the-run data
A Room class that writes once every time the player passes into a new room (level) and contains the random seed for that room
A Player class that writes the player position and a few other stats at set intervals (still need to determine how long that interval is; once per second may be plenty)
An Enemy class that writes the enemy position, similar to the Player class, except it’s not in every room (there’s also only ever one enemy, so I only need to save it as often as the Player class).
A Door class that writes once whenever a player leaves a room
A Closing class, for end-of-the-run data
There is a flag in the Player class that will toggle when a door is used, so the file will need to save data something like this:
When I read the file back, I’ll need to read it back similarly, but will have the info I need in each of those blocks of class data that lets me know what to expect next.
Any suggestions, or is BinaryFormatter still a valid way to do all this?
I’m not concerned with people making edits; the file will exist only for a few moments while it is being uploaded to the Steam account, and even should it be interrupted and saved/edited, I should be able to tell by analysis that this was the case.
I’ll take a look into other serializers like you suggested, thanks!
Yes, modding Unity games is trivial. There’s whole readily available software that lets you mod and inject code into Unity’s runtime environment. If someone wanted to capture this temporary serialised file, it would be trivial to do so.
Yes, but that’s the purpose of doing these run saves. My intention is to make something I can pass the runs through in order to verify that they’re legitimate. If they modify the game, it will be clear because they’ll be doing things that aren’t possible, or doing things that aren’t lining up with the seed, or it has data contradicting an earlier run.