Completion history

Source: src/dungeon_history.c · 240 lines · 23 functions.

Module responsibility

Stores, sorts and validates completed runs for the Dungeon high-score and history screens.

See how this module fits into the complete Dungeon system →

Module entry points

These functions are exposed to the rest of the Dungeon system. Open one to follow its decisions and helper calls.

How to read these diagrams

Each function has an expandable structural diagram derived from the current C source. Decisions split into True and False, loop bodies are indented, and arrows show statement order.

These are source diagrams before preprocessing, not runtime traces. Callback execution, task scheduling and macro expansion are not inferred.

Functions in this file

updateChecksum() · source line 15
static void updateChecksum(uint32_t *checksum, uint8_t value)
  1. Execute*checksum = (*checksum ^ value) * CHECKSUM_PRIME;
Read original function source
static void updateChecksum(uint32_t *checksum, uint8_t value) { *checksum = (*checksum ^ value) * CHECKSUM_PRIME; }
updateChecksumU16() · source line 16
static void updateChecksumU16(uint32_t *checksum, uint16_t value)

Calls into this documentation:

  1. ExecuteupdateChecksum(checksum, (uint8_t)value);
  2. ExecuteupdateChecksum(checksum, (uint8_t)(value >> 8));
Read original function source
static void updateChecksumU16(uint32_t *checksum, uint16_t value) { updateChecksum(checksum, (uint8_t)value); updateChecksum(checksum, (uint8_t)(value >> 8)); }
updateChecksumU32() · source line 17
static void updateChecksumU32(uint32_t *checksum, uint32_t value)

Calls into this documentation:

  1. ExecuteupdateChecksum(checksum, (uint8_t)value);
  2. ExecuteupdateChecksum(checksum, (uint8_t)(value >> 8));
  3. ExecuteupdateChecksum(checksum, (uint8_t)(value >> 16));
  4. ExecuteupdateChecksum(checksum, (uint8_t)(value >> 24));
Read original function source
static void updateChecksumU32(uint32_t *checksum, uint32_t value)
{
    updateChecksum(checksum, (uint8_t)value); updateChecksum(checksum, (uint8_t)(value >> 8));
    updateChecksum(checksum, (uint8_t)(value >> 16)); updateChecksum(checksum, (uint8_t)(value >> 24));
}
writeByte() · source line 23
static bool writeByte(FILE *file, uint8_t value, DungeonHistoryResult *result)
  1. Decisionfwrite(&value, 1, 1, file) != 1U
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Execute++result->bytes;
  3. Exit this functionreturn true;
Read original function source
static bool writeByte(FILE *file, uint8_t value, DungeonHistoryResult *result)
{
    if (fwrite(&value, 1, 1, file) != 1U) return false;
    ++result->bytes;
    return true;
}
writeU16() · source line 30
static bool writeU16(FILE *file, uint16_t value, DungeonHistoryResult *result)

Calls into this documentation:

  1. Exit this functionreturn writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result);
Read original function source
static bool writeU16(FILE *file, uint16_t value, DungeonHistoryResult *result)
{
    return writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result);
}
writeU32() · source line 35
static bool writeU32(FILE *file, uint32_t value, DungeonHistoryResult *result)

Calls into this documentation:

  1. Exit this functionreturn writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result) && writeByte(file, (uint8_t)(value >> 16), result) && writeByte(file, (uint8_t)(value >> 24), result);
Read original function source
static bool writeU32(FILE *file, uint32_t value, DungeonHistoryResult *result)
{
    return writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result) &&
           writeByte(file, (uint8_t)(value >> 16), result) && writeByte(file, (uint8_t)(value >> 24), result);
}
readByte() · source line 41
static bool readByte(FILE *file, uint8_t *value, DungeonHistoryResult *result)
  1. Decisionfread(value, 1, 1, file) != 1U
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Execute++result->bytes;
  3. Exit this functionreturn true;
Read original function source
static bool readByte(FILE *file, uint8_t *value, DungeonHistoryResult *result)
{
    if (fread(value, 1, 1, file) != 1U) return false;
    ++result->bytes;
    return true;
}
readU16() · source line 48
static bool readU16(FILE *file, uint16_t *value, DungeonHistoryResult *result)

Calls into this documentation:

  1. Executeuint8_t low, high;
  2. Decision!readByte(file, &low, result) || !readByte(file, &high, result)
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  3. Execute*value = (uint16_t)low | ((uint16_t)high << 8);
  4. Exit this functionreturn true;
Read original function source
static bool readU16(FILE *file, uint16_t *value, DungeonHistoryResult *result)
{
    uint8_t low, high;
    if (!readByte(file, &low, result) || !readByte(file, &high, result)) return false;
    *value = (uint16_t)low | ((uint16_t)high << 8);
    return true;
}
readU32() · source line 56
static bool readU32(FILE *file, uint32_t *value, DungeonHistoryResult *result)
  1. Executeuint8_t bytes[4];
  2. Decisionfread(bytes, 1, sizeof(bytes), file) != sizeof(bytes)
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  3. Executeresult->bytes += sizeof(bytes);
  4. Execute*value = (uint32_t)bytes[0] | ((uint32_t)bytes[1] << 8) | ((uint32_t)bytes[2] << 16) | ((uint32_t)bytes[3] << 24);
  5. Exit this functionreturn true;
