actualitzada la carpeta release a SDL3

migrat a resources.pack
This commit is contained in:
2025-10-31 22:58:37 +01:00
parent 70bfced50d
commit 8c6bea897c
513 changed files with 377587 additions and 29821 deletions

View File

@@ -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))

View File

@@ -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;

View File

@@ -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);
}

View 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

View 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

View 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

View 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

View 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

View 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