Arduino - write highscores to txt file on SD card

Solution 1:

I solved this by writing the data to a sqlite database on the SD card. this way I can simply add rows, and then use a query to return the top 10 players.

UPDATE AS REQUESTED IN THE COMMENTS.

my table is called highscores and its structure is:

  • name: String
  • score: String
  • hash: String

has is sha256(name + score + salt) and ensures that nobody modified either the name or the score.

#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include <sqlite3.h>
#include <SPI.h>
#include <FS.h>
#include "SD.h"
#include "mbedtls/md.h"

#include "highscores.h"

[...]
WebServer server(80);
int rc;
sqlite3_stmt *res;
int rec_count = 0;
const char *tail;
sqlite3 *db1;
char *zErrMsg = 0;
File myFile;
[...]

void setup(void) {

 [...]
  server.on("/highscores", handleHighscores);
  server.on("/gethighscores", handleGetHighscores);

  sqlite3_initialize();
  if (openDb("/sd/mydb.db3", &db1))
    return;
 [...]
}

void loop(void) {
  server.handleClient();

 if (progStep == SAVEHIGHSCORE)
    {


      String tmp = shooter + rankingAvgScore + "fajlo5a5%9k";
      const char *payload = tmp.c_str();

      byte shaResult[32];

      mbedtls_md_context_t ctx;
      mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
      const size_t payloadLength = strlen(payload);

      mbedtls_md_init(&ctx);
      mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
      mbedtls_md_starts(&ctx);
      mbedtls_md_update(&ctx, (const unsigned char *) payload, payloadLength);
      mbedtls_md_finish(&ctx, shaResult);
      mbedtls_md_free(&ctx);

      String shastr;
      for (int i = 0; i < sizeof(shaResult); i++) {
        char str[3];
        sprintf(str, "%02x", (int)shaResult[i]);
        shastr = shastr + str;
      }

      String sql = "insert into highscores ('name','score','hash') values ('";
      sql += shooter;
      sql += "','";
      sql += rankingAvgScore;
      sql += "','";
      sql += shastr;
      sql += "')";
      
      rc = db_exec(db1, sql.c_str());

      sql = "Select * from highscores order by score desc limit 10";

      rc = sqlite3_prepare_v2(db1, sql.c_str(), 1000, &res, &tail);
      if (rc != SQLITE_OK) {
        String resp = "Failed to fetch data: ";
        resp += sqlite3_errmsg(db1);
        Serial.println(resp.c_str());
        return;
      }
      rec_count = 0;
      while (sqlite3_step(res) == SQLITE_ROW) {
        highscoreNames[rec_count] = (const char *) sqlite3_column_text(res, 1);
        highscoreScores[rec_count] = sqlite3_column_double(res, 2);
        highscoreHash[rec_count] = (const char *) sqlite3_column_text(res, 3);
        rec_count++;
      }
      sqlite3_finalize(res);
     
      progStep = END;

    }
}

this sends the webpage over on request.

void handleHighscores() {
  Serial.println("GET /");
  server.send(200, "text/html", htmlhighscores);
}

this reads the values from the database when requested by the webpage..

void handleGetHighscores()
{

  /* read highscores from sqlite db*/

  String sql = "Select * from highscores order by score desc limit 10";

  rc = sqlite3_prepare_v2(db1, sql.c_str(), 1000, &res, &tail);
  if (rc != SQLITE_OK) {
    String resp = "Failed to fetch data: ";
    resp += sqlite3_errmsg(db1);
    Serial.println(resp.c_str());
    return;
  }
  rec_count = 0;
  while (sqlite3_step(res) == SQLITE_ROW) {
    highscoreNames[rec_count] = (const char *) sqlite3_column_text(res, 1);
    highscoreScores[rec_count] = sqlite3_column_double(res, 2);
    highscoreHash[rec_count] = (const char *) sqlite3_column_text(res, 3);    
    rec_count++;
  }
  sqlite3_finalize(res);

  /* end read highschores from sqlite db*/

  StaticJsonDocument<2000> doc;
  JsonObject root = doc.to<JsonObject>();
  JsonObject values = root["highscores"].to<JsonObject>();

  //String highscoreNames[10];
  //double highscoreScores[10];

  for (int i = 0; i <= 9; i++) {
     String tmp = highscoreNames[i] + highscoreScores[i] + "fajlo5a5%9k";
      const char *payload = tmp.c_str();
      Serial.println(tmp);
      byte shaResult[32];

      mbedtls_md_context_t ctx;
      mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
      const size_t payloadLength = strlen(payload);

      mbedtls_md_init(&ctx);
      mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
      mbedtls_md_starts(&ctx);
      mbedtls_md_update(&ctx, (const unsigned char *) payload, payloadLength);
      mbedtls_md_finish(&ctx, shaResult);
      mbedtls_md_free(&ctx);

      String shastr;
      for (int i = 0; i < sizeof(shaResult); i++) {
        char str[3];
        sprintf(str, "%02x", (int)shaResult[i]);
        shastr = shastr + str;
      }
    String tmp2 = "name" + (String)(i+1);
    values[tmp2] = highscoreNames[i];
    tmp2 = "score" + (String)(i+1);
    values[tmp2] = highscoreScores[i];
    Serial.println(highscoreHash[i]);
    Serial.println(shastr);
    
    if ((highscoreHash[i] != shastr) && (highscoreScores[i] > 0))
    {
      values[tmp2] = (String)highscoreScores[i] + (String)" Hacked.. :D";
    }
      
  }
 
 
  Serial.println("Json SENT");
  Serial.println(highscoreScores[0]);
  String json;
  serializeJsonPretty(doc, json);
  //Serial.println(json);
  server.send(200, "text/plane", json);
}