Read original function source
static bool readU32(FILE *file, uint32_t *value, DungeonHistoryResult *result)
{
    uint8_t bytes[4];
    if (fread(bytes, 1, sizeof(bytes), file) != sizeof(bytes)) return false;
    result->bytes += sizeof(bytes);
    *value = (uint32_t)bytes[0] | ((uint32_t)bytes[1] << 8) | ((uint32_t)bytes[2] << 16) | ((uint32_t)bytes[3] << 24);
    return true;
}
updateRecordChecksum() · source line 65
static void updateRecordChecksum(uint32_t *checksum, const DungeonHistoryRecord *record)

Calls into this documentation:

  1. ExecuteupdateChecksumU32(checksum, record->gold);
  2. ExecuteupdateChecksumU32(checksum, record->moves);
  3. ExecuteupdateChecksumU32(checksum, record->timePlayedSeconds);
  4. ExecuteupdateChecksumU32(checksum, record->startedAtEpoch);
  5. ExecuteupdateChecksumU32(checksum, record->finishedAtEpoch);
  6. ExecuteupdateChecksumU32(checksum, record->experience);
  7. ExecuteupdateChecksumU16(checksum, record->level);
  8. ExecuteupdateChecksumU16(checksum, record->attack);
  9. ExecuteupdateChecksumU16(checksum, record->defence);
  10. ExecuteupdateChecksumU16(checksum, record->keys);
  11. ExecuteupdateChecksumU16(checksum, record->healthPotions);
  12. ExecuteupdateChecksumU16(checksum, record->unspentStatPoints);
  13. ExecuteupdateChecksumU16(checksum, record->shopPurchaseCount);
  14. ExecuteupdateChecksum(checksum, record->commentId);
  15. ExecuteupdateChecksum(checksum, 0U);
Read original function source
static void updateRecordChecksum(uint32_t *checksum, const DungeonHistoryRecord *record)
{
    updateChecksumU32(checksum, record->gold); updateChecksumU32(checksum, record->moves);
    updateChecksumU32(checksum, record->timePlayedSeconds); updateChecksumU32(checksum, record->startedAtEpoch);
    updateChecksumU32(checksum, record->finishedAtEpoch); updateChecksumU32(checksum, record->experience);
    updateChecksumU16(checksum, record->level); updateChecksumU16(checksum, record->attack);
    updateChecksumU16(checksum, record->defence); updateChecksumU16(checksum, record->keys);
    updateChecksumU16(checksum, record->healthPotions); updateChecksumU16(checksum, record->unspentStatPoints);
    updateChecksumU16(checksum, record->shopPurchaseCount); updateChecksum(checksum, record->commentId);
    updateChecksum(checksum, 0U);
}
calculateChecksum() · source line 77
static uint32_t calculateChecksum(void)

Calls into this documentation:

  1. Executeuint32_t checksum = CHECKSUM_START;
  2. ExecuteupdateChecksum(&checksum, record_count);
  3. FOR loopunsigned index = 0; index < DUNGEON_HISTORY_MAX_RECORDS; ++index

    Repeat body while the loop condition permits; then continue below.

    1. ExecuteupdateRecordChecksum(&checksum, &records[index]);
  4. Exit this functionreturn checksum;
Read original function source
static uint32_t calculateChecksum(void)
{
    uint32_t checksum = CHECKSUM_START;
    updateChecksum(&checksum, record_count);
    for (unsigned index = 0; index < DUNGEON_HISTORY_MAX_RECORDS; ++index) updateRecordChecksum(&checksum, &records[index]);
    return checksum;
}
writeRecord() · source line 85
static bool writeRecord(FILE *file, const DungeonHistoryRecord *record, DungeonHistoryResult *result)

Calls into this documentation:

  1. Exit this functionreturn writeU32(file, record->gold, result) && writeU32(file, record->moves, result) && writeU32(file, record->timePlayedSeconds, result) && writeU32(file, record->startedAtEpoch, result) && writeU32(file, record->finishedAtEpoch, result) && writeU32(file, record->experience, result) && writeU16(file, record->level, result) && writeU16(file, record->attack, result) && writeU16(file, record->defence, result) && writeU16(file, record->keys, result) && writeU16(file, record->healthPotions, result) && writeU16(file, record->unspentStatPoints, result) && writeU16(file, record->shopPurchaseCount, result) && writeByte(file, record->commentId, result) && writeByte(file, 0U, result);
Read original function source
static bool writeRecord(FILE *file, const DungeonHistoryRecord *record, DungeonHistoryResult *result)
{
    return writeU32(file, record->gold, result) && writeU32(file, record->moves, result) &&
           writeU32(file, record->timePlayedSeconds, result) && writeU32(file, record->startedAtEpoch, result) &&
           writeU32(file, record->finishedAtEpoch, result) && writeU32(file, record->experience, result) &&
           writeU16(file, record->level, result) && writeU16(file, record->attack, result) &&
           writeU16(file, record->defence, result) && writeU16(file, record->keys, result) &&
           writeU16(file, record->healthPotions, result) && writeU16(file, record->unspentStatPoints, result) &&
           writeU16(file, record->shopPurchaseCount, result) && writeByte(file, record->commentId, result) &&
           writeByte(file, 0U, result);
}
readRecord() · source line 97
static bool readRecord(FILE *file, DungeonHistoryRecord *record, DungeonHistoryResult *result)

