forked from jaildesigner-jailgames/jaildoctors_dilemma
actualitzada la carpeta release a SDL3
migrat a resources.pack
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
#include "core/resources/asset.hpp" // Para Asset, AssetType
|
||||
#include "core/resources/resource.hpp" // Para Resource
|
||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
||||
#include "game/options.hpp" // Para Options, options, OptionsVideo, Border
|
||||
#include "game/ui/notifier.hpp" // Para Notifier
|
||||
|
||||
@@ -385,21 +386,8 @@ auto Screen::getRendererSurface() -> std::shared_ptr<Surface> { return (*rendere
|
||||
auto Screen::getBorderSurface() -> std::shared_ptr<Surface> { return border_surface_; }
|
||||
|
||||
auto loadData(const std::string& filepath) -> std::vector<uint8_t> {
|
||||
// Fallback a filesystem
|
||||
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;
|
||||
// Load using ResourceHelper (supports both filesystem and pack)
|
||||
return jdd::ResourceHelper::loadFile(filepath);
|
||||
}
|
||||
|
||||
// Carga el contenido de los archivos GLSL
|
||||
|
||||
@@ -16,24 +16,16 @@
|
||||
|
||||
#include "core/rendering/gif.hpp" // Para Gif
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
||||
|
||||
// Carga una paleta desde un archivo .gif
|
||||
auto loadPalette(const std::string& file_path) -> Palette {
|
||||
// Abrir el archivo en modo binario
|
||||
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
// Load file using ResourceHelper (supports both filesystem and pack)
|
||||
auto buffer = jdd::ResourceHelper::loadFile(file_path);
|
||||
if (buffer.empty()) {
|
||||
throw std::runtime_error("Error opening file: " + file_path);
|
||||
}
|
||||
|
||||
// Obtener el tamaño del archivo y leerlo en un buffer
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<Uint8> buffer(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
throw std::runtime_error("Error reading file: " + file_path);
|
||||
}
|
||||
|
||||
// Cargar la paleta usando los datos del buffer
|
||||
std::vector<uint32_t> pal = GIF::Gif::loadPalette(buffer.data());
|
||||
if (pal.empty()) {
|
||||
@@ -55,16 +47,21 @@ auto readPalFile(const std::string& file_path) -> Palette {
|
||||
Palette palette{};
|
||||
palette.fill(0); // Inicializar todo con 0 (transparente por defecto)
|
||||
|
||||
std::ifstream file(file_path);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("No se pudo abrir el archivo .pal");
|
||||
// Load file using ResourceHelper (supports both filesystem and pack)
|
||||
auto file_data = jdd::ResourceHelper::loadFile(file_path);
|
||||
if (file_data.empty()) {
|
||||
throw std::runtime_error("No se pudo abrir el archivo .pal: " + file_path);
|
||||
}
|
||||
|
||||
// Convert bytes to string for parsing
|
||||
std::string content(file_data.begin(), file_data.end());
|
||||
std::istringstream stream(content);
|
||||
|
||||
std::string line;
|
||||
int line_number = 0;
|
||||
int color_index = 0;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
while (std::getline(stream, line)) {
|
||||
++line_number;
|
||||
|
||||
// Ignorar las tres primeras líneas del archivo
|
||||
@@ -89,7 +86,7 @@ auto readPalFile(const std::string& file_path) -> Palette {
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
|
||||
return palette;
|
||||
}
|
||||
|
||||
@@ -108,24 +105,13 @@ Surface::Surface(const std::string& file_path)
|
||||
|
||||
// Carga una superficie desde un archivo
|
||||
auto Surface::loadSurface(const std::string& file_path) -> SurfaceData {
|
||||
// Abrir el archivo usando std::ifstream para manejo automático del recurso
|
||||
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
// Load file using ResourceHelper (supports both filesystem and pack)
|
||||
std::vector<Uint8> buffer = jdd::ResourceHelper::loadFile(file_path);
|
||||
if (buffer.empty()) {
|
||||
std::cerr << "Error opening file: " << file_path << '\n';
|
||||
throw std::runtime_error("Error opening file");
|
||||
}
|
||||
|
||||
// Obtener el tamaño del archivo
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
// Leer el contenido del archivo en un buffer
|
||||
std::vector<Uint8> buffer(size);
|
||||
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||
std::cerr << "Error reading file: " << file_path << '\n';
|
||||
throw std::runtime_error("Error reading file");
|
||||
}
|
||||
|
||||
// Crear un objeto Gif y llamar a la función loadGif
|
||||
Uint16 w = 0;
|
||||
Uint16 h = 0;
|
||||
|
||||
@@ -9,21 +9,27 @@
|
||||
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/resources/resource.hpp" // Para Resource
|
||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
||||
#include "utils/utils.hpp" // Para printWithDots
|
||||
|
||||
// Carga las animaciones en un vector(Animations) desde un fichero
|
||||
auto loadAnimationsFromFile(const std::string& file_path) -> Animations {
|
||||
std::ifstream file(file_path);
|
||||
if (!file) {
|
||||
// Load file using ResourceHelper (supports both filesystem and pack)
|
||||
auto file_data = jdd::ResourceHelper::loadFile(file_path);
|
||||
if (file_data.empty()) {
|
||||
std::cerr << "Error: Fichero no encontrado " << file_path << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + file_path);
|
||||
}
|
||||
|
||||
printWithDots("Animation : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
|
||||
|
||||
// Convert bytes to string and parse
|
||||
std::string content(file_data.begin(), file_data.end());
|
||||
std::istringstream stream(content);
|
||||
|
||||
std::vector<std::string> buffer;
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
while (std::getline(stream, line)) {
|
||||
if (!line.empty()) {
|
||||
buffer.push_back(line);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
#include <cstddef> // Para size_t
|
||||
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream
|
||||
#include <iostream> // Para cerr
|
||||
#include <sstream> // Para istringstream
|
||||
#include <stdexcept> // Para runtime_error
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/surface_sprite.hpp" // Para SSprite
|
||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
||||
#include "utils/utils.hpp" // Para getFileName, stringToColor, printWithDots
|
||||
|
||||
// Llena una estructuta TextFile desde un fichero
|
||||
@@ -25,46 +27,43 @@ auto loadTextFile(const std::string& file_path) -> std::shared_ptr<TextFile> {
|
||||
tf->box_height = 0;
|
||||
}
|
||||
|
||||
// Abre el fichero para leer los valores
|
||||
std::ifstream file(file_path);
|
||||
|
||||
if (file.is_open() && file.good()) {
|
||||
std::string buffer;
|
||||
|
||||
// Lee los dos primeros valores del fichero
|
||||
std::getline(file, buffer);
|
||||
std::getline(file, buffer);
|
||||
tf->box_width = std::stoi(buffer);
|
||||
|
||||
std::getline(file, buffer);
|
||||
std::getline(file, buffer);
|
||||
tf->box_height = std::stoi(buffer);
|
||||
|
||||
// lee el resto de datos del fichero
|
||||
auto index = 32;
|
||||
auto line_read = 0;
|
||||
while (std::getline(file, buffer)) {
|
||||
// Almacena solo las lineas impares
|
||||
if (line_read % 2 == 1) {
|
||||
tf->offset[index++].w = std::stoi(buffer);
|
||||
}
|
||||
|
||||
// Limpia el buffer
|
||||
buffer.clear();
|
||||
line_read++;
|
||||
};
|
||||
|
||||
// Cierra el fichero
|
||||
printWithDots("Text File : ", getFileName(file_path), "[ LOADED ]");
|
||||
file.close();
|
||||
}
|
||||
|
||||
// El fichero no se puede abrir
|
||||
else {
|
||||
// Load file using ResourceHelper (supports both filesystem and pack)
|
||||
auto file_data = jdd::ResourceHelper::loadFile(file_path);
|
||||
if (file_data.empty()) {
|
||||
std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
|
||||
}
|
||||
|
||||
// Convert bytes to string and parse
|
||||
std::string content(file_data.begin(), file_data.end());
|
||||
std::istringstream stream(content);
|
||||
std::string buffer;
|
||||
|
||||
// Lee los dos primeros valores del fichero
|
||||
std::getline(stream, buffer);
|
||||
std::getline(stream, buffer);
|
||||
tf->box_width = std::stoi(buffer);
|
||||
|
||||
std::getline(stream, buffer);
|
||||
std::getline(stream, buffer);
|
||||
tf->box_height = std::stoi(buffer);
|
||||
|
||||
// lee el resto de datos del fichero
|
||||
auto index = 32;
|
||||
auto line_read = 0;
|
||||
while (std::getline(stream, buffer)) {
|
||||
// Almacena solo las lineas impares
|
||||
if (line_read % 2 == 1) {
|
||||
tf->offset[index++].w = std::stoi(buffer);
|
||||
}
|
||||
|
||||
// Limpia el buffer
|
||||
buffer.clear();
|
||||
line_read++;
|
||||
};
|
||||
|
||||
printWithDots("Text File : ", getFileName(file_path), "[ LOADED ]");
|
||||
|
||||
// Establece las coordenadas para cada caracter ascii de la cadena y su ancho
|
||||
for (int i = 32; i < 128; ++i) {
|
||||
tf->offset[i].x = ((i - 32) % 15) * tf->box_width;
|
||||
|
||||
@@ -58,10 +58,22 @@ void Asset::loadFromFile(const std::string& config_file_path, const std::string&
|
||||
return;
|
||||
}
|
||||
|
||||
// Read entire file into string
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
file.close();
|
||||
|
||||
// Parse using loadFromString
|
||||
loadFromString(buffer.str(), prefix, system_folder);
|
||||
}
|
||||
|
||||
// Carga recursos desde un string de configuración (para release con pack)
|
||||
void Asset::loadFromString(const std::string& config_content, const std::string& prefix, const std::string& system_folder) {
|
||||
std::istringstream stream(config_content);
|
||||
std::string line;
|
||||
int line_number = 0;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
while (std::getline(stream, line)) {
|
||||
++line_number;
|
||||
|
||||
// Limpiar espacios en blanco al principio y final
|
||||
@@ -85,7 +97,7 @@ void Asset::loadFromFile(const std::string& config_file_path, const std::string&
|
||||
// Verificar que tenemos al menos tipo y ruta
|
||||
if (parts.size() < 2) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Warning: Malformed line %d in config file (insufficient fields)",
|
||||
"Warning: Malformed line %d in config (insufficient fields)",
|
||||
line_number);
|
||||
continue;
|
||||
}
|
||||
@@ -114,14 +126,13 @@ void Asset::loadFromFile(const std::string& config_file_path, const std::string&
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error parsing line %d in config file: %s",
|
||||
"Error parsing line %d in config: %s",
|
||||
line_number,
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Loaded " << file_list_.size() << " assets from config file" << '\n';
|
||||
file.close();
|
||||
std::cout << "Loaded " << file_list_.size() << " assets from config" << '\n';
|
||||
}
|
||||
|
||||
// Devuelve la ruta completa a un fichero (búsqueda O(1))
|
||||
|
||||
@@ -33,6 +33,7 @@ class Asset {
|
||||
// --- Métodos para la gestión de recursos ---
|
||||
void add(const std::string& file_path, Type type, bool required = true, bool absolute = false);
|
||||
void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Con soporte para variables
|
||||
void loadFromString(const std::string& config_content, const std::string& prefix = "", const std::string& system_folder = ""); // Para cargar desde pack (release)
|
||||
[[nodiscard]] auto get(const std::string& filename) const -> std::string; // Obtiene la ruta completa
|
||||
[[nodiscard]] auto loadData(const std::string& filename) const -> std::vector<uint8_t>; // Carga datos del archivo
|
||||
[[nodiscard]] auto check() const -> bool;
|
||||
|
||||
@@ -8,13 +8,14 @@
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <utility>
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/text.hpp" // Para Text, loadTextFile
|
||||
#include "core/resources/asset.hpp" // Para AssetType, Asset
|
||||
#include "external/jail_audio.h" // Para JA_DeleteMusic, JA_DeleteSound, JA_Loa...
|
||||
#include "game/gameplay/room.hpp" // Para RoomData, loadRoomFile, loadRoomTileFile
|
||||
#include "game/options.hpp" // Para Options, OptionsGame, options
|
||||
#include "utils/utils.hpp" // Para getFileName, printWithDots, PaletteColor
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/text.hpp" // Para Text, loadTextFile
|
||||
#include "core/resources/asset.hpp" // Para AssetType, Asset
|
||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
||||
#include "external/jail_audio.h" // Para JA_DeleteMusic, JA_DeleteSound, JA_Loa...
|
||||
#include "game/gameplay/room.hpp" // Para RoomData, loadRoomFile, loadRoomTileFile
|
||||
#include "game/options.hpp" // Para Options, OptionsGame, options
|
||||
#include "utils/utils.hpp" // Para getFileName, printWithDots, PaletteColor
|
||||
struct JA_Music_t; // lines 17-17
|
||||
struct JA_Sound_t; // lines 18-18
|
||||
|
||||
@@ -188,7 +189,20 @@ void Resource::loadSounds() {
|
||||
|
||||
for (const auto& l : list) {
|
||||
auto name = getFileName(l);
|
||||
sounds_.emplace_back(name, JA_LoadSound(l.c_str()));
|
||||
JA_Sound_t* sound = nullptr;
|
||||
|
||||
// Try loading from resource pack first
|
||||
auto audio_data = jdd::ResourceHelper::loadFile(l);
|
||||
if (!audio_data.empty()) {
|
||||
sound = JA_LoadSound(audio_data.data(), static_cast<Uint32>(audio_data.size()));
|
||||
}
|
||||
|
||||
// Fallback to file path if memory loading failed
|
||||
if (sound == nullptr) {
|
||||
sound = JA_LoadSound(l.c_str());
|
||||
}
|
||||
|
||||
sounds_.emplace_back(name, sound);
|
||||
printWithDots("Sound : ", name, "[ LOADED ]");
|
||||
updateLoadingProgress();
|
||||
}
|
||||
@@ -202,7 +216,20 @@ void Resource::loadMusics() {
|
||||
|
||||
for (const auto& l : list) {
|
||||
auto name = getFileName(l);
|
||||
musics_.emplace_back(name, JA_LoadMusic(l.c_str()));
|
||||
JA_Music_t* music = nullptr;
|
||||
|
||||
// Try loading from resource pack first
|
||||
auto audio_data = jdd::ResourceHelper::loadFile(l);
|
||||
if (!audio_data.empty()) {
|
||||
music = JA_LoadMusic(audio_data.data(), static_cast<Uint32>(audio_data.size()));
|
||||
}
|
||||
|
||||
// Fallback to file path if memory loading failed
|
||||
if (music == nullptr) {
|
||||
music = JA_LoadMusic(l.c_str());
|
||||
}
|
||||
|
||||
musics_.emplace_back(name, music);
|
||||
printWithDots("Music : ", name, "[ LOADED ]");
|
||||
updateLoadingProgress(1);
|
||||
}
|
||||
|
||||
178
source/core/resources/resource_helper.cpp
Normal file
178
source/core/resources/resource_helper.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
// resource_helper.cpp
|
||||
// Resource helper implementation
|
||||
|
||||
#include "resource_helper.hpp"
|
||||
|
||||
#include "resource_loader.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace jdd {
|
||||
namespace ResourceHelper {
|
||||
|
||||
static bool resource_system_initialized = false;
|
||||
|
||||
// Initialize the resource system
|
||||
auto initializeResourceSystem(const std::string& pack_file, bool enable_fallback)
|
||||
-> bool {
|
||||
if (resource_system_initialized) {
|
||||
std::cout << "ResourceHelper: Already initialized\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
std::cout << "ResourceHelper: Initializing with pack: " << pack_file << '\n';
|
||||
std::cout << "ResourceHelper: Fallback enabled: " << (enable_fallback ? "Yes" : "No")
|
||||
<< '\n';
|
||||
|
||||
bool success = ResourceLoader::get().initialize(pack_file, enable_fallback);
|
||||
if (success) {
|
||||
resource_system_initialized = true;
|
||||
std::cout << "ResourceHelper: Initialization successful\n";
|
||||
} else {
|
||||
std::cerr << "ResourceHelper: Initialization failed\n";
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Shutdown the resource system
|
||||
void shutdownResourceSystem() {
|
||||
if (resource_system_initialized) {
|
||||
ResourceLoader::get().shutdown();
|
||||
resource_system_initialized = false;
|
||||
std::cout << "ResourceHelper: Shutdown complete\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Load a file
|
||||
auto loadFile(const std::string& filepath) -> std::vector<uint8_t> {
|
||||
if (!resource_system_initialized) {
|
||||
std::cerr << "ResourceHelper: System not initialized, loading from filesystem\n";
|
||||
// Fallback to direct filesystem access
|
||||
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);
|
||||
file.read(reinterpret_cast<char*>(data.data()), file_size);
|
||||
return data;
|
||||
}
|
||||
|
||||
// Determine if we should use the pack
|
||||
if (shouldUseResourcePack(filepath)) {
|
||||
// Convert to pack path
|
||||
std::string pack_path = getPackPath(filepath);
|
||||
|
||||
// Try to load from pack
|
||||
auto data = ResourceLoader::get().loadResource(pack_path);
|
||||
if (!data.empty()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// If pack loading failed, try filesystem as fallback
|
||||
std::cerr << "ResourceHelper: Pack failed for " << pack_path
|
||||
<< ", trying filesystem\n";
|
||||
}
|
||||
|
||||
// Load from filesystem
|
||||
return ResourceLoader::get().loadResource(filepath);
|
||||
}
|
||||
|
||||
// Check if a file exists
|
||||
auto fileExists(const std::string& filepath) -> bool {
|
||||
if (!resource_system_initialized) {
|
||||
return std::filesystem::exists(filepath);
|
||||
}
|
||||
|
||||
// Check pack if appropriate
|
||||
if (shouldUseResourcePack(filepath)) {
|
||||
std::string pack_path = getPackPath(filepath);
|
||||
if (ResourceLoader::get().resourceExists(pack_path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check filesystem
|
||||
return std::filesystem::exists(filepath);
|
||||
}
|
||||
|
||||
// Convert asset path to pack path
|
||||
auto getPackPath(const std::string& asset_path) -> std::string {
|
||||
std::string path = asset_path;
|
||||
|
||||
// Convert backslashes to forward slashes
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
|
||||
// Remove leading slashes
|
||||
while (!path.empty() && path[0] == '/') {
|
||||
path = path.substr(1);
|
||||
}
|
||||
|
||||
// Remove "./" prefix if present
|
||||
if (path.find("./") == 0) {
|
||||
path = path.substr(2);
|
||||
}
|
||||
|
||||
// Remove "../" prefixes (for macOS bundle paths in development)
|
||||
while (path.find("../") == 0) {
|
||||
path = path.substr(3);
|
||||
}
|
||||
|
||||
// Remove "Resources/" prefix if present (for macOS bundle)
|
||||
const std::string resources_prefix = "Resources/";
|
||||
if (path.find(resources_prefix) == 0) {
|
||||
path = path.substr(resources_prefix.length());
|
||||
}
|
||||
|
||||
// Remove "data/" prefix if present
|
||||
const std::string data_prefix = "data/";
|
||||
if (path.find(data_prefix) == 0) {
|
||||
path = path.substr(data_prefix.length());
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// Check if file should use resource pack
|
||||
auto shouldUseResourcePack(const std::string& filepath) -> bool {
|
||||
std::string path = filepath;
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
|
||||
// Don't use pack for most config files (except config/assets.txt which is loaded
|
||||
// directly via ResourceLoader::loadAssetsConfig() in release builds)
|
||||
if (path.find("config/") != std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use pack for data files
|
||||
if (path.find("data/") != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it looks like a data file (has common extensions)
|
||||
if (path.find(".ogg") != std::string::npos || path.find(".wav") != std::string::npos ||
|
||||
path.find(".gif") != std::string::npos || path.find(".png") != std::string::npos ||
|
||||
path.find(".pal") != std::string::npos || path.find(".ani") != std::string::npos ||
|
||||
path.find(".tmx") != std::string::npos || path.find(".room") != std::string::npos ||
|
||||
path.find(".txt") != std::string::npos || path.find(".glsl") != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if pack is loaded
|
||||
auto isPackLoaded() -> bool {
|
||||
if (!resource_system_initialized) {
|
||||
return false;
|
||||
}
|
||||
return ResourceLoader::get().isPackLoaded();
|
||||
}
|
||||
|
||||
} // namespace ResourceHelper
|
||||
} // namespace jdd
|
||||
42
source/core/resources/resource_helper.hpp
Normal file
42
source/core/resources/resource_helper.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// resource_helper.hpp
|
||||
// Helper functions for resource loading (bridge to pack system)
|
||||
|
||||
#ifndef RESOURCE_HELPER_HPP
|
||||
#define RESOURCE_HELPER_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace jdd {
|
||||
namespace ResourceHelper {
|
||||
|
||||
// Initialize the resource system
|
||||
// pack_file: Path to resources.pack
|
||||
// enable_fallback: Allow loading from filesystem if pack not available
|
||||
auto initializeResourceSystem(const std::string& pack_file = "resources.pack",
|
||||
bool enable_fallback = true) -> bool;
|
||||
|
||||
// Shutdown the resource system
|
||||
void shutdownResourceSystem();
|
||||
|
||||
// Load a file (tries pack first, then filesystem if fallback enabled)
|
||||
auto loadFile(const std::string& filepath) -> std::vector<uint8_t>;
|
||||
|
||||
// Check if a file exists
|
||||
auto fileExists(const std::string& filepath) -> bool;
|
||||
|
||||
// Convert an asset path to a pack path
|
||||
// Example: "data/music/title.ogg" -> "music/title.ogg"
|
||||
auto getPackPath(const std::string& asset_path) -> std::string;
|
||||
|
||||
// Check if a file should use the resource pack
|
||||
// Returns false for config/ files (always from filesystem)
|
||||
auto shouldUseResourcePack(const std::string& filepath) -> bool;
|
||||
|
||||
// Check if pack is loaded
|
||||
auto isPackLoaded() -> bool;
|
||||
|
||||
} // namespace ResourceHelper
|
||||
} // namespace jdd
|
||||
|
||||
#endif // RESOURCE_HELPER_HPP
|
||||
199
source/core/resources/resource_loader.cpp
Normal file
199
source/core/resources/resource_loader.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
// resource_loader.cpp
|
||||
// Resource loader implementation
|
||||
|
||||
#include "resource_loader.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace jdd {
|
||||
|
||||
// Get singleton instance
|
||||
auto ResourceLoader::get() -> ResourceLoader& {
|
||||
static ResourceLoader instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Initialize with a pack file
|
||||
auto ResourceLoader::initialize(const std::string& pack_file, bool enable_fallback)
|
||||
-> bool {
|
||||
if (initialized_) {
|
||||
std::cout << "ResourceLoader: Already initialized\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
fallback_to_files_ = enable_fallback;
|
||||
|
||||
// Try to load the pack file
|
||||
if (!pack_file.empty() && fileExistsOnFilesystem(pack_file)) {
|
||||
std::cout << "ResourceLoader: Loading pack file: " << pack_file << '\n';
|
||||
resource_pack_ = std::make_unique<ResourcePack>();
|
||||
if (resource_pack_->loadPack(pack_file)) {
|
||||
std::cout << "ResourceLoader: Pack loaded successfully\n";
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
std::cerr << "ResourceLoader: Failed to load pack file\n";
|
||||
resource_pack_.reset();
|
||||
} else {
|
||||
std::cout << "ResourceLoader: Pack file not found: " << pack_file << '\n';
|
||||
}
|
||||
|
||||
// If pack loading failed and fallback is disabled, fail
|
||||
if (!fallback_to_files_) {
|
||||
std::cerr << "ResourceLoader: Pack required but not found (fallback disabled)\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, fallback to filesystem
|
||||
std::cout << "ResourceLoader: Using filesystem fallback\n";
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load a resource
|
||||
auto ResourceLoader::loadResource(const std::string& filename) -> std::vector<uint8_t> {
|
||||
if (!initialized_) {
|
||||
std::cerr << "ResourceLoader: Not initialized\n";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Try pack first if available
|
||||
if (resource_pack_ && resource_pack_->isLoaded()) {
|
||||
if (resource_pack_->hasResource(filename)) {
|
||||
auto data = resource_pack_->getResource(filename);
|
||||
if (!data.empty()) {
|
||||
return data;
|
||||
}
|
||||
std::cerr << "ResourceLoader: Failed to extract from pack: " << filename
|
||||
<< '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to filesystem if enabled
|
||||
if (fallback_to_files_) {
|
||||
return loadFromFilesystem(filename);
|
||||
}
|
||||
|
||||
std::cerr << "ResourceLoader: Resource not found: " << filename << '\n';
|
||||
return {};
|
||||
}
|
||||
|
||||
// Check if a resource exists
|
||||
auto ResourceLoader::resourceExists(const std::string& filename) -> bool {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check pack first
|
||||
if (resource_pack_ && resource_pack_->isLoaded()) {
|
||||
if (resource_pack_->hasResource(filename)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check filesystem if fallback enabled
|
||||
if (fallback_to_files_) {
|
||||
return fileExistsOnFilesystem(filename);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if pack is loaded
|
||||
auto ResourceLoader::isPackLoaded() const -> bool {
|
||||
return resource_pack_ && resource_pack_->isLoaded();
|
||||
}
|
||||
|
||||
// Get pack statistics
|
||||
auto ResourceLoader::getPackResourceCount() const -> size_t {
|
||||
if (resource_pack_ && resource_pack_->isLoaded()) {
|
||||
return resource_pack_->getResourceCount();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
void ResourceLoader::shutdown() {
|
||||
resource_pack_.reset();
|
||||
initialized_ = false;
|
||||
std::cout << "ResourceLoader: Shutdown complete\n";
|
||||
}
|
||||
|
||||
// Load from filesystem
|
||||
auto ResourceLoader::loadFromFilesystem(const std::string& filepath)
|
||||
-> std::vector<uint8_t> {
|
||||
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)) {
|
||||
std::cerr << "ResourceLoader: Failed to read file: " << filepath << '\n';
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Check if file exists on filesystem
|
||||
auto ResourceLoader::fileExistsOnFilesystem(const std::string& filepath) -> bool {
|
||||
return std::filesystem::exists(filepath);
|
||||
}
|
||||
|
||||
// Validate pack integrity
|
||||
auto ResourceLoader::validatePack() const -> bool {
|
||||
if (!initialized_ || !resource_pack_ || !resource_pack_->isLoaded()) {
|
||||
std::cerr << "ResourceLoader: Cannot validate - pack not loaded\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate pack checksum
|
||||
uint32_t checksum = resource_pack_->calculatePackChecksum();
|
||||
|
||||
if (checksum == 0) {
|
||||
std::cerr << "ResourceLoader: Pack checksum is zero (invalid)\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << "ResourceLoader: Pack checksum: 0x" << std::hex << checksum << std::dec
|
||||
<< '\n';
|
||||
std::cout << "ResourceLoader: Pack validation successful\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load assets.txt from pack
|
||||
auto ResourceLoader::loadAssetsConfig() const -> std::string {
|
||||
if (!initialized_ || !resource_pack_ || !resource_pack_->isLoaded()) {
|
||||
std::cerr << "ResourceLoader: Cannot load assets config - pack not loaded\n";
|
||||
return "";
|
||||
}
|
||||
|
||||
// Try to load config/assets.txt from pack
|
||||
std::string config_path = "config/assets.txt";
|
||||
|
||||
if (!resource_pack_->hasResource(config_path)) {
|
||||
std::cerr << "ResourceLoader: assets.txt not found in pack: " << config_path << '\n';
|
||||
return "";
|
||||
}
|
||||
|
||||
auto data = resource_pack_->getResource(config_path);
|
||||
if (data.empty()) {
|
||||
std::cerr << "ResourceLoader: Failed to load assets.txt from pack\n";
|
||||
return "";
|
||||
}
|
||||
|
||||
// Convert bytes to string
|
||||
std::string config_content(data.begin(), data.end());
|
||||
std::cout << "ResourceLoader: Loaded assets.txt from pack (" << data.size()
|
||||
<< " bytes)\n";
|
||||
|
||||
return config_content;
|
||||
}
|
||||
|
||||
} // namespace jdd
|
||||
69
source/core/resources/resource_loader.hpp
Normal file
69
source/core/resources/resource_loader.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
// resource_loader.hpp
|
||||
// Singleton resource loader for managing pack and filesystem access
|
||||
|
||||
#ifndef RESOURCE_LOADER_HPP
|
||||
#define RESOURCE_LOADER_HPP
|
||||
|
||||
#include "resource_pack.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace jdd {
|
||||
|
||||
// Singleton class for loading resources from pack or filesystem
|
||||
class ResourceLoader {
|
||||
public:
|
||||
// Get singleton instance
|
||||
static auto get() -> ResourceLoader&;
|
||||
|
||||
// Initialize with a pack file (optional)
|
||||
auto initialize(const std::string& pack_file, bool enable_fallback = true) -> bool;
|
||||
|
||||
// Load a resource (tries pack first, then filesystem if fallback enabled)
|
||||
auto loadResource(const std::string& filename) -> std::vector<uint8_t>;
|
||||
|
||||
// Check if a resource exists
|
||||
auto resourceExists(const std::string& filename) -> bool;
|
||||
|
||||
// Check if pack is loaded
|
||||
auto isPackLoaded() const -> bool;
|
||||
|
||||
// Get pack statistics
|
||||
auto getPackResourceCount() const -> size_t;
|
||||
|
||||
// Validate pack integrity (checksum)
|
||||
auto validatePack() const -> bool;
|
||||
|
||||
// Load assets.txt from pack (for release builds)
|
||||
auto loadAssetsConfig() const -> std::string;
|
||||
|
||||
// Cleanup
|
||||
void shutdown();
|
||||
|
||||
private:
|
||||
ResourceLoader() = default;
|
||||
~ResourceLoader() = default;
|
||||
|
||||
// Disable copy/move
|
||||
ResourceLoader(const ResourceLoader&) = delete;
|
||||
ResourceLoader& operator=(const ResourceLoader&) = delete;
|
||||
ResourceLoader(ResourceLoader&&) = delete;
|
||||
ResourceLoader& operator=(ResourceLoader&&) = delete;
|
||||
|
||||
// Load from filesystem
|
||||
static auto loadFromFilesystem(const std::string& filepath) -> std::vector<uint8_t>;
|
||||
|
||||
// Check if file exists on filesystem
|
||||
static auto fileExistsOnFilesystem(const std::string& filepath) -> bool;
|
||||
|
||||
// Member data
|
||||
std::unique_ptr<ResourcePack> resource_pack_;
|
||||
bool fallback_to_files_{true};
|
||||
bool initialized_{false};
|
||||
};
|
||||
|
||||
} // namespace jdd
|
||||
|
||||
#endif // RESOURCE_LOADER_HPP
|
||||
303
source/core/resources/resource_pack.cpp
Normal file
303
source/core/resources/resource_pack.cpp
Normal file
@@ -0,0 +1,303 @@
|
||||
// resource_pack.cpp
|
||||
// Resource pack implementation for JailDoctor's Dilemma
|
||||
|
||||
#include "resource_pack.hpp"
|
||||
|
||||
#include <SDL3/SDL_filesystem.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace jdd {
|
||||
|
||||
// Calculate CRC32 checksum for data verification
|
||||
auto ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t {
|
||||
uint32_t checksum = 0x12345678;
|
||||
for (unsigned char byte : data) {
|
||||
checksum = ((checksum << 5) + checksum) + byte;
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
// XOR encryption (symmetric - same function for encrypt/decrypt)
|
||||
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) {
|
||||
// XOR is symmetric
|
||||
encryptData(data, key);
|
||||
}
|
||||
|
||||
// Read entire file into memory
|
||||
auto ResourcePack::readFile(const std::string& filepath) -> std::vector<uint8_t> {
|
||||
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
std::cerr << "ResourcePack: Failed to open file: " << filepath << '\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 << "ResourcePack: Failed to read file: " << filepath << '\n';
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Add a single file to the pack
|
||||
auto ResourcePack::addFile(const std::string& filepath, const std::string& pack_name)
|
||||
-> bool {
|
||||
auto file_data = readFile(filepath);
|
||||
if (file_data.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceEntry entry;
|
||||
entry.filename = pack_name;
|
||||
entry.offset = data_.size();
|
||||
entry.size = file_data.size();
|
||||
entry.checksum = calculateChecksum(file_data);
|
||||
|
||||
// Append file data to the data block
|
||||
data_.insert(data_.end(), file_data.begin(), file_data.end());
|
||||
|
||||
resources_[pack_name] = entry;
|
||||
|
||||
std::cout << "Added: " << pack_name << " (" << file_data.size() << " bytes)\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add all files from a directory recursively
|
||||
auto ResourcePack::addDirectory(const std::string& dir_path,
|
||||
const std::string& base_path) -> bool {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
|
||||
std::cerr << "ResourcePack: Directory not found: " << dir_path << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string current_base = base_path.empty() ? "" : base_path + "/";
|
||||
|
||||
for (const auto& entry : fs::recursive_directory_iterator(dir_path)) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string full_path = entry.path().string();
|
||||
std::string relative_path = entry.path().lexically_relative(dir_path).string();
|
||||
|
||||
// Convert backslashes to forward slashes (Windows compatibility)
|
||||
std::replace(relative_path.begin(), relative_path.end(), '\\', '/');
|
||||
|
||||
// Skip development files
|
||||
if (relative_path.find(".world") != std::string::npos ||
|
||||
relative_path.find(".tsx") != std::string::npos) {
|
||||
std::cout << "Skipping development file: " << relative_path << '\n';
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string pack_name = current_base + relative_path;
|
||||
addFile(full_path, pack_name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Save the pack to a file
|
||||
auto ResourcePack::savePack(const std::string& pack_file) -> bool {
|
||||
std::ofstream file(pack_file, std::ios::binary);
|
||||
if (!file) {
|
||||
std::cerr << "ResourcePack: Failed to create pack file: " << pack_file << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write header
|
||||
file.write(MAGIC_HEADER.data(), MAGIC_HEADER.size());
|
||||
file.write(reinterpret_cast<const char*>(&VERSION), sizeof(VERSION));
|
||||
|
||||
// Write resource count
|
||||
uint32_t resource_count = static_cast<uint32_t>(resources_.size());
|
||||
file.write(reinterpret_cast<const char*>(&resource_count), sizeof(resource_count));
|
||||
|
||||
// Write resource entries
|
||||
for (const auto& [name, entry] : resources_) {
|
||||
// Write filename length and name
|
||||
uint32_t name_len = static_cast<uint32_t>(entry.filename.length());
|
||||
file.write(reinterpret_cast<const char*>(&name_len), sizeof(name_len));
|
||||
file.write(entry.filename.c_str(), name_len);
|
||||
|
||||
// Write offset, size, checksum
|
||||
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));
|
||||
}
|
||||
|
||||
// Encrypt data
|
||||
std::vector<uint8_t> encrypted_data = data_;
|
||||
encryptData(encrypted_data, DEFAULT_ENCRYPT_KEY);
|
||||
|
||||
// Write encrypted data size and data
|
||||
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);
|
||||
|
||||
std::cout << "\nPack saved successfully: " << pack_file << '\n';
|
||||
std::cout << "Resources: " << resource_count << '\n';
|
||||
std::cout << "Total size: " << data_size << " bytes\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load a pack from a file
|
||||
auto ResourcePack::loadPack(const std::string& pack_file) -> bool {
|
||||
std::ifstream file(pack_file, std::ios::binary);
|
||||
if (!file) {
|
||||
std::cerr << "ResourcePack: Failed to open pack file: " << pack_file << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read and verify header
|
||||
std::array<char, 4> header{};
|
||||
file.read(header.data(), header.size());
|
||||
if (header != MAGIC_HEADER) {
|
||||
std::cerr << "ResourcePack: Invalid pack header\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read and verify version
|
||||
uint32_t version = 0;
|
||||
file.read(reinterpret_cast<char*>(&version), sizeof(version));
|
||||
if (version != VERSION) {
|
||||
std::cerr << "ResourcePack: Unsupported pack version: " << version << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read resource count
|
||||
uint32_t resource_count = 0;
|
||||
file.read(reinterpret_cast<char*>(&resource_count), sizeof(resource_count));
|
||||
|
||||
// Read resource entries
|
||||
resources_.clear();
|
||||
for (uint32_t i = 0; i < resource_count; ++i) {
|
||||
// Read filename
|
||||
uint32_t name_len = 0;
|
||||
file.read(reinterpret_cast<char*>(&name_len), sizeof(name_len));
|
||||
|
||||
std::string filename(name_len, '\0');
|
||||
file.read(filename.data(), name_len);
|
||||
|
||||
// Read entry data
|
||||
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;
|
||||
}
|
||||
|
||||
// Read encrypted data
|
||||
uint64_t data_size = 0;
|
||||
file.read(reinterpret_cast<char*>(&data_size), sizeof(data_size));
|
||||
|
||||
data_.resize(data_size);
|
||||
file.read(reinterpret_cast<char*>(data_.data()), data_size);
|
||||
|
||||
// Decrypt data
|
||||
decryptData(data_, DEFAULT_ENCRYPT_KEY);
|
||||
|
||||
loaded_ = true;
|
||||
|
||||
std::cout << "ResourcePack loaded: " << pack_file << '\n';
|
||||
std::cout << "Resources: " << resource_count << '\n';
|
||||
std::cout << "Data size: " << data_size << " bytes\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get a resource by name
|
||||
auto ResourcePack::getResource(const std::string& filename) -> std::vector<uint8_t> {
|
||||
auto it = resources_.find(filename);
|
||||
if (it == resources_.end()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const ResourceEntry& entry = it->second;
|
||||
|
||||
// Extract data slice
|
||||
if (entry.offset + entry.size > data_.size()) {
|
||||
std::cerr << "ResourcePack: Invalid offset/size for: " << filename << '\n';
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> result(data_.begin() + entry.offset,
|
||||
data_.begin() + entry.offset + entry.size);
|
||||
|
||||
// Verify checksum
|
||||
uint32_t checksum = calculateChecksum(result);
|
||||
if (checksum != entry.checksum) {
|
||||
std::cerr << "ResourcePack: Checksum mismatch for: " << filename << '\n';
|
||||
std::cerr << " Expected: 0x" << std::hex << entry.checksum << '\n';
|
||||
std::cerr << " Got: 0x" << std::hex << checksum << std::dec << '\n';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if a resource exists
|
||||
auto ResourcePack::hasResource(const std::string& filename) const -> bool {
|
||||
return resources_.find(filename) != resources_.end();
|
||||
}
|
||||
|
||||
// Get list of all resources
|
||||
auto ResourcePack::getResourceList() const -> std::vector<std::string> {
|
||||
std::vector<std::string> list;
|
||||
list.reserve(resources_.size());
|
||||
for (const auto& [name, entry] : resources_) {
|
||||
list.push_back(name);
|
||||
}
|
||||
std::sort(list.begin(), list.end());
|
||||
return list;
|
||||
}
|
||||
|
||||
// Calculate overall pack checksum for validation
|
||||
auto ResourcePack::calculatePackChecksum() const -> uint32_t {
|
||||
if (!loaded_ || data_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Combine checksums of all resources for a global checksum
|
||||
uint32_t global_checksum = 0x87654321;
|
||||
|
||||
// Sort resources by name for deterministic checksum
|
||||
std::vector<std::string> sorted_names;
|
||||
sorted_names.reserve(resources_.size());
|
||||
for (const auto& [name, entry] : resources_) {
|
||||
sorted_names.push_back(name);
|
||||
}
|
||||
std::sort(sorted_names.begin(), sorted_names.end());
|
||||
|
||||
// Combine individual checksums
|
||||
for (const auto& name : sorted_names) {
|
||||
const auto& entry = resources_.at(name);
|
||||
global_checksum = ((global_checksum << 5) + global_checksum) + entry.checksum;
|
||||
global_checksum = ((global_checksum << 5) + global_checksum) + entry.size;
|
||||
}
|
||||
|
||||
return global_checksum;
|
||||
}
|
||||
|
||||
} // namespace jdd
|
||||
94
source/core/resources/resource_pack.hpp
Normal file
94
source/core/resources/resource_pack.hpp
Normal file
@@ -0,0 +1,94 @@
|
||||
// resource_pack.hpp
|
||||
// Resource pack file format and management for JailDoctor's Dilemma
|
||||
// Based on Coffee Crisis Arcade Edition resource pack system
|
||||
|
||||
#ifndef RESOURCE_PACK_HPP
|
||||
#define RESOURCE_PACK_HPP
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace jdd {
|
||||
|
||||
// Entry metadata for each resource in the pack
|
||||
struct ResourceEntry {
|
||||
std::string filename; // Relative path within pack
|
||||
uint64_t offset; // Byte offset in data block
|
||||
uint64_t size; // Size in bytes
|
||||
uint32_t checksum; // CRC32 checksum for verification
|
||||
};
|
||||
|
||||
// Resource pack file format
|
||||
// Header: "JDDI" (4 bytes) + Version (4 bytes)
|
||||
// Metadata: Count + array of ResourceEntry
|
||||
// Data: Encrypted data block
|
||||
class ResourcePack {
|
||||
public:
|
||||
ResourcePack() = default;
|
||||
~ResourcePack() = default;
|
||||
|
||||
// Disable copy/move
|
||||
ResourcePack(const ResourcePack&) = delete;
|
||||
ResourcePack& operator=(const ResourcePack&) = delete;
|
||||
ResourcePack(ResourcePack&&) = delete;
|
||||
ResourcePack& operator=(ResourcePack&&) = delete;
|
||||
|
||||
// Add a single file to the pack
|
||||
auto addFile(const std::string& filepath, const std::string& pack_name) -> bool;
|
||||
|
||||
// Add all files from a directory recursively
|
||||
auto addDirectory(const std::string& dir_path, const std::string& base_path = "")
|
||||
-> bool;
|
||||
|
||||
// Save the pack to a file
|
||||
auto savePack(const std::string& pack_file) -> bool;
|
||||
|
||||
// Load a pack from a file
|
||||
auto loadPack(const std::string& pack_file) -> bool;
|
||||
|
||||
// Get a resource by name
|
||||
auto getResource(const std::string& filename) -> std::vector<uint8_t>;
|
||||
|
||||
// Check if a resource exists
|
||||
auto hasResource(const std::string& filename) const -> bool;
|
||||
|
||||
// Get list of all resources
|
||||
auto getResourceList() const -> std::vector<std::string>;
|
||||
|
||||
// Check if pack is loaded
|
||||
auto isLoaded() const -> bool { return loaded_; }
|
||||
|
||||
// Get pack statistics
|
||||
auto getResourceCount() const -> size_t { return resources_.size(); }
|
||||
auto getDataSize() const -> size_t { return data_.size(); }
|
||||
|
||||
// Calculate overall pack checksum (for validation)
|
||||
auto calculatePackChecksum() const -> uint32_t;
|
||||
|
||||
private:
|
||||
static constexpr std::array<char, 4> MAGIC_HEADER = {'J', 'D', 'D', 'I'};
|
||||
static constexpr uint32_t VERSION = 1;
|
||||
static constexpr const char* DEFAULT_ENCRYPT_KEY = "JDDI_RESOURCES_2024";
|
||||
|
||||
// Calculate CRC32 checksum
|
||||
static auto calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t;
|
||||
|
||||
// XOR encryption/decryption
|
||||
static void encryptData(std::vector<uint8_t>& data, const std::string& key);
|
||||
static void decryptData(std::vector<uint8_t>& data, const std::string& key);
|
||||
|
||||
// Read file from disk
|
||||
static auto readFile(const std::string& filepath) -> std::vector<uint8_t>;
|
||||
|
||||
// Member data
|
||||
std::unordered_map<std::string, ResourceEntry> resources_;
|
||||
std::vector<uint8_t> data_; // Encrypted data block
|
||||
bool loaded_{false};
|
||||
};
|
||||
|
||||
} // namespace jdd
|
||||
|
||||
#endif // RESOURCE_PACK_HPP
|
||||
@@ -16,6 +16,8 @@
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/resources/asset.hpp" // Para Asset, AssetType
|
||||
#include "core/resources/resource.hpp" // Para Resource
|
||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
||||
#include "core/resources/resource_loader.hpp" // Para ResourceLoader
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#include "game/gameplay/cheevos.hpp" // Para Cheevos
|
||||
#include "game/options.hpp" // Para Options, options, OptionsVideo
|
||||
@@ -45,18 +47,75 @@ Director::Director(std::vector<std::string> const& args) {
|
||||
// Comprueba los parametros del programa
|
||||
executable_path_ = getPath(checkProgramArguments(args));
|
||||
|
||||
// Crea el objeto que controla los ficheros de recursos
|
||||
Asset::init(executable_path_);
|
||||
|
||||
// Crea la carpeta del sistema donde guardar datos
|
||||
createSystemFolder("jailgames");
|
||||
createSystemFolder("jailgames/jaildoctors_dilemma");
|
||||
|
||||
// Si falta algún fichero no inicia el programa
|
||||
// Determinar el prefijo de ruta según la plataforma
|
||||
#ifdef MACOS_BUNDLE
|
||||
const std::string PREFIX = "/../Resources";
|
||||
#else
|
||||
const std::string PREFIX;
|
||||
#endif
|
||||
|
||||
// Preparar ruta al pack (en macOS bundle está en Contents/Resources/)
|
||||
std::string pack_path = executable_path_ + PREFIX + "/resources.pack";
|
||||
|
||||
#ifdef RELEASE_BUILD
|
||||
// ============================================================
|
||||
// RELEASE BUILD: Pack-first architecture
|
||||
// ============================================================
|
||||
std::cout << "\n** RELEASE MODE: Pack-first initialization\n";
|
||||
|
||||
// 1. Initialize resource pack system (required, no fallback)
|
||||
std::cout << "Initializing resource pack: " << pack_path << '\n';
|
||||
if (!jdd::ResourceHelper::initializeResourceSystem(pack_path, false)) {
|
||||
std::cerr << "ERROR: Failed to load resources.pack (required in release builds)\n";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// 2. Validate pack integrity
|
||||
std::cout << "Validating pack integrity..." << '\n';
|
||||
if (!jdd::ResourceLoader::get().validatePack()) {
|
||||
std::cerr << "ERROR: Pack validation failed\n";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// 3. Load assets.txt from pack
|
||||
std::cout << "Loading assets configuration from pack..." << '\n';
|
||||
std::string assets_config = jdd::ResourceLoader::get().loadAssetsConfig();
|
||||
if (assets_config.empty()) {
|
||||
std::cerr << "ERROR: Failed to load assets.txt from pack\n";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// 4. Initialize Asset system with config from pack
|
||||
// NOTE: In release, don't use executable_path or PREFIX - paths in pack are relative
|
||||
// Pass empty string to avoid issues when running from different directories
|
||||
Asset::init(""); // Empty executable_path in release
|
||||
Asset::get()->loadFromString(assets_config, "", system_folder_); // Empty PREFIX for pack
|
||||
std::cout << "Asset system initialized from pack\n";
|
||||
|
||||
#else
|
||||
// ============================================================
|
||||
// DEVELOPMENT BUILD: Filesystem-first architecture
|
||||
// ============================================================
|
||||
std::cout << "\n** DEVELOPMENT MODE: Filesystem-first initialization\n";
|
||||
|
||||
// 1. Initialize Asset system from filesystem
|
||||
Asset::init(executable_path_);
|
||||
|
||||
// 2. Load and verify assets from disk
|
||||
if (!setFileList()) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// 3. Initialize resource pack system (optional, with fallback)
|
||||
std::cout << "Initializing resource pack (development mode): " << pack_path << '\n';
|
||||
jdd::ResourceHelper::initializeResourceSystem(pack_path, true);
|
||||
|
||||
#endif
|
||||
|
||||
// Carga las opciones desde un fichero
|
||||
Options::loadFromFile(Asset::get()->get("config.txt"));
|
||||
|
||||
@@ -66,13 +125,32 @@ Director::Director(std::vector<std::string> const& args) {
|
||||
// Crea los objetos
|
||||
Screen::init();
|
||||
SDL_HideCursor();
|
||||
|
||||
// Initialize resources (works for both release and development)
|
||||
Resource::init();
|
||||
Notifier::init("", "8bithud");
|
||||
Screen::get()->setNotificationsEnabled(true);
|
||||
|
||||
// Special handling for gamecontrollerdb.txt - SDL needs filesystem path
|
||||
#ifdef RELEASE_BUILD
|
||||
// In release, construct the path manually (not from Asset which has empty executable_path)
|
||||
std::string gamecontroller_db = executable_path_ + PREFIX + "/gamecontrollerdb.txt";
|
||||
Input::init(gamecontroller_db);
|
||||
#else
|
||||
// In development, use Asset as normal
|
||||
Input::init(Asset::get()->get("gamecontrollerdb.txt"));
|
||||
#endif
|
||||
|
||||
initInput();
|
||||
Debug::init();
|
||||
|
||||
// Special handling for cheevos.bin - also needs filesystem path
|
||||
#ifdef RELEASE_BUILD
|
||||
std::string cheevos_path = system_folder_ + "/cheevos.bin";
|
||||
Cheevos::init(cheevos_path);
|
||||
#else
|
||||
Cheevos::init(Asset::get()->get("cheevos.bin"));
|
||||
#endif
|
||||
}
|
||||
|
||||
Director::~Director() {
|
||||
@@ -85,6 +163,7 @@ Director::~Director() {
|
||||
Input::destroy();
|
||||
Notifier::destroy();
|
||||
Resource::destroy();
|
||||
jdd::ResourceHelper::shutdownResourceSystem(); // Shutdown resource pack system
|
||||
Audio::destroy();
|
||||
Screen::destroy();
|
||||
Asset::destroy();
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/surface_sprite.hpp" // Para SSprite
|
||||
#include "core/resources/resource.hpp" // Para Resource
|
||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#include "game/gameplay/item_tracker.hpp" // Para ItemTracker
|
||||
#include "game/gameplay/scoreboard.hpp" // Para ScoreboardData
|
||||
@@ -946,16 +947,21 @@ auto Room::setItem(Item::Data* item, const std::string& key, const std::string&
|
||||
auto Room::loadRoomTileFile(const std::string& file_path, bool verbose) -> std::vector<int> {
|
||||
std::vector<int> tile_map_file;
|
||||
const std::string FILENAME = file_path.substr(file_path.find_last_of("\\/") + 1);
|
||||
std::ifstream file(file_path);
|
||||
|
||||
// El fichero se puede abrir
|
||||
if (file.good()) {
|
||||
// Load file using ResourceHelper (supports both filesystem and pack)
|
||||
auto file_data = jdd::ResourceHelper::loadFile(file_path);
|
||||
|
||||
if (!file_data.empty()) {
|
||||
// Convert bytes to string and parse
|
||||
std::string content(file_data.begin(), file_data.end());
|
||||
std::istringstream stream(content);
|
||||
std::string line;
|
||||
|
||||
// Procesa el fichero linea a linea
|
||||
while (std::getline(file, line)) { // Lee el fichero linea a linea
|
||||
while (std::getline(stream, line)) { // Lee el fichero linea a linea
|
||||
if (line.find("data encoding") != std::string::npos) {
|
||||
// Lee la primera linea
|
||||
std::getline(file, line);
|
||||
std::getline(stream, line);
|
||||
while (line != "</data>") { // Procesa lineas mientras haya
|
||||
std::stringstream ss(line);
|
||||
std::string tmp;
|
||||
@@ -964,18 +970,15 @@ auto Room::loadRoomTileFile(const std::string& file_path, bool verbose) -> std::
|
||||
}
|
||||
|
||||
// Lee la siguiente linea
|
||||
std::getline(file, line);
|
||||
std::getline(stream, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cierra el fichero
|
||||
if (verbose) {
|
||||
std::cout << "TileMap loaded: " << FILENAME.c_str() << '\n';
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
else { // El fichero no se puede abrir
|
||||
if (verbose) {
|
||||
std::cout << "Warning: Unable to open " << FILENAME.c_str() << " file" << '\n';
|
||||
@@ -995,20 +998,24 @@ auto Room::loadRoomFile(const std::string& file_path, bool verbose) -> Data {
|
||||
const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);
|
||||
room.number = FILE_NAME.substr(0, FILE_NAME.find_last_of('.'));
|
||||
|
||||
std::ifstream file(file_path);
|
||||
// Load file using ResourceHelper (supports both filesystem and pack)
|
||||
auto file_data = jdd::ResourceHelper::loadFile(file_path);
|
||||
|
||||
// El fichero se puede abrir
|
||||
if (file.good()) {
|
||||
if (!file_data.empty()) {
|
||||
// Convert bytes to string and parse
|
||||
std::string content(file_data.begin(), file_data.end());
|
||||
std::istringstream stream(content);
|
||||
std::string line;
|
||||
|
||||
// Procesa el fichero linea a linea
|
||||
while (std::getline(file, line)) {
|
||||
while (std::getline(stream, line)) {
|
||||
// Si la linea contiene el texto [enemy] se realiza el proceso de carga de un enemigo
|
||||
if (line == "[enemy]") {
|
||||
room.enemies.push_back(loadEnemyFromFile(file, FILE_NAME, verbose));
|
||||
room.enemies.push_back(loadEnemyFromFile(stream, FILE_NAME, verbose));
|
||||
}
|
||||
// Si la linea contiene el texto [item] se realiza el proceso de carga de un item
|
||||
else if (line == "[item]") {
|
||||
room.items.push_back(loadItemFromFile(file, FILE_NAME, verbose));
|
||||
room.items.push_back(loadItemFromFile(stream, FILE_NAME, verbose));
|
||||
}
|
||||
// En caso contrario se parsea el fichero para buscar las variables y los valores
|
||||
else {
|
||||
@@ -1019,14 +1026,11 @@ auto Room::loadRoomFile(const std::string& file_path, bool verbose) -> Data {
|
||||
}
|
||||
}
|
||||
|
||||
// Cierra el fichero
|
||||
if (verbose) {
|
||||
std::cout << "Room loaded: " << FILE_NAME.c_str() << '\n';
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
// El fichero no se puede abrir
|
||||
else {
|
||||
else { // El fichero no se puede abrir
|
||||
std::cout << "Warning: Unable to open " << FILE_NAME.c_str() << " file" << '\n';
|
||||
}
|
||||
|
||||
@@ -1049,7 +1053,7 @@ void Room::logUnknownParameter(const std::string& file_name, const std::string&
|
||||
}
|
||||
|
||||
// Carga un bloque [enemy]...[/enemy] desde un archivo
|
||||
auto Room::loadEnemyFromFile(std::ifstream& file, const std::string& file_name, bool verbose) -> Enemy::Data {
|
||||
auto Room::loadEnemyFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Enemy::Data {
|
||||
Enemy::Data enemy;
|
||||
enemy.flip = false;
|
||||
enemy.mirror = false;
|
||||
@@ -1069,7 +1073,7 @@ auto Room::loadEnemyFromFile(std::ifstream& file, const std::string& file_name,
|
||||
}
|
||||
|
||||
// Carga un bloque [item]...[/item] desde un archivo
|
||||
auto Room::loadItemFromFile(std::ifstream& file, const std::string& file_name, bool verbose) -> Item::Data {
|
||||
auto Room::loadItemFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Item::Data {
|
||||
Item::Data item;
|
||||
item.counter = 0;
|
||||
item.color1 = stringToColor("yellow");
|
||||
|
||||
@@ -162,6 +162,6 @@ class Room {
|
||||
static auto setItem(Item::Data* item, const std::string& key, const std::string& value) -> bool; // Asigna variables a una estructura ItemData
|
||||
static auto parseKeyValue(const std::string& line) -> std::pair<std::string, std::string>;
|
||||
static void logUnknownParameter(const std::string& file_name, const std::string& key, bool verbose);
|
||||
static auto loadEnemyFromFile(std::ifstream& file, const std::string& file_name, bool verbose) -> Enemy::Data;
|
||||
static auto loadItemFromFile(std::ifstream& file, const std::string& file_name, bool verbose) -> Item::Data;
|
||||
static auto loadEnemyFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Enemy::Data;
|
||||
static auto loadItemFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Item::Data;
|
||||
};
|
||||
Reference in New Issue
Block a user