initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
bin/
|
||||||
|
asset/
|
||||||
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
.PHONY: all build clean lib
|
||||||
|
|
||||||
|
CXX = g++
|
||||||
|
WSL = wsl --shell-type login
|
||||||
|
EMXX = $(WSL) em++
|
||||||
|
EMAR = $(WSL) emar
|
||||||
|
|
||||||
|
CXXFLAGS = -std=c++20 -O2
|
||||||
|
|
||||||
|
SRC_FILES = ./src/main.cpp \
|
||||||
|
./src/lib.cpp
|
||||||
|
|
||||||
|
|
||||||
|
LIB_FILES = ./src/lib.cpp
|
||||||
|
|
||||||
|
all: build-lib build
|
||||||
|
|
||||||
|
run: build
|
||||||
|
./bin/assetpacker.exe
|
||||||
|
|
||||||
|
build-lib:
|
||||||
|
@mkdir -p bin
|
||||||
|
@mkdir -p bin/include/asset_packer
|
||||||
|
@cp -r ./src/include/* ./bin/include/asset_packer
|
||||||
|
$(CXX) $(CXXFLAGS) -I./src/include -c $(LIB_FILES) -o ./bin/asset_packer.o
|
||||||
|
ar rcs ./bin/libassetpacker.a ./bin/asset_packer.o
|
||||||
|
|
||||||
|
build-lib-web:
|
||||||
|
@mkdir -p bin
|
||||||
|
@mkdir -p bin/include/asset_packer
|
||||||
|
@cp -r ./src/include/* ./bin/include/asset_packer
|
||||||
|
$(EMXX) $(CXXFLAGS) -I./src/include -c $(LIB_FILES) -o ./bin/asset_packer.web.o
|
||||||
|
$(EMAR) rcs ./bin/libassetpacker.web.a ./bin/asset_packer.web.o
|
||||||
|
|
||||||
|
build:
|
||||||
|
@mkdir -p bin
|
||||||
|
@cp -r ./asset/ ./bin/
|
||||||
|
$(CXX) $(CXXFLAGS) $(SRC_FILES) -I./src/include -o ./bin/assetpacker.exe
|
||||||
|
|
||||||
|
compile_commands.json: clean
|
||||||
|
@echo "Generating compile_commands.json..."
|
||||||
|
bear -- $(MAKE)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -r -f ./bin
|
||||||
|
@rm -f compile_commands.json
|
||||||
|
|
||||||
|
test:
|
||||||
|
@mkdir -p bin
|
||||||
|
@cp -r ./asset/ ./bin/stuff/
|
||||||
|
$(CXX) $(CXXFLAGS) -I./src/include $(LIB_FILES) ./src/test.cpp -o ./bin/test.exe
|
||||||
|
./bin/test.exe
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// asset.hpp
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
// Struct size is exactly 80 bytes
|
||||||
|
struct AssetMetadata {
|
||||||
|
char name[64]; // Fixed size string for the filename
|
||||||
|
uint64_t offset; // Where the raw file bytes begin in the blob
|
||||||
|
uint64_t size; // How many bytes long the file is
|
||||||
|
};
|
||||||
|
|
||||||
|
class AssetReader{
|
||||||
|
private:
|
||||||
|
std::string blob_path;
|
||||||
|
std::unordered_map<std::string,AssetMetadata> index_table;
|
||||||
|
public:
|
||||||
|
AssetReader(const std::string& path): blob_path(path){}
|
||||||
|
~AssetReader() = default;
|
||||||
|
bool load_index();
|
||||||
|
std::vector<uint8_t> extract_asset(const std::string& asset_name);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class AssetPacker{
|
||||||
|
public:
|
||||||
|
AssetPacker() = default;
|
||||||
|
~AssetPacker() = default;
|
||||||
|
void pack_assets(const std::string& output_blob_path, const std::vector<std::string>& files_to_pack);
|
||||||
|
};
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
// lib.cpp
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
#include <format>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <asset.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
bool AssetReader::load_index(){
|
||||||
|
std::ifstream blob(blob_path,std::ios::binary);
|
||||||
|
if(!blob) return false;
|
||||||
|
|
||||||
|
uint32_t file_count = 0;
|
||||||
|
blob.read(reinterpret_cast<char*>(&file_count),sizeof(file_count));
|
||||||
|
for (uint32_t i = 0; i < file_count; i++){
|
||||||
|
AssetMetadata meta;
|
||||||
|
blob.read(reinterpret_cast<char*>(&meta), sizeof(AssetMetadata));
|
||||||
|
index_table[std::string(meta.name)] = meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
blob.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> AssetReader::extract_asset(const std::string& asset_name){
|
||||||
|
if (!index_table.contains(asset_name)){
|
||||||
|
std::cerr << "Asset not found in blob: " << asset_name << "\n";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
AssetMetadata meta = index_table[asset_name];
|
||||||
|
std::ifstream blob(blob_path,std::ios::binary);
|
||||||
|
|
||||||
|
blob.seekg(meta.offset);
|
||||||
|
std::vector<uint8_t> buffer(meta.size);
|
||||||
|
blob.read(reinterpret_cast<char*>(buffer.data()),meta.size);
|
||||||
|
blob.close();
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void AssetPacker::pack_assets(const std::string& output_blob_path, const std::vector<std::string>& files_to_pack){
|
||||||
|
std::ofstream blob(output_blob_path,std::ios::binary);
|
||||||
|
if (!blob){std::cerr << "Failed to create blob file!" << std::endl; return;}
|
||||||
|
|
||||||
|
uint32_t file_count = files_to_pack.size();
|
||||||
|
blob.write(reinterpret_cast<char*>(&file_count), sizeof(file_count));
|
||||||
|
// Write how many files there are
|
||||||
|
|
||||||
|
|
||||||
|
uint64_t current_data_offset = sizeof(file_count) + (file_count * sizeof(AssetMetadata)); // create and offset pointer for the number of file and our files metadata
|
||||||
|
|
||||||
|
std::vector<AssetMetadata> metadata_table;
|
||||||
|
|
||||||
|
for (auto &file_path : files_to_pack){
|
||||||
|
|
||||||
|
std::ifstream file(file_path,std::ios::binary | std::ios::ate);
|
||||||
|
|
||||||
|
if (!file){std::cerr << "Skipping missing file " << file_path << std::endl; continue; }
|
||||||
|
|
||||||
|
uint64_t file_size = file.tellg();
|
||||||
|
|
||||||
|
// create metadata
|
||||||
|
AssetMetadata meta{};
|
||||||
|
size_t length = file_path.copy(meta.name, sizeof(meta.name) - 1);
|
||||||
|
meta.name[length] = '\0'; // Explicitly guarantee null-termination
|
||||||
|
|
||||||
|
meta.offset = current_data_offset;
|
||||||
|
meta.size = file_size;
|
||||||
|
|
||||||
|
metadata_table.push_back(meta);
|
||||||
|
current_data_offset += file_size;
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// write meta data
|
||||||
|
for (const auto& meta : metadata_table) {
|
||||||
|
blob.write(reinterpret_cast<const char*>(&meta), sizeof(AssetMetadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
// second pass
|
||||||
|
for (const auto& file_path: files_to_pack){
|
||||||
|
std::ifstream file (file_path,std::ios::binary);
|
||||||
|
if (!file) continue;
|
||||||
|
blob << file.rdbuf();
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto meta :metadata_table){
|
||||||
|
std:: cout << std::format("stored {}",meta.name) << std::endl;
|
||||||
|
}
|
||||||
|
blob.close();
|
||||||
|
std::cout << "Successfully packed " << metadata_table.size() << " files into " << output_blob_path << "\n";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#include <asset.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
int main(int argc, const char** argv){
|
||||||
|
|
||||||
|
std::string asset_folder_path;
|
||||||
|
std::string blob_path;
|
||||||
|
|
||||||
|
if (argc == 1){
|
||||||
|
std::cerr << argc-1 << " arguments found " << std::endl;
|
||||||
|
std::cerr << "Format: ./asset_packer ASSET_FOLDER_PATH [BLOB_PATH]" << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::string(argv[1]) == "--help" ||std::string(argv[1]) == "--help" ){
|
||||||
|
std::cerr << "Format: ./asset_packer ASSET_FOLDER_PATH [BLOB_PATH] " << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc == 2){
|
||||||
|
asset_folder_path = argv[1];
|
||||||
|
blob_path = "data.dat";
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (argc == 3){
|
||||||
|
blob_path = argv[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> files_to_pack = {};
|
||||||
|
|
||||||
|
fs::path target_path = asset_folder_path;
|
||||||
|
if (fs::exists(target_path) && fs::is_directory(target_path)){
|
||||||
|
for (const auto& entry : fs::directory_iterator(target_path)){
|
||||||
|
if(entry.is_regular_file()){
|
||||||
|
files_to_pack.push_back(entry.path().relative_path().string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetPacker asset_packer;
|
||||||
|
asset_packer.pack_assets(blob_path, files_to_pack);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <cassert>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
// Include your library headers here
|
||||||
|
#include <asset.hpp>
|
||||||
|
|
||||||
|
void run_asset_pipeline_tests() {
|
||||||
|
std::cout << "[TEST] Initializing Asset Pipeline Tests...\n";
|
||||||
|
|
||||||
|
// Setup temporary testing files
|
||||||
|
std::string test_blob = "test_data.dat";
|
||||||
|
std::string text_file = "test_config.json";
|
||||||
|
std::string fake_png = "test_player.png";
|
||||||
|
|
||||||
|
// 1. Create dummy files on disk to pack
|
||||||
|
std::ofstream f1(text_file);
|
||||||
|
f1 << "{ \"speed\": 250 }";
|
||||||
|
f1.close();
|
||||||
|
|
||||||
|
std::ofstream f2(fake_png, std::ios::binary);
|
||||||
|
f2 << "FAKE_PNG_BINARY_DATA_HEADER";
|
||||||
|
f2.close();
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// TEST 1: Packing Verification
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
AssetPacker packer;
|
||||||
|
std::vector<std::string> files_to_pack = { text_file, fake_png };
|
||||||
|
|
||||||
|
// This should run without throwing errors or truncating files
|
||||||
|
packer.pack_assets(test_blob, files_to_pack);
|
||||||
|
assert(std::filesystem::exists(test_blob) && "FAIL: Blob file was not created!");
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// TEST 2: Reading and Integrity Verification (Round-Trip)
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
AssetReader reader(test_blob);
|
||||||
|
bool index_loaded = reader.load_index();
|
||||||
|
assert(index_loaded && "FAIL: Reader could not parse blob index!");
|
||||||
|
|
||||||
|
// Extract the JSON back out
|
||||||
|
std::vector<uint8_t> extracted_json_bytes = reader.extract_asset(text_file);
|
||||||
|
std::string extracted_json(extracted_json_bytes.begin(), extracted_json_bytes.end());
|
||||||
|
|
||||||
|
// Assert that the file contents match exactly what we put in
|
||||||
|
assert(extracted_json == "{ \"speed\": 250 }" && "FAIL: Extracted data does not match original data!");
|
||||||
|
std::cout << " -> Pass: Data round-trip integrity verified.\n";
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// TEST 3: Graceful Missing Asset Handling
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
std::vector<uint8_t> missing = reader.extract_asset("non_existent_file.png");
|
||||||
|
assert(missing.empty() && "FAIL: Requesting a missing file should return an empty buffer!");
|
||||||
|
std::cout << " -> Pass: Missing file boundaries handled gracefully.\n";
|
||||||
|
|
||||||
|
// Clean up test file clutter from the hard drive
|
||||||
|
std::filesystem::remove(text_file);
|
||||||
|
std::filesystem::remove(fake_png);
|
||||||
|
std::filesystem::remove(test_blob);
|
||||||
|
|
||||||
|
std::cout << "[TEST] ALL ASSET PIPELINE TESTS PASSED SUCCESSFULY!\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
run_asset_pipeline_tests();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user