328 lines
12 KiB
C++
328 lines
12 KiB
C++
#include "manage_hiscore_table.hpp"
|
|
|
|
#include <SDL3/SDL.h> // Para SDL_ReadIO, SDL_WriteIO, SDL_CloseIO, SDL_GetError, SDL_IOFromFile, SDL_LogError, SDL_LogCategory, SDL_LogInfo
|
|
|
|
#include <algorithm> // Para __sort_fn, sort
|
|
#include <array> // Para array
|
|
#include <functional> // Para identity
|
|
#include <iterator> // Para distance
|
|
#include <ranges> // Para __find_if_fn, find_if
|
|
#include <utility> // Para move
|
|
|
|
#include "ui/logger.hpp" // Para info
|
|
#include "utils.hpp" // Para getFileName
|
|
|
|
// Resetea la tabla a los valores por defecto
|
|
void ManageHiScoreTable::clear() {
|
|
// Limpia la tabla
|
|
table_.clear();
|
|
|
|
// Añade 10 entradas predefinidas
|
|
table_.emplace_back("BRY", 1000000);
|
|
table_.emplace_back("USUFO", 500000);
|
|
table_.emplace_back("GLUCA", 100000);
|
|
table_.emplace_back("PARRA", 50000);
|
|
table_.emplace_back("CAGAM", 10000);
|
|
table_.emplace_back("PEPE", 5000);
|
|
table_.emplace_back("ROSIT", 1000);
|
|
table_.emplace_back("SAM", 500);
|
|
table_.emplace_back("PACMQ", 200);
|
|
table_.emplace_back("PELEC", 100);
|
|
|
|
/*
|
|
table_.emplace_back("BRY", 1000);
|
|
table_.emplace_back("USUFO", 500);
|
|
table_.emplace_back("GLUCA", 100);
|
|
table_.emplace_back("PARRA", 50);
|
|
table_.emplace_back("CAGAM", 10);
|
|
table_.emplace_back("PEPE", 5);
|
|
table_.emplace_back("ROSIT", 4);
|
|
table_.emplace_back("SAM", 3);
|
|
table_.emplace_back("PACMQ", 2);
|
|
table_.emplace_back("PELEC", 1);
|
|
*/
|
|
|
|
/*
|
|
table_.emplace_back("BRY", 5000000);
|
|
table_.emplace_back("USUFO", 5000000);
|
|
table_.emplace_back("GLUCA", 5000000);
|
|
table_.emplace_back("PARRA", 5000000);
|
|
table_.emplace_back("CAGAM", 5000000);
|
|
table_.emplace_back("PEPE", 5000000);
|
|
table_.emplace_back("ROSIT", 5000000);
|
|
table_.emplace_back("SAM", 5000000);
|
|
table_.emplace_back("PACMQ", 5000000);
|
|
table_.emplace_back("PELEC", 5000000);
|
|
*/
|
|
|
|
sort();
|
|
}
|
|
|
|
// Añade un elemento a la tabla
|
|
auto ManageHiScoreTable::add(const HiScoreEntry& entry) -> int {
|
|
// Añade la entrada a la tabla
|
|
table_.push_back(entry);
|
|
|
|
// Ordena la tabla
|
|
sort();
|
|
|
|
// Encontrar la posición del nuevo elemento
|
|
auto it = std::ranges::find_if(table_, [&](const HiScoreEntry& e) -> bool {
|
|
return e.name == entry.name && e.score == entry.score && e.one_credit_complete == entry.one_credit_complete;
|
|
});
|
|
|
|
int position = -1;
|
|
if (it != table_.end()) {
|
|
position = std::distance(table_.begin(), it);
|
|
}
|
|
|
|
// Deja solo las 10 primeras entradas
|
|
if (table_.size() > 10) {
|
|
table_.resize(10);
|
|
|
|
// Si el nuevo elemento quedó fuera del top 10
|
|
if (position >= 10) {
|
|
position = NO_ENTRY; // No entró en el top 10
|
|
}
|
|
}
|
|
|
|
// Devuelve la posición
|
|
return position;
|
|
}
|
|
|
|
// Ordena la tabla
|
|
void ManageHiScoreTable::sort() {
|
|
struct
|
|
{
|
|
auto operator()(const HiScoreEntry& a, const HiScoreEntry& b) const -> bool { return a.score > b.score; }
|
|
} score_descending_comparator;
|
|
|
|
std::ranges::sort(table_, score_descending_comparator);
|
|
}
|
|
|
|
// Carga la tabla desde un fichero
|
|
auto ManageHiScoreTable::loadFromFile(const std::string& file_path) -> bool {
|
|
auto* file = SDL_IOFromFile(file_path.c_str(), "rb");
|
|
|
|
if (file == nullptr) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Unable to load %s file! %s", getFileName(file_path).c_str(), SDL_GetError());
|
|
clear();
|
|
return false;
|
|
}
|
|
|
|
// Validar header (magic number + version + table size)
|
|
if (!validateMagicNumber(file, file_path)) {
|
|
SDL_CloseIO(file);
|
|
clear();
|
|
return false;
|
|
}
|
|
|
|
if (!validateVersion(file, file_path)) {
|
|
SDL_CloseIO(file);
|
|
clear();
|
|
return false;
|
|
}
|
|
|
|
int table_size = 0;
|
|
if (!readTableSize(file, file_path, table_size)) {
|
|
SDL_CloseIO(file);
|
|
clear();
|
|
return false;
|
|
}
|
|
|
|
// Leer todas las entradas
|
|
Table temp_table;
|
|
bool success = true;
|
|
for (int i = 0; i < table_size; ++i) {
|
|
HiScoreEntry entry;
|
|
if (!readEntry(file, file_path, i, entry)) {
|
|
success = false;
|
|
break;
|
|
}
|
|
temp_table.push_back(entry);
|
|
}
|
|
|
|
// Verificar checksum
|
|
if (success) {
|
|
success = verifyChecksum(file, file_path, temp_table);
|
|
}
|
|
|
|
SDL_CloseIO(file);
|
|
|
|
// Si todo fue bien, actualizar la tabla; si no, usar valores por defecto
|
|
if (success) {
|
|
table_ = std::move(temp_table);
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\nReading file: %s (loaded %d entries successfully)", getFileName(file_path).c_str(), table_size);
|
|
} else {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "File %s is corrupted - loading default values", getFileName(file_path).c_str());
|
|
clear();
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// Métodos auxiliares privados para loadFromFile
|
|
|
|
auto ManageHiScoreTable::validateMagicNumber(SDL_IOStream* file, const std::string& file_path) -> bool {
|
|
std::array<char, 4> magic;
|
|
if (SDL_ReadIO(file, magic.data(), 4) != 4 || magic[0] != 'C' || magic[1] != 'C' || magic[2] != 'A' || magic[3] != 'E') {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Invalid magic number in %s - file may be corrupted or old format", getFileName(file_path).c_str());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto ManageHiScoreTable::validateVersion(SDL_IOStream* file, const std::string& file_path) -> bool {
|
|
int version = 0;
|
|
if (SDL_ReadIO(file, &version, sizeof(int)) != sizeof(int)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Cannot read version in %s", getFileName(file_path).c_str());
|
|
return false;
|
|
}
|
|
|
|
if (version != FILE_VERSION) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Unsupported file version %d in %s (expected %d)", version, getFileName(file_path).c_str(), FILE_VERSION);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto ManageHiScoreTable::readTableSize(SDL_IOStream* file, const std::string& file_path, int& table_size) -> bool {
|
|
if (SDL_ReadIO(file, &table_size, sizeof(int)) != sizeof(int)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Cannot read table size in %s", getFileName(file_path).c_str());
|
|
return false;
|
|
}
|
|
|
|
if (table_size < 0 || table_size > MAX_TABLE_SIZE) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Invalid table size %d in %s (expected 0-%d)", table_size, getFileName(file_path).c_str(), MAX_TABLE_SIZE);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto ManageHiScoreTable::readEntry(SDL_IOStream* file, const std::string& file_path, int index, HiScoreEntry& entry) -> bool {
|
|
// Leer y validar puntuación
|
|
if (SDL_ReadIO(file, &entry.score, sizeof(int)) != sizeof(int)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Cannot read score for entry %d in %s", index, getFileName(file_path).c_str());
|
|
return false;
|
|
}
|
|
|
|
if (entry.score < 0 || entry.score > MAX_SCORE) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Invalid score %d for entry %d in %s", entry.score, index, getFileName(file_path).c_str());
|
|
return false;
|
|
}
|
|
|
|
// Leer y validar tamaño del nombre
|
|
int name_size = 0;
|
|
if (SDL_ReadIO(file, &name_size, sizeof(int)) != sizeof(int)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Cannot read name size for entry %d in %s", index, getFileName(file_path).c_str());
|
|
return false;
|
|
}
|
|
|
|
if (name_size < 0 || name_size > MAX_NAME_SIZE) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Invalid name size %d for entry %d in %s", name_size, index, getFileName(file_path).c_str());
|
|
return false;
|
|
}
|
|
|
|
// Leer el nombre
|
|
std::vector<char> name_buffer(name_size + 1);
|
|
if (SDL_ReadIO(file, name_buffer.data(), name_size) != static_cast<size_t>(name_size)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Cannot read name for entry %d in %s", index, getFileName(file_path).c_str());
|
|
return false;
|
|
}
|
|
name_buffer[name_size] = '\0';
|
|
entry.name = std::string(name_buffer.data());
|
|
|
|
// Leer one_credit_complete
|
|
int occ_value = 0;
|
|
if (SDL_ReadIO(file, &occ_value, sizeof(int)) != sizeof(int)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Cannot read one_credit_complete for entry %d in %s", index, getFileName(file_path).c_str());
|
|
return false;
|
|
}
|
|
entry.one_credit_complete = (occ_value != 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
auto ManageHiScoreTable::verifyChecksum(SDL_IOStream* file, const std::string& file_path, const Table& temp_table) -> bool {
|
|
unsigned int stored_checksum = 0;
|
|
if (SDL_ReadIO(file, &stored_checksum, sizeof(unsigned int)) != sizeof(unsigned int)) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Cannot read checksum in %s", getFileName(file_path).c_str());
|
|
return false;
|
|
}
|
|
|
|
unsigned int calculated_checksum = calculateChecksum(temp_table);
|
|
if (stored_checksum != calculated_checksum) {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Checksum mismatch in %s (stored: 0x%08X, calculated: 0x%08X) - file is corrupted", getFileName(file_path).c_str(), stored_checksum, calculated_checksum);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Calcula checksum de la tabla
|
|
auto ManageHiScoreTable::calculateChecksum(const Table& table) -> unsigned int {
|
|
unsigned int checksum = 0x12345678; // Magic seed
|
|
|
|
for (const auto& entry : table) {
|
|
// Checksum del score
|
|
checksum = ((checksum << 5) + checksum) + static_cast<unsigned int>(entry.score);
|
|
|
|
// Checksum del nombre
|
|
for (char c : entry.name) {
|
|
checksum = ((checksum << 5) + checksum) + static_cast<unsigned int>(c);
|
|
}
|
|
|
|
// Checksum de one_credit_complete
|
|
checksum = ((checksum << 5) + checksum) + (entry.one_credit_complete ? 1U : 0U);
|
|
}
|
|
|
|
return checksum;
|
|
}
|
|
|
|
// Guarda la tabla en un fichero
|
|
auto ManageHiScoreTable::saveToFile(const std::string& file_path) -> bool {
|
|
auto success = true;
|
|
auto* file = SDL_IOFromFile(file_path.c_str(), "w+b");
|
|
|
|
if (file != nullptr) {
|
|
// Escribe magic number "CCAE"
|
|
constexpr std::array<char, 4> MAGIC = {'C', 'C', 'A', 'E'};
|
|
SDL_WriteIO(file, MAGIC.data(), 4);
|
|
|
|
// Escribe versión del formato
|
|
int version = FILE_VERSION;
|
|
SDL_WriteIO(file, &version, sizeof(int));
|
|
|
|
// Guarda el número de entradas en la tabla
|
|
int table_size = static_cast<int>(table_.size());
|
|
SDL_WriteIO(file, &table_size, sizeof(int));
|
|
|
|
// Guarda los datos de cada entrada
|
|
for (int i = 0; i < table_size; ++i) {
|
|
const HiScoreEntry& entry = table_.at(i);
|
|
|
|
// Guarda la puntuación
|
|
SDL_WriteIO(file, &entry.score, sizeof(int));
|
|
|
|
// Guarda el tamaño del nombre y luego el nombre
|
|
int name_size = static_cast<int>(entry.name.size());
|
|
SDL_WriteIO(file, &name_size, sizeof(int));
|
|
SDL_WriteIO(file, entry.name.c_str(), name_size);
|
|
|
|
// Guarda el valor de one_credit_complete como un entero (0 o 1)
|
|
int occ_value = entry.one_credit_complete ? 1 : 0;
|
|
SDL_WriteIO(file, &occ_value, sizeof(int));
|
|
}
|
|
|
|
// Calcula y escribe el checksum
|
|
unsigned int checksum = calculateChecksum(table_);
|
|
SDL_WriteIO(file, &checksum, sizeof(unsigned int));
|
|
|
|
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Writing file: %s", getFileName(file_path).c_str());
|
|
Logger::info("Writing file: " + getFileName(file_path));
|
|
SDL_CloseIO(file);
|
|
} else {
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Unable to save %s file! %s", getFileName(file_path).c_str(), SDL_GetError());
|
|
success = false;
|
|
}
|
|
return success;
|
|
} |