initial commit

This commit is contained in:
2026-07-10 10:51:48 +01:00
commit eb49ffa4bd
6 changed files with 314 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
bin/
asset/
+54
View File
@@ -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
+33
View File
@@ -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
View File
@@ -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";
}
+49
View File
@@ -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;
}
+71
View File
@@ -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;
}