reestructuració

This commit is contained in:
2026-04-17 17:15:38 +02:00
parent 55caef3210
commit 5fec0110b3
66 changed files with 221 additions and 217 deletions

View File

@@ -0,0 +1,178 @@
#include "core/resources/asset.h"
#include <SDL3/SDL.h>
#include <stddef.h> // for size_t
#include <iostream> // for basic_ostream, operator<<, cout, endl
#include "core/resources/resource_helper.h"
// Constructor
Asset::Asset(std::string executablePath) {
this->executablePath = executablePath.substr(0, executablePath.find_last_of("\\/"));
longestName = 0;
verbose = true;
}
// Añade un elemento a la lista
void Asset::add(std::string file, enum assetType type, bool required, bool absolute) {
item_t temp;
temp.file = absolute ? file : executablePath + file;
temp.type = type;
temp.required = required;
fileList.push_back(temp);
const std::string filename = file.substr(file.find_last_of("\\/") + 1);
longestName = SDL_max(longestName, filename.size());
}
// Devuelve el fichero de un elemento de la lista a partir de una cadena
std::string Asset::get(std::string text) {
for (auto f : fileList) {
const size_t lastIndex = f.file.find_last_of("/") + 1;
const std::string file = f.file.substr(lastIndex, std::string::npos);
if (file == text) {
return f.file;
}
}
if (verbose) {
std::cout << "Warning: file " << text.c_str() << " not found" << std::endl;
}
return "";
}
// Comprueba que existen todos los elementos
bool Asset::check() {
bool success = true;
if (verbose) {
std::cout << "\n** Checking files" << std::endl;
std::cout << "Executable path is: " << executablePath << std::endl;
std::cout << "Sample filepath: " << fileList.back().file << std::endl;
}
// Comprueba la lista de ficheros clasificandolos por tipo
for (int type = 0; type < t_maxAssetType; ++type) {
// Comprueba si hay ficheros de ese tipo
bool any = false;
for (auto f : fileList) {
if ((f.required) && (f.type == type)) {
any = true;
}
}
// Si hay ficheros de ese tipo, comprueba si existen
if (any) {
if (verbose) {
std::cout << "\n>> " << getTypeName(type).c_str() << " FILES" << std::endl;
}
for (auto f : fileList) {
if ((f.required) && (f.type == type)) {
success &= checkFile(f.file);
}
}
}
}
// Resultado
if (verbose) {
if (success) {
std::cout << "\n** All files OK.\n"
<< std::endl;
} else {
std::cout << "\n** A file is missing. Exiting.\n"
<< std::endl;
}
}
return success;
}
// Comprueba que existe un fichero
bool Asset::checkFile(std::string path) {
bool success = false;
std::string result = "ERROR";
// Comprueba si existe el fichero (pack o filesystem)
const std::string filename = path.substr(path.find_last_of("\\/") + 1);
if (ResourceHelper::shouldUseResourcePack(path)) {
auto bytes = ResourceHelper::loadFile(path);
if (!bytes.empty()) {
result = "OK";
success = true;
}
} else {
SDL_IOStream *file = SDL_IOFromFile(path.c_str(), "rb");
if (file != nullptr) {
result = "OK";
success = true;
SDL_CloseIO(file);
}
}
if (verbose) {
std::cout.setf(std::ios::left, std::ios::adjustfield);
std::cout << "Checking file: ";
std::cout.width(longestName + 2);
std::cout.fill('.');
std::cout << filename + " ";
std::cout << " [" + result + "]" << std::endl;
}
return success;
}
// Devuelve el nombre del tipo de recurso
std::string Asset::getTypeName(int type) {
switch (type) {
case t_bitmap:
return "BITMAP";
break;
case t_music:
return "MUSIC";
break;
case t_sound:
return "SOUND";
break;
case t_font:
return "FONT";
break;
case t_lang:
return "LANG";
break;
case t_data:
return "DATA";
break;
case t_room:
return "ROOM";
break;
case t_enemy:
return "ENEMY";
break;
case t_item:
return "ITEM";
break;
default:
return "ERROR";
break;
}
}
// Establece si ha de mostrar texto por pantalla
void Asset::setVerbose(bool value) {
verbose = value;
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include <string> // for string, basic_string
#include <vector> // for vector
enum assetType {
t_bitmap,
t_music,
t_sound,
t_font,
t_lang,
t_data,
t_room,
t_enemy,
t_item,
t_maxAssetType
};
// Clase Asset
class Asset {
public:
// Estructura para definir un item
struct item_t {
std::string file; // Ruta del fichero desde la raiz del directorio
enum assetType type; // Indica el tipo de recurso
bool required; // Indica si es un fichero que debe de existir
};
private:
// Variables
int longestName; // Contiene la longitud del nombre de fichero mas largo
std::vector<item_t> fileList; // Listado con todas las rutas a los ficheros
std::string executablePath; // Ruta al ejecutable
bool verbose; // Indica si ha de mostrar información por pantalla
// Comprueba que existe un fichero
bool checkFile(std::string executablePath);
// Devuelve el nombre del tipo de recurso
std::string getTypeName(int type);
public:
// Constructor
Asset(std::string path);
// Añade un elemento a la lista
void add(std::string file, enum assetType type, bool required = true, bool absolute = false);
// Devuelve un elemento de la lista a partir de una cadena
std::string get(std::string text);
// Devuelve toda la lista de items registrados
const std::vector<item_t> &getAll() const { return fileList; }
// Comprueba que existen todos los elementos
bool check();
// Establece si ha de mostrar texto por pantalla
void setVerbose(bool value);
};

View File

@@ -0,0 +1,218 @@
#include "core/resources/resource.h"
#include <algorithm>
#include <filesystem>
#include <iostream>
#include <sstream>
#include "core/audio/jail_audio.hpp"
#include "core/rendering/text.h"
#include "core/rendering/texture.h"
#include "core/resources/asset.h"
#include "core/resources/resource_helper.h"
#include "game/ui/menu.h"
Resource *Resource::instance_ = nullptr;
static std::string basename(const std::string &path) {
return path.substr(path.find_last_of("\\/") + 1);
}
static std::string stem(const std::string &path) {
std::string b = basename(path);
size_t dot = b.find_last_of('.');
if (dot == std::string::npos) return b;
return b.substr(0, dot);
}
void Resource::init(SDL_Renderer *renderer, Asset *asset, Input *input) {
if (instance_ == nullptr) {
instance_ = new Resource(renderer, asset, input);
instance_->preloadAll();
}
}
void Resource::destroy() {
delete instance_;
instance_ = nullptr;
}
Resource *Resource::get() {
return instance_;
}
Resource::Resource(SDL_Renderer *renderer, Asset *asset, Input *input)
: renderer_(renderer),
asset_(asset),
input_(input) {}
Resource::~Resource() {
for (auto &[name, m] : menus_) delete m;
menus_.clear();
for (auto &[name, t] : texts_) delete t;
texts_.clear();
for (auto &[name, t] : textures_) delete t;
textures_.clear();
for (auto &[name, s] : sounds_) JA_DeleteSound(s);
sounds_.clear();
for (auto &[name, m] : musics_) JA_DeleteMusic(m);
musics_.clear();
}
void Resource::preloadAll() {
const auto &items = asset_->getAll();
// Pass 1: texturas, sonidos, músicas, animaciones (raw lines), demo, lenguajes
for (const auto &it : items) {
if (!ResourceHelper::shouldUseResourcePack(it.file) && it.type != t_lang) {
// Ficheros absolutos (config.txt, score.bin, systemFolder) — no se precargan
continue;
}
auto bytes = ResourceHelper::loadFile(it.file);
if (bytes.empty()) continue;
const std::string bname = basename(it.file);
switch (it.type) {
case t_bitmap: {
auto *tex = new Texture(renderer_, bytes);
textures_[bname] = tex;
break;
}
case t_sound: {
JA_Sound_t *s = JA_LoadSound(bytes.data(), (uint32_t)bytes.size());
if (s) sounds_[bname] = s;
break;
}
case t_music: {
JA_Music_t *m = JA_LoadMusic(bytes.data(), (Uint32)bytes.size());
if (m) musics_[bname] = m;
break;
}
case t_data: {
if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".ani") {
std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
std::stringstream ss(content);
std::vector<std::string> lines;
std::string line;
while (std::getline(ss, line)) {
lines.push_back(line);
}
animationLines_[bname] = std::move(lines);
} else if (bname == "demo.bin") {
demoBytes_ = bytes;
} else if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".men") {
// Menús: se construyen en pass 2 porque dependen de textos y sonidos
}
break;
}
case t_font:
// Fonts: se emparejan en pass 2
break;
case t_lang:
// Lenguaje: lo sigue leyendo la clase Lang a través de ResourceHelper
break;
default:
break;
}
}
// Pass 2: Text (fuentes emparejadas png+txt) y Menus (dependen de Text+sonidos)
// Fuentes: construimos un Text por cada par basename.png + basename.txt
// Acumulamos los bytes encontrados por stem (basename sin ext.)
std::unordered_map<std::string, std::vector<uint8_t>> fontPngs;
std::unordered_map<std::string, std::vector<uint8_t>> fontTxts;
for (const auto &it : items) {
if (it.type != t_font) continue;
auto bytes = ResourceHelper::loadFile(it.file);
if (bytes.empty()) continue;
const std::string s = stem(it.file);
const std::string bname = basename(it.file);
if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".png") {
fontPngs[s] = std::move(bytes);
} else if (bname.size() >= 4 && bname.substr(bname.size() - 4) == ".txt") {
fontTxts[s] = std::move(bytes);
}
}
for (const auto &[s, png] : fontPngs) {
auto itTxt = fontTxts.find(s);
if (itTxt == fontTxts.end()) continue;
Text *t = new Text(png, itTxt->second, renderer_);
texts_[s] = t;
}
// Menus: usan aún Menu::loadFromBytes que internamente llama a asset->get() y
// Text/JA_LoadSound por path. Funciona en modo fallback; en pack estricto
// requiere que Menu se adapte a cargar desde ResourceHelper. Por ahora
// lo dejamos así y será una migración del paso 7.
for (const auto &it : items) {
if (it.type != t_data) continue;
const std::string bname = basename(it.file);
if (bname.size() < 4 || bname.substr(bname.size() - 4) != ".men") continue;
auto bytes = ResourceHelper::loadFile(it.file);
if (bytes.empty()) continue;
Menu *m = new Menu(renderer_, asset_, input_, "");
m->loadFromBytes(bytes, bname);
const std::string s = stem(it.file);
menus_[s] = m;
}
}
Texture *Resource::getTexture(const std::string &name) {
auto it = textures_.find(name);
if (it == textures_.end()) {
std::cerr << "Resource::getTexture: missing " << name << '\n';
return nullptr;
}
return it->second;
}
JA_Sound_t *Resource::getSound(const std::string &name) {
auto it = sounds_.find(name);
if (it == sounds_.end()) {
std::cerr << "Resource::getSound: missing " << name << '\n';
return nullptr;
}
return it->second;
}
JA_Music_t *Resource::getMusic(const std::string &name) {
auto it = musics_.find(name);
if (it == musics_.end()) {
std::cerr << "Resource::getMusic: missing " << name << '\n';
return nullptr;
}
return it->second;
}
std::vector<std::string> &Resource::getAnimationLines(const std::string &name) {
auto it = animationLines_.find(name);
if (it == animationLines_.end()) {
static std::vector<std::string> empty;
std::cerr << "Resource::getAnimationLines: missing " << name << '\n';
return empty;
}
return it->second;
}
Text *Resource::getText(const std::string &name) {
auto it = texts_.find(name);
if (it == texts_.end()) {
std::cerr << "Resource::getText: missing " << name << '\n';
return nullptr;
}
return it->second;
}
Menu *Resource::getMenu(const std::string &name) {
auto it = menus_.find(name);
if (it == menus_.end()) {
std::cerr << "Resource::getMenu: missing " << name << '\n';
return nullptr;
}
return it->second;
}

