feat(资源系统): 添加资源管理、压缩加密和字符编码功能

实现完整的资源管理系统,包括以下功能:
1. 资源包(AssetBundle)支持文件系统、打包文件和加密资源
2. 资源服务(ResourceService)提供同步/异步加载和引用计数管理
3. 压缩工具(Compression)支持Zlib、LZ4和Zstd算法
4. 加密工具(AESCipher)实现AES-256加密/解密
5. 字符编码工具(UnicodeUtils)提供UTF8/16/32和GBK转换
6. 资源句柄(ResourceHandle)实现自动引用计数和生命周期管理

同时添加相关依赖库(zlib/lz4/zstd/openssl)和构建配置
This commit is contained in:
ChestnutYueyue 2026-02-19 17:36:37 +08:00
parent 8e385beeae
commit a705a3d300
15 changed files with 2603 additions and 4 deletions

View File

@ -0,0 +1,134 @@
#pragma once
#include <extra2d/core/types.h>
#include <cstdint>
#include <vector>
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<uint8_t>& key, const std::vector<uint8_t>& 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<uint8_t> encrypt(const std::vector<uint8_t>& data);
/**
* @brief
* @param data
* @param size
* @return
*/
std::vector<uint8_t> encrypt(const uint8_t* data, size_t size);
/**
* @brief
* @param data
* @return
*/
std::vector<uint8_t> decrypt(const std::vector<uint8_t>& data);
/**
* @brief
* @param data
* @param size
* @return
*/
std::vector<uint8_t> decrypt(const uint8_t* data, size_t size);
/**
* @brief
* @param data
* @return true
*/
bool encryptInPlace(std::vector<uint8_t>& data);
/**
* @brief
* @param data
* @return true
*/
bool decryptInPlace(std::vector<uint8_t>& data);
/**
* @brief
* @return true
*/
bool hasKey() const { return keySet_; }
/**
* @brief
*/
void clearKey();
/**
* @brief
* @param key
* @param iv IV
*/
static void generateRandomKey(std::vector<uint8_t>& key, std::vector<uint8_t>& iv);
/**
* @brief
* @param password
* @param salt
* @param key
* @param iv IV
* @param iterations
*/
static void deriveKeyFromPassword(const std::string& password,
const std::vector<uint8_t>& salt,
std::vector<uint8_t>& key,
std::vector<uint8_t>& iv,
int iterations = 10000);
private:
class Impl;
UniquePtr<Impl> impl_;
std::vector<uint8_t> key_;
std::vector<uint8_t> iv_;
bool keySet_ = false;
};
} // namespace extra2d

View File

@ -0,0 +1,144 @@
#pragma once
#include <extra2d/core/types.h>
#include <cstdint>
#include <vector>
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<uint8_t> zlibCompress(const std::vector<uint8_t>& data, int level = 6);
/**
* @brief Zlib
* @param data
* @param originalSize
* @return
*/
static std::vector<uint8_t> zlibDecompress(const std::vector<uint8_t>& data,
size_t originalSize = 0);
/**
* @brief LZ4
* @param data
* @return
*/
static std::vector<uint8_t> lz4Compress(const std::vector<uint8_t>& data);
/**
* @brief LZ4
* @param data
* @param originalSize
* @return
*/
static std::vector<uint8_t> lz4Decompress(const std::vector<uint8_t>& data,
size_t originalSize);
/**
* @brief Zstd
* @param data
* @param level 1-22 3
* @return
*/
static std::vector<uint8_t> zstdCompress(const std::vector<uint8_t>& data, int level = 3);
/**
* @brief Zstd
* @param data
* @param originalSize
* @return
*/
static std::vector<uint8_t> zstdDecompress(const std::vector<uint8_t>& data,
size_t originalSize = 0);
/**
* @brief
* @param data
* @param type
* @param level -1 使
* @return
*/
static std::vector<uint8_t> compress(const std::vector<uint8_t>& data,
CompressionType type,
int level = -1);
/**
* @brief
* @param data
* @param type
* @param originalSize
* @return
*/
static std::vector<uint8_t> decompress(const std::vector<uint8_t>& 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<uint8_t>& 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<uint8_t>& data);
/**
* @brief Adler32
* @param data
* @param size
* @return Adler32
*/
static uint32_t adler32(const uint8_t* data, size_t size);
};
} // namespace extra2d

View File

@ -0,0 +1,213 @@
#pragma once
#include <extra2d/core/types.h>
#include <string>
#include <string_view>
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

View File