Calls into this documentation:

  1. Executeuint8_t reserved;
  2. Exit this functionreturn readU32(file, &record->gold, result) && readU32(file, &record->moves, result) && readU32(file, &record->timePlayedSeconds, result) && readU32(file, &record->startedAtEpoch, result) && readU32(file, &record->finishedAtEpoch, result) && readU32(file, &record->experience, result) && readU16(file, &record->level, result) && readU16(file, &record->attack, result) && readU16(file, &record->defence, result) && readU16(file, &record->keys, result) && readU16(file, &record->healthPotions, result) && readU16(file, &record->unspentStatPoints, result) && readU16(file, &record->shopPurchaseCount, result) && readByte(file, &record->commentId, result) && readByte(file, &reserved, result);
Read original function source
static bool readRecord(FILE *file, DungeonHistoryRecord *record, DungeonHistoryResult *result)
{
    uint8_t reserved;
    return readU32(file, &record->gold, result) && readU32(file, &record->moves, result) &&
           readU32(file, &record->timePlayedSeconds, result) && readU32(file, &record->startedAtEpoch, result) &&
           readU32(file, &record->finishedAtEpoch, result) && readU32(file, &record->experience, result) &&
           readU16(file, &record->level, result) && readU16(file, &record->attack, result) &&
           readU16(file, &record->defence, result) && readU16(file, &record->keys, result) &&
           readU16(file, &record->healthPotions, result) && readU16(file, &record->unspentStatPoints, result) &&
           readU16(file, &record->shopPurchaseCount, result) && readByte(file, &record->commentId, result) &&
           readByte(file, &reserved, result);
}
betterRecord() · source line 110
static bool betterRecord(const DungeonHistoryRecord *left, const DungeonHistoryRecord *right)
  1. Decisionleft->gold != right->gold
    True
    1. Exit this functionreturn left->gold > right->gold;
    False

    Continue after this decision.

  2. Decisionleft->moves != right->moves
    True
    1. Exit this functionreturn left->moves < right->moves;
    False

    Continue after this decision.

  3. Exit this functionreturn left->timePlayedSeconds < right->timePlayedSeconds;
Read original function source
static bool betterRecord(const DungeonHistoryRecord *left, const DungeonHistoryRecord *right)
{
    if (left->gold != right->gold) return left->gold > right->gold;
    if (left->moves != right->moves) return left->moves < right->moves;
    return left->timePlayedSeconds < right->timePlayedSeconds;
}
sortRecords() · source line 117
static void sortRecords(void)

Calls into this documentation:

  1. FOR loopunsigned index = 1; index < record_count; ++index

    Repeat body while the loop condition permits; then continue below.

      1. ExecuteDungeonHistoryRecord value = records[index];
      2. Executeunsigned position = index;
      3. WHILE loopposition > 0U && betterRecord(&value, &records[position - 1U])

        Repeat body while the loop condition permits; then continue below.

          1. Executerecords[position] = records[position - 1U];
          2. Execute--position;
      4. Executerecords[position] = value;
Read original function source
static void sortRecords(void)
{
    for (unsigned index = 1; index < record_count; ++index) {
        DungeonHistoryRecord value = records[index];
        unsigned position = index;
        while (position > 0U && betterRecord(&value, &records[position - 1U])) {
            records[position] = records[position - 1U];
            --position;
        }
        records[position] = value;
    }
}
sameCompletion() · source line 130
static bool sameCompletion(const DungeonHistoryRecord *record, const DungeonPlayerState *player)
  1. Exit this functionreturn record->gold == player->gold && record->moves == player->moves && record->timePlayedSeconds == player->timePlayedSeconds && record->startedAtEpoch == player->startedAtEpoch && record->finishedAtEpoch == player->finishedAtEpoch;
Read original function source
static bool sameCompletion(const DungeonHistoryRecord *record, const DungeonPlayerState *player)
{
    return record->gold == player->gold && record->moves == player->moves &&
           record->timePlayedSeconds == player->timePlayedSeconds &&
           record->startedAtEpoch == player->startedAtEpoch &&
           record->finishedAtEpoch == player->finishedAtEpoch;
}
saveHistory() · source line 138
static bool saveHistory(DungeonHistoryResult *result)

Calls into this documentation:

  1. Executememset(result, 0, sizeof(*result));
  2. Executeresult->error = DUNGEON_HISTORY_OPEN_WRITE_FAILED;
  3. ExecuteFILE *file = fopen(HISTORY_FILE, "wb");
  4. Decisionfile == NULL
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  5. Executeconst uint8_t magic[] = { 'D', 'H', 'I', 'S' };
  6. Executeconst uint8_t reserved[] = { 0U, 0U, 0U };
  7. Executebool written = fwrite(magic, 1, sizeof(magic), file) == sizeof(magic);
  8. Executeresult->bytes += written ? sizeof(magic) : 0U;
  9. Executewritten = written && writeU16(file, HISTORY_VERSION, result) && writeU16(file, HISTORY_RECORD_SIZE, result) && writeByte(file, record_count, result) && fwrite(reserved, 1, sizeof(reserved), file) == sizeof(reserved);
  10. Decisionwritten
    True
    1. Executeresult->bytes += sizeof(reserved);
    False

    Continue after this decision.

  11. Executewritten = written && writeU32(file, calculateChecksum(), result);
  12. FOR loopunsigned index = 0; written && index < DUNGEON_HISTORY_MAX_RECORDS; ++index

    Repeat body while the loop condition permits; then continue below.

    1. Executewritten = writeRecord(file, &records[index], result);
  13. Executefclose(file);
  14. Decision!written || result->bytes != HISTORY_FILE_SIZE
    True
      1. Executeresult->error = DUNGEON_HISTORY_WRITE_FAILED;
      2. Exit this functionreturn false;
    False

    Continue after this decision.

  15. Executeresult->error = DUNGEON_HISTORY_OK;
  16. Exit this functionreturn true;
