reestructuració
This commit is contained in:
178
source/core/resources/asset.cpp
Normal file
178
source/core/resources/asset.cpp
Normal 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;
|
||||
}
|
||||
60
source/core/resources/asset.h
Normal file
60
source/core/resources/asset.h
Normal 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);
|
||||
};
|
||||
218
source/core/resources/resource.cpp
Normal file
218
source/core/resources/resource.cpp
Normal 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;
|
||||
}
|
||||
53
source/core/resources/resource.h
Normal file
53
source/core/resources/resource.h
Normal 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_;
|
||||
};
|
||||
77
source/core/resources/resource_helper.cpp
Normal file
77
source/core/resources/resource_helper.cpp
Normal 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
|
||||
15
source/core/resources/resource_helper.h
Normal file
15
source/core/resources/resource_helper.h
Normal 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
|
||||
133
source/core/resources/resource_loader.cpp
Normal file
133
source/core/resources/resource_loader.cpp
Normal 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;
|
||||
}
|
||||
39
source/core/resources/resource_loader.h
Normal file
39
source/core/resources/resource_loader.h
Normal 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);
|
||||
};
|
||||
223
source/core/resources/resource_pack.cpp
Normal file
223
source/core/resources/resource_pack.cpp
Normal 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;
|
||||
}
|
||||
44
source/core/resources/resource_pack.h
Normal file
44
source/core/resources/resource_pack.h
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user