Initial engine repository

This commit is contained in:
2026-06-08 19:58:35 +08:00
commit 7a925c3736
137 changed files with 162421 additions and 0 deletions
@@ -0,0 +1,397 @@
#pragma once
#include <filesystem>
#include <frostbite2D/types/type_alias.h>
#include <optional>
#include <string>
#include <vector>
namespace frostbite2D {
namespace fs = std::filesystem;
/**
* @brief 文件信息结构体
*/
struct FileInfo {
std::string name; ///< 文件名(包含扩展名)
std::string extension; ///< 文件扩展名(包含点,如 ".txt"
std::string fullPath; ///< 完整路径
uint64 size = 0; ///< 文件大小(字节)
bool isDirectory = false; ///< 是否为目录
bool isRegularFile = false; ///< 是否为普通文件
bool exists = false; ///< 是否存在
};
/**
* @brief 资源管理类(单例)
*
* 提供文件读写、目录操作、路径处理等功能。
* 支持设置工作目录,所有相对路径将基于工作目录解析。
* 支持 UTF-8 编码的中文路径。
*
* @example
* auto& asset = Asset::get();
* asset.setWorkingDirectory("D:/游戏/资源");
* std::string content;
* asset.readTextFile("配置/设置.json", content);
*/
class Asset {
public:
/**
* @brief 获取单例实例
* @return 资源管理器实例引用
*/
static Asset &get();
Asset(const Asset &) = delete;
Asset &operator=(const Asset &) = delete;
// ---------------------------------------------------------------------------
// 文件读写
// ---------------------------------------------------------------------------
/**
* @brief 读取文本文件
* @param path 文件路径(相对或绝对路径)
* @param outContent 输出文件内容
* @return 读取成功返回 true
*/
bool readTextFile(const std::string &path, std::string &outContent);
/**
* @brief 写入文本文件
* @param path 文件路径
* @param content 要写入的内容
* @param append 是否追加模式,默认覆盖
* @return 写入成功返回 true
*/
bool writeTextFile(const std::string &path, const std::string &content,
bool append = false);
/**
* @brief 读取二进制文件
* @param path 文件路径
* @param outData 输出字节数组
* @return 读取成功返回 true
*/
bool readBinaryFile(const std::string &path, std::vector<uint8> &outData);
/**
* @brief 写入二进制文件
* @param path 文件路径
* @param data 要写入的字节数组
* @param append 是否追加模式,默认覆盖
* @return 写入成功返回 true
*/
bool writeBinaryFile(const std::string &path, const std::vector<uint8> &data,
bool append = false);
/**
* @brief 读取文本文件到字符串(便捷方法)
* @param path 文件路径
* @return 文件内容,失败返回 std::nullopt
*/
std::optional<std::string> readFileToString(const std::string &path);
/**
* @brief 读取二进制文件到字节数组(便捷方法)
* @param path 文件路径
* @return 字节数组,失败返回 std::nullopt
*/
std::optional<std::vector<uint8>> readFileToBytes(const std::string &path);
// ---------------------------------------------------------------------------
// 文件/目录检查
// ---------------------------------------------------------------------------
/**
* @brief 检查路径是否存在
* @param path 路径
* @return 存在返回 true
*/
bool exists(const std::string &path) const;
/**
* @brief 检查路径是否为目录
* @param path 路径
* @return 是目录返回 true
*/
bool isDirectory(const std::string &path) const;
/**
* @brief 检查路径是否为普通文件
* @param path 路径
* @return 是普通文件返回 true
*/
bool isRegularFile(const std::string &path) const;
/**
* @brief 检查文件或目录是否为空
* @param path 路径
* @return 为空返回 true
*/
bool isEmpty(const std::string &path) const;
// ---------------------------------------------------------------------------
// 目录操作
// ---------------------------------------------------------------------------
/**
* @brief 创建单层目录
* @param path 目录路径
* @return 创建成功返回 true
*/
bool createDirectory(const std::string &path);
/**
* @brief 递归创建多层目录
* @param path 目录路径
* @return 创建成功返回 true
*/
bool createDirectories(const std::string &path);
/**
* @brief 删除文件
* @param path 文件路径
* @return 删除成功返回 true
*/
bool removeFile(const std::string &path);
/**
* @brief 删除目录及其所有内容
* @param path 目录路径
* @return 删除成功返回 true
*/
bool removeDirectory(const std::string &path);
// ---------------------------------------------------------------------------
// 文件操作
// ---------------------------------------------------------------------------
/**
* @brief 复制文件
* @param from 源文件路径
* @param to 目标文件路径
* @param overwrite 是否覆盖已存在的文件
* @return 复制成功返回 true
*/
bool copyFile(const std::string &from, const std::string &to,
bool overwrite = false);
/**
* @brief 移动文件
* @param from 源文件路径
* @param to 目标文件路径
* @return 移动成功返回 true
*/
bool moveFile(const std::string &from, const std::string &to);
/**
* @brief 重命名文件或目录
* @param oldPath 原路径
* @param newPath 新路径
* @return 重命名成功返回 true
*/
bool rename(const std::string &oldPath, const std::string &newPath);
// ---------------------------------------------------------------------------
// 目录遍历
// ---------------------------------------------------------------------------
/**
* @brief 列出目录中的所有文件
* @param directoryPath 目录路径
* @param recursive 是否递归遍历子目录
* @return 文件路径列表
*/
std::vector<std::string> listFiles(const std::string &directoryPath,
bool recursive = false);
/**
* @brief 列出目录中的所有子目录
* @param directoryPath 目录路径
* @param recursive 是否递归遍历子目录
* @return 子目录路径列表
*/
std::vector<std::string> listDirectories(const std::string &directoryPath,
bool recursive = false);
/**
* @brief 列出目录中的所有文件和子目录
* @param directoryPath 目录路径
* @param recursive 是否递归遍历子目录
* @return 所有项目路径列表
*/
std::vector<std::string> listAll(const std::string &directoryPath,
bool recursive = false);
/**
* @brief 按扩展名列出文件
* @param directoryPath 目录路径
* @param extension 文件扩展名(如 ".png"
* @param recursive 是否递归遍历子目录
* @return 匹配的文件路径列表
*/
std::vector<std::string>
listFilesWithExtension(const std::string &directoryPath,
const std::string &extension, bool recursive = false);
// ---------------------------------------------------------------------------
// 文件信息
// ---------------------------------------------------------------------------
/**
* @brief 获取文件详细信息
* @param path 文件路径
* @return 文件信息结构体
*/
FileInfo getFileInfo(const std::string &path);
/**
* @brief 获取文件大小
* @param path 文件路径
* @return 文件大小(字节)
*/
uint64 getFileSize(const std::string &path) const;
// ---------------------------------------------------------------------------
// 路径处理
// ---------------------------------------------------------------------------
/**
* @brief 获取文件名(包含扩展名)
* @param path 文件路径
* @return 文件名
*/
std::string getFileName(const std::string &path) const;
/**
* @brief 获取文件名(不含扩展名)
* @param path 文件路径
* @return 文件名
*/
std::string getFileNameWithoutExtension(const std::string &path) const;
/**
* @brief 获取文件扩展名
* @param path 文件路径
* @return 扩展名(包含点,如 ".txt"
*/
std::string getExtension(const std::string &path) const;
/**
* @brief 获取父目录路径
* @param path 文件路径
* @return 父目录路径
*/
std::string getParentPath(const std::string &path) const;
/**
* @brief 获取绝对路径
* @param path 相对或绝对路径
* @return 绝对路径
*/
std::string getAbsolutePath(const std::string &path) const;
/**
* @brief 获取规范路径(解析符号链接和相对路径)
* @param path 路径
* @return 规范化路径
*/
std::string getCanonicalPath(const std::string &path) const;
/**
* @brief 合并两个路径
* @param left 左侧路径
* @param right 右侧路径
* @return 合并后的路径
*/
std::string combinePath(const std::string &left,
const std::string &right) const;
/**
* @brief 规范化路径(转换为系统首选格式)
* @param path 路径
* @return 规范化路径
*/
std::string normalizePath(const std::string &path) const;
// ---------------------------------------------------------------------------
// 工作目录
// ---------------------------------------------------------------------------
/**
* @brief 获取当前工作目录
* @return 当前工作目录路径
*/
std::string getCurrentPath() const;
/**
* @brief 设置当前工作目录
* @param path 目录路径
* @return 设置成功返回 true
*/
bool setCurrentPath(const std::string &path);
/**
* @brief 设置资源工作目录
*
* 设置后,所有相对路径操作都将基于此目录。
*
* @param path 工作目录路径
*/
void setWorkingDirectory(const std::string &path);
/**
* @brief 获取资源工作目录
* @return 工作目录路径
*/
const std::string &getWorkingDirectory() const;
/**
* @brief 解析相对路径为完整路径
* @param relativePath 相对路径
* @return 完整路径
*/
std::string resolvePath(const std::string &relativePath) const;
// ---------------------------------------------------------------------------
// 资源根目录
// ---------------------------------------------------------------------------
/**
* @brief 设置资源根目录
* @param root 资源根目录路径
*/
void setAssetRoot(const std::string &root);
/**
* @brief 获取资源根目录
* @return 资源根目录路径
*/
const std::string &getAssetRoot() const;
/**
* @brief 解析资源相对路径
*
* 将相对路径解析为:工作目录/资源根目录/相对路径
*
* @param relativePath 相对路径
* @return 完整路径
*/
std::string resolveAssetPath(const std::string &relativePath) const;
private:
Asset() = default;
~Asset() = default;
fs::path toPath(const std::string &path) const;
std::string fromPath(const fs::path &path) const;
std::string resolveFullPath(const std::string &path) const;
std::string workingDirectory_;
std::string assetRoot_;
};
} // namespace frostbite2D
@@ -0,0 +1,353 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <optional>
#include <random>
#include <string>
#include <unordered_map>
#include <vector>
namespace frostbite2D {
/**
* @brief 音频项类型枚举
*/
enum class AudioEntryType {
None, ///< invalid
Effect, ///< one-shot sound effect
Ambient, ///< looping ambient track
Music, ///< background music
Random ///< random sound group
};
/**
* @brief 音效配置
*/
struct AudioEffect {
std::string id; ///< 音效 ID
std::string file; ///< 文件路径
std::string subgroup; ///< 子组(可选)
int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置)
int32 loopDelayRange = -1;///< 循环延迟范围(-1 表示未设置)
};
/**
* @brief 音乐配置
*/
struct AudioMusic {
std::string id; ///< 音乐 ID
std::string file; ///< 文件路径
int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置)
};
/**
* @brief ?????????
*/
struct AudioAmbient {
std::string id; ///< ?????ID
std::string file; ///< ??????
int32 loopDelay = -1; ///< ????????1 ?????????
int32 loopDelayRange = -1;///< ???????????1 ?????????
};
/**
* @brief 随机音效项
*/
struct RandomItem {
std::string tag; ///< 音效标签(ID
int32 probability = 0; ///< 概率权重
};
/**
* @brief 随机音效组
*/
struct AudioRandom {
std::string id; ///< 随机组 ID
std::vector<RandomItem> items; ///< 随机项列表
};
/**
* @brief 统一的音频条目(包含类型和数据)
*/
struct AudioEntry {
AudioEntryType type = AudioEntryType::None; ///< 条目类型
const AudioEffect* effect = nullptr; ///< 音效指针(type == Effect 时有效)
const AudioAmbient* ambient = nullptr;///< ???????????ype == Ambient ??????
const AudioMusic* music = nullptr; ///< 音乐指针(type == Music 时有效)
const AudioRandom* random = nullptr; ///< 随机组指针(type == Random 时有效)
/// @brief 判断是否有效
bool isValid() const { return type != AudioEntryType::None; }
/// @brief 判断是否为音效
bool isEffect() const { return type == AudioEntryType::Effect; }
/// @brief ????????????
bool isAmbient() const { return type == AudioEntryType::Ambient; }
/// @brief 判断是否为音乐
bool isMusic() const { return type == AudioEntryType::Music; }
/// @brief 判断是否为随机组
bool isRandom() const { return type == AudioEntryType::Random; }
/// @brief 安全获取文件路径
std::string getFilePath() const {
if (type == AudioEntryType::Effect && effect)
return effect->file;
if (type == AudioEntryType::Ambient && ambient)
return ambient->file;
if (type == AudioEntryType::Music && music)
return music->file;
return "";
}
/// @brief 安全获取子组
std::string getSubgroup() const {
if (type == AudioEntryType::Effect && effect)
return effect->subgroup;
return "";
}
};
/**
* @brief 音频数据库(单例)
*
* 用于解析和访问 audio.xml 文件中的音频配置。
* 提供统一的查询接口,自动判断音频类型。
*
* @example
* auto& audioDB = AudioDatabase::get();
* if (audioDB.loadFromFile("audio.xml")) {
* // 统一查询
* if (auto entry = audioDB.get("GN_FINAL_SHOT_01")) {
* // 使用 entry
* }
*
* // 获取文件路径
* if (auto filePath = audioDB.getFilePath("GN_FINAL_SHOT_01")) {
* // 使用 *filePath
* }
*
* // 获取音效(自动处理随机组)
* if (auto soundId = audioDB.getSound("R_GN_FINAL_SHOT")) {
* // 使用 *soundId
* }
* }
*/
class AudioDatabase {
public:
/**
* @brief 获取单例实例
* @return 音频数据库实例引用
*/
static AudioDatabase& get();
AudioDatabase(const AudioDatabase&) = delete;
AudioDatabase& operator=(const AudioDatabase&) = delete;
// ---------------------------------------------------------------------------
// 加载和解析
// ---------------------------------------------------------------------------
/**
* @brief 从文件加载音频数据库
* @param path XML 文件路径
* @return 加载成功返回 true
*/
bool loadFromFile(const std::string& path);
/**
* @brief 从字符串加载音频数据库
* @param xmlContent XML 内容字符串
* @return 加载成功返回 true
*/
bool loadFromString(const std::string& xmlContent);
/**
* @brief 清空数据库
*/
void clear();
/**
* @brief 检查数据库是否已加载
* @return 已加载返回 true
*/
bool isLoaded() const;
// ---------------------------------------------------------------------------
// 统一查询接口
// ---------------------------------------------------------------------------
/**
* @brief 统一查询:输入任意 ID,自动判断类型
* @param id 音频 ID(可以是 EFFECT、MUSIC 或 RANDOM 的 ID
* @return 音频条目,未找到返回 std::nullopt
*/
std::optional<AudioEntry> get(const std::string& id);
/**
* @brief 获取音效 ID(自动处理 RANDOM 类型)
*
* 如果 id 是 RANDOM 类型,会按概率随机选择一个音效;
* 如果 id 是 EFFECT 类型,直接返回该 ID。
*
* @param id 音效 ID 或随机组 ID
* @return 音效 ID,未找到返回 std::nullopt
*/
std::optional<std::string> getSound(const std::string& id);
/**
* @brief 获取文件路径
*
* 对于 EFFECT 和 MUSIC 类型,返回对应的文件路径;
* 对于 RANDOM 类型,先随机选择一个音效再返回其文件路径。
*
* @param id 音频 ID
* @return 文件路径,未找到返回 std::nullopt
*/
std::optional<std::string> getFilePath(const std::string& id);
// ---------------------------------------------------------------------------
// 类型特定查询(高级用法)
// ---------------------------------------------------------------------------
/**
* @brief 获取音效配置
* @param id 音效 ID
* @return 音效配置指针,未找到返回 std::nullopt
*/
std::optional<const AudioEffect*> getEffect(const std::string& id);
/**
* @brief ????????????
* @param id ?????ID
* @return ????????????????????? std::nullopt
*/
std::optional<const AudioAmbient*> getAmbient(const std::string& id);
/**
* @brief 获取音乐配置
* @param id 音乐 ID
* @return 音乐配置指针,未找到返回 std::nullopt
*/
std::optional<const AudioMusic*> getMusic(const std::string& id);
/**
* @brief 获取随机音效组配置
* @param id 随机组 ID
* @return 随机组配置指针,未找到返回 std::nullopt
*/
std::optional<const AudioRandom*> getRandom(const std::string& id);
// ---------------------------------------------------------------------------
// 统计信息
// ---------------------------------------------------------------------------
/**
* @brief 获取音效数量
*/
size_t effectCount() const;
/**
* @brief ???????????
*/
size_t ambientCount() const;
/**
* @brief 获取音乐数量
*/
size_t musicCount() const;
/**
* @brief 获取随机组数量
*/
size_t randomCount() const;
// ---------------------------------------------------------------------------
// 便捷访问方法(推荐使用)
// ---------------------------------------------------------------------------
/**
* @brief 检查 ID 是否存在
* @param id 音频 ID
* @return 存在返回 true
*/
bool has(const std::string& id);
/**
* @brief 获取文件路径(不存在返回空字符串)
* @param id 音频 ID
* @return 文件路径,未找到返回空字符串
*/
std::string filePath(const std::string& id);
/**
* @brief 获取音效 ID(自动处理随机组,不存在返回空字符串)
* @param idOrRandomId 音效 ID 或随机组 ID
* @return 音效 ID,未找到返回空字符串
*/
std::string soundId(const std::string& idOrRandomId);
/**
* @brief 获取子组(EFFECT 的 SUBGROUP 属性)
* @param id 音频 ID
* @return 子组名,未找到或不存在返回空字符串
*/
std::string subgroup(const std::string& id);
/**
* @brief 获取条目类型
* @param id 音频 ID
* @return 条目类型,未找到返回 AudioEntryType::None
*/
AudioEntryType typeOf(const std::string& id);
// ---------------------------------------------------------------------------
// 带检查的访问方法
// ---------------------------------------------------------------------------
/**
* @brief 尝试获取文件路径,带成功标志
* @param id 音频 ID
* @param outPath 输出文件路径
* @return 成功返回 true
*/
bool tryGetFilePath(const std::string& id, std::string& outPath);
/**
* @brief 尝试获取音效 ID,带成功标志
* @param id 音效 ID 或随机组 ID
* @param outSoundId 输出音效 ID
* @return 成功返回 true
*/
bool tryGetSoundId(const std::string& id, std::string& outSoundId);
private:
AudioDatabase() = default;
~AudioDatabase() = default;
/**
* @brief 初始化随机数生成器
*/
void initializeRNG();
/**
* @brief 从随机组中按概率选择一个音效
* @param randomGroup 随机组配置
* @return 选中的音效 ID
*/
std::string selectFromRandom(const AudioRandom& randomGroup);
std::unordered_map<std::string, AudioEffect> effectMap_; ///< 音效映射
std::unordered_map<std::string, AudioAmbient> ambientMap_; ///< ????????
std::unordered_map<std::string, AudioMusic> musicMap_; ///< 音乐映射
std::unordered_map<std::string, AudioRandom> randomMap_; ///< 随机组映射
std::mt19937 rng_; ///< 随机数生成器
bool rngInitialized_ = false; ///< 随机数生成器是否已初始化
bool loaded_ = false;
};
} // namespace frostbite2D
@@ -0,0 +1,62 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <fstream>
#include <string>
#include <vector>
namespace frostbite2D {
class BinaryFileStreamReader {
public:
BinaryFileStreamReader() = default;
explicit BinaryFileStreamReader(const std::string& filePath);
~BinaryFileStreamReader();
bool open(const std::string& filePath);
void close();
bool isOpen() const;
size_t tell() const;
void seek(size_t pos);
void skip(size_t count);
bool eof() const;
size_t size() const;
size_t remaining() const;
size_t lastReadCount() const;
size_t read(char* buffer, size_t size);
std::vector<uint8> readBytes(size_t size);
template <typename T>
T read() {
T value{};
read(reinterpret_cast<char*>(&value), sizeof(T));
return value;
}
int8 readInt8();
int16 readInt16();
int32 readInt32();
int64 readInt64();
uint8 readUInt8();
uint16 readUInt16();
uint32 readUInt32();
uint64 readUInt64();
float readFloat();
double readDouble();
std::string readString(size_t length);
std::string readNullTerminatedString(size_t maxLength = 4096);
private:
std::ifstream stream_;
std::string filePath_;
size_t size_ = 0;
size_t position_ = 0;
size_t lastReadCount_ = 0;
};
} // namespace frostbite2D
@@ -0,0 +1,269 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <string>
#include <vector>
#include <cstring>
#include <algorithm>
namespace frostbite2D {
/**
* @brief 二进制文件读取器
*
* 将整个二进制文件加载到内存中,提供便捷的读取接口。
* 支持各种基本类型的读取、定位操作和 CRC 解码。
*
* @example
* BinaryReader reader("data.bin");
* if (reader.isOpen()) {
* int32 value = reader.read<int32>();
* std::string str = reader.readString(10);
* }
*/
class BinaryReader {
public:
/**
* @brief 默认构造函数
*/
BinaryReader() = default;
/**
* @brief 从文件构造
* @param filePath 文件路径
*/
explicit BinaryReader(const std::string& filePath);
/**
* @brief 从内存数据构造
* @param data 数据指针
* @param size 数据大小
*/
BinaryReader(const char* data, size_t size);
/**
* @brief 析构函数
*/
~BinaryReader() = default;
// ---------------------------------------------------------------------------
// 文件操作
// ---------------------------------------------------------------------------
/**
* @brief 打开文件
* @param filePath 文件路径
* @return 打开成功返回 true
*/
bool open(const std::string& filePath);
/**
* @brief 从内存加载数据
* @param data 数据指针
* @param size 数据大小
*/
void loadFromMemory(const char* data, size_t size);
/**
* @brief 关闭并清空数据
*/
void close();
/**
* @brief 检查文件是否成功打开
* @return 已打开返回 true
*/
bool isOpen() const;
// ---------------------------------------------------------------------------
// 位置操作
// ---------------------------------------------------------------------------
/**
* @brief 获取当前读取位置
* @return 当前位置(字节偏移)
*/
size_t tell() const;
/**
* @brief 设置读取位置
* @param pos 目标位置(字节偏移)
*/
void seek(size_t pos);
/**
* @brief 跳过指定字节数
* @param count 要跳过的字节数
*/
void skip(size_t count);
/**
* @brief 检查是否已到达文件末尾
* @return 到达末尾返回 true
*/
bool eof() const;
// ---------------------------------------------------------------------------
// 信息获取
// ---------------------------------------------------------------------------
/**
* @brief 获取数据总大小
* @return 数据大小(字节)
*/
size_t size() const;
/**
* @brief 获取剩余可读取的字节数
* @return 剩余字节数
*/
size_t remaining() const;
/**
* @brief 获取上一次读取的字节数
* @return 上次读取的字节数
*/
size_t lastReadCount() const;
// ---------------------------------------------------------------------------
// 原始数据读取
// ---------------------------------------------------------------------------
/**
* @brief 读取原始字节数据
* @param buffer 输出缓冲区
* @param size 要读取的字节数
* @return 实际读取的字节数
*/
size_t read(char* buffer, size_t size);
/**
* @brief 读取原始字节数据到 vector
* @param size 要读取的字节数
* @return 读取的数据
*/
std::vector<uint8> readBytes(size_t size);
/**
* @brief 获取数据指针(不移动读取位置)
* @return 当前位置的数据指针
*/
const char* data() const;
/**
* @brief 获取当前位置的数据指针(不移动读取位置)
* @return 当前位置的数据指针
*/
const char* currentData() const;
// ---------------------------------------------------------------------------
// 类型化读取(模板方法)
// ---------------------------------------------------------------------------
/**
* @brief 读取指定类型的值
* @tparam T 要读取的类型
* @return 读取的值
*/
template <typename T>
T read() {
T value;
read(reinterpret_cast<char*>(&value), sizeof(T));
return value;
}
/**
* @brief 读取 int8
* @return 读取的值
*/
int8 readInt8();
/**
* @brief 读取 int16
* @return 读取的值
*/
int16 readInt16();
/**
* @brief 读取 int32
* @return 读取的值
*/
int32 readInt32();
/**
* @brief 读取 int64
* @return 读取的值
*/
int64 readInt64();
/**
* @brief 读取 uint8
* @return 读取的值
*/
uint8 readUInt8();
/**
* @brief 读取 uint16
* @return 读取的值
*/
uint16 readUInt16();
/**
* @brief 读取 uint32
* @return 读取的值
*/
uint32 readUInt32();
/**
* @brief 读取 uint64
* @return 读取的值
*/
uint64 readUInt64();
/**
* @brief 读取 float
* @return 读取的值
*/
float readFloat();
/**
* @brief 读取 double
* @return 读取的值
*/
double readDouble();
// ---------------------------------------------------------------------------
// 字符串读取
// ---------------------------------------------------------------------------
/**
* @brief 读取指定长度的字符串
* @param length 字符串长度(字节)
* @return 读取的字符串
*/
std::string readString(size_t length);
/**
* @brief 读取以 null 结尾的字符串
* @return 读取的字符串
*/
std::string readNullTerminatedString();
// ---------------------------------------------------------------------------
// CRC 解码
// ---------------------------------------------------------------------------
/**
* @brief CRC 解码
* @param length 要解码的数据长度(字节)
* @param crc32 CRC32 值
*/
void crcDecode(size_t length, uint32 crc32);
private:
std::vector<char> data_; ///< 存储的数据
size_t position_ = 0; ///< 当前读取位置
size_t lastReadCount_ = 0; ///< 上一次读取的字节数
};
} // namespace frostbite2D
@@ -0,0 +1,131 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/resource/asset.h>
#include <optional>
#include <string>
#include <vector>
#include <list>
#include <memory>
#include <unordered_map>
#include <zlib.h>
namespace frostbite2D {
class BinaryFileStreamReader;
struct ImageFrame {
int32 type = 0;
int32 compressionType = 0;
int32 width = 0;
int32 height = 0;
int32 xPos = 0;
int32 yPos = 0;
int32 frameXPos = 0;
int32 frameYPos = 0;
uint32 offset = 0;
int32 size = 0;
std::vector<uint8> data;
};
struct ImgRef {
std::string path;
std::string npkFile;
size_t frameCount = 0;
uint32 offset = 0;
uint32 size = 0;
bool loaded = false;
};
struct CachedImageData {
std::vector<ImageFrame> frames;
uint64 lastUseTime = 0;
size_t memoryUsage = 0;
};
class NpkArchive {
public:
static NpkArchive& get();
NpkArchive(const NpkArchive&) = delete;
NpkArchive& operator=(const NpkArchive&) = delete;
NpkArchive(NpkArchive&&) = delete;
NpkArchive& operator=(NpkArchive&&) = delete;
void setImagePackDirectory(const std::string& dir);
const std::string& getImagePackDirectory() const;
void init();
void close();
bool isOpen() const;
bool hasImg(const std::string& path) const;
std::optional<ImgRef> getImg(const std::string& path);
std::vector<std::string> listImgs() const;
std::optional<ImageFrame> getImageFrame(const ImgRef& img, size_t index);
size_t getFrameCount(const ImgRef& img) const;
void setCacheSize(size_t maxBytes);
void clearCache();
size_t getCacheUsage() const;
void setDefaultImg(const std::string& imgPath, size_t frameIndex = 0);
const std::string& getDefaultImgPath() const;
size_t getDefaultImgFrame() const;
private:
enum class IndexCacheValidationMode : uint32 {
TrustCache = 1,
StrictSourceState = 2
};
struct SourceFileState {
std::string fileName;
uint64 size = 0;
int64 writeTime = 0;
bool operator==(const SourceFileState& other) const {
return fileName == other.fileName && size == other.size &&
writeTime == other.writeTime;
}
};
NpkArchive() = default;
~NpkArchive() = default;
std::string normalizePath(const std::string& path) const;
std::string getCacheFilePath() const;
IndexCacheValidationMode getIndexCacheValidationMode() const;
std::vector<SourceFileState> collectSourceFileStates() const;
bool loadIndexCache(const std::vector<SourceFileState>& sourceFiles);
bool saveIndexCache(const std::vector<SourceFileState>& sourceFiles) const;
void scanNpkFiles();
bool parseNpkFile(const std::string& npkPath);
bool loadImgData(ImgRef& img);
void parseColor(const uint8* tab, int type, uint8* saveByte, int offset);
void evictCacheIfNeeded(size_t requiredSize);
void updateCacheUsage(const std::string& imgPath);
std::string readNpkInfoString(BinaryFileStreamReader& reader);
static const uint8 NPK_KEY[256];
std::string imagePackDirectory_ = "ImagePacks2";
bool initialized_ = false;
std::unordered_map<std::string, ImgRef> imgIndex_;
std::unordered_map<std::string, CachedImageData> imageCache_;
std::list<std::string> lruList_;
std::unordered_map<std::string, std::list<std::string>::iterator> lruLookup_;
size_t maxCacheSize_ = 512 * 1024 * 1024;
size_t currentCacheSize_ = 0;
std::string defaultImgPath_;
size_t defaultImgFrame_ = 0;
bool verboseFallbackLog_ =
#ifndef NDEBUG
true;
#else
false;
#endif
};
}
@@ -0,0 +1,95 @@
#pragma once
#include <frostbite2D/resource/binary_file_stream_reader.h>
#include <frostbite2D/types/type_alias.h>
#include <map>
#include <optional>
#include <string>
#include <vector>
namespace frostbite2D {
/**
* @brief PVF ???????
*/
struct PvfFileInfo {
size_t offset = 0; ///< ????????????
uint32 crc32 = 0; ///< CRC32 ???
size_t length = 0; ///< ????????
bool decoded = false; ///< ?????
};
/**
* @brief ?????????
*/
struct RawData {
std::unique_ptr<char[]> data; ///< ??????????????????
size_t size; ///< ????????
};
/**
* @brief PVF ??????
*
* ??????? PVF ???????????
* ??????????????????????????
*/
class PvfArchive {
public:
static PvfArchive& get();
PvfArchive(const PvfArchive&) = delete;
PvfArchive& operator=(const PvfArchive&) = delete;
PvfArchive(PvfArchive&&) = delete;
PvfArchive& operator=(PvfArchive&&) = delete;
bool open(const std::string& filePath = "Script.pvf");
void close();
bool isOpen() const;
void init();
void initHeader();
void initBinStringTable();
void initLoadStrings();
bool hasFile(const std::string& path) const;
std::optional<PvfFileInfo> getFileInfo(const std::string& path) const;
std::vector<std::string> listFiles() const;
std::optional<std::string> getFileContent(const std::string& path);
std::optional<std::vector<uint8>> getFileBytes(const std::string& path);
std::optional<RawData> getFileRawData(const std::string& path);
std::optional<std::string> getBinString(int key) const;
std::optional<std::string> getLoadString(const std::string& type,
const std::string& key) const;
bool hasBinString(int key) const;
bool hasLoadString(const std::string& type, const std::string& key) const;
std::string normalizePath(const std::string& path) const;
std::string resolvePath(const std::string& baseDir,
const std::string& path) const;
private:
PvfArchive() = default;
~PvfArchive() = default;
std::vector<std::string> splitString(const std::string& str,
const std::string& delimiter) const;
bool decodeFile(const std::string& normalizedPath, PvfFileInfo& info);
void clearInitData();
std::optional<std::vector<uint8>> readArchiveBytes(size_t absoluteOffset,
size_t length);
std::optional<std::vector<uint8>> readDecodedFileBytes(
const PvfFileInfo& info);
static void crcDecodeBuffer(std::vector<uint8>& data, uint32 crc32);
BinaryFileStreamReader reader_;
size_t dataStartPos_ = 0;
std::map<std::string, PvfFileInfo> fileInfo_;
std::map<int, std::string> binStringTable_;
std::map<std::string, std::map<std::string, std::string>> loadStrings_;
std::map<std::string, std::vector<uint8>> decodedFileCache_;
};
} // namespace frostbite2D
@@ -0,0 +1,115 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <json/json.hpp>
#include <optional>
#include <string>
#include <vector>
namespace frostbite2D {
/**
* @brief Save system runtime configuration.
*/
struct SaveSystemConfig {
int32 slotCount = 10;
std::string saveRoot = ".save";
std::string indexFile = "index.json";
std::string slotPrefix = "slot_";
};
/**
* @brief Optional display metadata stored in each slot document.
*/
struct SaveMeta {
std::string displayName;
double playTimeSeconds = 0.0;
std::string userTag;
};
/**
* @brief Cached per-slot summary used by save slot list UI.
*/
struct SaveSlotInfo {
int32 slot = -1;
bool exists = false;
std::string updatedAtUtc;
uint64 fileSize = 0;
int32 schemaVersion = 1;
SaveMeta meta;
};
/**
* @brief Manual save/load service using Asset as the only filesystem backend.
*/
class SaveSystem {
public:
/**
* @brief Get singleton instance.
*/
static SaveSystem& get();
SaveSystem(const SaveSystem&) = delete;
SaveSystem& operator=(const SaveSystem&) = delete;
/**
* @brief Initialize save root, slot cache and index file.
*/
bool init(const SaveSystemConfig& config = SaveSystemConfig{});
/**
* @brief Save payload into a fixed slot and update slot index metadata.
*/
bool saveSlot(int32 slot, const nlohmann::json& payload,
const SaveMeta& meta = SaveMeta{});
/**
* @brief Load payload object from a slot.
*/
std::optional<nlohmann::json> loadSlotPayload(int32 slot) const;
/**
* @brief Query summary info for one slot.
*/
std::optional<SaveSlotInfo> getSlotInfo(int32 slot) const;
/**
* @brief List summary info of all slots.
*/
std::vector<SaveSlotInfo> listSlots() const;
/**
* @brief Delete a slot file and clear its index entry.
*/
bool deleteSlot(int32 slot);
/**
* @brief Check whether slot has an existing save file.
*/
bool hasSlot(int32 slot) const;
private:
SaveSystem() = default;
~SaveSystem() = default;
bool isValidSlot(int32 slot) const;
void resetSlots();
bool ensureSaveDirectory() const;
bool refreshSlotsFromDisk();
bool tryReadSlotDocument(int32 slot, nlohmann::json& outDoc) const;
bool writeIndexFile() const;
std::string indexPath() const;
std::string slotPath(int32 slot) const;
std::string slotFileName(int32 slot) const;
static std::string makeUtcTimestamp();
static nlohmann::json toJson(const SaveMeta& meta);
static SaveMeta fromJson(const nlohmann::json& json);
SaveSystemConfig config_;
std::vector<SaveSlotInfo> slots_;
bool initialized_ = false;
};
} // namespace frostbite2D
@@ -0,0 +1,208 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/resource/pvf_archive.h>
#include <optional>
#include <string>
#include <vector>
#include <cstring>
namespace frostbite2D {
/**
* @brief 脚本指令操作码枚举
*/
enum class ScriptOpcode : uint8 {
Integer = 2, ///< 整数值
Float = 4, ///< 浮点值
StringRef5 = 5, ///< 二进制字符串引用(类型5)
StringRef6 = 6, ///< 二进制字符串引用(类型6)
StringRef7 = 7, ///< 二进制字符串引用(类型7)
StringRef8 = 8, ///< 二进制字符串引用(类型8)
ExtendedString9 = 9, ///< 扩展字符串(带额外字节)
ExtendedString10 = 10, ///< 扩展字符串
};
/**
* @brief 脚本值类型
*/
enum class ScriptValueType {
Empty, ///< 空值
Integer, ///< 整数值
Float, ///< 浮点值
String, ///< 字符串值
};
/**
* @brief 脚本解析结果值
*/
struct ScriptValue {
ScriptValueType type = ScriptValueType::Empty; ///< 值类型
int32 intValue = 0; ///< 整数值(type == Integer 时有效)
double floatValue = 0.0; ///< 浮点值(type == Float 时有效)
std::string stringValue; ///< 字符串值(type == String 时有效)
/**
* @brief 判断是否为空值
*/
bool isEmpty() const { return type == ScriptValueType::Empty; }
/**
* @brief 转换为字符串表示
*/
std::string toString() const;
};
/**
* @brief 脚本二进制数据解析器
*
* 用于解析 PVF 存档中的脚本二进制数据。
* 每条指令为 5 字节:1 字节操作码 + 4 字节数据。
* 支持与 PvfArchive 交互获取字符串资源。
*
* @example
* auto& archive = PvfArchive::get();
* if (auto rawData = archive.getFileRawData("script/example.bin")) {
* ScriptParser parser(*rawData, "script/example.bin");
*
* // 迭代解析
* while (!parser.isEnd()) {
* if (auto value = parser.next()) {
* // 处理 value
* }
* }
*
* // 或者一次性解析所有
* auto allValues = parser.parseAll();
* }
*/
class ScriptParser {
public:
/**
* @brief 从 RawData 构造
* @param data 原始数据
* @param filePath 文件路径(用于提取文件类型)
*/
ScriptParser(const RawData& data, const std::string& filePath);
/**
* @brief 从字节数组构造
* @param data 字节数组
* @param filePath 文件路径(用于提取文件类型)
*/
ScriptParser(const std::vector<uint8>& data, const std::string& filePath);
/**
* @brief 从指针和大小构造
* @param data 数据指针
* @param size 数据大小
* @param filePath 文件路径(用于提取文件类型)
*/
ScriptParser(const char* data, size_t size, const std::string& filePath);
// ---------------------------------------------------------------------------
// 迭代器风格 API
// ---------------------------------------------------------------------------
/**
* @brief 获取下一个值
* @return 解析的值,失败或结束返回 std::nullopt
*/
std::optional<ScriptValue> next();
/**
* @brief 回退一个指令
*/
void back();
/**
* @brief 检查是否已到达数据末尾
* @return 到达末尾返回 true
*/
bool isEnd() const;
/**
* @brief 重置解析位置到开头
*/
void reset();
// ---------------------------------------------------------------------------
// 批量解析
// ---------------------------------------------------------------------------
/**
* @brief 解析所有值
* @return 所有解析值的列表
*/
std::vector<ScriptValue> parseAll();
// ---------------------------------------------------------------------------
// 状态查询
// ---------------------------------------------------------------------------
/**
* @brief 获取当前解析位置
* @return 当前位置(字节偏移)
*/
size_t position() const;
/**
* @brief 获取数据总大小
* @return 数据大小(字节)
*/
size_t size() const;
/**
* @brief 获取文件路径
* @return 文件路径
*/
const std::string& filePath() const;
/**
* @brief 获取文件类型(从路径提取)
* @return 文件类型
*/
const std::string& fileType() const;
/**
* @brief 检查解析器是否有效(有数据)
* @return 有效返回 true
*/
bool isValid() const;
private:
/**
* @brief 从文件路径提取文件类型
* @param filePath 文件路径
* @return 文件类型
*/
std::string extractFileType(const std::string& filePath) const;
/**
* @brief 读取 1 字节
* @param offset 偏移位置
* @return 字节值
*/
uint8 readByte(size_t offset) const;
/**
* @brief 读取 4 字节整数
* @param offset 偏移位置
* @return 整数值
*/
int32 readInt32(size_t offset) const;
/**
* @brief 解析单个值
* @param offset 偏移位置
* @return 解析的值
*/
std::optional<ScriptValue> parseValueAt(size_t offset) const;
std::vector<char> data_; ///< 数据副本
size_t position_ = 2; ///< 当前解析位置(从第 2 字节开始)
std::string filePath_; ///< 文件路径
std::string fileType_; ///< 文件类型
};
} // namespace frostbite2D
@@ -0,0 +1,84 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/resource/binary_reader.h>
#include <frostbite2D/resource/asset.h>
#include <optional>
#include <string>
#include <map>
#include <vector>
#include <list>
#include <memory>
namespace frostbite2D {
class BinaryFileStreamReader;
struct AudioRef {
std::string path;
std::string npkFile;
uint32 offset = 0;
uint32 size = 0;
bool loaded = false;
};
struct CachedAudioData {
std::vector<uint8> data;
uint64 lastUseTime = 0;
size_t memoryUsage = 0;
};
class SoundPackArchive {
public:
static SoundPackArchive& get();
SoundPackArchive(const SoundPackArchive&) = delete;
SoundPackArchive& operator=(const SoundPackArchive&) = delete;
SoundPackArchive(SoundPackArchive&&) = delete;
SoundPackArchive& operator=(SoundPackArchive&&) = delete;
void setSoundPackDirectory(const std::string& dir);
const std::string& getSoundPackDirectory() const;
void init();
void close();
bool isOpen() const;
bool hasAudio(const std::string& path) const;
std::optional<AudioRef> getAudio(const std::string& path);
std::vector<std::string> listAudios() const;
std::optional<std::vector<uint8>> getAudioData(const AudioRef& audio);
void setCacheSize(size_t maxBytes);
void clearCache();
size_t getCacheUsage() const;
void setDefaultAudio(const std::string& audioPath);
const std::string& getDefaultAudioPath() const;
private:
SoundPackArchive() = default;
~SoundPackArchive() = default;
std::string normalizePath(const std::string& path) const;
void scanNpkFiles();
bool parseNpkFile(const std::string& npkPath);
bool loadAudioData(AudioRef& audio);
void evictCacheIfNeeded(size_t requiredSize);
void updateCacheUsage(const std::string& audioPath);
std::string readNpkInfoString(BinaryFileStreamReader& reader);
static const uint8 NPK_KEY[256];
std::string soundPackDirectory_ = "SoundPacks";
bool initialized_ = false;
std::map<std::string, AudioRef> audioIndex_;
std::map<std::string, CachedAudioData> audioCache_;
std::list<std::string> lruList_;
size_t maxCacheSize_ = 256 * 1024 * 1024;
size_t currentCacheSize_ = 0;
std::string defaultAudioPath_;
};
}