forked from jaildesigner-jailgames/jaildoctors_dilemma
actualitzada la carpeta release a SDL3
migrat a resources.pack
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user