Read original function source
static bool saveHistory(DungeonHistoryResult *result)
{
    memset(result, 0, sizeof(*result));
    result->error = DUNGEON_HISTORY_OPEN_WRITE_FAILED;
    FILE *file = fopen(HISTORY_FILE, "wb");
    if (file == NULL) return false;
    const uint8_t magic[] = { 'D', 'H', 'I', 'S' };
    const uint8_t reserved[] = { 0U, 0U, 0U };
    bool written = fwrite(magic, 1, sizeof(magic), file) == sizeof(magic);
    result->bytes += written ? sizeof(magic) : 0U;
    written = written && writeU16(file, HISTORY_VERSION, result) && writeU16(file, HISTORY_RECORD_SIZE, result) &&
              writeByte(file, record_count, result) && fwrite(reserved, 1, sizeof(reserved), file) == sizeof(reserved);
    if (written) result->bytes += sizeof(reserved);
    written = written && writeU32(file, calculateChecksum(), result);
    for (unsigned index = 0; written && index < DUNGEON_HISTORY_MAX_RECORDS; ++index)
        written = writeRecord(file, &records[index], result);
    fclose(file);
    if (!written || result->bytes != HISTORY_FILE_SIZE) { result->error = DUNGEON_HISTORY_WRITE_FAILED; return false; }
    result->error = DUNGEON_HISTORY_OK;
    return true;
}
initDungeonHistory() · source line 160
void initDungeonHistory(void)
  1. Executememset(records, 0, sizeof(records));
  2. Executerecord_count = 0U;
Read original function source
void initDungeonHistory(void) { memset(records, 0, sizeof(records)); record_count = 0U; }
loadDungeonHistory() · source line 162
bool loadDungeonHistory(DungeonHistoryResult *result)

Calls into this documentation:

  1. Decisionresult == NULL
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. ExecuteinitDungeonHistory();
  3. Executememset(result, 0, sizeof(*result));
  4. Executeresult->error = DUNGEON_HISTORY_OPEN_READ_FAILED;
  5. ExecuteFILE *file = fopen(HISTORY_FILE, "rb");
  6. Decisionfile == NULL
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  7. Decisionfseek(file, 0L, SEEK_END) != 0 || ftell(file) != (long)HISTORY_FILE_SIZE
    True
      1. Executeresult->error = DUNGEON_HISTORY_BAD_FILE;
      2. Executefclose(file);
      3. Exit this functionreturn false;
    False

    Continue after this decision.

  8. Executerewind(file);
  9. Executeuint8_t magic[4], count, reserved[3];
  10. Executeuint16_t version, record_size;
  11. Executeuint32_t stored_checksum;
  12. Executebool read = fread(magic, 1, sizeof(magic), file) == sizeof(magic);
  13. Executeresult->bytes += read ? sizeof(magic) : 0U;
  14. Executeread = read && readU16(file, &version, result) && readU16(file, &record_size, result) && readByte(file, &count, result) && fread(reserved, 1, sizeof(reserved), file) == sizeof(reserved);
  15. Decisionread
    True
    1. Executeresult->bytes += sizeof(reserved);
    False

    Continue after this decision.

  16. Executeread = read && readU32(file, &stored_checksum, result);
  17. FOR loopunsigned index = 0; read && index < DUNGEON_HISTORY_MAX_RECORDS; ++index

    Repeat body while the loop condition permits; then continue below.

    1. Executeread = readRecord(file, &records[index], result);
  18. Executefclose(file);
  19. Decision!read || result->bytes != HISTORY_FILE_SIZE
    True
      1. ExecuteinitDungeonHistory();
      2. Executeresult->error = DUNGEON_HISTORY_READ_FAILED;
      3. Exit this functionreturn false;
    False

    Continue after this decision.

  20. Decisionmemcmp(magic, "DHIS", sizeof(magic)) != 0 || version != HISTORY_VERSION || record_size != HISTORY_RECORD_SIZE || count > DUNGEON_HISTORY_MAX_RECORDS
    True
      1. ExecuteinitDungeonHistory();
      2. Executeresult->error = DUNGEON_HISTORY_BAD_FILE;
      3. Exit this functionreturn false;
    False

    Continue after this decision.

  21. Executerecord_count = count;
  22. Decisionstored_checksum != calculateChecksum()
    True
      1. ExecuteinitDungeonHistory();
      2. Executeresult->error = DUNGEON_HISTORY_CHECKSUM_FAILED;
      3. Exit this functionreturn false;
    False

    Continue after this decision.

  23. ExecutesortRecords();
  24. Executeresult->error = DUNGEON_HISTORY_OK;
  25. Exit this functionreturn true;