@ -9,6 +9,9 @@
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/core/module.h> #include <extra2d/core/module.h>
#include <extra2d/core/registry.h> #include <extra2d/core/registry.h>
#include <extra2d/core/unicode_utils.h>
#include <extra2d/core/compression.h>
#include <extra2d/core/aes_cipher.h>
// Config removed - app info now in Application class // Config removed - app info now in Application class
@ -54,6 +57,12 @@
#include <extra2d/services/timer_service.h> #include <extra2d/services/timer_service.h>
#include <extra2d/services/camera_service.h> #include <extra2d/services/camera_service.h>
#include <extra2d/services/logger_service.h> #include <extra2d/services/logger_service.h>
#include <extra2d/services/resource_service.h>
// Resources
#include <extra2d/resources/resource_handle.h>
#include <extra2d/resources/resource_system.h>
#include <extra2d/resources/asset_bundle.h>
// Application // Application
#include <extra2d/app/application.h> #include <extra2d/app/application.h>

View File

@ -0,0 +1,129 @@
#pragma once
#include <cstdint>
#include <extra2d/core/types.h>
#include <functional>
#include <memory>
#include <string>
#include <vector>
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<uint8_t> read(const std::string &path) const = 0;
/**
* @brief
* @param path
* @param callback
*/
virtual void
readAsync(const std::string &path,
std::function<void(std::vector<uint8_t>)> callback) const = 0;
/**
* @brief
* @param path
* @return
*/
virtual std::unique_ptr<std::istream> 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<std::string> 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<uint8_t> encryptionKey;
std::vector<uint8_t> encryptionIv;
};
} // namespace extra2d

View File

@ -0,0 +1,237 @@
#pragma once
#include <algorithm>
#include <extra2d/core/types.h>
namespace extra2d {
/**
* @brief
*
*
*/
template <typename T> 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 <typename T> 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<T> *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<T> *manager_;
};
/**
* @brief
*/
struct TextureData;
/**
* @brief
*/
struct FontData;
/**
* @brief
*/
struct ShaderData;
/**
* @brief
*/
struct MaterialData;
/**
* @brief
*/
struct MeshData;
/**
* @brief
*/
struct AudioData;
/**
* @brief
*/
using TextureHandle = ResourceHandle<TextureData>;
using FontHandle = ResourceHandle<FontData>;
using ShaderHandle = ResourceHandle<ShaderData>;
using MaterialHandle = ResourceHandle<MaterialData>;
using MeshHandle = ResourceHandle<MeshData>;
using AudioHandle = ResourceHandle<AudioData>;
} // namespace extra2d

View File