View File

@@ -0,0 +1,53 @@
#pragma once
#include <SDL3/SDL.h>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
class Asset;
class Input;
class Menu;
class Text;
class Texture;
struct JA_Music_t;
struct JA_Sound_t;
// Precarga y posee todos los recursos del juego durante toda la vida de la app.
// Singleton inicializado desde Director; las escenas consultan handles via get*().
class Resource {
public:
static void init(SDL_Renderer *renderer, Asset *asset, Input *input);
static void destroy();
static Resource *get();
Texture *getTexture(const std::string &name);
JA_Sound_t *getSound(const std::string &name);
JA_Music_t *getMusic(const std::string &name);
std::vector<std::string> &getAnimationLines(const std::string &name);
Text *getText(const std::string &name); // name sin extensión: "smb2", "nokia2", ...
Menu *getMenu(const std::string &name); // name sin extensión: "title", "options", ...
const std::vector<uint8_t> &getDemoBytes() const { return demoBytes_; }
private:
Resource(SDL_Renderer *renderer, Asset *asset, Input *input);
~Resource();
void preloadAll();
SDL_Renderer *renderer_;
Asset *asset_;
Input *input_;
std::unordered_map<std::string, Texture *> textures_;
std::unordered_map<std::string, JA_Sound_t *> sounds_;
std::unordered_map<std::string, JA_Music_t *> musics_;
std::unordered_map<std::string, std::vector<std::string>> animationLines_;
std::unordered_map<std::string, Text *> texts_;
std::unordered_map<std::string, Menu *> menus_;
std::vector<uint8_t> demoBytes_;
static Resource *instance_;
};

