feat(utils): 添加二进制文件读取器 BinaryReader
refactor(assets): 将着色器路径从 "shaders" 改为 "assets/shaders" refactor(build): 统一各平台资源目录复制逻辑为 assets 目录 style: 更新 .gitignore 忽略参考代码目录和 .pvf 文件
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,3 +12,5 @@ build/
|
||||
*.json
|
||||
|
||||
.vscode/compile_commands.json
|
||||
参考代码/
|
||||
*.pvf
|
||||
|
||||
269
Frostbite2D/include/frostbite2D/utils/binary_reader.h
Normal file
269
Frostbite2D/include/frostbite2D/utils/binary_reader.h
Normal file
@@ -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
|
||||
@@ -28,7 +28,7 @@ bool Renderer::init() {
|
||||
}
|
||||
|
||||
//初始化着色器管理器
|
||||
if (!shaderManager_.init("shaders")) {
|
||||
if (!shaderManager_.init("assets/shaders")) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize shader manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
235
Frostbite2D/src/frostbite2D/utils/binary_reader.cpp
Normal file
235
Frostbite2D/src/frostbite2D/utils/binary_reader.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
#include <frostbite2D/utils/binary_reader.h>
|
||||
#include <frostbite2D/utils/asset.h>
|
||||
#include <fstream>
|
||||
#include <SDL.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
BinaryReader::BinaryReader(const std::string& filePath) {
|
||||
open(filePath);
|
||||
}
|
||||
|
||||
BinaryReader::BinaryReader(const char* data, size_t size) {
|
||||
loadFromMemory(data, size);
|
||||
}
|
||||
|
||||
bool BinaryReader::open(const std::string& filePath) {
|
||||
close();
|
||||
|
||||
Asset& asset = Asset::get();
|
||||
auto dataOpt = asset.readFileToBytes(filePath);
|
||||
|
||||
if (!dataOpt.has_value()) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "BinaryReader: 无法打开文件: %s", filePath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<uint8>& bytes = dataOpt.value();
|
||||
data_.resize(bytes.size());
|
||||
std::memcpy(data_.data(), bytes.data(), bytes.size());
|
||||
|
||||
position_ = 0;
|
||||
lastReadCount_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BinaryReader::loadFromMemory(const char* data, size_t size) {
|
||||
close();
|
||||
|
||||
if (data && size > 0) {
|
||||
data_.resize(size);
|
||||
std::memcpy(data_.data(), data, size);
|
||||
}
|
||||
|
||||
position_ = 0;
|
||||
lastReadCount_ = 0;
|
||||
}
|
||||
|
||||
void BinaryReader::close() {
|
||||
data_.clear();
|
||||
position_ = 0;
|
||||
lastReadCount_ = 0;
|
||||
}
|
||||
|
||||
bool BinaryReader::isOpen() const {
|
||||
return !data_.empty();
|
||||
}
|
||||
|
||||
size_t BinaryReader::tell() const {
|
||||
return position_;
|
||||
}
|
||||
|
||||
void BinaryReader::seek(size_t pos) {
|
||||
position_ = std::clamp(pos, static_cast<size_t>(0), data_.size());
|
||||
}
|
||||
|
||||
void BinaryReader::skip(size_t count) {
|
||||
seek(position_ + count);
|
||||
}
|
||||
|
||||
bool BinaryReader::eof() const {
|
||||
return position_ >= data_.size();
|
||||
}
|
||||
|
||||
size_t BinaryReader::size() const {
|
||||
return data_.size();
|
||||
}
|
||||
|
||||
size_t BinaryReader::remaining() const {
|
||||
if (position_ >= data_.size()) {
|
||||
return 0;
|
||||
}
|
||||
return data_.size() - position_;
|
||||
}
|
||||
|
||||
size_t BinaryReader::lastReadCount() const {
|
||||
return lastReadCount_;
|
||||
}
|
||||
|
||||
size_t BinaryReader::read(char* buffer, size_t size) {
|
||||
if (!buffer || size == 0 || eof()) {
|
||||
lastReadCount_ = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t bytesToRead = std::min(size, remaining());
|
||||
std::memcpy(buffer, data_.data() + position_, bytesToRead);
|
||||
position_ += bytesToRead;
|
||||
lastReadCount_ = bytesToRead;
|
||||
|
||||
if (bytesToRead != size) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "BinaryReader: 读取数据不完整,期望 %zu 字节,实际读取 %zu 字节", size, bytesToRead);
|
||||
}
|
||||
|
||||
return bytesToRead;
|
||||
}
|
||||
|
||||
std::vector<uint8> BinaryReader::readBytes(size_t size) {
|
||||
std::vector<uint8> result;
|
||||
size_t bytesToRead = std::min(size, remaining());
|
||||
|
||||
if (bytesToRead > 0) {
|
||||
result.resize(bytesToRead);
|
||||
read(reinterpret_cast<char*>(result.data()), bytesToRead);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* BinaryReader::data() const {
|
||||
return data_.data();
|
||||
}
|
||||
|
||||
const char* BinaryReader::currentData() const {
|
||||
if (eof()) {
|
||||
return nullptr;
|
||||
}
|
||||
return data_.data() + position_;
|
||||
}
|
||||
|
||||
int8 BinaryReader::readInt8() {
|
||||
return read<int8>();
|
||||
}
|
||||
|
||||
int16 BinaryReader::readInt16() {
|
||||
return read<int16>();
|
||||
}
|
||||
|
||||
int32 BinaryReader::readInt32() {
|
||||
return read<int32>();
|
||||
}
|
||||
|
||||
int64 BinaryReader::readInt64() {
|
||||
return read<int64>();
|
||||
}
|
||||
|
||||
uint8 BinaryReader::readUInt8() {
|
||||
return read<uint8>();
|
||||
}
|
||||
|
||||
uint16 BinaryReader::readUInt16() {
|
||||
return read<uint16>();
|
||||
}
|
||||
|
||||
uint32 BinaryReader::readUInt32() {
|
||||
return read<uint32>();
|
||||
}
|
||||
|
||||
uint64 BinaryReader::readUInt64() {
|
||||
return read<uint64>();
|
||||
}
|
||||
|
||||
float BinaryReader::readFloat() {
|
||||
return read<float>();
|
||||
}
|
||||
|
||||
double BinaryReader::readDouble() {
|
||||
return read<double>();
|
||||
}
|
||||
|
||||
std::string BinaryReader::readString(size_t length) {
|
||||
if (length == 0 || eof()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t bytesToRead = std::min(length, remaining());
|
||||
std::string result(data_.data() + position_, bytesToRead);
|
||||
position_ += bytesToRead;
|
||||
lastReadCount_ = bytesToRead;
|
||||
|
||||
if (bytesToRead != length) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "BinaryReader: 读取字符串不完整,期望 %zu 字节,实际读取 %zu 字节", length, bytesToRead);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string BinaryReader::readNullTerminatedString() {
|
||||
if (eof()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t startPos = position_;
|
||||
while (position_ < data_.size() && data_[position_] != '\0') {
|
||||
++position_;
|
||||
}
|
||||
|
||||
size_t length = position_ - startPos;
|
||||
std::string result(data_.data() + startPos, length);
|
||||
|
||||
if (position_ < data_.size()) {
|
||||
++position_;
|
||||
}
|
||||
|
||||
lastReadCount_ = length + 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
void BinaryReader::crcDecode(size_t length, uint32 crc32) {
|
||||
if (length == 0 || eof()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32 key = 0x81A79011;
|
||||
size_t originalPos = position_;
|
||||
size_t bytesToProcess = std::min(length, remaining());
|
||||
|
||||
for (size_t i = 0; i < bytesToProcess; i += 4) {
|
||||
size_t pos = position_;
|
||||
uint32 value = read<uint32>();
|
||||
|
||||
uint32 decoded = (value ^ key ^ crc32);
|
||||
decoded = (decoded >> 6) | ((decoded << (32 - 6)) & 0xFFFFFFFF);
|
||||
|
||||
if (pos + 3 < data_.size()) {
|
||||
data_[pos] = static_cast<char>((decoded >> 0) & 0xFF);
|
||||
data_[pos + 1] = static_cast<char>((decoded >> 8) & 0xFF);
|
||||
data_[pos + 2] = static_cast<char>((decoded >> 16) & 0xFF);
|
||||
data_[pos + 3] = static_cast<char>((decoded >> 24) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
seek(originalPos + bytesToProcess);
|
||||
}
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -6,9 +6,11 @@
|
||||
#include <frostbite2D/graphics/texture.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include <frostbite2D/2d/sprite.h>
|
||||
#include <frostbite2D/scene/scene.h>
|
||||
#include <frostbite2D/scene/scene_manager.h>
|
||||
#include <frostbite2D/2d/sprite.h>
|
||||
#include <frostbite2D/utils/binary_reader.h>
|
||||
|
||||
|
||||
using namespace frostbite2D;
|
||||
|
||||
@@ -36,7 +38,7 @@ int main(int argc, char **argv) {
|
||||
SDL_Log("Testing colored quad...");
|
||||
|
||||
// 尝试加载精灵
|
||||
auto sprite = Sprite::createFromFile("assets\\player.png");
|
||||
auto sprite = Sprite::createFromFile("assets/player.png");
|
||||
if (sprite) {
|
||||
sprite->SetPosition(100, 100);
|
||||
menuScene->AddActor(sprite);
|
||||
@@ -45,6 +47,7 @@ int main(int argc, char **argv) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite from file!");
|
||||
}
|
||||
|
||||
|
||||
app.run();
|
||||
|
||||
app.shutdown();
|
||||
|
||||
@@ -16,39 +16,15 @@ target("Frostbite2D")
|
||||
|
||||
-- 复制着色器文件到输出目录
|
||||
after_build(function (target)
|
||||
-- 复制 shaders 目录
|
||||
local shaders_dir = path.join(os.projectdir(), "Game/shaders")
|
||||
-- 复制 assets 目录
|
||||
local assets_dir = path.join(os.projectdir(), "Game/assets")
|
||||
local output_dir = target:targetdir()
|
||||
local target_shaders_dir = path.join(output_dir, "shaders")
|
||||
local target_assets_dir = path.join(output_dir, "assets")
|
||||
|
||||
if os.isdir(shaders_dir) then
|
||||
-- 确保目标目录存在
|
||||
if not os.isdir(target_shaders_dir) then
|
||||
os.mkdir(target_shaders_dir)
|
||||
end
|
||||
|
||||
-- 复制所有着色器文件
|
||||
for _, file in ipairs(os.files(path.join(shaders_dir, "*.*"))) do
|
||||
local filename = path.filename(file)
|
||||
local target_file = path.join(target_shaders_dir, filename)
|
||||
os.cp(file, target_file)
|
||||
end
|
||||
end
|
||||
|
||||
-- 复制图标文件到输出目录
|
||||
local icons_dir = path.join(os.projectdir(), "assets/icons")
|
||||
local target_icons_dir = path.join(output_dir, "assets/icons")
|
||||
|
||||
if os.isdir(icons_dir) then
|
||||
if not os.isdir(target_icons_dir) then
|
||||
os.mkdir(target_icons_dir)
|
||||
end
|
||||
for _, file in ipairs(os.files(path.join(icons_dir, "*.*"))) do
|
||||
local filename = path.filename(file)
|
||||
local target_file = path.join(target_icons_dir, filename)
|
||||
os.cp(file, target_file)
|
||||
print("Copy icon: " .. filename)
|
||||
end
|
||||
if os.isdir(assets_dir) then
|
||||
os.rm(target_assets_dir)
|
||||
os.cp(assets_dir, output_dir)
|
||||
print("Copy assets directory: " .. assets_dir .. " -> " .. target_assets_dir)
|
||||
end
|
||||
end)
|
||||
target_end()
|
||||
|
||||
@@ -16,26 +16,17 @@ target("Frostbite2D")
|
||||
add_packages("libsdl2")
|
||||
add_packages("glm")
|
||||
|
||||
-- 复制着色器文件到输出目录
|
||||
-- 复制 assets 目录到输出目录
|
||||
after_build(function (target)
|
||||
-- 复制 shaders 目录
|
||||
local shaders_dir = path.join(os.projectdir(), "Game/shaders")
|
||||
-- 复制 assets 目录
|
||||
local assets_dir = path.join(os.projectdir(), "Game/assets")
|
||||
local output_dir = target:targetdir()
|
||||
local target_shaders_dir = path.join(output_dir, "shaders")
|
||||
local target_assets_dir = path.join(output_dir, "assets")
|
||||
|
||||
if os.isdir(shaders_dir) then
|
||||
-- 确保目标目录存在
|
||||
if not os.isdir(target_shaders_dir) then
|
||||
os.mkdir(target_shaders_dir)
|
||||
end
|
||||
|
||||
-- 复制所有着色器文件
|
||||
for _, file in ipairs(os.files(path.join(shaders_dir, "*.*"))) do
|
||||
local filename = path.filename(file)
|
||||
local target_file = path.join(target_shaders_dir, filename)
|
||||
os.cp(file, target_file)
|
||||
print("Copy shader: " .. filename)
|
||||
end
|
||||
if os.isdir(assets_dir) then
|
||||
os.rm(target_assets_dir)
|
||||
os.cp(assets_dir, output_dir)
|
||||
print("Copy assets directory: " .. assets_dir .. " -> " .. target_assets_dir)
|
||||
end
|
||||
|
||||
-- 复制 SDL2 DLL (Windows 平台)
|
||||
|
||||
@@ -69,23 +69,15 @@ target("Frostbite2D")
|
||||
print("Generated NRO: " .. nro_file)
|
||||
end
|
||||
|
||||
-- 复制 shaders 目录
|
||||
local shaders_dir = path.join(os.projectdir(), "Game/shaders")
|
||||
local target_shaders_dir = path.join(output_dir, "shaders")
|
||||
-- 复制 assets 目录
|
||||
local assets_dir = path.join(os.projectdir(), "Game/assets")
|
||||
local output_dir = target:targetdir()
|
||||
local target_assets_dir = path.join(output_dir, "assets")
|
||||
|
||||
if os.isdir(shaders_dir) then
|
||||
-- 确保目标目录存在
|
||||
if not os.isdir(target_shaders_dir) then
|
||||
os.mkdir(target_shaders_dir)
|
||||
end
|
||||
|
||||
-- 复制所有着色器文件
|
||||
for _, file in ipairs(os.files(path.join(shaders_dir, "*.*"))) do
|
||||
local filename = path.filename(file)
|
||||
local target_file = path.join(target_shaders_dir, filename)
|
||||
os.cp(file, target_file)
|
||||
print("Copy shader: " .. filename)
|
||||
end
|
||||
if os.isdir(assets_dir) then
|
||||
os.rm(target_assets_dir)
|
||||
os.cp(assets_dir, output_dir)
|
||||
print("Copy assets directory: " .. assets_dir .. " -> " .. target_assets_dir)
|
||||
end
|
||||
end)
|
||||
target_end()
|
||||
|
||||
Reference in New Issue
Block a user