Read original function source
bool loadDungeonHistory(DungeonHistoryResult *result)
{
    if (result == NULL) return false;
    initDungeonHistory();
    memset(result, 0, sizeof(*result));
    result->error = DUNGEON_HISTORY_OPEN_READ_FAILED;
    FILE *file = fopen(HISTORY_FILE, "rb");
    if (file == NULL) return false;
    if (fseek(file, 0L, SEEK_END) != 0 || ftell(file) != (long)HISTORY_FILE_SIZE) { result->error = DUNGEON_HISTORY_BAD_FILE; fclose(file); return false; }
    rewind(file);
    uint8_t magic[4], count, reserved[3];
    uint16_t version, record_size;
    uint32_t stored_checksum;
    bool read = fread(magic, 1, sizeof(magic), file) == sizeof(magic);
    result->bytes += read ? sizeof(magic) : 0U;
    read = read && readU16(file, &version, result) && readU16(file, &record_size, result) &&
           readByte(file, &count, result) && fread(reserved, 1, sizeof(reserved), file) == sizeof(reserved);
    if (read) result->bytes += sizeof(reserved);
    read = read && readU32(file, &stored_checksum, result);
    for (unsigned index = 0; read && index < DUNGEON_HISTORY_MAX_RECORDS; ++index) read = readRecord(file, &records[index], result);
    fclose(file);
    if (!read || result->bytes != HISTORY_FILE_SIZE) { initDungeonHistory(); result->error = DUNGEON_HISTORY_READ_FAILED; return false; }
    if (memcmp(magic, "DHIS", sizeof(magic)) != 0 || version != HISTORY_VERSION || record_size != HISTORY_RECORD_SIZE || count > DUNGEON_HISTORY_MAX_RECORDS) {
        initDungeonHistory(); result->error = DUNGEON_HISTORY_BAD_FILE; return false;
    }
    record_count = count;
    if (stored_checksum != calculateChecksum()) { initDungeonHistory(); result->error = DUNGEON_HISTORY_CHECKSUM_FAILED; return false; }
    sortRecords();
    result->error = DUNGEON_HISTORY_OK;
    return true;
}
recordDungeonCompletion() · source line 194
bool recordDungeonCompletion(const DungeonPlayerState *player, DungeonHistoryResult *result, uint8_t *rank)

Calls into this documentation:

  1. Decisionplayer == NULL || result == NULL || rank == NULL
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Execute*rank = 0U;
  3. Decision!loadDungeonHistory(result)
    True
      1. Decisionresult->error != DUNGEON_HISTORY_OPEN_READ_FAILED
        True
        1. Exit this functionreturn false;
        False

        Continue after this decision.

      2. ExecuteinitDungeonHistory();
      3. Executeresult->created = true;
    False

    Continue after this decision.

  4. FOR loopunsigned index = 0; index < record_count; ++index

    Repeat body while the loop condition permits; then continue below.

      1. DecisionsameCompletion(&records[index], player)
        True
          1. Execute*rank = (uint8_t)(index + 1U);
          2. Executeresult->error = DUNGEON_HISTORY_OK;
          3. Exit this functionreturn true;
        False

        Continue after this decision.

  5. ExecuteDungeonHistoryRecord record;
  6. Executememset(&record, 0, sizeof(record));
  7. Executerecord.gold = player->gold;
  8. Executerecord.moves = player->moves;
  9. Executerecord.timePlayedSeconds = player->timePlayedSeconds;
  10. Executerecord.startedAtEpoch = player->startedAtEpoch;
  11. Executerecord.finishedAtEpoch = player->finishedAtEpoch;
  12. Executerecord.experience = player->experience;
  13. Executerecord.level = player->level;
  14. Executerecord.attack = player->attack;
  15. Executerecord.defence = player->defence;
  16. Executerecord.keys = player->keys;
  17. Executerecord.healthPotions = player->healthPotions;
  18. Executerecord.unspentStatPoints = player->unspentStatPoints;
  19. Executerecord.shopPurchaseCount = player->shopPurchaseCount;
  20. Decisionrecord_count < DUNGEON_HISTORY_MAX_RECORDS
    True
    1. Executerecords[record_count++] = record;
    False
    1. DecisionbetterRecord(&record, &records[record_count - 1U])
      True
      1. Executerecords[record_count - 1U] = record;
      False
        1. Executeresult->error = DUNGEON_HISTORY_OK;
        2. Exit this functionreturn true;
  21. ExecutesortRecords();
  22. FOR loopunsigned index = 0; index < record_count; ++index

    Repeat body while the loop condition permits; then continue below.

    1. DecisionsameCompletion(&records[index], player)
      True
        1. Execute*rank = (uint8_t)(index + 1U);
        2. Exit enclosing loop or switchbreak;
      False

      Continue after this decision.

  23. Executeconst bool created = result->created;
  24. Decision!saveHistory(result)
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  25. Executeresult->created = created;
  26. Exit this functionreturn true;