View File

@@ -0,0 +1,77 @@
#include "core/resources/resource_helper.h"
#include <algorithm>
#include <cstddef>
#include <fstream>
#include <iostream>
#include "core/resources/resource_loader.h"
namespace ResourceHelper {
static bool resource_system_initialized = false;
bool initializeResourceSystem(const std::string& pack_file, bool enable_fallback) {
auto& loader = ResourceLoader::getInstance();
bool ok = loader.initialize(pack_file, enable_fallback);
resource_system_initialized = ok;
if (ok && loader.getLoadedResourceCount() > 0) {
std::cout << "Resource system initialized with pack: " << pack_file << '\n';
} else if (ok) {
std::cout << "Resource system using fallback mode (filesystem only)" << '\n';
}
return ok;
}
void shutdownResourceSystem() {
if (resource_system_initialized) {
ResourceLoader::getInstance().shutdown();
resource_system_initialized = false;
}
}
std::vector<uint8_t> loadFile(const std::string& filepath) {
if (resource_system_initialized && shouldUseResourcePack(filepath)) {
auto& loader = ResourceLoader::getInstance();
std::string pack_path = getPackPath(filepath);
auto data = loader.loadResource(pack_path);
if (!data.empty()) {
return data;
}
}
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
return {};
}
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(file_size);
if (!file.read(reinterpret_cast<char*>(data.data()), file_size)) {
return {};
}
return data;
}
bool shouldUseResourcePack(const std::string& filepath) {
// Solo entran al pack los ficheros dentro de data/
return filepath.find("data/") != std::string::npos;
}
std::string getPackPath(const std::string& asset_path) {
std::string pack_path = asset_path;
std::replace(pack_path.begin(), pack_path.end(), '\\', '/');
// Toma la última aparición de "data/" como prefijo a quitar
size_t last_data = pack_path.rfind("data/");
if (last_data != std::string::npos) {
pack_path = pack_path.substr(last_data + 5);
}
return pack_path;
}
} // namespace ResourceHelper

