saving a highscore in a game

Hi guys,

lets suppose I create a game maybe a console game or even a GUI game using SDL or SFML,how would you save the highscore(make it persistent),the obvious choice would be to use an ifstream and ofstream and save it to a .txt file,

but this isn't suffice as the user could cheat and edit that score directly in the text file itself. how do games implement this,I have not seen any information online on how to do this

thanks

It can be tempting to modify text files so making it a little more difficult is probably a good idea, but personally I don't think it's worth doing anything extremely advanced. If people want to cheat why not let them?

Here are some ideas:

* You could store the data in binary format. It's not hard to hack using a hex editor but at least it requires a little bit of conscious effort and it stops most regular users.

* You could use a checksum. If the file has been tampered with and the checksum has not been updated accordingly you can just discard the scores. If the user knows the algorithm it wouldn't be that difficult to get around it but most people would not be prepared to put in this kind of effort, and if they do don't you think they deserve a high score. ;)
Last edited on
very true the second idea sounds like the way to go,

I'm experimenting with binary files at the moment, but seem to be having an issue calculating the size when reading,

so lets suppose we want to read an int from a file then two strings like in my example,how would we first read that int (well that's probably a bad example all we would have to do is sizeof(int) as it will always be the same(on most systems)) but for reading a string,strings will differ in sizes so how do we successfully read the string from the file,how do we get the size of the string we want to read?

thanks


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

#include <iostream>
#include <fstream>

using namespace std;


void BinaryWrite(int age, string name, string number){

   ofstream out;

   out.open("bibin.dat",ios::out | ios::binary | ios::app);

   if(!out.is_open()){

     cout << "error" << endl;
   }

   const char* nameCStr = name.c_str();
   const char* numberCStr = number.c_str();
//
   out.write((char*)&age,sizeof(age));
   out.write(nameCStr,sizeof(nameCStr));
   out.write(numberCStr,sizeof(numberCStr));

   out.close();
}

void BinaryRead(){

   ifstream in;

   in.open("bibin.dat",ios::in | ios::binary);

   if(!in.is_open()){

      cout << "error opening" << endl;
   }

   char* ageStr;
   int age;
   char* name;
   char* number;

    in.seekg(0,ios::end);
    int size = (int) in.tellg();
     in.seekg(0,ios::beg);

     in.read((char*)ageStr,sizeof(int));
//
//   cout << ageStr << endl;




}

int main()
{
    BinaryWrite(22,"adam","hey");
    BinaryRead();

    return 0;
}


the program also seems to crash when reading from the binary file I'm not sure why also the cast I am doing is redundant(in BinaryRead() ) right? as age str is already a char*
Last edited on
The solution that many flash games with online high scores was not to solve the problem, you will still see people with hacked high scores in those games, because at the end, there is no way to stop hackers in single player games.

And most cheaters are not smart enough to look inside your appdata or windows registry to figure it out.

And for single player games, cheaters are the last people you should worry about (unless you have an online highscore board, but there's nothing you can do easily about that).
If you want the user to be able to transfer the file between different computers on different systems you can use fixed size integer types such as std::uint32_t in <cstdint>.

Probably the simplest way to write a string to a binary file is by just writing all the characters including the null characters and then use getline to read it back.
1
2
// write
out.write(name.c_str(), name.length() + 1);
1
2
3
// read
std::string name;
std::getline(in, name, '\0');

Another way is to write the length followed by all the characters (without the null character), but it requires a bit more code.
1
2
3
4
// write
std::uint32_t name_length = name.length();
out.write(reinterpret_cast<char*>(name_length), sizeof(name_length));
out.write(name.data(), name_length);
1
2
3
4
5
6
// read
std::uint32_t name_length;
in.read(reinterpret_cast<char*>(name_length), sizeof(name_length));
std::string name;
name.resize(name_length);
in.read(name.data(), name_length);
Last edited on
just wondering why my program crashes though isn't the data (int) I saved to the file 32 bits? now when I try to read it back from the file to my program the program crashes :/


thanks guys
I'm not sure what ageStr is for. You want to read the data into the age variable.

 
in.read(reinterpret_cast<char*>(&age), sizeof(age));
Why don't you use a text file and encrypt it ?
@Thomas that would actually be a very good idea





I'm not sure what ageStr is for. You want to read the data into the age variable.


that worked perfect,but how would I then cast it back to an int? as casted the age to a char* with in.read(reinterpret_cast<char*>(&age), sizeof(age)); or the cstyle cast in.read((char*)&age,sizeof(int));

didn't this line

in.read((char*)&age,sizeof(int));

or this line

in.read(reinterpret_cast<char*>(&age), sizeof(age));

cast the int to a char pointer?? for some reason I can still very much do arithmetic on it so I'm guessing it is still an int,but didn't the lines above not cast it??




thanks
Last edited on
Casting does not change the type of existing variables. &age gives us an int* and it is this pointer that is cast to a char*. The read function then reads the specified number of bytes from file and writes them to the bytes where the pointer points to.

Note that the read function does not know anything about the original type of the thing that it writes to. It just treats it as a sequence of bytes.
Last edited on
Casting does not change the type of existing variables. &age gives us an int* and it is this pointer that is cast to a char*. The read function then reads the specified number of bytes from file and writes them to the bytes where the pointer points to.


thanks Peter,that kind of makes sense but why a char* why not an int pointer? or maybe more fittingly a void* ?

thanks

Functions such as memcpy uses void* which is much more convenient to use because they don't require explicit casting. I don't know why char* is used here but I suspect it goes back a long time to C where most pointer conversions happened implicitly so it probably didn't matter much which one was used. If these functions were added today they would probably have used std::byte*.
Last edited on
Topic archived. No new replies allowed.