From cb9f497fbba7c6944dca3d00dfbcc60fd0c9e3cf Mon Sep 17 00:00:00 2001 From: Lenheart <947330670@qq.com> Date: Wed, 18 Mar 2026 04:18:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=B5=84=E6=BA=90=E5=8A=A0=E8=BD=BD):=20?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2stb=5Fimage=E4=B8=BASDL=5Fimage=E5=B9=B6?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=84=9A=E6=9C=AC=E8=A7=A3=E6=9E=90=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将stb_image替换为SDL_image以解决Switch平台兼容性问题 - 添加PVF资源包解析器和脚本解析器功能 - 修改各平台配置文件添加SDL_image依赖 - 更新纹理加载逻辑使用SDL_image API - 新增脚本解析相关类用于处理游戏脚本数据 --- .opencode/plans/clean_xmake_cache.md | 46 +++ .opencode/plans/fix_sdl_image_link.md | 66 ++++ .opencode/plans/replace_stb_with_sdl_image.md | 169 +++++++++ .opencode/plans/stbi_debug_fix.md | 74 ++++ .opencode/plans/stbi_switch_fix.md | 68 ++++ .opencode/plans/switch_asset_fix.md | 74 ++++ .opencode/plans/switch_stb_fix.md | 51 +++ .../include/frostbite2D/utils/pvf_archive.h | 222 ++++++++++++ .../include/frostbite2D/utils/script_parser.h | 208 +++++++++++ Frostbite2D/src/frostbite2D/2d/sprite.cpp | 6 +- .../src/frostbite2D/graphics/texture.cpp | 71 +++- .../src/frostbite2D/utils/pvf_archive.cpp | 334 ++++++++++++++++++ .../src/frostbite2D/utils/script_parser.cpp | 222 ++++++++++++ Game/src/main.cpp | 58 ++- platform/linux.lua | 2 + platform/mingw.lua | 2 + platform/switch.lua | 6 +- 17 files changed, 1647 insertions(+), 32 deletions(-) create mode 100644 .opencode/plans/clean_xmake_cache.md create mode 100644 .opencode/plans/fix_sdl_image_link.md create mode 100644 .opencode/plans/replace_stb_with_sdl_image.md create mode 100644 .opencode/plans/stbi_debug_fix.md create mode 100644 .opencode/plans/stbi_switch_fix.md create mode 100644 .opencode/plans/switch_asset_fix.md create mode 100644 .opencode/plans/switch_stb_fix.md create mode 100644 Frostbite2D/include/frostbite2D/utils/pvf_archive.h create mode 100644 Frostbite2D/include/frostbite2D/utils/script_parser.h create mode 100644 Frostbite2D/src/frostbite2D/utils/pvf_archive.cpp create mode 100644 Frostbite2D/src/frostbite2D/utils/script_parser.cpp diff --git a/.opencode/plans/clean_xmake_cache.md b/.opencode/plans/clean_xmake_cache.md new file mode 100644 index 0000000..f2dda44 --- /dev/null +++ b/.opencode/plans/clean_xmake_cache.md @@ -0,0 +1,46 @@ +# 清理 Xmake 缓存并重新构建计划 + +## 问题描述 +- 已在 `sprite.cpp` 中添加 `printf("LoadA")` 调试代码 +- 但运行时该调试语句未执行 +- 说明 xmake 仍在使用旧的编译缓存 + +## 解决方案 + +### 步骤 1: 清理 Xmake 缓存 +运行以下命令清理所有编译缓存: + +```bash +xmake clean -a +``` + +或者更彻底的清理(删除整个 build 目录): +```bash +xmake clean +rmdir /s /q build # Windows +# 或者 +rm -rf build # Linux/Mac +``` + +### 步骤 2: 重新配置项目(可选) +为了确保配置也是最新的: + +```bash +xmake f -c +``` + +### 步骤 3: 重新构建 Switch 版本 +```bash +xmake build -p switch +``` + +## 验证步骤 +1. 清理完成后,查看 build 目录是否被删除 +2. 重新构建时,应该能看到所有源文件被重新编译 +3. 运行新构建的 NRO 文件,验证 `printf("LoadA")` 是否输出 + +## 预期结果 +- 所有源文件被重新编译 +- 新的修改生效 +- `printf("LoadA")` 能够正常输出 +- 可以进一步定位真正的崩溃位置 diff --git a/.opencode/plans/fix_sdl_image_link.md b/.opencode/plans/fix_sdl_image_link.md new file mode 100644 index 0000000..0e5727a --- /dev/null +++ b/.opencode/plans/fix_sdl_image_link.md @@ -0,0 +1,66 @@ +# 修复 SDL2_image 链接错误计划 + +## 问题描述 +链接时出现大量 undefined reference 错误,主要是关于 libjpeg 的函数: +- `jpeg_calc_output_dimensions` +- `jpeg_CreateDecompress` +- `jpeg_destroy_decompress` +- `jpeg_finish_decompress` +等等 + +## 问题原因 +SDL2_image 依赖多个第三方库来支持不同的图片格式: +- libjpeg - JPEG 格式支持 +- libpng - PNG 格式支持 +- libtiff - TIFF 格式支持 +- libwebp - WebP 格式支持 +等等 + +需要在 switch.lua 中添加这些依赖库的链接。 + +## 修复方案 + +### 修改 platform/switch.lua +在 `add_syslinks` 中添加 SDL2_image 所需的依赖库。 + +根据 devkitPro/portlibs 的通常配置,需要添加: +- libpng +- libjpeg +- zlib (libpng 依赖) +- 可能还需要其他库 + +## 具体修复代码 + +修改 `platform/switch.lua` 中的 `add_syslinks` 部分: + +```lua +-- 修改前 +add_syslinks("SDL2_mixer", "SDL2_image", "SDL2", "opusfile", "opus", "vorbisidec", "ogg", + "modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau", + {public = true}) + +-- 修改后 +add_syslinks("SDL2_mixer", "SDL2_image", "SDL2", + "png", "jpeg", "z", -- SDL2_image 依赖 + "opusfile", "opus", "vorbisidec", "ogg", + "modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau", + {public = true}) +``` + +## 库的顺序说明 +链接顺序很重要,被依赖的库要放在后面: +- SDL2_image 依赖 png、jpeg、z +- png 依赖 z +- 所以顺序是: SDL2_image → png → jpeg → z + +## 备选方案 +如果上面的库还不够,可以尝试添加更多库: +- `tiff` - TIFF 支持 +- `webp` - WebP 支持 +- `lzma` - 某些图片格式可能需要 + +## 测试步骤 +1. 应用修复 +2. 清理缓存 +3. 重新编译 +4. 验证链接成功 diff --git a/.opencode/plans/replace_stb_with_sdl_image.md b/.opencode/plans/replace_stb_with_sdl_image.md new file mode 100644 index 0000000..8e4c0ec --- /dev/null +++ b/.opencode/plans/replace_stb_with_sdl_image.md @@ -0,0 +1,169 @@ +# 将 stb_image 替换为 SDL_image 完整计划 + +## 概述 +将项目中的 stb_image 完全替换为 SDL_image,以解决 Switch 平台兼容性问题。 + +## 修改文件清单 + +### 1. 平台配置文件(3个文件) +- `platform/mingw.lua` - Windows/Mingw 配置 +- `platform/linux.lua` - Linux 配置 +- `platform/switch.lua` - Switch 配置 + +### 2. 核心代码文件(1个文件) +- `Frostbite2D/src/frostbite2D/graphics/texture.cpp` - 纹理加载实现 + +## 详细修改方案 + +### 修改 1: platform/mingw.lua +**变更**: 添加 SDL_image 依赖 +```lua +-- 修改前 +add_requires("libsdl2", {configs = {shared = true}}) +add_requires("glm") + +-- 修改后 +add_requires("libsdl2", {configs = {shared = true}}) +add_requires("libsdl2_image", {configs = {shared = true}}) +add_requires("glm") + +-- 在 add_packages 部分添加 +add_packages("libsdl2") +add_packages("libsdl2_image") +add_packages("glm") +``` + +### 修改 2: platform/linux.lua +**变更**: 添加 SDL_image 依赖 +```lua +-- 修改前 +add_requires("libsdl2", {configs = {shared = true,wayland = true}}) +add_requires("glm") + +-- 修改后 +add_requires("libsdl2", {configs = {shared = true,wayland = true}}) +add_requires("libsdl2_image") +add_requires("glm") + +-- 在 add_packages 部分添加 +add_packages("libsdl2") +add_packages("libsdl2_image") +add_packages("glm") +``` + +### 修改 3: platform/switch.lua +**变更**: 添加 SDL2_image 库链接 +```lua +-- 修改前 +add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg", + "modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau", + {public = true}) + +-- 修改后 +add_syslinks("SDL2_mixer", "SDL2_image", "SDL2", "opusfile", "opus", "vorbisidec", "ogg", + "modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau", + {public = true}) +``` + +### 修改 4: texture.cpp +**变更**: 完全重写 `loadFromFile` 函数,使用 SDL_image +```cpp +#include +#include +#include +#include + +// 移除所有 stb_image 相关的定义和 include +// #define STB_IMAGE_IMPLEMENTATION +// #include + +namespace frostbite2D { + +Ptr Texture::loadFromFile(const std::string& path) { + Asset& asset = Asset::get(); + std::string resolvedPath = asset.resolveAssetPath(path); + std::vector fileData; + + SDL_Log("LoadQ"); + if (!asset.readBinaryFile(path, fileData)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read texture file: %s", resolvedPath.c_str()); + return nullptr; + } + SDL_Log("LoadQ1: file size = %zu bytes", fileData.size()); + + if (fileData.empty()) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Texture file is empty: %s", resolvedPath.c_str()); + return nullptr; + } + + // 使用 SDL_image 从内存加载 + SDL_RWops* rw = SDL_RWFromConstMem(fileData.data(), static_cast(fileData.size())); + if (!rw) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create RWops: %s", SDL_GetError()); + return nullptr; + } + + SDL_Surface* surface = IMG_Load_RW(rw, 1); + if (!surface) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load image: %s", IMG_GetError()); + SDL_FreeRW(rw); + return nullptr; + } + + SDL_Log("LoadQ2: %dx%d, format: %d", surface->w, surface->h, surface->format->format); + + // 确定格式 + GLenum format; + int channels; + if (surface->format->BytesPerPixel == 4) { + format = GL_RGBA; + channels = 4; + } else { + format = GL_RGB; + channels = 3; + } + + int width = surface->w; + int height = surface->h; + + uint32_t textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_2D, textureID); + + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, surface->pixels); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + + SDL_FreeSurface(surface); + + auto texture = Ptr(new Texture(width, height, textureID)); + texture->path_ = resolvedPath; + texture->channels_ = channels; + + SDL_Log("Loaded texture: %s (%dx%d, %d channels)", resolvedPath.c_str(), width, height, channels); + + return texture; +} + +// ... 其余函数保持不变 +} +``` + +## 实施步骤 + +1. 修改 3 个平台配置文件,添加 SDL_image 依赖 +2. 修改 texture.cpp,替换 stb_image 为 SDL_image +3. 清理 xmake 缓存 +4. 重新编译项目 +5. 测试所有平台 + +## 注意事项 + +- SDL_image 支持的图片格式包括: PNG, JPEG, BMP, GIF, TIFF, WebP 等 +- 确保所有平台都能正确链接 SDL_image 库 +- Switch 平台的 devkitPro/portlibs 应该已经包含 SDL2_image diff --git a/.opencode/plans/stbi_debug_fix.md b/.opencode/plans/stbi_debug_fix.md new file mode 100644 index 0000000..173acb3 --- /dev/null +++ b/.opencode/plans/stbi_debug_fix.md @@ -0,0 +1,74 @@ +# stbi_load_from_memory 失败调试和修复计划 + +## 问题描述 +`stbi_load_from_memory()` 执行失败,但不知道具体失败原因。 + +## 修复方案 + +### 1. 添加详细的调试信息 +修改 `texture.cpp`,添加以下调试信息: +- 输出读取到的文件大小 +- 输出 stbi 的失败原因(使用 `stbi_failure_reason()`) + +### 2. 修改代码 +**文件**: `Frostbite2D/src/frostbite2D/graphics/texture.cpp` + +**变更**: +```cpp +Ptr Texture::loadFromFile(const std::string& path) { + Asset& asset = Asset::get(); + std::string resolvedPath = asset.resolveAssetPath(path); + std::vector fileData; + SDL_Log("LoadQ"); + if (!asset.readBinaryFile(path, fileData)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read texture file: %s", resolvedPath.c_str()); + return nullptr; + } + SDL_Log("LoadQ1: file size = %zu bytes", fileData.size()); + + // 检查文件是否为空 + if (fileData.empty()) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Texture file is empty: %s", resolvedPath.c_str()); + return nullptr; + } + + int width, height, channels; + uint8* data = stbi_load_from_memory(fileData.data(), static_cast(fileData.size()), &width, &height, &channels, 0); + SDL_Log("LoadQ2"); + + if (!data) { + const char* failureReason = stbi_failure_reason(); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load texture: %s, reason: %s", resolvedPath.c_str(), failureReason ? failureReason : "unknown"); + return nullptr; + } + + SDL_Log("LoadQ3: %dx%d, %d channels", width, height, channels); + + // ... 其余代码保持不变 +} +``` + +## 调试步骤 +1. 应用上述修改 +2. 重新编译并运行 +3. 查看日志输出,特别注意: + - `file size` 的值是多少 + - `stbi_failure_reason()` 输出的失败原因是什么 + +## 根据失败原因的可能解决方案 + +### 如果失败原因是 "png" 相关 +- 检查 PNG 文件是否损坏 +- 尝试用其他图片格式(如 BMP)测试 + +### 如果失败原因是 "out of memory" +- 检查图片是否太大 +- 考虑调整 stbi 的内存分配设置 + +### 如果文件大小为 0 +- 检查文件路径是否正确 +- 检查 Asset 类的读取是否有问题 +- 检查文件是否存在于 Switch 上 + +### 如果是其他原因 +- 根据具体的失败原因进一步分析 diff --git a/.opencode/plans/stbi_switch_fix.md b/.opencode/plans/stbi_switch_fix.md new file mode 100644 index 0000000..f119104 --- /dev/null +++ b/.opencode/plans/stbi_switch_fix.md @@ -0,0 +1,68 @@ +# Switch 平台 stbi_load_from_memory 崩溃修复计划 + +## 问题描述 +- 文件读取成功:129244 字节 +- 但调用 `stbi_load_from_memory()` 时直接崩溃 +- 说明 stbi_image 在 Switch 平台上有兼容性问题 + +## 可能的原因 + +1. **stbi_image 的内存分配问题** - stbi 默认使用 malloc/free,在 Switch 上可能有问题 +2. **缺少 stbi_image 的平台特定配置** +3. **SIMD 优化问题** - stbi_image 的 ARM NEON 优化在 Switch 上可能有问题 +4. **栈溢出** - stbi_image 可能在栈上分配了太大的结构 + +## 修复方案 + +### 方案 1: 配置 stbi_image 使用自定义内存分配(推荐) +修改 texture.cpp,在 include stb_image.h 之前定义自定义的内存分配函数。 + +### 方案 2: 禁用 stbi_image 的 SIMD 优化 +定义 `STBI_NO_SIMD` 来禁用 NEON 优化。 + +### 方案 3: 两者结合(最稳妥) +同时使用自定义内存分配和禁用 SIMD 优化。 + +## 具体修复代码 + +修改 `Frostbite2D/src/frostbite2D/graphics/texture.cpp` 的开头部分: + +```cpp +#include +#include + +// 自定义 stbi_image 的内存分配函数 +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,sz) realloc(p,sz) +#define STBI_FREE(p) free(p) + +// 禁用 SIMD 优化,避免 Switch 平台兼容性问题 +#define STBI_NO_SIMD + +#define STB_IMAGE_IMPLEMENTATION +#include "SDL_log.h" +#include +#include +#include +#include +#include +``` + +## 额外调试建议 + +如果上述修复后仍然崩溃,可以添加更多调试来定位具体位置: + +1. 在 stbi_load_from_memory 调用前后添加 printf +2. 尝试用最简单的图片(小尺寸 BMP)测试 +3. 检查 stbi_image 的版本 + +## 测试步骤 +1. 应用修复 +2. 清理缓存并重新编译 +3. 运行测试 +4. 查看是否仍然崩溃 + +## 预期结果 +- stbi_load_from_memory 不再崩溃 +- 图片能够正常加载 +- LoadQ2 和 LoadQ3 日志能够正常输出 diff --git a/.opencode/plans/switch_asset_fix.md b/.opencode/plans/switch_asset_fix.md new file mode 100644 index 0000000..f5f1f11 --- /dev/null +++ b/.opencode/plans/switch_asset_fix.md @@ -0,0 +1,74 @@ +# Switch 平台 Asset 类崩溃修复计划 + +## 问题描述 +调用 `Sprite::createFromFile("assets/player.png")` 时崩溃,甚至连 printf 都没执行。崩溃发生在 `Asset::readBinaryFile()` 内部的 `fs::exists()` 调用上。 + +## 问题根因分析 + +Switch 平台上的 `std::filesystem` 实现可能不稳定或不完整,导致 `fs::exists()` 调用直接崩溃。 + +## 修复方案 + +修改 `Asset::readBinaryFile()` 函数,**移除文件存在性检查**,直接尝试打开文件。这样可以避免调用有问题的 `fs::exists()`。 + +### 修复: 修改 asset.cpp +**文件**: `Frostbite2D/src/frostbite2D/utils/asset.cpp` + +**变更**: +- 移除 `readBinaryFile()` 中的 `exists()` 检查 +- 直接尝试打开文件,通过 `file.is_open()` 来判断是否成功 + +**代码修改**: +```cpp +bool Asset::readBinaryFile(const std::string &path, + std::vector &outData) { + std::string fullPath = resolveFullPath(path); + + // 移除 fs::exists() 检查,直接尝试打开文件 + std::ifstream file(toPath(fullPath), std::ios::in | std::ios::binary); + if (!file.is_open()) { + return false; + } + + file.seekg(0, std::ios::end); + auto size = file.tellg(); + file.seekg(0, std::ios::beg); + + outData.resize(static_cast(size)); + file.read(reinterpret_cast(outData.data()), size); + file.close(); + return true; +} +``` + +### 可选: 同时修复 readTextFile +为了保持一致性,也可以对 `readTextFile()` 做同样的修改: + +```cpp +bool Asset::readTextFile(const std::string &path, std::string &outContent) { + std::string fullPath = resolveFullPath(path); + + // 移除 fs::exists() 检查,直接尝试打开文件 + std::ifstream file(toPath(fullPath), std::ios::in | std::ios::binary); + if (!file.is_open()) { + return false; + } + + std::ostringstream ss; + ss << file.rdbuf(); + outContent = ss.str(); + file.close(); + return true; +} +``` + +## 测试步骤 +1. 应用修复 +2. 重新构建 Switch 版本: `xmake build -p switch` +3. 在 Switch 上运行生成的 NRO 文件 +4. 验证图片能正常加载 + +## 预期结果 +- 不再崩溃 +- 文件能正常读取 +- 图片能正常加载和显示 diff --git a/.opencode/plans/switch_stb_fix.md b/.opencode/plans/switch_stb_fix.md new file mode 100644 index 0000000..90795a9 --- /dev/null +++ b/.opencode/plans/switch_stb_fix.md @@ -0,0 +1,51 @@ +# Switch 平台 stbi_load 崩溃修复计划 + +## 问题描述 +在 Switch 平台运行时,调用 `Sprite::createFromFile("assets/player.png")` 会在 `stbi_load` 这一行崩溃。 + +## 修复方案(简化版) + +只修改一个文件:使用 Asset 类读取文件到内存,再用 `stbi_load_from_memory` 加载。 + +### 修复: 修改 texture.cpp +**文件**: `Frostbite2D/src/frostbite2D/graphics/texture.cpp` + +**变更**: +- 使用 `Asset::readBinaryFile()` 读取文件到内存 +- 使用 `stbi_load_from_memory()` 替代 `stbi_load()` + +**代码修改**: +```cpp +Ptr Texture::loadFromFile(const std::string& path) { + Asset& asset = Asset::get(); + std::vector fileData; + + if (!asset.readBinaryFile(path, fileData)) { + std::string resolvedPath = asset.resolveAssetPath(path); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read texture file: %s", resolvedPath.c_str()); + return nullptr; + } + + int width, height, channels; + uint8* data = stbi_load_from_memory(fileData.data(), static_cast(fileData.size()), &width, &height, &channels, 0); + + if (!data) { + std::string resolvedPath = asset.resolveAssetPath(path); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load texture: %s", resolvedPath.c_str()); + return nullptr; + } + + // ... 其余代码保持不变 +} +``` + +## 测试步骤 +1. 应用修复 +2. 重新构建 Switch 版本: `xmake build -p switch` +3. 在 Switch 上运行生成的 NRO 文件 +4. 验证图片能正常加载 + +## 预期结果 +- stbi_load 不再崩溃 +- 图片能正常加载和显示 +- 程序能正常运行 diff --git a/Frostbite2D/include/frostbite2D/utils/pvf_archive.h b/Frostbite2D/include/frostbite2D/utils/pvf_archive.h new file mode 100644 index 0000000..3751dbd --- /dev/null +++ b/Frostbite2D/include/frostbite2D/utils/pvf_archive.h @@ -0,0 +1,222 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +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 data; ///< 原始数据指针(智能指针自动管理内存) + size_t size; ///< 数据大小(字节) +}; + +/** + * @brief PVF 资源包归档类 + * + * 用于读取和解析 PVF 格式的游戏资源包文件。 + * 提供文件索引管理、字符串资源访问和文件内容读取功能。 + * + * @example + * auto& archive = PvfArchive::get(); + * if (archive.open("Script.pvf")) { + * archive.init(); + * auto content = archive.getFileContent("script/example.txt"); + * } + */ +class PvfArchive { +public: + /** + * @brief 获取单例实例 + * @return PVF 归档实例引用 + */ + static PvfArchive& get(); + + PvfArchive(const PvfArchive&) = delete; + PvfArchive& operator=(const PvfArchive&) = delete; + PvfArchive(PvfArchive&&) = delete; + PvfArchive& operator=(PvfArchive&&) = delete; + + // --------------------------------------------------------------------------- + // 文件操作 + // --------------------------------------------------------------------------- + + /** + * @brief 打开 PVF 文件 + * @param filePath 文件路径 + * @return 打开成功返回 true + */ + bool open(const std::string& filePath = "Script.pvf"); + + /** + * @brief 关闭并清空所有数据 + */ + void close(); + + /** + * @brief 检查是否已打开 + * @return 已打开返回 true + */ + bool isOpen() const; + + // --------------------------------------------------------------------------- + // 初始化 + // --------------------------------------------------------------------------- + + /** + * @brief 完整初始化(解析头部、加载字符串表) + */ + void init(); + + /** + * @brief 解析 PVF 文件头部并建立文件索引 + */ + void initHeader(); + + /** + * @brief 加载二进制字符串表 (stringtable.bin) + */ + void initBinStringTable(); + + /** + * @brief 加载本地化字符串 (n_string.lst) + */ + void initLoadStrings(); + + // --------------------------------------------------------------------------- + // 文件信息查询 + // --------------------------------------------------------------------------- + + /** + * @brief 检查文件是否存在 + * @param path 文件路径 + * @return 存在返回 true + */ + bool hasFile(const std::string& path) const; + + /** + * @brief 获取文件信息 + * @param path 文件路径 + * @return 文件信息,不存在返回 std::nullopt + */ + std::optional getFileInfo(const std::string& path) const; + + /** + * @brief 获取所有文件路径列表 + * @return 文件路径列表 + */ + std::vector listFiles() const; + + // --------------------------------------------------------------------------- + // 文件内容读取 + // --------------------------------------------------------------------------- + + /** + * @brief 获取文件内容作为字符串 + * @param path 文件路径 + * @return 文件内容,失败返回 std::nullopt + */ + std::optional getFileContent(const std::string& path); + + /** + * @brief 获取文件内容作为字节数组 + * @param path 文件路径 + * @return 字节数组,失败返回 std::nullopt + */ + std::optional> getFileBytes(const std::string& path); + + /** + * @brief 获取文件原始数据(使用智能指针管理内存,已包含 CRC 解密) + * + * 与原始 GetFileContentChar 功能相同,但使用智能指针避免内存泄漏。 + * 数据会在首次访问时自动进行 CRC 解密。 + * + * @param path 文件路径 + * @return RawData 结构体,包含数据指针和大小;失败返回 std::nullopt + */ + std::optional getFileRawData(const std::string& path); + + // --------------------------------------------------------------------------- + // 字符串资源访问 + // --------------------------------------------------------------------------- + + /** + * @brief 获取二进制字符串 + * @param key 字符串键 + * @return 字符串,不存在返回 std::nullopt + */ + std::optional getBinString(int key) const; + + /** + * @brief 获取本地化字符串 + * @param type 字符串类型 + * @param key 字符串键 + * @return 字符串,不存在返回 std::nullopt + */ + std::optional getLoadString(const std::string& type, const std::string& key) const; + + /** + * @brief 检查二进制字符串是否存在 + * @param key 字符串键 + * @return 存在返回 true + */ + bool hasBinString(int key) const; + + /** + * @brief 检查本地化字符串是否存在 + * @param type 字符串类型 + * @param key 字符串键 + * @return 存在返回 true + */ + bool hasLoadString(const std::string& type, const std::string& key) const; + +private: + PvfArchive() = default; + ~PvfArchive() = default; + + /** + * @brief 规范化路径(转为小写) + * @param path 原始路径 + * @return 规范化后的路径 + */ + std::string normalizePath(const std::string& path) const; + + /** + * @brief 分割字符串 + * @param str 要分割的字符串 + * @param delimiter 分隔符 + * @return 分割后的字符串列表 + */ + std::vector splitString(const std::string& str, const std::string& delimiter) const; + + /** + * @brief 解码文件内容(内部使用) + * @param info 文件信息(会修改 decoded 标志) + * @return 成功返回 true + */ + bool decodeFile(PvfFileInfo& info); + + BinaryReader reader_; ///< 二进制读取器 + size_t dataStartPos_ = 0; ///< 数据起始位置 + std::map fileInfo_; ///< 文件信息映射 + std::map binStringTable_; ///< 二进制字符串表 + std::map> loadStrings_; ///< 本地化字符串 +}; + +} // namespace frostbite2D diff --git a/Frostbite2D/include/frostbite2D/utils/script_parser.h b/Frostbite2D/include/frostbite2D/utils/script_parser.h new file mode 100644 index 0000000..60c0e0a --- /dev/null +++ b/Frostbite2D/include/frostbite2D/utils/script_parser.h @@ -0,0 +1,208 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +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& 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 next(); + + /** + * @brief 回退一个指令 + */ + void back(); + + /** + * @brief 检查是否已到达数据末尾 + * @return 到达末尾返回 true + */ + bool isEnd() const; + + /** + * @brief 重置解析位置到开头 + */ + void reset(); + + // --------------------------------------------------------------------------- + // 批量解析 + // --------------------------------------------------------------------------- + + /** + * @brief 解析所有值 + * @return 所有解析值的列表 + */ + std::vector 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 parseValueAt(size_t offset) const; + + std::vector data_; ///< 数据副本 + size_t position_ = 2; ///< 当前解析位置(从第 2 字节开始) + std::string filePath_; ///< 文件路径 + std::string fileType_; ///< 文件类型 +}; + +} // namespace frostbite2D diff --git a/Frostbite2D/src/frostbite2D/2d/sprite.cpp b/Frostbite2D/src/frostbite2D/2d/sprite.cpp index fa7ce97..4ba90b9 100644 --- a/Frostbite2D/src/frostbite2D/2d/sprite.cpp +++ b/Frostbite2D/src/frostbite2D/2d/sprite.cpp @@ -18,9 +18,9 @@ Sprite::Sprite(Ptr texture) Sprite::~Sprite() { } -Ptr Sprite::createFromFile(const std::string& path) { - Ptr texture = Texture::loadFromFile(path); - if (!texture) { +Ptr Sprite::createFromFile(const std::string &path) { + Ptr texture = Texture::loadFromFile(path); + if (!texture) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load texture: %s", path.c_str()); return nullptr; } diff --git a/Frostbite2D/src/frostbite2D/graphics/texture.cpp b/Frostbite2D/src/frostbite2D/graphics/texture.cpp index dbacf16..0e55a35 100644 --- a/Frostbite2D/src/frostbite2D/graphics/texture.cpp +++ b/Frostbite2D/src/frostbite2D/graphics/texture.cpp @@ -1,9 +1,10 @@ -#define STB_IMAGE_IMPLEMENTATION #include "SDL_log.h" #include +#include +#include +#include #include #include -#include #include namespace frostbite2D { @@ -21,37 +22,69 @@ Texture::~Texture() { Ptr Texture::loadFromFile(const std::string& path) { Asset& asset = Asset::get(); std::string resolvedPath = asset.resolveAssetPath(path); - - int width, height, channels; - uint8* data = stbi_load(resolvedPath.c_str(), &width, &height, &channels, 0); - - if (!data) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load texture: %s", resolvedPath.c_str()); + std::vector fileData; + + if (!asset.readBinaryFile(path, fileData)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to read texture file: %s", resolvedPath.c_str()); return nullptr; } - - uint32_t format = channels == 4 ? GL_RGBA : GL_RGB; - + + if (fileData.empty()) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Texture file is empty: %s", + resolvedPath.c_str()); + return nullptr; + } + + SDL_RWops *rw = + SDL_RWFromConstMem(fileData.data(), static_cast(fileData.size())); + if (!rw) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create RWops: %s", + SDL_GetError()); + return nullptr; + } + + SDL_Surface *surface = IMG_Load_RW(rw, 1); + if (!surface) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load image: %s", + IMG_GetError()); + SDL_FreeRW(rw); + return nullptr; + } + + GLenum format; + int channels; + if (surface->format->BytesPerPixel == 4) { + format = GL_RGBA; + channels = 4; + } else { + format = GL_RGB; + channels = 3; + } + + int width = surface->w; + int height = surface->h; + uint32_t textureID; glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_2D, textureID); - - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); - + + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, + GL_UNSIGNED_BYTE, surface->pixels); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); - stbi_image_free(data); - + + SDL_FreeSurface(surface); + auto texture = Ptr(new Texture(width, height, textureID)); texture->path_ = resolvedPath; texture->channels_ = channels; - SDL_Log("Loaded texture: %s (%dx%d, %d channels)", resolvedPath.c_str(), width, height, channels); - return texture; } @@ -122,4 +155,4 @@ void Texture::setFilterMode(uint32_t minFilter, uint32_t magFilter) { unbind(); } -} \ No newline at end of file +} diff --git a/Frostbite2D/src/frostbite2D/utils/pvf_archive.cpp b/Frostbite2D/src/frostbite2D/utils/pvf_archive.cpp new file mode 100644 index 0000000..db3a11d --- /dev/null +++ b/Frostbite2D/src/frostbite2D/utils/pvf_archive.cpp @@ -0,0 +1,334 @@ +#include +#include +#include +#include + +namespace frostbite2D { + +PvfArchive& PvfArchive::get() { + static PvfArchive instance; + return instance; +} + +bool PvfArchive::open(const std::string& filePath) { + close(); + + if (!reader_.open(filePath)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: 无法打开文件: %s", filePath.c_str()); + return false; + } + + return true; +} + +void PvfArchive::close() { + reader_.close(); + dataStartPos_ = 0; + fileInfo_.clear(); + binStringTable_.clear(); + loadStrings_.clear(); +} + +bool PvfArchive::isOpen() const { + return reader_.isOpen(); +} + +void PvfArchive::init() { + if (!isOpen()) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: 请先打开文件再初始化"); + return; + } + + initHeader(); + initBinStringTable(); + initLoadStrings(); +} + +void PvfArchive::initHeader() { + if (!isOpen()) { + return; + } + + reader_.seek(0); + + int32 uuidLength = reader_.readInt32(); + std::string uuid = reader_.readString(uuidLength); + (void)uuid; + + int32 version = reader_.readInt32(); + (void)version; + + int32 alignedIndexHeaderSize = reader_.readInt32(); + int32 indexHeaderCrc = reader_.readInt32(); + int32 indexSize = reader_.readInt32(); + + size_t firstPos = reader_.tell(); + reader_.crcDecode(alignedIndexHeaderSize, indexHeaderCrc); + + dataStartPos_ = alignedIndexHeaderSize + 56; + size_t currPos = 0; + + for (int32 i = 0; i < indexSize; ++i) { + reader_.seek(firstPos + currPos); + + int32 fileNumber = reader_.readInt32(); + (void)fileNumber; + + int32 filePathLength = reader_.readInt32(); + std::string fileName = normalizePath(reader_.readString(filePathLength)); + + int32 fileLength = reader_.readInt32(); + int32 crc32 = reader_.readInt32(); + int32 relativeOffset = reader_.readInt32(); + + if (fileLength > 0) { + int32 realFileLength = (fileLength + 3) & 0xFFFFFFFC; + PvfFileInfo info; + info.offset = relativeOffset; + info.crc32 = static_cast(crc32); + info.length = realFileLength; + info.decoded = false; + fileInfo_[fileName] = info; + } + + currPos += 20; + currPos += filePathLength; + } +} + +void PvfArchive::initBinStringTable() { + std::string tablePath = "stringtable.bin"; + auto infoOpt = getFileInfo(tablePath); + + if (!infoOpt.has_value()) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: stringtable.bin 文件不存在"); + return; + } + + PvfFileInfo info = infoOpt.value(); + reader_.seek(dataStartPos_ + info.offset); + reader_.crcDecode(info.length, info.crc32); + reader_.seek(dataStartPos_ + info.offset); + + size_t fileHeaderPos = reader_.tell(); + int32 count = reader_.readInt32(); + + for (int32 i = 0; i < count; ++i) { + reader_.seek(fileHeaderPos + i * 4 + 4); + int32 startPos = reader_.readInt32(); + int32 endPos = reader_.readInt32(); + int32 len = endPos - startPos; + + reader_.seek(fileHeaderPos + startPos + 4); + std::string str = reader_.readString(len); + binStringTable_[i] = str; + } +} + +void PvfArchive::initLoadStrings() { + std::string lstPath = "n_string.lst"; + auto infoOpt = getFileInfo(lstPath); + + if (!infoOpt.has_value()) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "PvfArchive: n_string.lst 文件不存在"); + return; + } + + PvfFileInfo info = infoOpt.value(); + reader_.seek(dataStartPos_ + info.offset); + reader_.crcDecode(info.length, info.crc32); + reader_.seek(dataStartPos_ + info.offset); + + size_t fileHeaderPos = reader_.tell(); + int16 flag = reader_.readInt16(); + (void)flag; + + size_t i = 2; + while (i < info.length) { + if ((info.length - i) >= 10) { + reader_.seek(fileHeaderPos + i + 6); + int32 findKey = reader_.readInt32(); + + auto binStrOpt = getBinString(findKey); + if (!binStrOpt.has_value()) { + i += 10; + continue; + } + + std::string key = binStrOpt.value(); + size_t slashPos = key.find('/'); + std::string type = (slashPos != std::string::npos) ? normalizePath(key.substr(0, slashPos)) : ""; + + if (!key.empty()) { + auto fileInfoOpt = getFileInfo(key); + if (fileInfoOpt.has_value()) { + auto contentOpt = getFileContent(key); + if (contentOpt.has_value()) { + std::string content = contentOpt.value(); + std::vector lines = splitString(content, "\n"); + + for (const auto& line : lines) { + size_t gtPos = line.find('>'); + if (gtPos != std::string::npos && gtPos + 1 < line.length()) { + std::string strKey = line.substr(0, gtPos); + std::string strValue = line.substr(gtPos + 1); + loadStrings_[type][strKey] = strValue; + } + } + } + } + } + } else { + break; + } + i += 10; + } +} + +bool PvfArchive::hasFile(const std::string& path) const { + return fileInfo_.find(normalizePath(path)) != fileInfo_.end(); +} + +std::optional PvfArchive::getFileInfo(const std::string& path) const { + std::string normalizedPath = normalizePath(path); + auto it = fileInfo_.find(normalizedPath); + if (it != fileInfo_.end()) { + return it->second; + } + return std::nullopt; +} + +std::vector PvfArchive::listFiles() const { + std::vector files; + files.reserve(fileInfo_.size()); + for (const auto& pair : fileInfo_) { + files.push_back(pair.first); + } + return files; +} + +std::optional PvfArchive::getFileContent(const std::string& path) { + std::string normalizedPath = normalizePath(path); + auto it = fileInfo_.find(normalizedPath); + if (it == fileInfo_.end()) { + return std::nullopt; + } + + PvfFileInfo& info = it->second; + if (!decodeFile(info)) { + return std::nullopt; + } + + reader_.seek(dataStartPos_ + info.offset); + std::vector bytes = reader_.readBytes(info.length); + return std::string(reinterpret_cast(bytes.data()), bytes.size()); +} + +std::optional> PvfArchive::getFileBytes(const std::string& path) { + std::string normalizedPath = normalizePath(path); + auto it = fileInfo_.find(normalizedPath); + if (it == fileInfo_.end()) { + return std::nullopt; + } + + PvfFileInfo& info = it->second; + if (!decodeFile(info)) { + return std::nullopt; + } + + reader_.seek(dataStartPos_ + info.offset); + return reader_.readBytes(info.length); +} + +std::optional PvfArchive::getFileRawData(const std::string& path) { + std::string normalizedPath = normalizePath(path); + auto it = fileInfo_.find(normalizedPath); + if (it == fileInfo_.end()) { + return std::nullopt; + } + + PvfFileInfo& info = it->second; + if (!decodeFile(info)) { + return std::nullopt; + } + + reader_.seek(dataStartPos_ + info.offset); + + RawData result; + result.size = info.length; + result.data = std::make_unique(info.length); + reader_.read(result.data.get(), info.length); + + return result; +} + +std::optional PvfArchive::getBinString(int key) const { + auto it = binStringTable_.find(key); + if (it != binStringTable_.end()) { + return it->second; + } + return std::nullopt; +} + +std::optional PvfArchive::getLoadString(const std::string& type, const std::string& key) const { + std::string normalizedType = normalizePath(type); + auto typeIt = loadStrings_.find(normalizedType); + if (typeIt != loadStrings_.end()) { + auto keyIt = typeIt->second.find(key); + if (keyIt != typeIt->second.end()) { + return keyIt->second; + } + } + return std::nullopt; +} + +bool PvfArchive::hasBinString(int key) const { + return binStringTable_.find(key) != binStringTable_.end(); +} + +bool PvfArchive::hasLoadString(const std::string& type, const std::string& key) const { + std::string normalizedType = normalizePath(type); + auto typeIt = loadStrings_.find(normalizedType); + if (typeIt != loadStrings_.end()) { + return typeIt->second.find(key) != typeIt->second.end(); + } + return false; +} + +std::string PvfArchive::normalizePath(const std::string& path) const { + std::string result = path; + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return std::tolower(c); }); + return result; +} + +std::vector PvfArchive::splitString(const std::string& str, const std::string& delimiter) const { + std::vector tokens; + size_t pos = 0; + size_t found; + + while ((found = str.find(delimiter, pos)) != std::string::npos) { + tokens.push_back(str.substr(pos, found - pos)); + pos = found + delimiter.length(); + } + + if (pos < str.length()) { + tokens.push_back(str.substr(pos)); + } + + return tokens; +} + +bool PvfArchive::decodeFile(PvfFileInfo& info) { + if (info.decoded) { + return true; + } + + reader_.seek(dataStartPos_ + info.offset); + reader_.crcDecode(info.length, info.crc32); + info.decoded = true; + + return true; +} + +} // namespace frostbite2D diff --git a/Frostbite2D/src/frostbite2D/utils/script_parser.cpp b/Frostbite2D/src/frostbite2D/utils/script_parser.cpp new file mode 100644 index 0000000..38cfcd4 --- /dev/null +++ b/Frostbite2D/src/frostbite2D/utils/script_parser.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#include +#include + +namespace frostbite2D { + +std::string ScriptValue::toString() const { + switch (type) { + case ScriptValueType::Integer: + return std::to_string(intValue); + case ScriptValueType::Float: + return std::to_string(floatValue); + case ScriptValueType::String: + return stringValue; + case ScriptValueType::Empty: + default: + return ""; + } +} + +ScriptParser::ScriptParser(const RawData& data, const std::string& filePath) + : filePath_(filePath) + , fileType_(extractFileType(filePath)) { + if (data.data && data.size > 0) { + data_.resize(data.size); + std::memcpy(data_.data(), data.data.get(), data.size); + } + if (data_.size() < 7) { + data_.clear(); + } +} + +ScriptParser::ScriptParser(const std::vector& data, const std::string& filePath) + : filePath_(filePath) + , fileType_(extractFileType(filePath)) { + if (data.size() > 0) { + data_.resize(data.size()); + std::memcpy(data_.data(), data.data(), data.size()); + } + if (data_.size() < 7) { + data_.clear(); + } +} + +ScriptParser::ScriptParser(const char* data, size_t size, const std::string& filePath) + : filePath_(filePath) + , fileType_(extractFileType(filePath)) { + if (data && size > 0) { + data_.resize(size); + std::memcpy(data_.data(), data, size); + } + if (data_.size() < 7) { + data_.clear(); + } +} + +std::optional ScriptParser::next() { + if (!isValid() || isEnd()) { + return std::nullopt; + } + + auto value = parseValueAt(position_); + if (value.has_value()) { + position_ += 5; + } + return value; +} + +void ScriptParser::back() { + if (position_ >= 7) { + position_ -= 5; + } +} + +bool ScriptParser::isEnd() const { + return !isValid() || position_ >= data_.size() || (data_.size() - position_) < 5; +} + +void ScriptParser::reset() { + position_ = 2; +} + +std::vector ScriptParser::parseAll() { + std::vector values; + reset(); + + while (!isEnd()) { + if (auto value = next()) { + values.push_back(*value); + } + } + + return values; +} + +size_t ScriptParser::position() const { + return position_; +} + +size_t ScriptParser::size() const { + return data_.size(); +} + +const std::string& ScriptParser::filePath() const { + return filePath_; +} + +const std::string& ScriptParser::fileType() const { + return fileType_; +} + +bool ScriptParser::isValid() const { + return !data_.empty(); +} + +std::string ScriptParser::extractFileType(const std::string& filePath) const { + size_t slashPos = filePath.find_first_of("/"); + if (slashPos == std::string::npos) { + return ""; + } + + std::string type = filePath.substr(0, slashPos); + std::transform(type.begin(), type.end(), type.begin(), + [](unsigned char c) { return std::tolower(c); }); + return type; +} + +uint8 ScriptParser::readByte(size_t offset) const { + if (offset >= data_.size()) { + return 0; + } + return static_cast(data_[offset]); +} + +int32 ScriptParser::readInt32(size_t offset) const { + if (offset + 4 > data_.size()) { + return 0; + } + int32 value; + std::memcpy(&value, data_.data() + offset, sizeof(int32)); + return value; +} + +std::optional ScriptParser::parseValueAt(size_t offset) const { + if (offset + 5 > data_.size()) { + return std::nullopt; + } + + ScriptValue result; + PvfArchive& archive = PvfArchive::get(); + + uint8 opcode = readByte(offset); + int32 data = readInt32(offset + 1); + + switch (static_cast(opcode)) { + case ScriptOpcode::Integer: { + result.type = ScriptValueType::Integer; + result.intValue = data; + break; + } + + case ScriptOpcode::Float: { + result.type = ScriptValueType::Float; + float floatValue; + std::memcpy(&floatValue, &data, sizeof(float)); + result.floatValue = floatValue; + break; + } + + case ScriptOpcode::StringRef5: + case ScriptOpcode::StringRef6: + case ScriptOpcode::StringRef7: + case ScriptOpcode::StringRef8: { + result.type = ScriptValueType::String; + if (auto str = archive.getBinString(data)) { + result.stringValue = *str; + } + break; + } + + case ScriptOpcode::ExtendedString9: { + result.type = ScriptValueType::String; + if (offset + 10 <= data_.size()) { + uint8 newOpcode = readByte(offset + 5); + (void)newOpcode; + int32 newData = readInt32(offset + 6); + + if (auto binStr = archive.getBinString(newData)) { + if (!binStr->empty()) { + if (auto loadStr = archive.getLoadString(fileType_, *binStr)) { + result.stringValue = *loadStr; + } + } + } + } + break; + } + + case ScriptOpcode::ExtendedString10: { + result.type = ScriptValueType::String; + if (auto binStr = archive.getBinString(data)) { + if (!binStr->empty()) { + if (auto loadStr = archive.getLoadString(fileType_, *binStr)) { + result.stringValue = *loadStr; + } + } + } + break; + } + + default: { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ScriptParser: 未知的操作码: %d", opcode); + return std::nullopt; + } + } + + return result; +} + +} // namespace frostbite2D diff --git a/Game/src/main.cpp b/Game/src/main.cpp index 48f3ace..f588756 100644 --- a/Game/src/main.cpp +++ b/Game/src/main.cpp @@ -10,7 +10,8 @@ #include #include #include - +#include +#include using namespace frostbite2D; @@ -18,8 +19,8 @@ int main(int argc, char **argv) { AppConfig config = AppConfig::createDefault(); config.appName = "Frostbite2D Test App"; config.appVersion = "1.0.0"; - config.windowConfig.width = 800; - config.windowConfig.height = 600; + config.windowConfig.width = 1920; + config.windowConfig.height = 1080; config.windowConfig.title = "Frostbite2D - Renderer Test"; Application& app = Application::get(); @@ -34,9 +35,6 @@ int main(int argc, char **argv) { auto menuScene = MakePtr(); SceneManager::get().PushScene(menuScene); - // 先测试彩色四边形,排除纹理问题 - SDL_Log("Testing colored quad..."); - // 尝试加载精灵 auto sprite = Sprite::createFromFile("assets/player.png"); if (sprite) { @@ -47,7 +45,53 @@ int main(int argc, char **argv) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from file!"); } - + auto &archive = PvfArchive::get(); + if (archive.open("assets/Script.pvf")) { + archive.init(); + // 文件内容读取 + if (auto rawData = archive.getFileRawData("region/balmayer_north.rgn")) { + ScriptParser parser(*rawData, "script/example.bin"); + // // 方式1:迭代解析 + // while (!parser.isEnd()) { + // if (auto value = parser.next()) { + // switch (value->type) { + // case ScriptValueType::Integer: + // SDL_Log("Integer: %d", value->intValue); + // break; + // case ScriptValueType::Float: + // SDL_Log("Float: %f", value->floatValue); + // break; + // case ScriptValueType::String: + // SDL_Log("String: %s", value->stringValue.c_str()); + // break; + // default: + // break; + // } + // } + // } + + // // 方式2:批量解析 + // parser.reset(); + auto allValues = parser.parseAll(); + for (const auto &value : allValues) { + // 处理 value + switch (value.type) { + case ScriptValueType::Integer: + SDL_Log("Integer: %d", value.intValue); + break; + case ScriptValueType::Float: + SDL_Log("Float: %f", value.floatValue); + break; + case ScriptValueType::String: + SDL_Log("String: %s", value.stringValue.c_str()); + break; + default: + break; + } + } + } + } + app.run(); app.shutdown(); diff --git a/platform/linux.lua b/platform/linux.lua index 76f1b4f..7c440a3 100644 --- a/platform/linux.lua +++ b/platform/linux.lua @@ -1,5 +1,6 @@ add_requires("libsdl2", {configs = {shared = true,wayland = true}}) +add_requires("libsdl2_image") add_requires("glm") target("Frostbite2D") @@ -12,6 +13,7 @@ target("Frostbite2D") add_includedirs(path.join(os.projectdir(), "Game/include")) add_packages("libsdl2") + add_packages("libsdl2_image") add_packages("glm") -- 复制着色器文件到输出目录 diff --git a/platform/mingw.lua b/platform/mingw.lua index 3ab8f48..8af4ee2 100644 --- a/platform/mingw.lua +++ b/platform/mingw.lua @@ -2,6 +2,7 @@ set_toolchains("mingw") add_requires("libsdl2", {configs = {shared = true}}) +add_requires("libsdl2_image", {configs = {shared = true}}) add_requires("glm") target("Frostbite2D") @@ -14,6 +15,7 @@ target("Frostbite2D") add_includedirs(path.join(os.projectdir(), "Game/include")) add_packages("libsdl2") + add_packages("libsdl2_image") add_packages("glm") -- 复制 assets 目录到输出目录 diff --git a/platform/switch.lua b/platform/switch.lua index 4744e25..6e933cb 100644 --- a/platform/switch.lua +++ b/platform/switch.lua @@ -40,9 +40,9 @@ target("Frostbite2D") add_includedirs(path.join(devkitPro, "portlibs/switch/include/SDL2")) add_linkdirs(path.join(devkitPro, "portlibs/switch/lib")) - add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg", - "modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau", - {public = true}) + add_syslinks("SDL2_mixer", "SDL2_image", "SDL2", "webp", "png", "jpeg", "z", "opusfile", "opus", "vorbisidec", "ogg", + "modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau", + {public = true}) add_syslinks("nx", "m")