@ -0,0 +1,358 @@
#pragma once
#include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/resources/asset_bundle.h>
#include <extra2d/resources/resource_handle.h>
#include <extra2d/services/resource_service.h>
#include <functional>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
namespace extra2d {
/**
* @brief 访
*
* 访
* ServiceLocator IResourceService
*/
class ResourceSystem {
public:
/**
* @brief
* @return
*/
static IResourceService *getService() {
auto service = ServiceLocator::instance().tryGetService<IResourceService>();
return service ? service.get() : nullptr;
}
/**
* @brief
* @tparam T
* @param path
* @return
*/
template <typename T> static ResourceHandle<T> load(const std::string &path) {
auto *service = getService();
if (service) {
return service->load<T>(path);
}
return ResourceHandle<T>();
}
/**
* @brief
* @tparam T
* @param path
* @param callback
*/
template <typename T>
static void loadAsync(const std::string &path,
std::function<void(ResourceHandle<T>)> callback) {
auto *service = getService();
if (service) {
service->loadAsync<T>(path, callback);
} else if (callback) {
callback(ResourceHandle<T>());
}
}
/**
* @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<uint8_t> 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<void(bool)> 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 <typename T> class BaseResourceManager : public ResourceManager<T> {
public:
/**
* @brief
*/
struct CacheEntry {
Ptr<T> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<T> resource, size_t memorySize = 0) {
if (!resource)
return;
std::lock_guard<std::mutex> 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<T> getFromCache(const std::string &path) {
std::lock_guard<std::mutex> 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<std::mutex> lock(mutex_);
return pathIndex_.find(path) != pathIndex_.end();
}
/**
* @brief 使
*/
void clearUnused() {
std::lock_guard<std::mutex> 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<std::mutex> lock(mutex_);
cache_.clear();
pathIndex_.clear();
totalMemory_ = 0;
}
/**
* @brief 使
* @return
*/
size_t getTotalMemory() const {
std::lock_guard<std::mutex> lock(mutex_);
return totalMemory_;
}
/**
* @brief
* @return
*/
size_t getCount() const {
std::lock_guard<std::mutex> 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<std::chrono::milliseconds>(
now.time_since_epoch());
return static_cast<uint64_t>(ms.count());
}
mutable std::mutex mutex_;
std::unordered_map<T *, CacheEntry> cache_;
std::unordered_map<std::string, T *> pathIndex_;
size_t totalMemory_ = 0;
};
} // namespace extra2d

View File

@ -287,7 +287,7 @@ std::string format_str(const char *fmt, Args &&...args) {
#define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__) #define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__)
#define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__) #define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__)
#define E2D_FATAL(...) E2D_LOG_FATAL(__VA_ARGS__) #define E2D_FATAL(...) E2D_LOG_FATAL(__VA_ARGS__)
#define E2D_DEBUG(...) E2D_LOG_DEBUG(__VA_ARGS__)
// 带颜色参数的日志宏 // 带颜色参数的日志宏
#define E2D_LOG_COLOR(level, color, ...) \ #define E2D_LOG_COLOR(level, color, ...) \
do { \ do { \

View File

@ -0,0 +1,180 @@
#pragma once
#include <extra2d/core/service_interface.h>
#include <extra2d/core/types.h>
#include <extra2d/resources/asset_bundle.h>
#include <extra2d/resources/resource_handle.h>
#include <functional>
#include <string>
#include <vector>
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<void(bool)> callback) = 0;
/**
* @brief
* @param path
*/
virtual void unloadBundle(const std::string &path) = 0;
/**
* @brief
* @tparam T
* @param path
* @return
*/
template <typename T> ResourceHandle<T> load(const std::string &path);
/**
* @brief
* @tparam T
* @param path
* @param callback
*/
template <typename T>
void loadAsync(const std::string &path,
std::function<void(ResourceHandle<T>)> 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<uint8_t> 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

View File

@ -0,0 +1,222 @@
#include <extra2d/core/aes_cipher.h>
#include <extra2d/services/logger_service.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/kdf.h>
#include <cstring>
#include <random>
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<Impl>()) {
}
AESCipher::~AESCipher() {
clearKey();
}
bool AESCipher::setKey(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& 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<uint8_t> AESCipher::encrypt(const std::vector<uint8_t>& data) {
return encrypt(data.data(), data.size());
}
std::vector<uint8_t> 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<uint8_t> 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<int>(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<uint8_t> AESCipher::decrypt(const std::vector<uint8_t>& data) {
return decrypt(data.data(), data.size());
}
std::vector<uint8_t> 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<uint8_t> 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<int>(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<uint8_t>& data) {
auto encrypted = encrypt(data);
if (encrypted.empty() && !data.empty()) {
return false;
}
data = std::move(encrypted);
return true;
}
bool AESCipher::decryptInPlace(std::vector<uint8_t>& 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<uint8_t>& key,
std::vector<uint8_t>& 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<uint8_t> 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<uint8_t>& salt,
std::vector<uint8_t>& key,
std::vector<uint8_t>& iv,
int iterations) {
key.resize(KEY_SIZE);
iv.resize(IV_SIZE);
if (salt.empty()) {
std::vector<uint8_t> defaultSalt(16, 0);
PKCS5_PBKDF2_HMAC(password.c_str(), static_cast<int>(password.length()),
defaultSalt.data(), static_cast<int>(defaultSalt.size()),
iterations, EVP_sha256(),
KEY_SIZE, key.data());
} else {
PKCS5_PBKDF2_HMAC(password.c_str(), static_cast<int>(password.length()),
salt.data(), static_cast<int>(salt.size()),
iterations, EVP_sha256(),
KEY_SIZE, key.data());
}
std::vector<uint8_t> ivDerive(IV_SIZE);
std::vector<uint8_t> saltForIv = salt.empty() ? std::vector<uint8_t>(16, 1) : salt;
for (auto& b : saltForIv) {
b = ~b;
}
PKCS5_PBKDF2_HMAC(password.c_str(), static_cast<int>(password.length()),
saltForIv.data(), static_cast<int>(saltForIv.size()),
iterations, EVP_sha256(),
IV_SIZE, iv.data());
}
} // namespace extra2d

View File

@ -0,0 +1,244 @@
#include <cstring>
#include <extra2d/core/compression.h>
#include <extra2d/services/logger_service.h>
#include <lz4.h>
#include <lz4hc.h>
#include <zlib.h>
#include <zstd.h>
namespace extra2d {
std::vector<uint8_t> Compression::zlibCompress(const std::vector<uint8_t> &data,
int level) {
if (data.empty()) {
return {};
}
uLongf compressedSize = compressBound(data.size());
std::vector<uint8_t> 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<uint8_t>
Compression::zlibDecompress(const std::vector<uint8_t> &data,
size_t originalSize) {
if (data.empty()) {
return {};
}
size_t outputSize = originalSize;
if (outputSize == 0) {
outputSize = data.size() * 4;
}
std::vector<uint8_t> 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<uint8_t>
Compression::lz4Compress(const std::vector<uint8_t> &data) {
if (data.empty()) {
return {};
}
int compressedSize = LZ4_compressBound(static_cast<int>(data.size()));
std::vector<uint8_t> compressed(compressedSize);
compressedSize =
LZ4_compress_default(reinterpret_cast<const char *>(data.data()),
reinterpret_cast<char *>(compressed.data()),
static_cast<int>(data.size()), compressedSize);
if (compressedSize <= 0) {
E2D_ERROR("LZ4 compression failed");
return {};
}
compressed.resize(compressedSize);
return compressed;
}
std::vector<uint8_t>
Compression::lz4Decompress(const std::vector<uint8_t> &data,
size_t originalSize) {
if (data.empty() || originalSize == 0) {
E2D_ERROR("LZ4 decompression requires original size");
return {};
}
std::vector<uint8_t> decompressed(originalSize);
int result = LZ4_decompress_safe(
reinterpret_cast<const char *>(data.data()),
reinterpret_cast<char *>(decompressed.data()),
static_cast<int>(data.size()), static_cast<int>(originalSize));
if (result < 0) {
E2D_ERROR("LZ4 decompression failed with error: {}", result);
return {};
}
return decompressed;
}
std::vector<uint8_t> Compression::zstdCompress(const std::vector<uint8_t> &data,
int level) {
if (data.empty()) {
return {};
}
size_t compressedSize = ZSTD_compressBound(data.size());
std::vector<uint8_t> 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<uint8_t>
Compression::zstdDecompress(const std::vector<uint8_t> &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<uint8_t> 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<uint8_t> Compression::compress(const std::vector<uint8_t> &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<uint8_t> Compression::decompress(const std::vector<uint8_t> &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<int>(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<uint8_t> &data) {
return crc32(data.data(), data.size());
}
uint32_t Compression::crc32(const uint8_t *data, size_t size) {
return static_cast<uint32_t>(::crc32(0L, data, static_cast<uInt>(size)));
}
uint32_t Compression::adler32(const std::vector<uint8_t> &data) {
return adler32(data.data(), data.size());
}
uint32_t Compression::adler32(const uint8_t *data, size_t size) {
return static_cast<uint32_t>(::adler32(1L, data, static_cast<uInt>(size)));
}
} // namespace extra2d

View File

@ -0,0 +1,328 @@
#include <cstring>
#include <extra2d/core/unicode_utils.h>
#include <simdutf/simdutf.h>
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<const char16_t *>(text.data()), text.size() / 2);
}
case TextEncoding::UTF32: {
if (text.size() % 4 != 0) {
return false;
}
return simdutf::validate_utf32(
reinterpret_cast<const char32_t *>(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

View File

@ -0,0 +1,389 @@
#include <extra2d/core/aes_cipher.h>
#include <extra2d/core/compression.h>
#include <extra2d/core/unicode_utils.h>
#include <extra2d/resources/asset_bundle.h>
#include <extra2d/resources/resource_system.h>
#include <extra2d/services/logger_service.h>
#include <extra2d/services/resource_service.h>
#include <filesystem>
#include <fstream>
#include <thread>
#include <unordered_map>
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<uint8_t> 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<uint8_t> data(size);
file.read(reinterpret_cast<char *>(data.data()), size);
return data;
}
void
readAsync(const std::string &path,
std::function<void(std::vector<uint8_t>)> callback) const override {
std::thread([this, path, callback]() {
auto data = read(path);
if (callback) {
callback(std::move(data));
}
}).detach();
}
std::unique_ptr<std::istream> openStream(const std::string &path) override {
auto fullPath = std::filesystem::path(rootPath_) / path;
return std::make_unique<std::ifstream>(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<std::chrono::seconds>(
ftime - std::filesystem::file_time_type::clock::now() +
std::chrono::system_clock::now());
info.modifiedTime = sctp.time_since_epoch().count();
}
return info;
}
std::vector<std::string> list() const override {
std::vector<std::string> 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<char *>(&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<char *>(&pathLen), sizeof(pathLen));
entry.path.resize(pathLen);
file.read(entry.path.data(), pathLen);
file.read(reinterpret_cast<char *>(&entry.offset), sizeof(entry.offset));
file.read(reinterpret_cast<char *>(&entry.compressedSize),
sizeof(entry.compressedSize));
file.read(reinterpret_cast<char *>(&entry.originalSize),
sizeof(entry.originalSize));
file.read(reinterpret_cast<char *>(&entry.checksum),
sizeof(entry.checksum));
file.read(reinterpret_cast<char *>(&entry.compression),
sizeof(entry.compression));
file.read(reinterpret_cast<char *>(&entry.encryption),
sizeof(entry.encryption));
file.read(reinterpret_cast<char *>(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<uint8_t> 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<uint8_t> data(entry.compressedSize);
file.read(reinterpret_cast<char *>(data.data()), entry.compressedSize);
if (entry.compression != 0) {
auto compressionType = static_cast<CompressionType>(entry.compression);
data = Compression::decompress(data, compressionType, entry.originalSize);
}
return data;
}
void
readAsync(const std::string &path,
std::function<void(std::vector<uint8_t>)> callback) const override {
std::thread([this, path, callback]() {
auto data = read(path);
if (callback) {
callback(std::move(data));
}
}).detach();
}
std::unique_ptr<std::istream> openStream(const std::string &path) override {
auto data = read(path);
auto stream = std::make_unique<std::stringstream>();
stream->write(reinterpret_cast<const char *>(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<std::string> list() const override {
std::vector<std::string> 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<BundleEntry> entries_;
std::unordered_map<std::string, size_t> entryMap_;
};
/**
* @brief
*/
class CryptoBundle : public PackBundle {
public:
CryptoBundle() = default;
bool load(const std::string &path, const std::vector<uint8_t> &key,
const std::vector<uint8_t> &iv) {
if (!cipher_.setKey(key, iv)) {
E2D_ERROR("Failed to set decryption key");
return false;
}
return PackBundle::load(path);
}
std::vector<uint8_t> read(const std::string &path) const override {
auto data = PackBundle::read(path);
if (!data.empty() && cipher_.hasKey()) {
const_cast<AESCipher &>(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<FileBundle>(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<PackBundle>();
if (!static_cast<PackBundle *>(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<void(bool)> 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<IAssetBundle> &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<uint8_t> 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<Ptr<IAssetBundle>> bundles_;
Stats stats_;
};
template <typename T>
ResourceHandle<T> IResourceService::load(const std::string &path) {
return ResourceHandle<T>();
}
template <typename T>
void IResourceService::loadAsync(
const std::string &path, std::function<void(ResourceHandle<T>)> callback) {
if (callback) {
callback(ResourceHandle<T>());
}
}
E2D_AUTO_REGISTER_SERVICE(IResourceService, ResourceServiceImpl);
} // namespace extra2d

View File

@ -100,6 +100,12 @@ if target_plat ~= "switch" then
add_requires("glm") add_requires("glm")
add_requires("nlohmann_json") 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" local backend = get_config("window_backend") or "sdl2"
if backend == "glfw" then if backend == "glfw" then

View File

@ -62,6 +62,15 @@ function define_extra2d_engine()
-- 平台配置 -- 平台配置
local plat = get_current_plat() 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 if plat == "switch" then
-- Nintendo Switch 平台配置 -- Nintendo Switch 平台配置
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro" local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
@ -81,7 +90,6 @@ function define_extra2d_engine()
end end
elseif plat == "mingw" or plat == "windows" then elseif plat == "mingw" or plat == "windows" then
-- Windows (MinGW) 平台配置 -- Windows (MinGW) 平台配置
add_packages("glm", "nlohmann_json", {public = true})
local backend = get_window_backend() local backend = get_window_backend()
if backend == "glfw" then if backend == "glfw" then
add_packages("glfw", {public = true}) add_packages("glfw", {public = true})
@ -92,7 +100,6 @@ function define_extra2d_engine()
{public = true}) {public = true})
elseif plat == "linux" then elseif plat == "linux" then
-- Linux 平台配置 -- Linux 平台配置
add_packages("glm", "nlohmann_json", {public = true})
local backend = get_window_backend() local backend = get_window_backend()
if backend == "glfw" then if backend == "glfw" then
add_packages("glfw", {public = true}) add_packages("glfw", {public = true})
@ -102,7 +109,6 @@ function define_extra2d_engine()
add_syslinks("GL", "dl", "pthread", {public = true}) add_syslinks("GL", "dl", "pthread", {public = true})
elseif plat == "macosx" then elseif plat == "macosx" then
-- macOS 平台配置 -- macOS 平台配置
add_packages("glm", "nlohmann_json", {public = true})
local backend = get_window_backend() local backend = get_window_backend()
if backend == "glfw" then if backend == "glfw" then
add_packages("glfw", {public = true}) add_packages("glfw", {public = true})