#include "manage_hiscore_table.hpp" #include // Para SDL_ReadIO, SDL_WriteIO, SDL_CloseIO, SDL_GetError, SDL_IOFromFile, SDL_LogError, SDL_LogCategory, SDL_LogInfo #include // Para __sort_fn, sort #include // Para array #include // Para identity #include // Para std::setw, std::setfill #include // Para std::cout #include // Para distance #include // Para __find_if_fn, find_if #include // Para move #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) { std::cout << "Error: Unable to load " << getFileName(file_path) << " file! " << SDL_GetError() << '\n'; 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); } else { std::cout << "File " << getFileName(file_path) << " is corrupted - loading default values" << '\n'; clear(); } return success; } // Métodos auxiliares privados para loadFromFile auto ManageHiScoreTable::validateMagicNumber(SDL_IOStream* file, const std::string& file_path) -> bool { std::array magic; if (SDL_ReadIO(file, magic.data(), 4) != 4 || magic[0] != 'C' || magic[1] != 'C' || magic[2] != 'A' || magic[3] != 'E') { std::cout << "Error: Invalid magic number in " << getFileName(file_path) << " - file may be corrupted or old format" << '\n'; 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)) { std::cout << "Error: Cannot read version in " << getFileName(file_path) << '\n'; return false; } if (version != FILE_VERSION) { std::cout << "Error: Unsupported file version " << version << " in " << getFileName(file_path) << " (expected " << FILE_VERSION << ")" << '\n'; 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)) { std::cout << "Error: Cannot read table size in " << getFileName(file_path) << '\n'; return false; } if (table_size < 0 || table_size > MAX_TABLE_SIZE) { std::cout << "Error: Invalid table size " << table_size << " in " << getFileName(file_path) << " (expected 0-" << MAX_TABLE_SIZE << ")" << '\n'; 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)) { std::cout << "Error: Cannot read score for entry " << index << " in " << getFileName(file_path) << '\n'; return false; } if (entry.score < 0 || entry.score > MAX_SCORE) { std::cout << "Error: Invalid score " << entry.score << " for entry " << index << " in " << getFileName(file_path) << '\n'; return false; } // Leer y validar tamaño del nombre int name_size = 0; if (SDL_ReadIO(file, &name_size, sizeof(int)) != sizeof(int)) { std::cout << "Error: Cannot read name size for entry " << index << " in " << getFileName(file_path) << '\n'; return false; } if (name_size < 0 || name_size > MAX_NAME_SIZE) { std::cout << "Error: Invalid name size " << name_size << " for entry " << index << " in " << getFileName(file_path) << '\n'; return false; } // Leer el nombre std::vector name_buffer(name_size + 1); if (SDL_ReadIO(file, name_buffer.data(), name_size) != static_cast(name_size)) { std::cout << "Error: Cannot read name for entry " << index << " in " << getFileName(file_path) << '\n'; 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)) { std::cout << "Error: Cannot read one_credit_complete for entry " << index << " in " << getFileName(file_path) << '\n'; 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)) { std::cout << "Error: Cannot read checksum in " << getFileName(file_path) << '\n'; return false; } unsigned int calculated_checksum = calculateChecksum(temp_table); if (stored_checksum != calculated_checksum) { std::cout << "Error: Checksum mismatch in " << getFileName(file_path) << " (stored: 0x" << std::hex << std::setw(8) << std::setfill('0') << stored_checksum << ", calculated: 0x" << std::setw(8) << std::setfill('0') << calculated_checksum << std::dec << ") - file is corrupted" << '\n'; 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(entry.score); // Checksum del nombre for (char c : entry.name) { checksum = ((checksum << 5) + checksum) + static_cast(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 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(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(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_CloseIO(file); } else { std::cout << "Error: Unable to save " << getFileName(file_path) << " file! " << SDL_GetError() << '\n'; success = false; } return success; }