// resource_pack.cpp // Resource pack implementation for JailDoctor's Dilemma #include "resource_pack.hpp" #include #include #include #include namespace Resource { // Calculate CRC32 checksum for data verification auto Pack::calculateChecksum(const std::vector& 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 Pack::encryptData(std::vector& 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 Pack::decryptData(std::vector& data, const std::string& key) { // XOR is symmetric encryptData(data, key); } // Read entire file into memory auto Pack::readFile(const std::string& filepath) -> std::vector { 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 data(file_size); if (!file.read(reinterpret_cast(data.data()), file_size)) { std::cerr << "ResourcePack: Failed to read file: " << filepath << '\n'; return {}; } return data; } // Add a single file to the pack auto Pack::addFile(const std::string& filepath, const std::string& pack_name) -> bool { auto file_data = readFile(filepath); if (file_data.empty()) { return false; } ResourceEntry entry{ .filename = pack_name, .offset = data_.size(), .size = file_data.size(), .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 Pack::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::ranges::replace(relative_path, '\\', '/'); // 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 Pack::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(&VERSION), sizeof(VERSION)); // Write resource count auto resource_count = static_cast(resources_.size()); file.write(reinterpret_cast(&resource_count), sizeof(resource_count)); // Write resource entries for (const auto& [name, entry] : resources_) { // Write filename length and name auto name_len = static_cast(entry.filename.length()); file.write(reinterpret_cast(&name_len), sizeof(name_len)); file.write(entry.filename.c_str(), name_len); // Write offset, size, checksum file.write(reinterpret_cast(&entry.offset), sizeof(entry.offset)); file.write(reinterpret_cast(&entry.size), sizeof(entry.size)); file.write(reinterpret_cast(&entry.checksum), sizeof(entry.checksum)); } // Encrypt data std::vector 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(&data_size), sizeof(data_size)); file.write(reinterpret_cast(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 Pack::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 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(&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(&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(&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(&entry.offset), sizeof(entry.offset)); file.read(reinterpret_cast(&entry.size), sizeof(entry.size)); file.read(reinterpret_cast(&entry.checksum), sizeof(entry.checksum)); resources_[filename] = entry; } // Read encrypted data uint64_t data_size = 0; file.read(reinterpret_cast(&data_size), sizeof(data_size)); data_.resize(data_size); file.read(reinterpret_cast(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 Pack::getResource(const std::string& filename) -> std::vector { 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 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 Pack::hasResource(const std::string& filename) const -> bool { return resources_.find(filename) != resources_.end(); } // Get list of all resources auto Pack::getResourceList() const -> std::vector { std::vector list; list.reserve(resources_.size()); for (const auto& [name, entry] : resources_) { list.push_back(name); } std::ranges::sort(list); return list; } // Calculate overall pack checksum for validation auto Pack::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 sorted_names; sorted_names.reserve(resources_.size()); for (const auto& [name, entry] : resources_) { sorted_names.push_back(name); } std::ranges::sort(sorted_names); // 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 Resource