Read original function source
bool recordDungeonCompletion(const DungeonPlayerState *player, DungeonHistoryResult *result, uint8_t *rank)
{
    if (player == NULL || result == NULL || rank == NULL) return false;
    *rank = 0U;
    if (!loadDungeonHistory(result)) {
        if (result->error != DUNGEON_HISTORY_OPEN_READ_FAILED) return false;
        initDungeonHistory();
        result->created = true;
    }
    for (unsigned index = 0; index < record_count; ++index) {
        if (sameCompletion(&records[index], player)) { *rank = (uint8_t)(index + 1U); result->error = DUNGEON_HISTORY_OK; return true; }
    }
    DungeonHistoryRecord record;
    memset(&record, 0, sizeof(record));
    record.gold = player->gold; record.moves = player->moves; record.timePlayedSeconds = player->timePlayedSeconds;
    record.startedAtEpoch = player->startedAtEpoch; record.finishedAtEpoch = player->finishedAtEpoch;
    record.experience = player->experience; record.level = player->level; record.attack = player->attack;
    record.defence = player->defence; record.keys = player->keys; record.healthPotions = player->healthPotions;
    record.unspentStatPoints = player->unspentStatPoints; record.shopPurchaseCount = player->shopPurchaseCount;
    if (record_count < DUNGEON_HISTORY_MAX_RECORDS) records[record_count++] = record;
    else if (betterRecord(&record, &records[record_count - 1U])) records[record_count - 1U] = record;
    else { result->error = DUNGEON_HISTORY_OK; return true; }
    sortRecords();
    for (unsigned index = 0; index < record_count; ++index)
        if (sameCompletion(&records[index], player)) { *rank = (uint8_t)(index + 1U); break; }
    const bool created = result->created;
    if (!saveHistory(result)) return false;
    result->created = created;
    return true;
}
dungeonHistoryCount() · source line 225
uint8_t dungeonHistoryCount(void)
  1. Exit this functionreturn record_count;
Read original function source
uint8_t dungeonHistoryCount(void) { return record_count; }
dungeonHistoryRecord() · source line 226
const DungeonHistoryRecord *dungeonHistoryRecord(uint8_t index)
  1. Exit this functionreturn index < record_count ? &records[index] : NULL;
Read original function source
const DungeonHistoryRecord *dungeonHistoryRecord(uint8_t index) { return index < record_count ? &records[index] : NULL; }
dungeonHistoryErrorText() · source line 228
const char *dungeonHistoryErrorText(DungeonHistoryError error)
  1. Select a caseerror

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase DUNGEON_HISTORY_OK:
      2. Exit this functionreturn "OK";
      3. Case / labelcase DUNGEON_HISTORY_OPEN_READ_FAILED:
      4. Exit this functionreturn "No completions yet";
      5. Case / labelcase DUNGEON_HISTORY_OPEN_WRITE_FAILED:
      6. Exit this functionreturn "Cannot create history";
      7. Case / labelcase DUNGEON_HISTORY_READ_FAILED:
      8. Exit this functionreturn "History read failed";
      9. Case / labelcase DUNGEON_HISTORY_WRITE_FAILED:
      10. Exit this functionreturn "History save failed";
      11. Case / labelcase DUNGEON_HISTORY_BAD_FILE:
      12. Exit this functionreturn "Bad history file";
      13. Case / labelcase DUNGEON_HISTORY_CHECKSUM_FAILED:
      14. Exit this functionreturn "History checksum failed";
      15. Case / labeldefault:
      16. Exit this functionreturn "History error";
Read original function source
const char *dungeonHistoryErrorText(DungeonHistoryError error)
{
    switch (error) {
        case DUNGEON_HISTORY_OK: return "OK";
        case DUNGEON_HISTORY_OPEN_READ_FAILED: return "No completions yet";
        case DUNGEON_HISTORY_OPEN_WRITE_FAILED: return "Cannot create history";
        case DUNGEON_HISTORY_READ_FAILED: return "History read failed";
        case DUNGEON_HISTORY_WRITE_FAILED: return "History save failed";
        case DUNGEON_HISTORY_BAD_FILE: return "Bad history file";
        case DUNGEON_HISTORY_CHECKSUM_FAILED: return "History checksum failed";
        default: return "History error";
    }
}
Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "dungeon_history.h"
#include <stdio.h>
#include <string.h>

#define HISTORY_FILE "/spiffs/dungeon_highscores.dat"
#define HISTORY_VERSION 1U
#define HISTORY_RECORD_SIZE 40U
#define HISTORY_FILE_SIZE 216U
#define CHECKSUM_START 2166136261UL
#define CHECKSUM_PRIME 16777619UL

static DungeonHistoryRecord records[DUNGEON_HISTORY_MAX_RECORDS];
static uint8_t record_count;

static void updateChecksum(uint32_t *checksum, uint8_t value) { *checksum = (*checksum ^ value) * CHECKSUM_PRIME; }
static void updateChecksumU16(uint32_t *checksum, uint16_t value) { updateChecksum(checksum, (uint8_t)value); updateChecksum(checksum, (uint8_t)(value >> 8)); }
static void updateChecksumU32(uint32_t *checksum, uint32_t value)
{
    updateChecksum(checksum, (uint8_t)value); updateChecksum(checksum, (uint8_t)(value >> 8));
    updateChecksum(checksum, (uint8_t)(value >> 16)); updateChecksum(checksum, (uint8_t)(value >> 24));
}

static bool writeByte(FILE *file, uint8_t value, DungeonHistoryResult *result)
{
    if (fwrite(&value, 1, 1, file) != 1U) return false;
    ++result->bytes;
    return true;
}

static bool writeU16(FILE *file, uint16_t value, DungeonHistoryResult *result)
{
    return writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result);
}

static bool writeU32(FILE *file, uint32_t value, DungeonHistoryResult *result)
{
    return writeByte(file, (uint8_t)value, result) && writeByte(file, (uint8_t)(value >> 8), result) &&
           writeByte(file, (uint8_t)(value >> 16), result) && writeByte(file, (uint8_t)(value >> 24), result);
}

static bool readByte(FILE *file, uint8_t *value, DungeonHistoryResult *result)
{
    if (fread(value, 1, 1, file) != 1U) return false;
    ++result->bytes;
    return true;
}

