diff options
Diffstat (limited to 'tools/asset_packer.cc')
| -rw-r--r-- | tools/asset_packer.cc | 562 |
1 files changed, 268 insertions, 294 deletions
diff --git a/tools/asset_packer.cc b/tools/asset_packer.cc index 5646716..e7f678b 100644 --- a/tools/asset_packer.cc +++ b/tools/asset_packer.cc @@ -35,12 +35,8 @@ static const std::map<std::string, ProcGenFunc> kAssetPackerProcGenFuncMap = { static bool HasImageExtension(const std::string& filename) { std::string ext = filename.substr(filename.find_last_of(".") + 1); - // simple case-insensitive check (assuming lowercase for simplicity or just - // basic checks) - if (ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "tga" || - ext == "bmp") - return true; - return false; + return ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "tga" || + ext == "bmp"; } static bool HasMeshExtension(const std::string& filename) { @@ -48,6 +44,35 @@ static bool HasMeshExtension(const std::string& filename) { return ext == "obj"; } +// Forward declaration +struct AssetBuildInfo; + +static bool ParseProceduralParams(const std::string& params_str, + std::vector<float>* out_params, + const std::string& asset_name) { + size_t current_pos = 0; + while (current_pos < params_str.length()) { + size_t comma_pos = params_str.find(',', current_pos); + std::string param_val_str = + (comma_pos == std::string::npos) + ? params_str.substr(current_pos) + : params_str.substr(current_pos, comma_pos - current_pos); + param_val_str.erase(0, param_val_str.find_first_not_of(" \t\r\n")); + param_val_str.erase(param_val_str.find_last_not_of(" \t\r\n") + 1); + try { + out_params->push_back(std::stof(param_val_str)); + } catch (...) { + fprintf(stderr, "Error: Invalid proc param for %s: %s\n", + asset_name.c_str(), param_val_str.c_str()); + return false; + } + if (comma_pos == std::string::npos) + break; + current_pos = comma_pos + 1; + } + return true; +} + // Helper struct to hold all information about an asset during parsing struct AssetBuildInfo { std::string name; @@ -63,35 +88,256 @@ struct AssetBuildInfo { std::string func_name_str_name; // ASSET_PROC_FUNC_STR_xxx for procedural }; +static bool ParseProceduralFunction(const std::string& compression_type_str, + AssetBuildInfo* info, bool is_gpu) { + const char* prefix = is_gpu ? "PROC_GPU(" : "PROC("; + size_t prefix_len = is_gpu ? 9 : 5; + + size_t open_paren = compression_type_str.find('('); + size_t close_paren = compression_type_str.rfind(')'); + if (open_paren == std::string::npos || close_paren == std::string::npos) { + fprintf(stderr, "Error: Invalid %s syntax for asset: %s, string: [%s]\n", + prefix, info->name.c_str(), compression_type_str.c_str()); + return false; + } + + std::string func_and_params_str = + compression_type_str.substr(open_paren + 1, close_paren - open_paren - 1); + size_t params_start = func_and_params_str.find(','); + + if (params_start != std::string::npos) { + info->proc_func_name = func_and_params_str.substr(0, params_start); + std::string params_str = func_and_params_str.substr(params_start + 1); + if (!ParseProceduralParams(params_str, &info->proc_params, info->name)) { + return false; + } + } else { + info->proc_func_name = func_and_params_str; + } + + if (is_gpu) { + if (info->proc_func_name != "gen_noise" && + info->proc_func_name != "gen_perlin" && + info->proc_func_name != "gen_grid") { + fprintf(stderr, + "Error: PROC_GPU only supports gen_noise, gen_perlin, gen_grid, " + "got: %s for asset: %s\n", + info->proc_func_name.c_str(), info->name.c_str()); + return false; + } + } else { + if (kAssetPackerProcGenFuncMap.find(info->proc_func_name) == + kAssetPackerProcGenFuncMap.end()) { + fprintf(stderr, + "Warning: Unknown procedural function: %s for asset: %s " + "(Runtime error will occur)\n", + info->proc_func_name.c_str(), info->name.c_str()); + } + } + + return true; +} + struct Vec3 { float x, y, z; - Vec3 operator+(const Vec3& o) const { - return {x + o.x, y + o.y, z + o.z}; - } + Vec3 operator+(const Vec3& o) const { return {x + o.x, y + o.y, z + o.z}; } Vec3 operator+=(const Vec3& o) { x += o.x; y += o.y; z += o.z; return *this; } - Vec3 operator-(const Vec3& o) const { - return {x - o.x, y - o.y, z - o.z}; - } - Vec3 operator*(float s) const { - return {x * s, y * s, z * s}; - } + Vec3 operator-(const Vec3& o) const { return {x - o.x, y - o.y, z - o.z}; } + Vec3 operator*(float s) const { return {x * s, y * s, z * s}; } static Vec3 cross(const Vec3& a, const Vec3& b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; } Vec3 normalize() const { float len = std::sqrt(x * x + y * y + z * z); - if (len > 1e-6f) - return {x / len, y / len, z / len}; - return {0, 0, 0}; + return (len > 1e-6f) ? Vec3{x / len, y / len, z / len} : Vec3{0, 0, 0}; } }; +struct Vertex { + float p[3], n[3], u[2]; +}; + +static bool ProcessMeshFile(const std::string& full_path, + std::vector<uint8_t>* buffer, + const std::string& asset_name) { + std::ifstream obj_file(full_path); + if (!obj_file.is_open()) { + fprintf(stderr, "Error: Could not open mesh file: %s\n", full_path.c_str()); + return false; + } + + std::vector<float> v_pos, v_norm, v_uv; + struct RawFace { + int v[3], vt[3], vn[3]; + }; + std::vector<RawFace> raw_faces; + + std::string obj_line; + while (std::getline(obj_file, obj_line)) { + if (obj_line.compare(0, 2, "v ") == 0) { + float x, y, z; + std::sscanf(obj_line.c_str(), "v %f %f %f", &x, &y, &z); + v_pos.push_back(x); + v_pos.push_back(y); + v_pos.push_back(z); + } else if (obj_line.compare(0, 3, "vn ") == 0) { + float x, y, z; + std::sscanf(obj_line.c_str(), "vn %f %f %f", &x, &y, &z); + v_norm.push_back(x); + v_norm.push_back(y); + v_norm.push_back(z); + } else if (obj_line.compare(0, 3, "vt ") == 0) { + float u, v; + std::sscanf(obj_line.c_str(), "vt %f %f", &u, &v); + v_uv.push_back(u); + v_uv.push_back(v); + } else if (obj_line.compare(0, 2, "f ") == 0) { + char s1[64], s2[64], s3[64]; + if (std::sscanf(obj_line.c_str(), "f %s %s %s", s1, s2, s3) == 3) { + std::string parts[3] = {s1, s2, s3}; + RawFace face = {}; + for (int i = 0; i < 3; ++i) { + int v_idx = 0, vt_idx = 0, vn_idx = 0; + if (parts[i].find("//") != std::string::npos) { + std::sscanf(parts[i].c_str(), "%d//%d", &v_idx, &vn_idx); + } else if (std::count(parts[i].begin(), parts[i].end(), '/') == 2) { + std::sscanf(parts[i].c_str(), "%d/%d/%d", &v_idx, &vt_idx, &vn_idx); + } else if (std::count(parts[i].begin(), parts[i].end(), '/') == 1) { + std::sscanf(parts[i].c_str(), "%d/%d", &v_idx, &vt_idx); + } else { + std::sscanf(parts[i].c_str(), "%d", &v_idx); + } + face.v[i] = v_idx; + face.vt[i] = vt_idx; + face.vn[i] = vn_idx; + } + raw_faces.push_back(face); + } + } + } + + // Generate normals if missing + if (v_norm.empty() && !v_pos.empty()) { + printf("Generating normals for %s...\n", asset_name.c_str()); + std::vector<Vec3> temp_normals(v_pos.size() / 3, {0, 0, 0}); + for (auto& face : raw_faces) { + int idx0 = face.v[0] - 1; + int idx1 = face.v[1] - 1; + int idx2 = face.v[2] - 1; + + if (idx0 >= 0 && idx1 >= 0 && idx2 >= 0) { + Vec3 p0 = {v_pos[idx0 * 3], v_pos[idx0 * 3 + 1], v_pos[idx0 * 3 + 2]}; + Vec3 p1 = {v_pos[idx1 * 3], v_pos[idx1 * 3 + 1], v_pos[idx1 * 3 + 2]}; + Vec3 p2 = {v_pos[idx2 * 3], v_pos[idx2 * 3 + 1], v_pos[idx2 * 3 + 2]}; + + Vec3 normal = Vec3::cross(p1 - p0, p2 - p0).normalize(); + temp_normals[idx0] += normal; + temp_normals[idx1] += normal; + temp_normals[idx2] += normal; + } + } + + for (const auto& n : temp_normals) { + Vec3 normalized = n.normalize(); + v_norm.push_back(normalized.x); + v_norm.push_back(normalized.y); + v_norm.push_back(normalized.z); + } + + for (auto& face : raw_faces) { + face.vn[0] = face.v[0]; + face.vn[1] = face.v[1]; + face.vn[2] = face.v[2]; + } + } + + // Build final vertices + std::vector<Vertex> final_vertices; + std::vector<uint32_t> final_indices; + std::map<std::string, uint32_t> vertex_map; + + for (const auto& face : raw_faces) { + for (int i = 0; i < 3; ++i) { + char key_buf[128]; + std::snprintf(key_buf, sizeof(key_buf), "%d/%d/%d", face.v[i], + face.vt[i], face.vn[i]); + std::string key = key_buf; + + if (vertex_map.find(key) == vertex_map.end()) { + vertex_map[key] = (uint32_t)final_vertices.size(); + + Vertex v = {}; + if (face.v[i] > 0) { + v.p[0] = v_pos[(face.v[i] - 1) * 3]; + v.p[1] = v_pos[(face.v[i] - 1) * 3 + 1]; + v.p[2] = v_pos[(face.v[i] - 1) * 3 + 2]; + } + if (face.vn[i] > 0) { + v.n[0] = v_norm[(face.vn[i] - 1) * 3]; + v.n[1] = v_norm[(face.vn[i] - 1) * 3 + 1]; + v.n[2] = v_norm[(face.vn[i] - 1) * 3 + 2]; + } + if (face.vt[i] > 0) { + v.u[0] = v_uv[(face.vt[i] - 1) * 2]; + v.u[1] = v_uv[(face.vt[i] - 1) * 2 + 1]; + } + final_vertices.push_back(v); + } + final_indices.push_back(vertex_map[key]); + } + } + + // Format: [num_vertices][Vertex*N][num_indices][uint32_t*N] + buffer->resize(sizeof(uint32_t) + final_vertices.size() * sizeof(Vertex) + + sizeof(uint32_t) + final_indices.size() * sizeof(uint32_t)); + uint8_t* out_ptr = buffer->data(); + *reinterpret_cast<uint32_t*>(out_ptr) = (uint32_t)final_vertices.size(); + out_ptr += sizeof(uint32_t); + std::memcpy(out_ptr, final_vertices.data(), + final_vertices.size() * sizeof(Vertex)); + out_ptr += final_vertices.size() * sizeof(Vertex); + *reinterpret_cast<uint32_t*>(out_ptr) = (uint32_t)final_indices.size(); + out_ptr += sizeof(uint32_t); + std::memcpy(out_ptr, final_indices.data(), + final_indices.size() * sizeof(uint32_t)); + + printf("Processed mesh asset %s: %zu vertices, %zu indices\n", + asset_name.c_str(), final_vertices.size(), final_indices.size()); + + return true; +} + +static bool ProcessImageFile(const std::string& full_path, + std::vector<uint8_t>* buffer, + const std::string& asset_name) { + int w, h, channels; + unsigned char* img_data = + stbi_load(full_path.c_str(), &w, &h, &channels, 4); // Force RGBA + if (!img_data) { + fprintf(stderr, "Error: Could not load image file: %s (Reason: %s)\n", + full_path.c_str(), stbi_failure_reason()); + return false; + } + + // Format: [Width(4)][Height(4)][Pixels...] + buffer->resize(sizeof(uint32_t) * 2 + w * h * 4); + uint32_t* header = reinterpret_cast<uint32_t*>(buffer->data()); + header[0] = (uint32_t)w; + header[1] = (uint32_t)h; + std::memcpy(buffer->data() + sizeof(uint32_t) * 2, img_data, w * h * 4); + + stbi_image_free(img_data); + printf("Processed image asset %s: %dx%d RGBA\n", asset_name.c_str(), w, h); + + return true; +} + int main(int argc, char* argv[]) { if (argc != 4) { fprintf(stderr, @@ -189,110 +435,15 @@ int main(int argc, char* argv[]) { if (compression_type_str.rfind("PROC_GPU(", 0) == 0) { info.is_procedural = true; info.is_gpu_procedural = true; - size_t open_paren = compression_type_str.find('('); - size_t close_paren = compression_type_str.rfind(')'); - if (open_paren == std::string::npos || - close_paren == std::string::npos) { - fprintf(stderr, - "Error: Invalid PROC_GPU() syntax for asset: %s, string: [%s]\n", - info.name.c_str(), compression_type_str.c_str()); - return 1; - } - std::string func_and_params_str = compression_type_str.substr( - open_paren + 1, close_paren - open_paren - 1); - - size_t params_start = func_and_params_str.find(','); - if (params_start != std::string::npos) { - std::string params_str = func_and_params_str.substr(params_start + 1); - info.proc_func_name = func_and_params_str.substr(0, params_start); - - size_t current_pos = 0; - while (current_pos < params_str.length()) { - size_t comma_pos = params_str.find(',', current_pos); - std::string param_val_str = - (comma_pos == std::string::npos) - ? params_str.substr(current_pos) - : params_str.substr(current_pos, comma_pos - current_pos); - param_val_str.erase(0, param_val_str.find_first_not_of(" \t\r\n")); - param_val_str.erase(param_val_str.find_last_not_of(" \t\r\n") + 1); - try { - info.proc_params.push_back(std::stof(param_val_str)); - } catch (...) { - fprintf(stderr, "Error: Invalid proc param for %s: %s\n", - info.name.c_str(), param_val_str.c_str()); - return 1; - } - if (comma_pos == std::string::npos) - break; - current_pos = comma_pos + 1; - } - } else { - info.proc_func_name = func_and_params_str; - } - - // Validate GPU procedural function name - if (info.proc_func_name != "gen_noise" && - info.proc_func_name != "gen_perlin" && - info.proc_func_name != "gen_grid") { - fprintf(stderr, - "Error: PROC_GPU only supports gen_noise, gen_perlin, gen_grid, got: %s for asset: %s\n", - info.proc_func_name.c_str(), info.name.c_str()); + if (!ParseProceduralFunction(compression_type_str, &info, true)) { return 1; } } else if (compression_type_str.rfind("PROC(", 0) == 0) { info.is_procedural = true; info.is_gpu_procedural = false; - size_t open_paren = compression_type_str.find('('); - size_t close_paren = compression_type_str.rfind(')'); - if (open_paren == std::string::npos || - close_paren == std::string::npos) { - fprintf(stderr, - "Error: Invalid PROC() syntax for asset: %s, string: [%s]\n", - info.name.c_str(), compression_type_str.c_str()); + if (!ParseProceduralFunction(compression_type_str, &info, false)) { return 1; } - std::string func_and_params_str = compression_type_str.substr( - open_paren + 1, close_paren - open_paren - 1); - - size_t params_start = func_and_params_str.find(','); - if (params_start != std::string::npos) { - std::string params_str = func_and_params_str.substr(params_start + 1); - info.proc_func_name = func_and_params_str.substr(0, params_start); - - size_t current_pos = 0; - while (current_pos < params_str.length()) { - size_t comma_pos = params_str.find(',', current_pos); - std::string param_val_str = - (comma_pos == std::string::npos) - ? params_str.substr(current_pos) - : params_str.substr(current_pos, comma_pos - current_pos); - param_val_str.erase(0, param_val_str.find_first_not_of(" \t\r\n")); - param_val_str.erase(param_val_str.find_last_not_of(" \t\r\n") + 1); - try { - info.proc_params.push_back(std::stof(param_val_str)); - } catch (...) { - fprintf(stderr, "Error: Invalid proc param for %s: %s\n", - info.name.c_str(), param_val_str.c_str()); - return 1; - } - if (comma_pos == std::string::npos) - break; - current_pos = comma_pos + 1; - } - } else { - info.proc_func_name = func_and_params_str; - } - - // Validate procedural function name - // kAssetPackerProcGenFuncMap is defined globally for validation - if (kAssetPackerProcGenFuncMap.find(info.proc_func_name) == - kAssetPackerProcGenFuncMap.end()) { - fprintf(stderr, - "Warning: Unknown procedural function: %s for asset: %s " - "(Runtime error will occur)\n", - info.proc_func_name.c_str(), info.name.c_str()); - // return 1; // Allow unknown functions for testing runtime handling - } } asset_build_infos.push_back(info); @@ -330,190 +481,13 @@ int main(int argc, char* argv[]) { bool is_mesh = HasMeshExtension(info.filename); if (is_image) { - int w, h, channels; - unsigned char* img_data = stbi_load( - full_path.c_str(), &w, &h, &channels, 4); // Force 4 channels (RGBA) - if (!img_data) { - fprintf(stderr, "Error: Could not load image file: %s (Reason: %s)\n", - full_path.c_str(), stbi_failure_reason()); + if (!ProcessImageFile(full_path, &buffer, info.name)) { return 1; } - - // Format: [Width(4)][Height(4)][Pixels...] - buffer.resize(sizeof(uint32_t) * 2 + w * h * 4); - uint32_t* header = reinterpret_cast<uint32_t*>(buffer.data()); - header[0] = (uint32_t)w; - header[1] = (uint32_t)h; - std::memcpy(buffer.data() + sizeof(uint32_t) * 2, img_data, w * h * 4); - - stbi_image_free(img_data); - printf("Processed image asset %s: %dx%d RGBA\n", info.name.c_str(), w, - h); } else if (is_mesh) { - std::ifstream obj_file(full_path); - if (!obj_file.is_open()) { - fprintf(stderr, "Error: Could not open mesh file: %s\n", - full_path.c_str()); + if (!ProcessMeshFile(full_path, &buffer, info.name)) { return 1; } - - std::vector<float> v_pos; - std::vector<float> v_norm; - std::vector<float> v_uv; - - struct RawFace { - int v[3]; - int vt[3]; - int vn[3]; - }; - std::vector<RawFace> raw_faces; - - struct Vertex { - float p[3], n[3], u[2]; - }; - std::vector<Vertex> final_vertices; - std::vector<uint32_t> final_indices; - std::map<std::string, uint32_t> vertex_map; - - std::string obj_line; - while (std::getline(obj_file, obj_line)) { - if (obj_line.compare(0, 2, "v ") == 0) { - float x, y, z; - std::sscanf(obj_line.c_str(), "v %f %f %f", &x, &y, &z); - v_pos.push_back(x); - v_pos.push_back(y); - v_pos.push_back(z); - } else if (obj_line.compare(0, 3, "vn ") == 0) { - float x, y, z; - std::sscanf(obj_line.c_str(), "vn %f %f %f", &x, &y, &z); - v_norm.push_back(x); - v_norm.push_back(y); - v_norm.push_back(z); - } else if (obj_line.compare(0, 3, "vt ") == 0) { - float u, v; - std::sscanf(obj_line.c_str(), "vt %f %f", &u, &v); - v_uv.push_back(u); - v_uv.push_back(v); - } else if (obj_line.compare(0, 2, "f ") == 0) { - char s1[64], s2[64], s3[64]; - if (std::sscanf(obj_line.c_str(), "f %s %s %s", s1, s2, s3) == 3) { - std::string parts[3] = {s1, s2, s3}; - RawFace face = {}; - for (int i = 0; i < 3; ++i) { - int v_idx = 0, vt_idx = 0, vn_idx = 0; - if (parts[i].find("//") != std::string::npos) { - std::sscanf(parts[i].c_str(), "%d//%d", &v_idx, &vn_idx); - } else if (std::count(parts[i].begin(), parts[i].end(), '/') == - 2) { - std::sscanf(parts[i].c_str(), "%d/%d/%d", &v_idx, &vt_idx, - &vn_idx); - } else if (std::count(parts[i].begin(), parts[i].end(), '/') == - 1) { - std::sscanf(parts[i].c_str(), "%d/%d", &v_idx, &vt_idx); - } else { - std::sscanf(parts[i].c_str(), "%d", &v_idx); - } - face.v[i] = v_idx; - face.vt[i] = vt_idx; - face.vn[i] = vn_idx; - } - raw_faces.push_back(face); - } - } - } - - // Generate normals if missing - if (v_norm.empty() && !v_pos.empty()) { - printf("Generating normals for %s...\n", info.name.c_str()); - std::vector<Vec3> temp_normals(v_pos.size() / 3, {0, 0, 0}); - for (auto& face : raw_faces) { - // Indices are 1-based in OBJ - int idx0 = face.v[0] - 1; - int idx1 = face.v[1] - 1; - int idx2 = face.v[2] - 1; - - if (idx0 >= 0 && idx1 >= 0 && idx2 >= 0) { - Vec3 p0 = {v_pos[idx0 * 3], v_pos[idx0 * 3 + 1], - v_pos[idx0 * 3 + 2]}; - Vec3 p1 = {v_pos[idx1 * 3], v_pos[idx1 * 3 + 1], - v_pos[idx1 * 3 + 2]}; - Vec3 p2 = {v_pos[idx2 * 3], v_pos[idx2 * 3 + 1], - v_pos[idx2 * 3 + 2]}; - - Vec3 normal = Vec3::cross(p1 - p0, p2 - p0).normalize(); - temp_normals[idx0] += normal; - temp_normals[idx1] += normal; - temp_normals[idx2] += normal; - } - } - - for (const auto& n : temp_normals) { - Vec3 normalized = n.normalize(); - v_norm.push_back(normalized.x); - v_norm.push_back(normalized.y); - v_norm.push_back(normalized.z); - } - - // Assign generated normals to faces - for (auto& face : raw_faces) { - face.vn[0] = face.v[0]; - face.vn[1] = face.v[1]; - face.vn[2] = face.v[2]; - } - } - - // Build final vertices - for (const auto& face : raw_faces) { - for (int i = 0; i < 3; ++i) { - // Reconstruct key string for uniqueness check - char key_buf[128]; - std::snprintf(key_buf, sizeof(key_buf), "%d/%d/%d", face.v[i], - face.vt[i], face.vn[i]); - std::string key = key_buf; - - if (vertex_map.find(key) == vertex_map.end()) { - vertex_map[key] = (uint32_t)final_vertices.size(); - - Vertex v = {}; - if (face.v[i] > 0) { - v.p[0] = v_pos[(face.v[i] - 1) * 3]; - v.p[1] = v_pos[(face.v[i] - 1) * 3 + 1]; - v.p[2] = v_pos[(face.v[i] - 1) * 3 + 2]; - } - if (face.vn[i] > 0) { - v.n[0] = v_norm[(face.vn[i] - 1) * 3]; - v.n[1] = v_norm[(face.vn[i] - 1) * 3 + 1]; - v.n[2] = v_norm[(face.vn[i] - 1) * 3 + 2]; - } - if (face.vt[i] > 0) { - v.u[0] = v_uv[(face.vt[i] - 1) * 2]; - v.u[1] = v_uv[(face.vt[i] - 1) * 2 + 1]; - } - final_vertices.push_back(v); - } - final_indices.push_back(vertex_map[key]); - } - } - - // Format: [num_vertices(u32)][Vertex * - // num_vertices][num_indices(u32)][uint32_t - // * num_indices] - buffer.resize( - sizeof(uint32_t) + final_vertices.size() * sizeof(Vertex) + - sizeof(uint32_t) + final_indices.size() * sizeof(uint32_t)); - uint8_t* out_ptr = buffer.data(); - *reinterpret_cast<uint32_t*>(out_ptr) = (uint32_t)final_vertices.size(); - out_ptr += sizeof(uint32_t); - std::memcpy(out_ptr, final_vertices.data(), - final_vertices.size() * sizeof(Vertex)); - out_ptr += final_vertices.size() * sizeof(Vertex); - *reinterpret_cast<uint32_t*>(out_ptr) = (uint32_t)final_indices.size(); - out_ptr += sizeof(uint32_t); - std::memcpy(out_ptr, final_indices.data(), - final_indices.size() * sizeof(uint32_t)); - - printf("Processed mesh asset %s: %zu vertices, %zu indices\n", - info.name.c_str(), final_vertices.size(), final_indices.size()); } else { std::ifstream asset_file(full_path, std::ios::binary); if (!asset_file.is_open()) { |