highschores.h file

const char htmlhighscores[] PROGMEM = R"=====(
<html>
<style>
div.divshooter{
  text-align: center;
  font-size: 18px;
  font-weight: bold;  
}
div.minimalistBlack {
  border: 0px solid #000000;
  width: 100%;
  text-align: left;
  border-collapse: collapse;
}
.divTable.minimalistBlack .divTableCell, .divTable.minimalistBlack .divTableHead {
  border: 1px solid #000000;
  padding: 5px 4px;
}
.divTable.minimalistBlack .divTableBody .divTableCell {
  font-size: 13px;
}
.divTable.minimalistBlack .divTableHeading {
  background: #CFCFCF;
  background: -moz-linear-gradient(top, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
  background: -webkit-linear-gradient(top, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
  background: linear-gradient(to bottom, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
  border-bottom: 3px solid #000000;
}
.divTable.minimalistBlack .divTableHeading .divTableHead {
  font-size: 15px;
  font-weight: bold;
  color: #000000;
  text-align: left;
}
.minimalistBlack .tableFootStyle {
  font-size: 14px;
  font-weight: bold;
  color: #000000;
  border-top: 3px solid #000000;
}
.minimalistBlack .tableFootStyle {
  font-size: 14px;
}
/* DivTable.com */
.divTable{ display: table; }
.divTableRow { display: table-row; }
.divTableHeading { display: table-header-group;}
.divTableCell, .divTableHead { display: table-cell;}
.divTableHeading { display: table-header-group;}
.divTableFoot { display: table-footer-group;}
.divTableBody { display: table-row-group;}
</style>
<script>
function getData() {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var jsonval = JSON.parse( this.responseText );
      
  
      document.getElementById("name1").innerHTML = jsonval.highscores["name1"];
      document.getElementById("score1").innerHTML = jsonval.highscores["score1"];
      document.getElementById("name2").innerHTML = jsonval.highscores["name2"];
      document.getElementById("score2").innerHTML = jsonval.highscores["score2"];
      document.getElementById("name3").innerHTML = jsonval.highscores["name3"];
      document.getElementById("score3").innerHTML = jsonval.highscores["score3"];
      document.getElementById("name4").innerHTML = jsonval.highscores["name4"];
      document.getElementById("score4").innerHTML = jsonval.highscores["score4"];
      document.getElementById("name5").innerHTML = jsonval.highscores["name5"];
      document.getElementById("score5").innerHTML = jsonval.highscores["score5"];
      document.getElementById("name6").innerHTML = jsonval.highscores["name6"];
      document.getElementById("score6").innerHTML = jsonval.highscores["score6"];
      document.getElementById("name7").innerHTML = jsonval.highscores["name7"];
      document.getElementById("score7").innerHTML = jsonval.highscores["score7"];
      document.getElementById("name8").innerHTML = jsonval.highscores["name8"];
      document.getElementById("score8").innerHTML = jsonval.highscores["score8"];
      document.getElementById("name9").innerHTML = jsonval.highscores["name9"];
      document.getElementById("score9").innerHTML = jsonval.highscores["score9"];
      document.getElementById("name10").innerHTML = jsonval.highscores["name10"];
      document.getElementById("score10").innerHTML = jsonval.highscores["score10"];

      console.log(this.responseText);
      
    }
  };
  xhttp.open("GET", "gethighscores", true);
  xhttp.send();
}


</script>
<body onload="getData();">
<h2>Highscores</h2>

<div class="divTable minimalistBlack">
<div class="divTableHeading">
<div class="divTableRow">
<div class="divTableHead">Rank</div>
<div class="divTableHead">Name</div>
<div class="divTableHead">Score</div>
</div>
</div>
<div class="divTableBody">

<div class = "divTableRow">
<div class = "divTableCell"><b><u>1</u></b></div>
<div class = "divTableCell" id="name1"></div>
<div class = "divTableCell" id="score1"></div>
</div>
<div class = "divTableRow">
<div class = "divTableCell"><b>2</b></div>
<div class = "divTableCell" id="name2"></div>
<div class = "divTableCell" id="score2"></div>
</div>
<div class = "divTableRow">
<div class = "divTableCell"><b>3</b></div>
<div class = "divTableCell" id="name3"></div>
<div class = "divTableCell" id="score3"></div>
</div>
<div class = "divTableRow">
<div class = "divTableCell">4</div>
<div class = "divTableCell" id="name4"></div>
<div class = "divTableCell" id="score4"></div>
</div>
<div class = "divTableRow">
<div class = "divTableCell">5</div>
<div class = "divTableCell" id="name5"></div>
<div class = "divTableCell" id="score5"></div>
</div>
<div class = "divTableRow">
<div class = "divTableCell">6</div>
<div class = "divTableCell" id="name6"></div>
<div class = "divTableCell" id="score6"></div>
</div>
<div class = "divTableRow">
<div class = "divTableCell">7</div>
<div class = "divTableCell" id="name7"></div>
<div class = "divTableCell" id="score7"></div>
</div>
<div class = "divTableRow">
<div class = "divTableCell">8</div>
<div class = "divTableCell" id="name8"></div>
<div class = "divTableCell" id="score8"></div>
</div>
<div class = "divTableRow">
<div class = "divTableCell">9</div>
<div class = "divTableCell" id="name9"></div>
<div class = "divTableCell" id="score9"></div>
</div>
<div class = "divTableRow">
<div class = "divTableCell">10</div>
<div class = "divTableCell" id="name10"></div>
<div class = "divTableCell" id="score10"></div>
</div>
</div>
</div>
</div>

</br></br>

<form action="/">
   <center> <input type="submit" value="Back to start" /></center>
</form>

</body>
</html>)=====";