static bool readU16(FILE *file, uint16_t *value, DungeonHistoryResult *result)
{
    uint8_t low, high;
    if (!readByte(file, &low, result) || !readByte(file, &high, result)) return false;
    *value = (uint16_t)low | ((uint16_t)high << 8);
    return true;
}

static bool readU32(FILE *file, uint32_t *value, DungeonHistoryResult *result)
{
    uint8_t bytes[4];
    if (fread(bytes, 1, sizeof(bytes), file) != sizeof(bytes)) return false;
    result->bytes += sizeof(bytes);
    *value = (uint32_t)bytes[0] | ((uint32_t)bytes[1] << 8) | ((uint32_t)bytes[2] << 16) | ((uint32_t)bytes[3] << 24);
    return true;
}

static void updateRecordChecksum(uint32_t *checksum, const DungeonHistoryRecord *record)
{
    updateChecksumU32(checksum, record->gold); updateChecksumU32(checksum, record->moves);
    updateChecksumU32(checksum, record->timePlayedSeconds); updateChecksumU32(checksum, record->startedAtEpoch);
    updateChecksumU32(checksum, record->finishedAtEpoch); updateChecksumU32(checksum, record->experience);
    updateChecksumU16(checksum, record->level); updateChecksumU16(checksum, record->attack);
    updateChecksumU16(checksum, record->defence); updateChecksumU16(checksum, record->keys);
    updateChecksumU16(checksum, record->healthPotions); updateChecksumU16(checksum, record->unspentStatPoints);
    updateChecksumU16(checksum, record->shopPurchaseCount); updateChecksum(checksum, record->commentId);
    updateChecksum(checksum, 0U);
}

static uint32_t calculateChecksum(void)
{
    uint32_t checksum = CHECKSUM_START;
    updateChecksum(&checksum, record_count);
    for (unsigned index = 0; index < DUNGEON_HISTORY_MAX_RECORDS; ++index) updateRecordChecksum(&checksum, &records[index]);
    return checksum;
}

static bool writeRecord(FILE *file, const DungeonHistoryRecord *record, DungeonHistoryResult *result)
{
    return writeU32(file, record->gold, result) && writeU32(file, record->moves, result) &&
           writeU32(file, record->timePlayedSeconds, result) && writeU32(file, record->startedAtEpoch, result) &&
           writeU32(file, record->finishedAtEpoch, result) && writeU32(file, record->experience, result) &&
           writeU16(file, record->level, result) && writeU16(file, record->attack, result) &&
           writeU16(file, record->defence, result) && writeU16(file, record->keys, result) &&
           writeU16(file, record->healthPotions, result) && writeU16(file, record->unspentStatPoints, result) &&
           writeU16(file, record->shopPurchaseCount, result) && writeByte(file, record->commentId, result) &&
           writeByte(file, 0U, result);
}

static bool readRecord(FILE *file, DungeonHistoryRecord *record, DungeonHistoryResult *result)
{
    uint8_t reserved;
    return readU32(file, &record->gold, result) && readU32(file, &record->moves, result) &&
           readU32(file, &record->timePlayedSeconds, result) && readU32(file, &record->startedAtEpoch, result) &&
           readU32(file, &record->finishedAtEpoch, result) && readU32(file, &record->experience, result) &&
           readU16(file, &record->level, result) && readU16(file, &record->attack, result) &&
           readU16(file, &record->defence, result) && readU16(file, &record->keys, result) &&
           readU16(file, &record->healthPotions, result) && readU16(file, &record->unspentStatPoints, result) &&
           readU16(file, &record->shopPurchaseCount, result) && readByte(file, &record->commentId, result) &&
           readByte(file, &reserved, result);
}

static bool betterRecord(const DungeonHistoryRecord *left, const DungeonHistoryRecord *right)
{
    if (left->gold != right->gold) return left->gold > right->gold;
    if (left->moves != right->moves) return left->moves < right->moves;
    return left->timePlayedSeconds < right->timePlayedSeconds;
}

static void sortRecords(void)
{
    for (unsigned index = 1; index < record_count; ++index) {
        DungeonHistoryRecord value = records[index];
        unsigned position = index;
        while (position > 0U && betterRecord(&value, &records[position - 1U])) {
            records[position] = records[position - 1U];
            --position;
        }
        records[position] = value;
    }
}

static bool sameCompletion(const DungeonHistoryRecord *record, const DungeonPlayerState *player)
{
    return record->gold == player->gold && record->moves == player->moves &&
           record->timePlayedSeconds == player->timePlayedSeconds &&
           record->startedAtEpoch == player->startedAtEpoch &&
           record->finishedAtEpoch == player->finishedAtEpoch;
}

static bool saveHistory(DungeonHistoryResult *result)
{
    memset(result, 0, sizeof(*result));
    result->error = DUNGEON_HISTORY_OPEN_WRITE_FAILED;
    FILE *file = fopen(HISTORY_FILE, "wb");
    if (file == NULL) return false;
    const uint8_t magic[] = { 'D', 'H', 'I', 'S' };
    const uint8_t reserved[] = { 0U, 0U, 0U };
    bool written = fwrite(magic, 1, sizeof(magic), file) == sizeof(magic);
    result->bytes += written ? sizeof(magic) : 0U;
    written = written && writeU16(file, HISTORY_VERSION, result) && writeU16(file, HISTORY_RECORD_SIZE, result) &&
              writeByte(file, record_count, result) && fwrite(reserved, 1, sizeof(reserved), file) == sizeof(reserved);
    if (written) result->bytes += sizeof(reserved);
    written = written && writeU32(file, calculateChecksum(), result);
    for (unsigned index = 0; written && index < DUNGEON_HISTORY_MAX_RECORDS; ++index)
        written = writeRecord(file, &records[index], result);
    fclose(file);
    if (!written || result->bytes != HISTORY_FILE_SIZE) { result->error = DUNGEON_HISTORY_WRITE_FAILED; return false; }
    result->error = DUNGEON_HISTORY_OK;
    return true;
}

