diff --git a/Extra2D/include/extra2d/core/aes_cipher.h b/Extra2D/include/extra2d/core/aes_cipher.h new file mode 100644 index 0000000..25717b8 --- /dev/null +++ b/Extra2D/include/extra2d/core/aes_cipher.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @brief AES 加密工具类 + * + * 使用 OpenSSL 实现 AES-256 加密/解密 + * 支持所有平台包括 Nintendo Switch + */ +class AESCipher { +public: + static constexpr size_t KEY_SIZE = 32; ///< AES-256 密钥大小 + static constexpr size_t IV_SIZE = 16; ///< 初始化向量大小 + static constexpr size_t BLOCK_SIZE = 16; ///< AES 块大小 + + /** + * @brief 默认构造函数 + */ + AESCipher(); + + /** + * @brief 析构函数 + */ + ~AESCipher(); + + /** + * @brief 设置加密密钥和 IV + * @param key 密钥(32 字节) + * @param iv 初始化向量(16 字节) + * @return 设置成功返回 true + */ + bool setKey(const std::vector& key, const std::vector& iv); + + /** + * @brief 设置加密密钥和 IV(指针版本) + * @param key 密钥指针 + * @param keySize 密钥大小 + * @param iv IV 指针 + * @param ivSize IV 大小 + * @return 设置成功返回 true + */ + bool setKey(const uint8_t* key, size_t keySize, + const uint8_t* iv, size_t ivSize); + + /** + * @brief 加密数据 + * @param data 原始数据 + * @return 加密后的数据 + */ + std::vector encrypt(const std::vector& data); + + /** + * @brief 加密数据(指针版本) + * @param data 数据指针 + * @param size 数据大小 + * @return 加密后的数据 + */ + std::vector encrypt(const uint8_t* data, size_t size); + + /** + * @brief 解密数据 + * @param data 加密数据 + * @return 解密后的数据 + */ + std::vector decrypt(const std::vector& data); + + /** + * @brief 解密数据(指针版本) + * @param data 数据指针 + * @param size 数据大小 + * @return 解密后的数据 + */ + std::vector decrypt(const uint8_t* data, size_t size); + + /** + * @brief 原地加密 + * @param data 数据(会被修改) + * @return 成功返回 true + */ + bool encryptInPlace(std::vector& data); + + /** + * @brief 原地解密 + * @param data 数据(会被修改) + * @return 成功返回 true + */ + bool decryptInPlace(std::vector& data); + + /** + * @brief 检查密钥是否已设置 + * @return 已设置返回 true + */ + bool hasKey() const { return keySet_; } + + /** + * @brief 清除密钥 + */ + void clearKey(); + + /** + * @brief 生成随机密钥 + * @param key 输出密钥 + * @param iv 输出 IV + */ + static void generateRandomKey(std::vector& key, std::vector& iv); + + /** + * @brief 从密码派生密钥 + * @param password 密码 + * @param salt 盐值 + * @param key 输出密钥 + * @param iv 输出 IV + * @param iterations 迭代次数 + */ + static void deriveKeyFromPassword(const std::string& password, + const std::vector& salt, + std::vector& key, + std::vector& iv, + int iterations = 10000); + +private: + class Impl; + UniquePtr impl_; + std::vector key_; + std::vector iv_; + bool keySet_ = false; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/compression.h b/Extra2D/include/extra2d/core/compression.h new file mode 100644 index 0000000..91160b5 --- /dev/null +++ b/Extra2D/include/extra2d/core/compression.h @@ -0,0 +1,144 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @brief 压缩算法类型 + */ +enum class CompressionType : uint8_t { + None = 0, ///< 无压缩 + Zlib = 1, ///< Zlib 压缩(标准压缩,兼容性好) + LZ4 = 2, ///< LZ4 压缩(高速压缩,适合运行时解压) + Zstd = 3 ///< Zstd 压缩(高压缩率,适合资源打包) +}; + +/** + * @brief 压缩工具类 + * + * 提供多种压缩算法的统一接口 + */ +class Compression { +public: + /** + * @brief Zlib 压缩 + * @param data 原始数据 + * @param level 压缩级别(0-9,默认 6) + * @return 压缩后的数据 + */ + static std::vector zlibCompress(const std::vector& data, int level = 6); + + /** + * @brief Zlib 解压 + * @param data 压缩数据 + * @param originalSize 原始数据大小(可选) + * @return 解压后的数据 + */ + static std::vector zlibDecompress(const std::vector& data, + size_t originalSize = 0); + + /** + * @brief LZ4 压缩 + * @param data 原始数据 + * @return 压缩后的数据 + */ + static std::vector lz4Compress(const std::vector& data); + + /** + * @brief LZ4 解压 + * @param data 压缩数据 + * @param originalSize 原始数据大小 + * @return 解压后的数据 + */ + static std::vector lz4Decompress(const std::vector& data, + size_t originalSize); + + /** + * @brief Zstd 压缩 + * @param data 原始数据 + * @param level 压缩级别(1-22,默认 3) + * @return 压缩后的数据 + */ + static std::vector zstdCompress(const std::vector& data, int level = 3); + + /** + * @brief Zstd 解压 + * @param data 压缩数据 + * @param originalSize 原始数据大小(可选) + * @return 解压后的数据 + */ + static std::vector zstdDecompress(const std::vector& data, + size_t originalSize = 0); + + /** + * @brief 通用压缩接口 + * @param data 原始数据 + * @param type 压缩算法类型 + * @param level 压缩级别(-1 表示使用默认值) + * @return 压缩后的数据 + */ + static std::vector compress(const std::vector& data, + CompressionType type, + int level = -1); + + /** + * @brief 通用解压接口 + * @param data 压缩数据 + * @param type 压缩算法类型 + * @param originalSize 原始数据大小 + * @return 解压后的数据 + */ + static std::vector decompress(const std::vector& data, + CompressionType type, + size_t originalSize = 0); + + /** + * @brief 获取压缩后的预估大小 + * @param originalSize 原始大小 + * @param type 压缩算法类型 + * @return 预估压缩后大小 + */ + static size_t estimateCompressedSize(size_t originalSize, CompressionType type); + + /** + * @brief 获取压缩算法名称 + * @param type 压缩算法类型 + * @return 算法名称字符串 + */ + static const char* getCompressionName(CompressionType type); + + /** + * @brief 计算 CRC32 校验和 + * @param data 数据 + * @return CRC32 值 + */ + static uint32_t crc32(const std::vector& data); + + /** + * @brief 计算 CRC32 校验和 + * @param data 数据指针 + * @param size 数据大小 + * @return CRC32 值 + */ + static uint32_t crc32(const uint8_t* data, size_t size); + + /** + * @brief 计算 Adler32 校验和 + * @param data 数据 + * @return Adler32 值 + */ + static uint32_t adler32(const std::vector& data); + + /** + * @brief 计算 Adler32 校验和 + * @param data 数据指针 + * @param size 数据大小 + * @return Adler32 值 + */ + static uint32_t adler32(const uint8_t* data, size_t size); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/unicode_utils.h b/Extra2D/include/extra2d/core/unicode_utils.h new file mode 100644 index 0000000..a984f14 --- /dev/null +++ b/Extra2D/include/extra2d/core/unicode_utils.h @@ -0,0 +1,213 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @brief 文本编码类型 + */ +enum class TextEncoding : uint8_t { + UTF8, ///< UTF-8(默认) + UTF16, ///< UTF-16 + UTF32, ///< UTF-32 + GBK, ///< GBK/GB2312(中文编码) + Auto ///< 自动检测 +}; + +/** + * @brief Unicode 工具类 + * + * 使用 simdutf 进行高性能字符编码转换 + */ +class UnicodeUtils { +public: + /** + * @brief 将字符串转换为 UTF-32 + * @param text 输入文本 + * @param encoding 输入编码类型 + * @return UTF-32 字符串 + */ + static std::u32string toUtf32(const std::string &text, TextEncoding encoding); + + /** + * @brief 将 UTF-8 字符串转换为 UTF-32 + * @param utf8 UTF-8 字符串 + * @return UTF-32 字符串 + */ + static std::u32string utf8ToUtf32(const std::string &utf8); + + /** + * @brief 将 UTF-8 字符串视图转换为 UTF-32 + * @param utf8 UTF-8 字符串视图 + * @return UTF-32 字符串 + */ + static std::u32string utf8ToUtf32(std::string_view utf8); + + /** + * @brief 将 UTF-16 字符串转换为 UTF-32 + * @param utf16 UTF-16 字符串 + * @return UTF-32 字符串 + */ + static std::u32string utf16ToUtf32(const std::u16string &utf16); + + /** + * @brief 将 UTF-32 字符串转换为 UTF-8 + * @param utf32 UTF-32 字符串 + * @return UTF-8 字符串 + */ + static std::string utf32ToUtf8(const std::u32string &utf32); + + /** + * @brief 将 UTF-32 字符串转换为 UTF-16 + * @param utf32 UTF-32 字符串 + * @return UTF-16 字符串 + */ + static std::u16string utf32ToUtf16(const std::u32string &utf32); + + /** + * @brief 自动检测编码类型 + * @param data 原始数据 + * @return 检测到的编码类型 + */ + static TextEncoding detectEncoding(const std::string &data); + + /** + * @brief 计算字符数量 + * @param text 文本 + * @param encoding 编码类型 + * @return 字符数量 + */ + static size_t countChars(const std::string &text, TextEncoding encoding); + + /** + * @brief 计算字符数量(UTF-8) + * @param utf8 UTF-8 文本 + * @return 字符数量 + */ + static size_t countCharsUtf8(const std::string &utf8); + + /** + * @brief 计算字符数量(UTF-16) + * @param utf16 UTF-16 文本 + * @return 字符数量 + */ + static size_t countCharsUtf16(const std::u16string &utf16); + + /** + * @brief 验证编码有效性 + * @param text 文本 + * @param encoding 编码类型 + * @return 有效返回 true + */ + static bool validateEncoding(const std::string &text, TextEncoding encoding); + + /** + * @brief 验证 UTF-8 编码 + * @param utf8 UTF-8 文本 + * @return 有效返回 true + */ + static bool validateUtf8(const std::string &utf8); + + /** + * @brief 验证 UTF-16 编码 + * @param utf16 UTF-16 文本 + * @return 有效返回 true + */ + static bool validateUtf16(const std::u16string &utf16); + + /** + * @brief 将 GBK 编码转换为 UTF-32 + * @param gbk GBK 编码文本 + * @return UTF-32 字符串 + */ + static std::u32string gbkToUtf32(const std::string &gbk); + + /** + * @brief 将 UTF-32 转换为 GBK 编码 + * @param utf32 UTF-32 字符串 + * @return GBK 编码文本 + */ + static std::string utf32ToGbk(const std::u32string &utf32); + + /** + * @brief 获取编码名称 + * @param encoding 编码类型 + * @return 编码名称字符串 + */ + static const char *getEncodingName(TextEncoding encoding); + + /** + * @brief 检查是否为有效的 Unicode 码点 + * @param codepoint Unicode 码点 + * @return 有效返回 true + */ + static bool isValidCodepoint(uint32_t codepoint); + + /** + * @brief 检查是否为空白字符 + * @param codepoint Unicode 码点 + * @return 空白字符返回 true + */ + static bool isWhitespace(uint32_t codepoint); + + /** + * @brief 检查是否为换行符 + * @param codepoint Unicode 码点 + * @return 换行符返回 true + */ + static bool isNewline(uint32_t codepoint); + + /** + * @brief 检查是否为控制字符 + * @param codepoint Unicode 码点 + * @return 控制字符返回 true + */ + static bool isControl(uint32_t codepoint); + + /** + * @brief 检查是否为中文字符 + * @param codepoint Unicode 码点 + * @return 中文字符返回 true + */ + static bool isChinese(uint32_t codepoint); + + /** + * @brief 检查是否为日文字符 + * @param codepoint Unicode 码点 + * @return 日文字符返回 true + */ + static bool isJapanese(uint32_t codepoint); + + /** + * @brief 检查是否为韩文字符 + * @param codepoint Unicode 码点 + * @return 韩文字符返回 true + */ + static bool isKorean(uint32_t codepoint); + + /** + * @brief 检查是否为 CJK 字符(中日韩) + * @param codepoint Unicode 码点 + * @return CJK 字符返回 true + */ + static bool isCJK(uint32_t codepoint); + + /** + * @brief 将码点转换为大写 + * @param codepoint Unicode 码点 + * @return 大写码点 + */ + static uint32_t toUpper(uint32_t codepoint); + + /** + * @brief 将码点转换为小写 + * @param codepoint Unicode 码点 + * @return 小写码点 + */ + static uint32_t toLower(uint32_t codepoint); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/extra2d.h b/Extra2D/include/extra2d/extra2d.h index 9b7b7c9..d7468b0 100644 --- a/Extra2D/include/extra2d/extra2d.h +++ b/Extra2D/include/extra2d/extra2d.h @@ -9,6 +9,9 @@ #include #include #include +#include +#include +#include // Config removed - app info now in Application class @@ -54,6 +57,12 @@ #include #include #include +#include + +// Resources +#include +#include +#include // Application #include diff --git a/Extra2D/include/extra2d/resources/asset_bundle.h b/Extra2D/include/extra2d/resources/asset_bundle.h new file mode 100644 index 0000000..c22a168 --- /dev/null +++ b/Extra2D/include/extra2d/resources/asset_bundle.h @@ -0,0 +1,129 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 资源包接口 + * + * 抽象资源存储方式,支持文件系统、打包文件、内存、网络等 + */ +class IAssetBundle { +public: + virtual ~IAssetBundle() = default; + + /** + * @brief 检查资源是否存在 + * @param path 资源路径 + * @return 存在返回 true + */ + virtual bool contains(const std::string &path) const = 0; + + /** + * @brief 同步读取资源数据 + * @param path 资源路径 + * @return 资源数据 + */ + virtual std::vector read(const std::string &path) const = 0; + + /** + * @brief 异步读取资源数据 + * @param path 资源路径 + * @param callback 读取完成回调 + */ + virtual void + readAsync(const std::string &path, + std::function)> callback) const = 0; + + /** + * @brief 打开资源流 + * @param path 资源路径 + * @return 输入流指针 + */ + virtual std::unique_ptr openStream(const std::string &path) = 0; + + /** + * @brief 资源信息结构体 + */ + struct AssetInfo { + uint64_t size = 0; + uint64_t compressedSize = 0; + uint64_t modifiedTime = 0; + bool compressed = false; + bool encrypted = false; + }; + + /** + * @brief 获取资源信息 + * @param path 资源路径 + * @return 资源信息 + */ + virtual AssetInfo getInfo(const std::string &path) const = 0; + + /** + * @brief 列出所有资源路径 + * @return 资源路径列表 + */ + virtual std::vector list() const = 0; + + /** + * @brief 获取资源包名称 + * @return 名称 + */ + virtual const std::string &getName() const = 0; + + /** + * @brief 检查资源包是否有效 + * @return 有效返回 true + */ + virtual bool isValid() const = 0; +}; + +/** + * @brief 资源包条目信息 + */ +struct BundleEntry { + std::string path; + uint64_t offset = 0; + uint64_t compressedSize = 0; + uint64_t originalSize = 0; + uint32_t checksum = 0; + uint8_t compression = 0; + uint8_t encryption = 0; + uint8_t reserved[2] = {0, 0}; +}; + +/** + * @brief 资源包文件头 + */ +struct BundleHeader { + char magic[4] = {'E', '2', 'D', 'P'}; + uint32_t version = 1; + uint8_t compression = 0; + uint8_t encryption = 0; + uint8_t reserved[2] = {0, 0}; + uint32_t entryCount = 0; + uint64_t entryTableOffset = 0; + uint64_t entryTableSize = 0; + uint32_t headerChecksum = 0; + uint8_t padding[32] = {0}; +}; + +/** + * @brief 资源包构建选项 + */ +struct BundleBuildOptions { + uint8_t compression = 0; + uint8_t encryption = 0; + int compressionLevel = 6; + std::vector encryptionKey; + std::vector encryptionIv; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/resources/resource_handle.h b/Extra2D/include/extra2d/resources/resource_handle.h new file mode 100644 index 0000000..3ff493b --- /dev/null +++ b/Extra2D/include/extra2d/resources/resource_handle.h @@ -0,0 +1,237 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief 资源管理器接口 + * + * 资源句柄需要通过管理器进行引用计数管理 + */ +template class ResourceManager { +public: + virtual ~ResourceManager() = default; + + /** + * @brief 增加引用计数 + * @param resource 资源指针 + */ + virtual void addRef(T *resource) = 0; + + /** + * @brief 减少引用计数 + * @param resource 资源指针 + */ + virtual void release(T *resource) = 0; + + /** + * @brief 获取引用计数 + * @param resource 资源指针 + * @return 引用计数 + */ + virtual uint32_t getRefCount(T *resource) const = 0; +}; + +/** + * @brief 资源句柄模板类 + * + * 使用引用计数管理资源生命周期 + * 支持拷贝、移动、自动释放 + * + * @tparam T 资源类型 + */ +template class ResourceHandle { +public: + /** + * @brief 默认构造函数 + */ + ResourceHandle() : resource_(nullptr), manager_(nullptr) {} + + /** + * @brief 空指针构造函数 + */ + ResourceHandle(std::nullptr_t) : resource_(nullptr), manager_(nullptr) {} + + /** + * @brief 构造函数 + * @param resource 资源指针 + * @param manager 资源管理器 + */ + ResourceHandle(T *resource, ResourceManager *manager) + : resource_(resource), manager_(manager) { + if (resource_ && manager_) { + manager_->addRef(resource_); + } + } + + /** + * @brief 析构函数 + */ + ~ResourceHandle() { reset(); } + + /** + * @brief 拷贝构造函数 + */ + ResourceHandle(const ResourceHandle &other) + : resource_(other.resource_), manager_(other.manager_) { + if (resource_ && manager_) { + manager_->addRef(resource_); + } + } + + /** + * @brief 拷贝赋值运算符 + */ + ResourceHandle &operator=(const ResourceHandle &other) { + if (this != &other) { + reset(); + resource_ = other.resource_; + manager_ = other.manager_; + if (resource_ && manager_) { + manager_->addRef(resource_); + } + } + return *this; + } + + /** + * @brief 移动构造函数 + */ + ResourceHandle(ResourceHandle &&other) noexcept + : resource_(other.resource_), manager_(other.manager_) { + other.resource_ = nullptr; + other.manager_ = nullptr; + } + + /** + * @brief 移动赋值运算符 + */ + ResourceHandle &operator=(ResourceHandle &&other) noexcept { + if (this != &other) { + reset(); + resource_ = other.resource_; + manager_ = other.manager_; + other.resource_ = nullptr; + other.manager_ = nullptr; + } + return *this; + } + + /** + * @brief 箭头运算符 + * @return 资源指针 + */ + T *operator->() const { return resource_; } + + /** + * @brief 解引用运算符 + * @return 资源引用 + */ + T &operator*() const { return *resource_; } + + /** + * @brief 获取原始指针 + * @return 资源指针 + */ + T *get() const { return resource_; } + + /** + * @brief 布尔转换运算符 + */ + explicit operator bool() const { return resource_ != nullptr; } + + /** + * @brief 检查有效性 + * @return 有效返回 true + */ + bool isValid() const { return resource_ != nullptr; } + + /** + * @brief 空指针比较 + */ + bool operator==(std::nullptr_t) const { return resource_ == nullptr; } + + /** + * @brief 空指针比较 + */ + bool operator!=(std::nullptr_t) const { return resource_ != nullptr; } + + /** + * @brief 重置句柄 + */ + void reset() { + if (resource_ && manager_) { + manager_->release(resource_); + } + resource_ = nullptr; + manager_ = nullptr; + } + + /** + * @brief 交换句柄 + * @param other 另一个句柄 + */ + void swap(ResourceHandle &other) noexcept { + std::swap(resource_, other.resource_); + std::swap(manager_, other.manager_); + } + + /** + * @brief 获取引用计数 + * @return 引用计数,无效句柄返回 0 + */ + uint32_t getRefCount() const { + if (resource_ && manager_) { + return manager_->getRefCount(resource_); + } + return 0; + } + +private: + T *resource_; + ResourceManager *manager_; +}; + +/** + * @brief 纹理数据前向声明 + */ +struct TextureData; + +/** + * @brief 字体数据前向声明 + */ +struct FontData; + +/** + * @brief 着色器数据前向声明 + */ +struct ShaderData; + +/** + * @brief 材质数据前向声明 + */ +struct MaterialData; + +/** + * @brief 网格数据前向声明 + */ +struct MeshData; + +/** + * @brief 音频数据前向声明 + */ +struct AudioData; + +/** + * @brief 资源句柄类型别名 + */ +using TextureHandle = ResourceHandle; +using FontHandle = ResourceHandle; +using ShaderHandle = ResourceHandle; +using MaterialHandle = ResourceHandle; +using MeshHandle = ResourceHandle; +using AudioHandle = ResourceHandle; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/resources/resource_system.h b/Extra2D/include/extra2d/resources/resource_system.h new file mode 100644 index 0000000..55895f8 --- /dev/null +++ b/Extra2D/include/extra2d/resources/resource_system.h @@ -0,0 +1,358 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 资源系统快捷访问 + * + * 提供全局静态方法,简化资源访问 + * 内部通过 ServiceLocator 获取 IResourceService + */ +class ResourceSystem { +public: + /** + * @brief 获取资源服务实例 + * @return 资源服务指针 + */ + static IResourceService *getService() { + auto service = ServiceLocator::instance().tryGetService(); + return service ? service.get() : nullptr; + } + + /** + * @brief 同步加载资源 + * @tparam T 资源类型 + * @param path 资源路径 + * @return 资源句柄 + */ + template static ResourceHandle load(const std::string &path) { + auto *service = getService(); + if (service) { + return service->load(path); + } + return ResourceHandle(); + } + + /** + * @brief 异步加载资源 + * @tparam T 资源类型 + * @param path 资源路径 + * @param callback 加载完成回调 + */ + template + static void loadAsync(const std::string &path, + std::function)> callback) { + auto *service = getService(); + if (service) { + service->loadAsync(path, callback); + } else if (callback) { + callback(ResourceHandle()); + } + } + + /** + * @brief 预加载资源 + * @param path 资源路径 + */ + static void preload(const std::string &path) { + auto *service = getService(); + if (service) { + service->preload(path); + } + } + + /** + * @brief 执行垃圾回收 + */ + static void gc() { + auto *service = getService(); + if (service) { + service->gc(); + } + } + + /** + * @brief 清除未使用的资源 + */ + static void clearUnused() { + auto *service = getService(); + if (service) { + service->clearUnused(); + } + } + + /** + * @brief 清除所有资源 + */ + static void clearAll() { + auto *service = getService(); + if (service) { + service->clearAll(); + } + } + + /** + * @brief 获取资源统计信息 + * @return 统计信息 + */ + static IResourceService::Stats getStats() { + auto *service = getService(); + if (service) { + return service->getStats(); + } + return IResourceService::Stats(); + } + + /** + * @brief 检查资源是否存在 + * @param path 资源路径 + * @return 存在返回 true + */ + static bool exists(const std::string &path) { + auto *service = getService(); + if (service) { + return service->exists(path); + } + return false; + } + + /** + * @brief 读取原始数据 + * @param path 资源路径 + * @return 原始数据 + */ + static std::vector readData(const std::string &path) { + auto *service = getService(); + if (service) { + return service->readData(path); + } + return {}; + } + + /** + * @brief 加载资源包 + * @param path 资源包路径 + * @return 加载成功返回 true + */ + static bool loadBundle(const std::string &path) { + auto *service = getService(); + if (service) { + return service->loadBundle(path); + } + return false; + } + + /** + * @brief 异步加载资源包 + * @param path 资源包路径 + * @param callback 加载完成回调 + */ + static void loadBundleAsync(const std::string &path, + std::function callback) { + auto *service = getService(); + if (service) { + service->loadBundleAsync(path, callback); + } else if (callback) { + callback(false); + } + } + + /** + * @brief 卸载资源包 + * @param path 资源包路径 + */ + static void unloadBundle(const std::string &path) { + auto *service = getService(); + if (service) { + service->unloadBundle(path); + } + } +}; + +/** + * @brief 基础资源管理器模板 + * + * 提供引用计数和缓存管理 + */ +template class BaseResourceManager : public ResourceManager { +public: + /** + * @brief 资源缓存条目 + */ + struct CacheEntry { + Ptr resource; + std::string path; + uint32_t refCount = 0; + size_t memorySize = 0; + uint64_t lastAccessTime = 0; + }; + + BaseResourceManager() = default; + ~BaseResourceManager() override = default; + + /** + * @brief 增加引用计数 + */ + void addRef(T *resource) override { + std::lock_guard lock(mutex_); + auto it = cache_.find(resource); + if (it != cache_.end()) { + it->second.refCount++; + it->second.lastAccessTime = getCurrentTime(); + } + } + + /** + * @brief 减少引用计数 + */ + void release(T *resource) override { + std::lock_guard lock(mutex_); + auto it = cache_.find(resource); + if (it != cache_.end()) { + it->second.refCount--; + if (it->second.refCount == 0) { + totalMemory_ -= it->second.memorySize; + cache_.erase(it); + } + } + } + + /** + * @brief 获取引用计数 + */ + uint32_t getRefCount(T *resource) const override { + std::lock_guard lock(mutex_); + auto it = cache_.find(resource); + if (it != cache_.end()) { + return it->second.refCount; + } + return 0; + } + + /** + * @brief 缓存资源 + * @param path 资源路径 + * @param resource 资源指针 + * @param memorySize 内存大小 + */ + void cache(const std::string &path, Ptr resource, size_t memorySize = 0) { + if (!resource) + return; + + std::lock_guard lock(mutex_); + CacheEntry entry; + entry.resource = resource; + entry.path = path; + entry.refCount = 1; + entry.memorySize = memorySize; + entry.lastAccessTime = getCurrentTime(); + + T *rawPtr = resource.get(); + cache_[rawPtr] = entry; + pathIndex_[path] = rawPtr; + totalMemory_ += memorySize; + } + + /** + * @brief 从缓存获取资源 + * @param path 资源路径 + * @return 资源指针,不存在返回 nullptr + */ + Ptr getFromCache(const std::string &path) { + std::lock_guard lock(mutex_); + auto it = pathIndex_.find(path); + if (it != pathIndex_.end()) { + auto cacheIt = cache_.find(it->second); + if (cacheIt != cache_.end()) { + cacheIt->second.refCount++; + cacheIt->second.lastAccessTime = getCurrentTime(); + return cacheIt->second.resource; + } + } + return nullptr; + } + + /** + * @brief 检查缓存是否存在 + * @param path 资源路径 + * @return 存在返回 true + */ + bool hasInCache(const std::string &path) const { + std::lock_guard lock(mutex_); + return pathIndex_.find(path) != pathIndex_.end(); + } + + /** + * @brief 清除未使用的资源 + */ + void clearUnused() { + std::lock_guard lock(mutex_); + for (auto it = cache_.begin(); it != cache_.end();) { + if (it->second.refCount == 0) { + totalMemory_ -= it->second.memorySize; + pathIndex_.erase(it->second.path); + it = cache_.erase(it); + } else { + ++it; + } + } + } + + /** + * @brief 清除所有资源 + */ + void clearAll() { + std::lock_guard lock(mutex_); + cache_.clear(); + pathIndex_.clear(); + totalMemory_ = 0; + } + + /** + * @brief 获取总内存使用量 + * @return 内存大小(字节) + */ + size_t getTotalMemory() const { + std::lock_guard lock(mutex_); + return totalMemory_; + } + + /** + * @brief 获取资源数量 + * @return 资源数量 + */ + size_t getCount() const { + std::lock_guard lock(mutex_); + return cache_.size(); + } + +protected: + /** + * @brief 获取当前时间戳 + * @return 时间戳(毫秒) + */ + static uint64_t getCurrentTime() { + auto now = std::chrono::steady_clock::now(); + auto ms = std::chrono::duration_cast( + now.time_since_epoch()); + return static_cast(ms.count()); + } + + mutable std::mutex mutex_; + std::unordered_map cache_; + std::unordered_map pathIndex_; + size_t totalMemory_ = 0; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/services/logger_service.h b/Extra2D/include/extra2d/services/logger_service.h index 9f17604..6c180fb 100644 --- a/Extra2D/include/extra2d/services/logger_service.h +++ b/Extra2D/include/extra2d/services/logger_service.h @@ -287,7 +287,7 @@ std::string format_str(const char *fmt, Args &&...args) { #define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__) #define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__) #define E2D_FATAL(...) E2D_LOG_FATAL(__VA_ARGS__) - +#define E2D_DEBUG(...) E2D_LOG_DEBUG(__VA_ARGS__) // 带颜色参数的日志宏 #define E2D_LOG_COLOR(level, color, ...) \ do { \ diff --git a/Extra2D/include/extra2d/services/resource_service.h b/Extra2D/include/extra2d/services/resource_service.h new file mode 100644 index 0000000..3c6bea2 --- /dev/null +++ b/Extra2D/include/extra2d/services/resource_service.h @@ -0,0 +1,180 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 资源服务接口 + * + * 提供统一的资源加载和管理功能 + * 支持同步/异步加载、自动缓存、引用计数 + */ +class IResourceService : public IService { +public: + ~IResourceService() override = default; + + /** + * @brief 初始化资源服务 + * @param assetRoot 资源根目录路径 + * @return 初始化成功返回 true + */ + virtual bool init(const std::string &assetRoot) = 0; + + /** + * @brief 关闭资源服务 + */ + virtual void shutdown() override = 0; + + /** + * @brief 加载资源包 + * @param path 资源包路径 + * @return 加载成功返回 true + */ + virtual bool loadBundle(const std::string &path) = 0; + + /** + * @brief 异步加载资源包 + * @param path 资源包路径 + * @param callback 加载完成回调 + */ + virtual void loadBundleAsync(const std::string &path, + std::function callback) = 0; + + /** + * @brief 卸载资源包 + * @param path 资源包路径 + */ + virtual void unloadBundle(const std::string &path) = 0; + + /** + * @brief 同步加载资源 + * @tparam T 资源类型 + * @param path 资源路径 + * @return 资源句柄 + */ + template ResourceHandle load(const std::string &path); + + /** + * @brief 异步加载资源 + * @tparam T 资源类型 + * @param path 资源路径 + * @param callback 加载完成回调 + */ + template + void loadAsync(const std::string &path, + std::function)> callback); + + /** + * @brief 预加载资源 + * @param path 资源路径 + */ + virtual void preload(const std::string &path) = 0; + + /** + * @brief 执行垃圾回收 + */ + virtual void gc() = 0; + + /** + * @brief 清除未使用的资源 + */ + virtual void clearUnused() = 0; + + /** + * @brief 清除所有资源 + */ + virtual void clearAll() = 0; + + /** + * @brief 资源统计信息 + */ + struct Stats { + size_t totalMemory = 0; + size_t textureMemory = 0; + size_t audioMemory = 0; + size_t totalCount = 0; + size_t textureCount = 0; + size_t audioCount = 0; + }; + + /** + * @brief 获取资源统计信息 + * @return 统计信息 + */ + virtual Stats getStats() const = 0; + + /** + * @brief 检查资源是否存在 + * @param path 资源路径 + * @return 存在返回 true + */ + virtual bool exists(const std::string &path) const = 0; + + /** + * @brief 读取原始数据 + * @param path 资源路径 + * @return 原始数据 + */ + virtual std::vector readData(const std::string &path) const = 0; + + /** + * @brief 获取资源根目录 + * @return 根目录路径 + */ + virtual const std::string &getAssetRoot() const = 0; + + ServiceInfo getServiceInfo() const override { + ServiceInfo info; + info.name = "ResourceService"; + info.priority = ServicePriority::Resource; + info.enabled = true; + return info; + } +}; + +/** + * @brief 资源类型标识 + */ +enum class ResourceType : uint8_t { + Unknown = 0, + Texture = 1, + Font = 2, + Shader = 3, + Material = 4, + Mesh = 5, + Audio = 6, + Config = 7, + Custom = 255 +}; + +/** + * @brief 资源加载状态 + */ +enum class ResourceState : uint8_t { + Unloaded = 0, + Loading = 1, + Loaded = 2, + Failed = 3, + Unloading = 4 +}; + +/** + * @brief 资源元数据 + */ +struct ResourceMeta { + std::string path; + ResourceType type = ResourceType::Unknown; + ResourceState state = ResourceState::Unloaded; + size_t size = 0; + uint32_t refCount = 0; + uint64_t lastAccessTime = 0; +}; + +} // namespace extra2d diff --git a/Extra2D/src/core/aes_cipher.cpp b/Extra2D/src/core/aes_cipher.cpp new file mode 100644 index 0000000..0298186 --- /dev/null +++ b/Extra2D/src/core/aes_cipher.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief AESCipher 实现细节 + */ +class AESCipher::Impl { +public: + EVP_CIPHER_CTX* ctx = nullptr; + + Impl() { + ctx = EVP_CIPHER_CTX_new(); + } + + ~Impl() { + if (ctx) { + EVP_CIPHER_CTX_free(ctx); + } + } +}; + +AESCipher::AESCipher() : impl_(makeUnique()) { +} + +AESCipher::~AESCipher() { + clearKey(); +} + +bool AESCipher::setKey(const std::vector& key, + const std::vector& iv) { + return setKey(key.data(), key.size(), iv.data(), iv.size()); +} + +bool AESCipher::setKey(const uint8_t* key, size_t keySize, + const uint8_t* iv, size_t ivSize) { + if (keySize != KEY_SIZE) { + E2D_ERROR("Invalid key size: expected {}, got {}", KEY_SIZE, keySize); + return false; + } + + if (ivSize != IV_SIZE) { + E2D_ERROR("Invalid IV size: expected {}, got {}", IV_SIZE, ivSize); + return false; + } + + key_.assign(key, key + keySize); + iv_.assign(iv, iv + ivSize); + keySet_ = true; + return true; +} + +std::vector AESCipher::encrypt(const std::vector& data) { + return encrypt(data.data(), data.size()); +} + +std::vector AESCipher::encrypt(const uint8_t* data, size_t size) { + if (!keySet_) { + E2D_ERROR("Key not set"); + return {}; + } + + if (!data || size == 0) { + return {}; + } + + int ciphertextLen = 0; + int finalLen = 0; + std::vector ciphertext(size + BLOCK_SIZE); + + if (EVP_EncryptInit_ex(impl_->ctx, EVP_aes_256_cbc(), nullptr, + key_.data(), iv_.data()) != 1) { + E2D_ERROR("EVP_EncryptInit_ex failed"); + return {}; + } + + if (EVP_EncryptUpdate(impl_->ctx, ciphertext.data(), &ciphertextLen, + data, static_cast(size)) != 1) { + E2D_ERROR("EVP_EncryptUpdate failed"); + return {}; + } + + if (EVP_EncryptFinal_ex(impl_->ctx, ciphertext.data() + ciphertextLen, + &finalLen) != 1) { + E2D_ERROR("EVP_EncryptFinal_ex failed"); + return {}; + } + + ciphertext.resize(ciphertextLen + finalLen); + return ciphertext; +} + +std::vector AESCipher::decrypt(const std::vector& data) { + return decrypt(data.data(), data.size()); +} + +std::vector AESCipher::decrypt(const uint8_t* data, size_t size) { + if (!keySet_) { + E2D_ERROR("Key not set"); + return {}; + } + + if (!data || size == 0) { + return {}; + } + + int plaintextLen = 0; + int finalLen = 0; + std::vector plaintext(size + BLOCK_SIZE); + + if (EVP_DecryptInit_ex(impl_->ctx, EVP_aes_256_cbc(), nullptr, + key_.data(), iv_.data()) != 1) { + E2D_ERROR("EVP_DecryptInit_ex failed"); + return {}; + } + + if (EVP_DecryptUpdate(impl_->ctx, plaintext.data(), &plaintextLen, + data, static_cast(size)) != 1) { + E2D_ERROR("EVP_DecryptUpdate failed"); + return {}; + } + + if (EVP_DecryptFinal_ex(impl_->ctx, plaintext.data() + plaintextLen, + &finalLen) != 1) { + E2D_ERROR("EVP_DecryptFinal_ex failed - wrong key or corrupted data"); + return {}; + } + + plaintext.resize(plaintextLen + finalLen); + return plaintext; +} + +bool AESCipher::encryptInPlace(std::vector& data) { + auto encrypted = encrypt(data); + if (encrypted.empty() && !data.empty()) { + return false; + } + data = std::move(encrypted); + return true; +} + +bool AESCipher::decryptInPlace(std::vector& data) { + auto decrypted = decrypt(data); + if (decrypted.empty() && !data.empty()) { + return false; + } + data = std::move(decrypted); + return true; +} + +void AESCipher::clearKey() { + if (!key_.empty()) { + std::memset(key_.data(), 0, key_.size()); + key_.clear(); + } + if (!iv_.empty()) { + std::memset(iv_.data(), 0, iv_.size()); + iv_.clear(); + } + keySet_ = false; +} + +void AESCipher::generateRandomKey(std::vector& key, + std::vector& iv) { + key.resize(KEY_SIZE); + iv.resize(IV_SIZE); + + if (RAND_bytes(key.data(), KEY_SIZE) != 1) { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dis(0, 255); + for (size_t i = 0; i < KEY_SIZE; ++i) { + key[i] = dis(gen); + } + for (size_t i = 0; i < IV_SIZE; ++i) { + iv[i] = dis(gen); + } + } else { + RAND_bytes(iv.data(), IV_SIZE); + } +} + +void AESCipher::deriveKeyFromPassword(const std::string& password, + const std::vector& salt, + std::vector& key, + std::vector& iv, + int iterations) { + key.resize(KEY_SIZE); + iv.resize(IV_SIZE); + + if (salt.empty()) { + std::vector defaultSalt(16, 0); + PKCS5_PBKDF2_HMAC(password.c_str(), static_cast(password.length()), + defaultSalt.data(), static_cast(defaultSalt.size()), + iterations, EVP_sha256(), + KEY_SIZE, key.data()); + } else { + PKCS5_PBKDF2_HMAC(password.c_str(), static_cast(password.length()), + salt.data(), static_cast(salt.size()), + iterations, EVP_sha256(), + KEY_SIZE, key.data()); + } + + std::vector ivDerive(IV_SIZE); + std::vector saltForIv = salt.empty() ? std::vector(16, 1) : salt; + for (auto& b : saltForIv) { + b = ~b; + } + + PKCS5_PBKDF2_HMAC(password.c_str(), static_cast(password.length()), + saltForIv.data(), static_cast(saltForIv.size()), + iterations, EVP_sha256(), + IV_SIZE, iv.data()); +} + +} // namespace extra2d diff --git a/Extra2D/src/core/compression.cpp b/Extra2D/src/core/compression.cpp new file mode 100644 index 0000000..686399f --- /dev/null +++ b/Extra2D/src/core/compression.cpp @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +std::vector Compression::zlibCompress(const std::vector &data, + int level) { + if (data.empty()) { + return {}; + } + + uLongf compressedSize = compressBound(data.size()); + std::vector compressed(compressedSize); + + int result = compress2(compressed.data(), &compressedSize, data.data(), + data.size(), level); + + if (result != Z_OK) { + E2D_ERROR("Zlib compression failed with error: {}", result); + return {}; + } + + compressed.resize(compressedSize); + return compressed; +} + +std::vector +Compression::zlibDecompress(const std::vector &data, + size_t originalSize) { + if (data.empty()) { + return {}; + } + + size_t outputSize = originalSize; + if (outputSize == 0) { + outputSize = data.size() * 4; + } + + std::vector decompressed(outputSize); + uLongf destLen = outputSize; + + int result = + uncompress(decompressed.data(), &destLen, data.data(), data.size()); + + if (result == Z_BUF_ERROR && originalSize == 0) { + outputSize *= 2; + decompressed.resize(outputSize); + destLen = outputSize; + result = + uncompress(decompressed.data(), &destLen, data.data(), data.size()); + } + + if (result != Z_OK) { + E2D_ERROR("Zlib decompression failed with error: {}", result); + return {}; + } + + decompressed.resize(destLen); + return decompressed; +} + +std::vector +Compression::lz4Compress(const std::vector &data) { + if (data.empty()) { + return {}; + } + + int compressedSize = LZ4_compressBound(static_cast(data.size())); + std::vector compressed(compressedSize); + + compressedSize = + LZ4_compress_default(reinterpret_cast(data.data()), + reinterpret_cast(compressed.data()), + static_cast(data.size()), compressedSize); + + if (compressedSize <= 0) { + E2D_ERROR("LZ4 compression failed"); + return {}; + } + + compressed.resize(compressedSize); + return compressed; +} + +std::vector +Compression::lz4Decompress(const std::vector &data, + size_t originalSize) { + if (data.empty() || originalSize == 0) { + E2D_ERROR("LZ4 decompression requires original size"); + return {}; + } + + std::vector decompressed(originalSize); + + int result = LZ4_decompress_safe( + reinterpret_cast(data.data()), + reinterpret_cast(decompressed.data()), + static_cast(data.size()), static_cast(originalSize)); + + if (result < 0) { + E2D_ERROR("LZ4 decompression failed with error: {}", result); + return {}; + } + + return decompressed; +} + +std::vector Compression::zstdCompress(const std::vector &data, + int level) { + if (data.empty()) { + return {}; + } + + size_t compressedSize = ZSTD_compressBound(data.size()); + std::vector compressed(compressedSize); + + compressedSize = ZSTD_compress(compressed.data(), compressedSize, data.data(), + data.size(), level); + + if (ZSTD_isError(compressedSize)) { + E2D_ERROR("Zstd compression failed: {}", ZSTD_getErrorName(compressedSize)); + return {}; + } + + compressed.resize(compressedSize); + return compressed; +} + +std::vector +Compression::zstdDecompress(const std::vector &data, + size_t originalSize) { + if (data.empty()) { + return {}; + } + + size_t outputSize = originalSize; + if (outputSize == 0) { + outputSize = ZSTD_getFrameContentSize(data.data(), data.size()); + if (outputSize == ZSTD_CONTENTSIZE_ERROR || + outputSize == ZSTD_CONTENTSIZE_UNKNOWN) { + outputSize = data.size() * 4; + } + } + + std::vector decompressed(outputSize); + + size_t result = ZSTD_decompress(decompressed.data(), outputSize, data.data(), + data.size()); + + if (ZSTD_isError(result)) { + E2D_ERROR("Zstd decompression failed: {}", ZSTD_getErrorName(result)); + return {}; + } + + decompressed.resize(result); + return decompressed; +} + +std::vector Compression::compress(const std::vector &data, + CompressionType type, int level) { + switch (type) { + case CompressionType::None: + return data; + case CompressionType::Zlib: + return zlibCompress(data, level >= 0 ? level : 6); + case CompressionType::LZ4: + return lz4Compress(data); + case CompressionType::Zstd: + return zstdCompress(data, level >= 0 ? level : 3); + default: + return data; + } +} + +std::vector Compression::decompress(const std::vector &data, + CompressionType type, + size_t originalSize) { + switch (type) { + case CompressionType::None: + return data; + case CompressionType::Zlib: + return zlibDecompress(data, originalSize); + case CompressionType::LZ4: + return lz4Decompress(data, originalSize); + case CompressionType::Zstd: + return zstdDecompress(data, originalSize); + default: + return data; + } +} + +size_t Compression::estimateCompressedSize(size_t originalSize, + CompressionType type) { + switch (type) { + case CompressionType::None: + return originalSize; + case CompressionType::Zlib: + return compressBound(originalSize); + case CompressionType::LZ4: + return LZ4_compressBound(static_cast(originalSize)); + case CompressionType::Zstd: + return ZSTD_compressBound(originalSize); + default: + return originalSize; + } +} + +const char *Compression::getCompressionName(CompressionType type) { + switch (type) { + case CompressionType::None: + return "None"; + case CompressionType::Zlib: + return "Zlib"; + case CompressionType::LZ4: + return "LZ4"; + case CompressionType::Zstd: + return "Zstd"; + default: + return "Unknown"; + } +} + +uint32_t Compression::crc32(const std::vector &data) { + return crc32(data.data(), data.size()); +} + +uint32_t Compression::crc32(const uint8_t *data, size_t size) { + return static_cast(::crc32(0L, data, static_cast(size))); +} + +uint32_t Compression::adler32(const std::vector &data) { + return adler32(data.data(), data.size()); +} + +uint32_t Compression::adler32(const uint8_t *data, size_t size) { + return static_cast(::adler32(1L, data, static_cast(size))); +} + +} // namespace extra2d diff --git a/Extra2D/src/core/unicode_utils.cpp b/Extra2D/src/core/unicode_utils.cpp new file mode 100644 index 0000000..4793cd0 --- /dev/null +++ b/Extra2D/src/core/unicode_utils.cpp @@ -0,0 +1,328 @@ +#include +#include +#include + +namespace extra2d { + +std::u32string UnicodeUtils::toUtf32(const std::string &text, + TextEncoding encoding) { + if (text.empty()) { + return {}; + } + + switch (encoding) { + case TextEncoding::UTF8: + return utf8ToUtf32(text); + case TextEncoding::UTF16: { + std::u16string utf16; + utf16.resize(text.size() / 2); + std::memcpy(utf16.data(), text.data(), text.size()); + return utf16ToUtf32(utf16); + } + case TextEncoding::UTF32: { + std::u32string utf32; + utf32.resize(text.size() / 4); + std::memcpy(utf32.data(), text.data(), text.size()); + return utf32; + } + case TextEncoding::GBK: + return gbkToUtf32(text); + case TextEncoding::Auto: + return utf8ToUtf32(text); + default: + return utf8ToUtf32(text); + } +} + +std::u32string UnicodeUtils::utf8ToUtf32(const std::string &utf8) { + if (utf8.empty()) { + return {}; + } + + size_t len = simdutf::utf32_length_from_utf8(utf8.data(), utf8.size()); + std::u32string utf32; + utf32.resize(len); + + size_t result = + simdutf::convert_utf8_to_utf32(utf8.data(), utf8.size(), utf32.data()); + + if (result == 0 && len > 0) { + return {}; + } + + utf32.resize(result); + return utf32; +} + +std::u32string UnicodeUtils::utf8ToUtf32(std::string_view utf8) { + if (utf8.empty()) { + return {}; + } + + size_t len = simdutf::utf32_length_from_utf8(utf8.data(), utf8.size()); + std::u32string utf32; + utf32.resize(len); + + size_t result = + simdutf::convert_utf8_to_utf32(utf8.data(), utf8.size(), utf32.data()); + + if (result == 0 && len > 0) { + return {}; + } + + utf32.resize(result); + return utf32; +} + +std::u32string UnicodeUtils::utf16ToUtf32(const std::u16string &utf16) { + if (utf16.empty()) { + return {}; + } + + size_t len = simdutf::utf32_length_from_utf16(utf16.data(), utf16.size()); + std::u32string utf32; + utf32.resize(len); + + size_t result = + simdutf::convert_utf16_to_utf32(utf16.data(), utf16.size(), utf32.data()); + + if (result == 0 && len > 0) { + return {}; + } + + utf32.resize(result); + return utf32; +} + +std::string UnicodeUtils::utf32ToUtf8(const std::u32string &utf32) { + if (utf32.empty()) { + return {}; + } + + size_t len = simdutf::utf8_length_from_utf32(utf32.data(), utf32.size()); + std::string utf8; + utf8.resize(len); + + size_t result = + simdutf::convert_utf32_to_utf8(utf32.data(), utf32.size(), utf8.data()); + + if (result == 0 && len > 0) { + return {}; + } + + utf8.resize(result); + return utf8; +} + +std::u16string UnicodeUtils::utf32ToUtf16(const std::u32string &utf32) { + if (utf32.empty()) { + return {}; + } + + size_t len = simdutf::utf16_length_from_utf32(utf32.data(), utf32.size()); + std::u16string utf16; + utf16.resize(len); + + size_t result = + simdutf::convert_utf32_to_utf16(utf32.data(), utf32.size(), utf16.data()); + + if (result == 0 && len > 0) { + return {}; + } + + utf16.resize(result); + return utf16; +} + +TextEncoding UnicodeUtils::detectEncoding(const std::string &data) { + if (data.empty()) { + return TextEncoding::UTF8; + } + + simdutf::encoding_type detected = + simdutf::autodetect_encoding(data.data(), data.size()); + + switch (detected) { + case simdutf::encoding_type::UTF8: + return TextEncoding::UTF8; + case simdutf::encoding_type::UTF16_LE: + case simdutf::encoding_type::UTF16_BE: + return TextEncoding::UTF16; + case simdutf::encoding_type::UTF32_LE: + case simdutf::encoding_type::UTF32_BE: + return TextEncoding::UTF32; + default: + return TextEncoding::UTF8; + } +} + +size_t UnicodeUtils::countChars(const std::string &text, + TextEncoding encoding) { + if (text.empty()) { + return 0; + } + + switch (encoding) { + case TextEncoding::UTF8: + return countCharsUtf8(text); + case TextEncoding::UTF16: { + std::u16string utf16; + utf16.resize(text.size() / 2); + std::memcpy(utf16.data(), text.data(), text.size()); + return countCharsUtf16(utf16); + } + case TextEncoding::UTF32: + return text.size() / 4; + default: + return countCharsUtf8(text); + } +} + +size_t UnicodeUtils::countCharsUtf8(const std::string &utf8) { + if (utf8.empty()) { + return 0; + } + return simdutf::utf32_length_from_utf8(utf8.data(), utf8.size()); +} + +size_t UnicodeUtils::countCharsUtf16(const std::u16string &utf16) { + if (utf16.empty()) { + return 0; + } + return simdutf::utf32_length_from_utf16(utf16.data(), utf16.size()); +} + +bool UnicodeUtils::validateEncoding(const std::string &text, + TextEncoding encoding) { + if (text.empty()) { + return true; + } + + switch (encoding) { + case TextEncoding::UTF8: + return validateUtf8(text); + case TextEncoding::UTF16: { + if (text.size() % 2 != 0) { + return false; + } + return simdutf::validate_utf16( + reinterpret_cast(text.data()), text.size() / 2); + } + case TextEncoding::UTF32: { + if (text.size() % 4 != 0) { + return false; + } + return simdutf::validate_utf32( + reinterpret_cast(text.data()), text.size() / 4); + } + default: + return validateUtf8(text); + } +} + +bool UnicodeUtils::validateUtf8(const std::string &utf8) { + if (utf8.empty()) { + return true; + } + return simdutf::validate_utf8(utf8.data(), utf8.size()); +} + +bool UnicodeUtils::validateUtf16(const std::u16string &utf16) { + if (utf16.empty()) { + return true; + } + return simdutf::validate_utf16(utf16.data(), utf16.size()); +} + +std::u32string UnicodeUtils::gbkToUtf32(const std::string &gbk) { + return utf8ToUtf32(gbk); +} + +std::string UnicodeUtils::utf32ToGbk(const std::u32string &utf32) { + return utf32ToUtf8(utf32); +} + +const char *UnicodeUtils::getEncodingName(TextEncoding encoding) { + switch (encoding) { + case TextEncoding::UTF8: + return "UTF-8"; + case TextEncoding::UTF16: + return "UTF-16"; + case TextEncoding::UTF32: + return "UTF-32"; + case TextEncoding::GBK: + return "GBK"; + case TextEncoding::Auto: + return "Auto"; + default: + return "Unknown"; + } +} + +bool UnicodeUtils::isValidCodepoint(uint32_t codepoint) { + return codepoint <= 0x10FFFF && !(codepoint >= 0xD800 && codepoint <= 0xDFFF); +} + +bool UnicodeUtils::isWhitespace(uint32_t codepoint) { + return codepoint == ' ' || codepoint == '\t' || codepoint == '\n' || + codepoint == '\r' || codepoint == '\v' || codepoint == '\f' || + codepoint == 0x00A0 || codepoint == 0x1680 || + (codepoint >= 0x2000 && codepoint <= 0x200A) || codepoint == 0x2028 || + codepoint == 0x2029 || codepoint == 0x202F || codepoint == 0x205F || + codepoint == 0x3000; +} + +bool UnicodeUtils::isNewline(uint32_t codepoint) { + return codepoint == '\n' || codepoint == '\r' || codepoint == 0x2028 || + codepoint == 0x2029; +} + +bool UnicodeUtils::isControl(uint32_t codepoint) { + return (codepoint <= 0x1F) || (codepoint >= 0x7F && codepoint <= 0x9F); +} + +bool UnicodeUtils::isChinese(uint32_t codepoint) { + return (codepoint >= 0x4E00 && codepoint <= 0x9FFF) || + (codepoint >= 0x3400 && codepoint <= 0x4DBF) || + (codepoint >= 0x20000 && codepoint <= 0x2A6DF) || + (codepoint >= 0x2A700 && codepoint <= 0x2B73F) || + (codepoint >= 0x2B740 && codepoint <= 0x2B81F) || + (codepoint >= 0x2B820 && codepoint <= 0x2CEAF) || + (codepoint >= 0xF900 && codepoint <= 0xFAFF) || + (codepoint >= 0x2F800 && codepoint <= 0x2FA1F); +} + +bool UnicodeUtils::isJapanese(uint32_t codepoint) { + return (codepoint >= 0x3040 && codepoint <= 0x309F) || + (codepoint >= 0x30A0 && codepoint <= 0x30FF) || + (codepoint >= 0x31F0 && codepoint <= 0x31FF) || + (codepoint >= 0xFF65 && codepoint <= 0xFF9F); +} + +bool UnicodeUtils::isKorean(uint32_t codepoint) { + return (codepoint >= 0xAC00 && codepoint <= 0xD7AF) || + (codepoint >= 0x1100 && codepoint <= 0x11FF) || + (codepoint >= 0x3130 && codepoint <= 0x318F) || + (codepoint >= 0xA960 && codepoint <= 0xA97F) || + (codepoint >= 0xD7B0 && codepoint <= 0xD7FF); +} + +bool UnicodeUtils::isCJK(uint32_t codepoint) { + return isChinese(codepoint) || isJapanese(codepoint) || isKorean(codepoint); +} + +uint32_t UnicodeUtils::toUpper(uint32_t codepoint) { + if (codepoint >= 'a' && codepoint <= 'z') { + return codepoint - 32; + } + return codepoint; +} + +uint32_t UnicodeUtils::toLower(uint32_t codepoint) { + if (codepoint >= 'A' && codepoint <= 'Z') { + return codepoint + 32; + } + return codepoint; +} + +} // namespace extra2d diff --git a/Extra2D/src/services/resource_service.cpp b/Extra2D/src/services/resource_service.cpp new file mode 100644 index 0000000..72a6dd8 --- /dev/null +++ b/Extra2D/src/services/resource_service.cpp @@ -0,0 +1,389 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 文件系统资源包实现 + */ +class FileBundle : public IAssetBundle { +public: + explicit FileBundle(const std::string &rootPath) : rootPath_(rootPath) { + if (!std::filesystem::exists(rootPath_)) { + E2D_WARN("FileBundle root path does not exist: {}", rootPath_); + } + } + + bool contains(const std::string &path) const override { + auto fullPath = std::filesystem::path(rootPath_) / path; + return std::filesystem::exists(fullPath); + } + + std::vector read(const std::string &path) const override { + auto fullPath = std::filesystem::path(rootPath_) / path; + if (!std::filesystem::exists(fullPath)) { + E2D_WARN("File not found: {}", path); + return {}; + } + + std::ifstream file(fullPath, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + E2D_WARN("Failed to open file: {}", path); + return {}; + } + + auto size = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector data(size); + file.read(reinterpret_cast(data.data()), size); + return data; + } + + void + readAsync(const std::string &path, + std::function)> callback) const override { + std::thread([this, path, callback]() { + auto data = read(path); + if (callback) { + callback(std::move(data)); + } + }).detach(); + } + + std::unique_ptr openStream(const std::string &path) override { + auto fullPath = std::filesystem::path(rootPath_) / path; + return std::make_unique(fullPath, std::ios::binary); + } + + AssetInfo getInfo(const std::string &path) const override { + AssetInfo info; + auto fullPath = std::filesystem::path(rootPath_) / path; + if (std::filesystem::exists(fullPath)) { + info.size = std::filesystem::file_size(fullPath); + auto ftime = std::filesystem::last_write_time(fullPath); + auto sctp = std::chrono::time_point_cast( + ftime - std::filesystem::file_time_type::clock::now() + + std::chrono::system_clock::now()); + info.modifiedTime = sctp.time_since_epoch().count(); + } + return info; + } + + std::vector list() const override { + std::vector result; + if (!std::filesystem::exists(rootPath_)) { + return result; + } + + for (const auto &entry : + std::filesystem::recursive_directory_iterator(rootPath_)) { + if (entry.is_regular_file()) { + auto relative = std::filesystem::relative(entry.path(), rootPath_); + result.push_back(relative.string()); + } + } + return result; + } + + const std::string &getName() const override { return rootPath_; } + bool isValid() const override { return std::filesystem::exists(rootPath_); } + +private: + std::string rootPath_; +}; + +/** + * @brief 打包资源包实现 + */ +class PackBundle : public IAssetBundle { +public: + PackBundle() = default; + ~PackBundle() override = default; + + bool load(const std::string &path) { + std::ifstream file(path, std::ios::binary); + if (!file.is_open()) { + E2D_ERROR("Failed to open pack file: {}", path); + return false; + } + + file.read(reinterpret_cast(&header_), sizeof(BundleHeader)); + if (std::string(header_.magic, 4) != "E2DP") { + E2D_ERROR("Invalid pack file magic: {}", path); + return false; + } + + file.seekg(header_.entryTableOffset); + entries_.clear(); + entries_.reserve(header_.entryCount); + + for (uint32_t i = 0; i < header_.entryCount; ++i) { + BundleEntry entry; + + uint16_t pathLen; + file.read(reinterpret_cast(&pathLen), sizeof(pathLen)); + entry.path.resize(pathLen); + file.read(entry.path.data(), pathLen); + + file.read(reinterpret_cast(&entry.offset), sizeof(entry.offset)); + file.read(reinterpret_cast(&entry.compressedSize), + sizeof(entry.compressedSize)); + file.read(reinterpret_cast(&entry.originalSize), + sizeof(entry.originalSize)); + file.read(reinterpret_cast(&entry.checksum), + sizeof(entry.checksum)); + file.read(reinterpret_cast(&entry.compression), + sizeof(entry.compression)); + file.read(reinterpret_cast(&entry.encryption), + sizeof(entry.encryption)); + file.read(reinterpret_cast(entry.reserved), + sizeof(entry.reserved)); + + entries_.push_back(entry); + entryMap_[entry.path] = i; + } + + filePath_ = path; + name_ = std::filesystem::path(path).filename().string(); + return true; + } + + bool contains(const std::string &path) const override { + return entryMap_.find(path) != entryMap_.end(); + } + + std::vector read(const std::string &path) const override { + auto it = entryMap_.find(path); + if (it == entryMap_.end()) { + E2D_WARN("Entry not found in pack: {}", path); + return {}; + } + + const auto &entry = entries_[it->second]; + std::ifstream file(filePath_, std::ios::binary); + if (!file.is_open()) { + E2D_ERROR("Failed to open pack file: {}", filePath_); + return {}; + } + + file.seekg(entry.offset); + std::vector data(entry.compressedSize); + file.read(reinterpret_cast(data.data()), entry.compressedSize); + + if (entry.compression != 0) { + auto compressionType = static_cast(entry.compression); + data = Compression::decompress(data, compressionType, entry.originalSize); + } + + return data; + } + + void + readAsync(const std::string &path, + std::function)> callback) const override { + std::thread([this, path, callback]() { + auto data = read(path); + if (callback) { + callback(std::move(data)); + } + }).detach(); + } + + std::unique_ptr openStream(const std::string &path) override { + auto data = read(path); + auto stream = std::make_unique(); + stream->write(reinterpret_cast(data.data()), data.size()); + stream->seekg(0); + return stream; + } + + AssetInfo getInfo(const std::string &path) const override { + AssetInfo info; + auto it = entryMap_.find(path); + if (it != entryMap_.end()) { + const auto &entry = entries_[it->second]; + info.size = entry.originalSize; + info.compressedSize = entry.compressedSize; + info.compressed = entry.compression != 0; + info.encrypted = entry.encryption != 0; + } + return info; + } + + std::vector list() const override { + std::vector result; + result.reserve(entries_.size()); + for (const auto &entry : entries_) { + result.push_back(entry.path); + } + return result; + } + + const std::string &getName() const override { return name_; } + bool isValid() const override { return !filePath_.empty(); } + +private: + std::string filePath_; + std::string name_; + BundleHeader header_; + std::vector entries_; + std::unordered_map entryMap_; +}; + +/** + * @brief 加密资源包实现 + */ +class CryptoBundle : public PackBundle { +public: + CryptoBundle() = default; + + bool load(const std::string &path, const std::vector &key, + const std::vector &iv) { + if (!cipher_.setKey(key, iv)) { + E2D_ERROR("Failed to set decryption key"); + return false; + } + return PackBundle::load(path); + } + + std::vector read(const std::string &path) const override { + auto data = PackBundle::read(path); + if (!data.empty() && cipher_.hasKey()) { + const_cast(cipher_).decryptInPlace(data); + } + return data; + } + +private: + AESCipher cipher_; +}; + +/** + * @brief 资源服务实现 + */ +class ResourceServiceImpl : public IResourceService { +public: + ResourceServiceImpl() = default; + ~ResourceServiceImpl() override { shutdown(); } + + bool initialize() override { + E2D_INFO("ResourceService initialized"); + return true; + } + + bool init(const std::string &assetRoot) override { + assetRoot_ = assetRoot; + + auto fileBundle = makePtr(assetRoot); + if (!fileBundle->isValid()) { + E2D_WARN("Asset root path does not exist: {}", assetRoot); + } + + bundles_.push_back(fileBundle); + E2D_INFO("ResourceService initialized with asset root: {}", assetRoot); + return true; + } + + void shutdown() override { + clearAll(); + bundles_.clear(); + E2D_INFO("ResourceService shutdown"); + } + + bool loadBundle(const std::string &path) override { + auto bundle = makePtr(); + if (!static_cast(bundle.get())->load(path)) { + E2D_ERROR("Failed to load bundle: {}", path); + return false; + } + + bundles_.insert(bundles_.begin(), bundle); + E2D_INFO("Loaded bundle: {}", path); + return true; + } + + void loadBundleAsync(const std::string &path, + std::function callback) override { + std::thread([this, path, callback]() { + bool success = loadBundle(path); + if (callback) { + callback(success); + } + }).detach(); + } + + void unloadBundle(const std::string &path) override { + auto it = std::find_if(bundles_.begin(), bundles_.end(), + [&path](const Ptr &bundle) { + return bundle->getName() == path; + }); + + if (it != bundles_.end()) { + bundles_.erase(it); + E2D_INFO("Unloaded bundle: {}", path); + } + } + + void preload(const std::string &path) override { readData(path); } + + void gc() override { clearUnused(); } + + void clearUnused() override { E2D_DEBUG("Clearing unused resources"); } + + void clearAll() override { E2D_DEBUG("Clearing all resources"); } + + Stats getStats() const override { return stats_; } + + bool exists(const std::string &path) const override { + for (const auto &bundle : bundles_) { + if (bundle->contains(path)) { + return true; + } + } + return false; + } + + std::vector readData(const std::string &path) const override { + for (const auto &bundle : bundles_) { + if (bundle->contains(path)) { + return bundle->read(path); + } + } + E2D_WARN("Resource not found: {}", path); + return {}; + } + + const std::string &getAssetRoot() const override { return assetRoot_; } + +private: + std::string assetRoot_; + std::vector> bundles_; + Stats stats_; +}; + +template +ResourceHandle IResourceService::load(const std::string &path) { + return ResourceHandle(); +} + +template +void IResourceService::loadAsync( + const std::string &path, std::function)> callback) { + if (callback) { + callback(ResourceHandle()); + } +} + +E2D_AUTO_REGISTER_SERVICE(IResourceService, ResourceServiceImpl); + +} // namespace extra2d diff --git a/xmake.lua b/xmake.lua index 0f41b56..544f36c 100644 --- a/xmake.lua +++ b/xmake.lua @@ -100,6 +100,12 @@ if target_plat ~= "switch" then add_requires("glm") add_requires("nlohmann_json") + -- 压缩加密库 + add_requires("zlib") + add_requires("lz4") + add_requires("zstd") + add_requires("openssl", {configs = {shared = false}}) + -- 窗口后端依赖 local backend = get_config("window_backend") or "sdl2" if backend == "glfw" then diff --git a/xmake/engine.lua b/xmake/engine.lua index 3b692c3..7a4cfd2 100644 --- a/xmake/engine.lua +++ b/xmake/engine.lua @@ -62,6 +62,15 @@ function define_extra2d_engine() -- 平台配置 local plat = get_current_plat() + -- 通用依赖包 + add_packages("glm", "nlohmann_json", {public = true}) + + -- simdutf 已包含在项目中 + add_includedirs("Extra2D/include/simdutf", {public = true}) + + -- 压缩和加密库 + add_packages("zlib", "lz4", "zstd", "openssl", {public = true}) + if plat == "switch" then -- Nintendo Switch 平台配置 local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro" @@ -81,7 +90,6 @@ function define_extra2d_engine() end elseif plat == "mingw" or plat == "windows" then -- Windows (MinGW) 平台配置 - add_packages("glm", "nlohmann_json", {public = true}) local backend = get_window_backend() if backend == "glfw" then add_packages("glfw", {public = true}) @@ -92,7 +100,6 @@ function define_extra2d_engine() {public = true}) elseif plat == "linux" then -- Linux 平台配置 - add_packages("glm", "nlohmann_json", {public = true}) local backend = get_window_backend() if backend == "glfw" then add_packages("glfw", {public = true}) @@ -102,7 +109,6 @@ function define_extra2d_engine() add_syslinks("GL", "dl", "pthread", {public = true}) elseif plat == "macosx" then -- macOS 平台配置 - add_packages("glm", "nlohmann_json", {public = true}) local backend = get_window_backend() if backend == "glfw" then add_packages("glfw", {public = true})