View File

@@ -0,0 +1,15 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace ResourceHelper {
bool initializeResourceSystem(const std::string& pack_file = "resources.pack", bool enable_fallback = true);
void shutdownResourceSystem();
std::vector<uint8_t> loadFile(const std::string& filepath);
bool shouldUseResourcePack(const std::string& filepath);
std::string getPackPath(const std::string& asset_path);
} // namespace ResourceHelper

View File

@@ -0,0 +1,133 @@
#include "core/resources/resource_loader.h"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include "core/resources/resource_pack.h"
std::unique_ptr<ResourceLoader> ResourceLoader::instance = nullptr;
ResourceLoader::ResourceLoader()
: resource_pack_(nullptr),
fallback_to_files_(true) {}
ResourceLoader& ResourceLoader::getInstance() {
if (!instance) {
instance = std::unique_ptr<ResourceLoader>(new ResourceLoader());
}
return *instance;
}
ResourceLoader::~ResourceLoader() {
shutdown();
}
bool ResourceLoader::initialize(const std::string& pack_file, bool enable_fallback) {
shutdown();
fallback_to_files_ = enable_fallback;
pack_path_ = pack_file;
if (std::filesystem::exists(pack_file)) {
resource_pack_ = new ResourcePack();
if (resource_pack_->loadPack(pack_file)) {
return true;
}
delete resource_pack_;
resource_pack_ = nullptr;
std::cerr << "Failed to load resource pack: " << pack_file << '\n';
}
if (fallback_to_files_) {
return true;
}
std::cerr << "Resource pack not found and fallback disabled: " << pack_file << '\n';
return false;
}
void ResourceLoader::shutdown() {
if (resource_pack_ != nullptr) {
delete resource_pack_;
resource_pack_ = nullptr;
}
}
std::vector<uint8_t> ResourceLoader::loadResource(const std::string& filename) {
if ((resource_pack_ != nullptr) && resource_pack_->hasResource(filename)) {
return resource_pack_->getResource(filename);
}
if (fallback_to_files_) {
return loadFromFile(filename);
}
std::cerr << "Resource not found: " << filename << '\n';
return {};
}
bool ResourceLoader::resourceExists(const std::string& filename) {
if ((resource_pack_ != nullptr) && resource_pack_->hasResource(filename)) {
return true;
}
if (fallback_to_files_) {
std::string full_path = getDataPath(filename);
return std::filesystem::exists(full_path);
}
return false;
}
std::vector<uint8_t> ResourceLoader::loadFromFile(const std::string& filename) {
std::string full_path = getDataPath(filename);
std::ifstream file(full_path, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Error: Could not open file: " << full_path << '\n';
return {};
}
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(file_size);
if (!file.read(reinterpret_cast<char*>(data.data()), file_size)) {
std::cerr << "Error: Could not read file: " << full_path << '\n';
return {};
}
return data;
}
std::string ResourceLoader::getDataPath(const std::string& filename) {
return "data/" + filename;
}
size_t ResourceLoader::getLoadedResourceCount() const {
if (resource_pack_ != nullptr) {
return resource_pack_->getResourceCount();
}
return 0;
}
std::vector<std::string> ResourceLoader::getAvailableResources() const {
if (resource_pack_ != nullptr) {
return resource_pack_->getResourceList();
}
std::vector<std::string> result;
if (fallback_to_files_ && std::filesystem::exists("data")) {
for (const auto& entry : std::filesystem::recursive_directory_iterator("data")) {
if (entry.is_regular_file()) {
std::string filename = std::filesystem::relative(entry.path(), "data").string();
std::replace(filename.begin(), filename.end(), '\\', '/');
result.push_back(filename);
}
}
}
return result;
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
class ResourcePack;
class ResourceLoader {
private:
static std::unique_ptr<ResourceLoader> instance;
ResourcePack* resource_pack_;
std::string pack_path_;
bool fallback_to_files_;
ResourceLoader();
public:
static ResourceLoader& getInstance();
~ResourceLoader();
bool initialize(const std::string& pack_file, bool enable_fallback = true);
void shutdown();
std::vector<uint8_t> loadResource(const std::string& filename);
bool resourceExists(const std::string& filename);
void setFallbackToFiles(bool enable) { fallback_to_files_ = enable; }
bool getFallbackToFiles() const { return fallback_to_files_; }
size_t getLoadedResourceCount() const;
std::vector<std::string> getAvailableResources() const;
private:
static std::vector<uint8_t> loadFromFile(const std::string& filename);
static std::string getDataPath(const std::string& filename);
};

View File

@@ -0,0 +1,223 @@
#include "core/resources/resource_pack.h"
#include <algorithm>
#include <array>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <utility>
const std::string ResourcePack::DEFAULT_ENCRYPT_KEY = "CCRS_RESOURCES__2026";
ResourcePack::ResourcePack()
: loaded_(false) {}
ResourcePack::~ResourcePack() {
clear();
}
uint32_t ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) {
uint32_t checksum = 0x12345678;
for (unsigned char i : data) {
checksum = ((checksum << 5) + checksum) + i;
}
return checksum;
}
void ResourcePack::encryptData(std::vector<uint8_t>& data, const std::string& key) {
if (key.empty()) return;
for (size_t i = 0; i < data.size(); ++i) {
data[i] ^= key[i % key.length()];
}
}
void ResourcePack::decryptData(std::vector<uint8_t>& data, const std::string& key) {
encryptData(data, key);
}
bool ResourcePack::loadPack(const std::string& pack_file) {
std::ifstream file(pack_file, std::ios::binary);
if (!file) {
std::cerr << "Error: Could not open pack file: " << pack_file << '\n';
return false;
}
std::array<char, 4> header;
file.read(header.data(), 4);
if (std::string(header.data(), 4) != "CCRS") {
std::cerr << "Error: Invalid pack file format" << '\n';
return false;
}
uint32_t version;
file.read(reinterpret_cast<char*>(&version), sizeof(version));
if (version != 1) {
std::cerr << "Error: Unsupported pack version: " << version << '\n';
return false;
}
uint32_t resource_count;
file.read(reinterpret_cast<char*>(&resource_count), sizeof(resource_count));
resources_.clear();
resources_.reserve(resource_count);
for (uint32_t i = 0; i < resource_count; ++i) {
uint32_t filename_length;
file.read(reinterpret_cast<char*>(&filename_length), sizeof(filename_length));
std::string filename(filename_length, '\0');
file.read(filename.data(), filename_length);
ResourceEntry entry;
entry.filename = filename;
file.read(reinterpret_cast<char*>(&entry.offset), sizeof(entry.offset));
file.read(reinterpret_cast<char*>(&entry.size), sizeof(entry.size));
file.read(reinterpret_cast<char*>(&entry.checksum), sizeof(entry.checksum));
resources_[filename] = entry;
}
uint64_t data_size;
file.read(reinterpret_cast<char*>(&data_size), sizeof(data_size));
data_.resize(data_size);
file.read(reinterpret_cast<char*>(data_.data()), data_size);
decryptData(data_, DEFAULT_ENCRYPT_KEY);
loaded_ = true;
return true;
}
bool ResourcePack::savePack(const std::string& pack_file) {
std::ofstream file(pack_file, std::ios::binary);
if (!file) {
std::cerr << "Error: Could not create pack file: " << pack_file << '\n';
return false;
}
file.write("CCRS", 4);
uint32_t version = 1;
file.write(reinterpret_cast<const char*>(&version), sizeof(version));
uint32_t resource_count = static_cast<uint32_t>(resources_.size());
file.write(reinterpret_cast<const char*>(&resource_count), sizeof(resource_count));
for (const auto& [filename, entry] : resources_) {
uint32_t filename_length = static_cast<uint32_t>(filename.length());
file.write(reinterpret_cast<const char*>(&filename_length), sizeof(filename_length));
file.write(filename.c_str(), filename_length);
file.write(reinterpret_cast<const char*>(&entry.offset), sizeof(entry.offset));
file.write(reinterpret_cast<const char*>(&entry.size), sizeof(entry.size));
file.write(reinterpret_cast<const char*>(&entry.checksum), sizeof(entry.checksum));
}
std::vector<uint8_t> encrypted_data = data_;
encryptData(encrypted_data, DEFAULT_ENCRYPT_KEY);
uint64_t data_size = encrypted_data.size();
file.write(reinterpret_cast<const char*>(&data_size), sizeof(data_size));
file.write(reinterpret_cast<const char*>(encrypted_data.data()), data_size);
return true;
}
bool ResourcePack::addFile(const std::string& filename, const std::string& filepath) {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Error: Could not open file: " << filepath << '\n';
return false;
}
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> file_data(file_size);
if (!file.read(reinterpret_cast<char*>(file_data.data()), file_size)) {
std::cerr << "Error: Could not read file: " << filepath << '\n';
return false;
}
ResourceEntry entry;
entry.filename = filename;
entry.offset = data_.size();
entry.size = file_data.size();
entry.checksum = calculateChecksum(file_data);
data_.insert(data_.end(), file_data.begin(), file_data.end());
resources_[filename] = entry;
return true;
}
bool ResourcePack::addDirectory(const std::string& directory) {
if (!std::filesystem::exists(directory)) {
std::cerr << "Error: Directory does not exist: " << directory << '\n';
return false;
}
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
if (entry.is_regular_file()) {
std::string filepath = entry.path().string();
std::string filename = std::filesystem::relative(entry.path(), directory).string();
std::replace(filename.begin(), filename.end(), '\\', '/');
if (!addFile(filename, filepath)) {
return false;
}
}
}
return true;
}
std::vector<uint8_t> ResourcePack::getResource(const std::string& filename) {
auto it = resources_.find(filename);
if (it == resources_.end()) {
std::cerr << "Error: Resource not found: " << filename << '\n';
return {};
}
const ResourceEntry& entry = it->second;
if (entry.offset + entry.size > data_.size()) {
std::cerr << "Error: Invalid resource data: " << filename << '\n';
return {};
}
std::vector<uint8_t> result(data_.begin() + entry.offset,
data_.begin() + entry.offset + entry.size);
uint32_t checksum = calculateChecksum(result);
if (checksum != entry.checksum) {
std::cerr << "Warning: Checksum mismatch for resource: " << filename << '\n';
}
return result;
}
bool ResourcePack::hasResource(const std::string& filename) const {
return resources_.find(filename) != resources_.end();
}
void ResourcePack::clear() {
resources_.clear();
data_.clear();
loaded_ = false;
}
size_t ResourcePack::getResourceCount() const {
return resources_.size();
}
std::vector<std::string> ResourcePack::getResourceList() const {
std::vector<std::string> result;
result.reserve(resources_.size());
for (const auto& [filename, entry] : resources_) {
result.push_back(filename);
}
return result;
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
struct ResourceEntry {
std::string filename;
uint64_t offset;
uint64_t size;
uint32_t checksum;
};
class ResourcePack {
private:
std::unordered_map<std::string, ResourceEntry> resources_;
std::vector<uint8_t> data_;
bool loaded_;
static uint32_t calculateChecksum(const std::vector<uint8_t>& data);
static void encryptData(std::vector<uint8_t>& data, const std::string& key);
static void decryptData(std::vector<uint8_t>& data, const std::string& key);
public:
ResourcePack();
~ResourcePack();
bool loadPack(const std::string& pack_file);
bool savePack(const std::string& pack_file);
bool addFile(const std::string& filename, const std::string& filepath);
bool addDirectory(const std::string& directory);
std::vector<uint8_t> getResource(const std::string& filename);
bool hasResource(const std::string& filename) const;
void clear();
size_t getResourceCount() const;
std::vector<std::string> getResourceList() const;
static const std::string DEFAULT_ENCRYPT_KEY;
};