void initDungeonHistory(void) { memset(records, 0, sizeof(records)); record_count = 0U; }

bool loadDungeonHistory(DungeonHistoryResult *result)
{
    if (result == NULL) return false;
    initDungeonHistory();
    memset(result, 0, sizeof(*result));
    result->error = DUNGEON_HISTORY_OPEN_READ_FAILED;
    FILE *file = fopen(HISTORY_FILE, "rb");
    if (file == NULL) return false;
    if (fseek(file, 0L, SEEK_END) != 0 || ftell(file) != (long)HISTORY_FILE_SIZE) { result->error = DUNGEON_HISTORY_BAD_FILE; fclose(file); return false; }
    rewind(file);
    uint8_t magic[4], count, reserved[3];
    uint16_t version, record_size;
    uint32_t stored_checksum;
    bool read = fread(magic, 1, sizeof(magic), file) == sizeof(magic);
    result->bytes += read ? sizeof(magic) : 0U;
    read = read && readU16(file, &version, result) && readU16(file, &record_size, result) &&
           readByte(file, &count, result) && fread(reserved, 1, sizeof(reserved), file) == sizeof(reserved);
    if (read) result->bytes += sizeof(reserved);
    read = read && readU32(file, &stored_checksum, result);
    for (unsigned index = 0; read && index < DUNGEON_HISTORY_MAX_RECORDS; ++index) read = readRecord(file, &records[index], result);
    fclose(file);
    if (!read || result->bytes != HISTORY_FILE_SIZE) { initDungeonHistory(); result->error = DUNGEON_HISTORY_READ_FAILED; return false; }
    if (memcmp(magic, "DHIS", sizeof(magic)) != 0 || version != HISTORY_VERSION || record_size != HISTORY_RECORD_SIZE || count > DUNGEON_HISTORY_MAX_RECORDS) {
        initDungeonHistory(); result->error = DUNGEON_HISTORY_BAD_FILE; return false;
    }
    record_count = count;
    if (stored_checksum != calculateChecksum()) { initDungeonHistory(); result->error = DUNGEON_HISTORY_CHECKSUM_FAILED; return false; }
    sortRecords();
    result->error = DUNGEON_HISTORY_OK;
    return true;
}

bool recordDungeonCompletion(const DungeonPlayerState *player, DungeonHistoryResult *result, uint8_t *rank)
{
    if (player == NULL || result == NULL || rank == NULL) return false;
    *rank = 0U;
    if (!loadDungeonHistory(result)) {
        if (result->error != DUNGEON_HISTORY_OPEN_READ_FAILED) return false;
        initDungeonHistory();
        result->created = true;
    }
    for (unsigned index = 0; index < record_count; ++index) {
        if (sameCompletion(&records[index], player)) { *rank = (uint8_t)(index + 1U); result->error = DUNGEON_HISTORY_OK; return true; }
    }
    DungeonHistoryRecord record;
    memset(&record, 0, sizeof(record));
    record.gold = player->gold; record.moves = player->moves; record.timePlayedSeconds = player->timePlayedSeconds;
    record.startedAtEpoch = player->startedAtEpoch; record.finishedAtEpoch = player->finishedAtEpoch;
    record.experience = player->experience; record.level = player->level; record.attack = player->attack;
    record.defence = player->defence; record.keys = player->keys; record.healthPotions = player->healthPotions;
    record.unspentStatPoints = player->unspentStatPoints; record.shopPurchaseCount = player->shopPurchaseCount;
    if (record_count < DUNGEON_HISTORY_MAX_RECORDS) records[record_count++] = record;
    else if (betterRecord(&record, &records[record_count - 1U])) records[record_count - 1U] = record;
    else { result->error = DUNGEON_HISTORY_OK; return true; }
    sortRecords();
    for (unsigned index = 0; index < record_count; ++index)
        if (sameCompletion(&records[index], player)) { *rank = (uint8_t)(index + 1U); break; }
    const bool created = result->created;
    if (!saveHistory(result)) return false;
    result->created = created;
    return true;
}

uint8_t dungeonHistoryCount(void) { return record_count; }
const DungeonHistoryRecord *dungeonHistoryRecord(uint8_t index) { return index < record_count ? &records[index] : NULL; }

const char *dungeonHistoryErrorText(DungeonHistoryError error)
{
    switch (error) {
        case DUNGEON_HISTORY_OK: return "OK";
        case DUNGEON_HISTORY_OPEN_READ_FAILED: return "No completions yet";
        case DUNGEON_HISTORY_OPEN_WRITE_FAILED: return "Cannot create history";
        case DUNGEON_HISTORY_READ_FAILED: return "History read failed";
        case DUNGEON_HISTORY_WRITE_FAILED: return "History save failed";
        case DUNGEON_HISTORY_BAD_FILE: return "Bad history file";
        case DUNGEON_HISTORY_CHECKSUM_FAILED: return "History checksum failed";
        default: return "History error";
    }
}