inital commit

This commit is contained in:
Jorijn van der Graaf 2025-05-06 12:37:14 +02:00
commit bf96eb69ae
23 changed files with 10205 additions and 0 deletions

96
Crafter.Asset-Asset.cpp Normal file
View file

@ -0,0 +1,96 @@
module;
#include <cstdint>
#include <string>
#include <vector>
#include <fstream>
#include <exception>
#include <filesystem>
#include <iostream>
#include <fstream>
module Crafter.Asset;
using namespace Crafter;
namespace fs = std::filesystem;
AssetEntry::AssetEntry() {
}
Asset::Asset() {}
Asset::Asset(std::vector<AssetEntry> entries): entries(entries) {}
struct HeaderData {
std::uint32_t nameLenght;
std::uint32_t typeLenght;
std::uint32_t offset;
std::uint32_t lenght;
};
void Asset::LoadFull(fs::path path) {
std::ifstream f(path, std::ios::binary);
if(!f) {
throw std::runtime_error(std::format("File {} not found", path.generic_string()));
}
std::uint32_t version;
f.read(reinterpret_cast<char*>(&version), sizeof(std::uint32_t));
std::uint32_t entryCount;
f.read(reinterpret_cast<char*>(&entryCount), sizeof(std::uint32_t));
entries.resize(entryCount);
std::vector<std::uint32_t> lenghts(entryCount);
for(std::uint32_t i = 0; i < entryCount; i++) {
HeaderData header;
f.read(reinterpret_cast<char*>(&header), sizeof(std::uint32_t)*4);
entries[i].name.resize(header.nameLenght);
f.read(reinterpret_cast<char*>(&entries[i].name[0]), header.nameLenght);
entries[i].type.resize(header.typeLenght);
f.read(reinterpret_cast<char*>(&entries[i].type[0]), header.typeLenght);
lenghts[i] = header.lenght;
}
for(std::uint32_t i = 0; i < entryCount; i++) {
entries[i].data.resize(lenghts[i]);
f.read(reinterpret_cast<char*>(entries[i].data.data()), lenghts[i]);
}
}
void Asset::LoadHeaders(fs::path path) {
}
void Asset::LoadSpecific(fs::path path, std::string entry) {
}
void Asset::LoadSpecific(fs::path path, std::vector<std::string> entries) {
}
void Asset::LoadSpecific(fs::path path, std::uint32_t entry) {
}
void Asset::LoadSpecific(fs::path path, std::vector<std::uint32_t> entries) {
}
void Asset::Save(fs::path path) {
std::ofstream f(path, std::ios::binary);
std::uint32_t version = 0;
f.write(reinterpret_cast<const char*>(&version), sizeof(std::uint32_t));
std::uint32_t entriesCount = entries.size();
f.write(reinterpret_cast<const char*>(&entriesCount), sizeof(std::uint32_t));
std::uint32_t offset = 0;
for(const AssetEntry& entry : entries) {
std::uint32_t nameSize = entry.name.size();
f.write(reinterpret_cast<const char*>(&nameSize), sizeof(std::uint32_t));
std::uint32_t typeSize = entry.type.size();
f.write(reinterpret_cast<const char*>(&typeSize), sizeof(std::uint32_t));
f.write(reinterpret_cast<const char*>(&offset), sizeof(std::uint32_t));
std::uint32_t dataSize = entry.data.size();
f.write(reinterpret_cast<const char*>(&dataSize), sizeof(std::uint32_t));
offset+=dataSize;
f.write(reinterpret_cast<const char*>(&entry.name[0]), entry.name.size());
f.write(reinterpret_cast<const char*>(&entry.type[0]), entry.type.size());
}
for(const AssetEntry& entry : entries) {
f.write(reinterpret_cast<const char*>(entry.data.data()), entry.data.size());
}
f.close();
}

194
Crafter.Asset-Asset.cppm Normal file
View file

@ -0,0 +1,194 @@
module;
#include <cstdint>
#include <string>
#include <vector>
#include <filesystem>
#include <unordered_map>
#include <iostream>
#include <functional>
#include <fstream>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
export module Crafter.Asset:Asset;
namespace fs = std::filesystem;
struct __attribute__((packed)) Float3 {
float x;
float y;
float z;
};
struct __attribute__((packed)) Float2 {
float x;
float y;
};
struct __attribute__((packed)) VertexUV {
Float3 position;
Float2 uv;
};
void SplitString(const std::string& s, const char delimiter, std::vector<std::string>& tokens) {
std::istringstream iss(s);
std::string token;
while (std::getline(iss, token, delimiter)) {
tokens.push_back(token);
}
}
uint32_t ProcessVertex(std::vector<VertexUV>& verticies, std::vector<Float3> positions, std::vector<Float2> uvs, uint32_t posIndex, uint32_t uvIndex) {
Float3 pos = positions[posIndex - 1];
Float2 uv = uvs[uvIndex - 1];
for (uint32_t i = 0; i < verticies.size(); i++) {
VertexUV vert = verticies[i];
if (vert.position.x == pos.x && vert.position.y == pos.y && vert.position.z == pos.z && vert.uv.x == uv.x && vert.uv.y == uv.y) {
return i;
}
}
verticies.push_back({ pos ,uv });
return verticies.size() - 1;
}
export namespace Crafter {
std::unordered_map<std::string, std::function<std::vector<char>(fs::path)>> serializers = {
{
"PNG:Crafter.Graphics.TextureR8B8G8A8",
[](fs::path file) {
int x,y,n;
unsigned char *imageData = stbi_load(file.generic_string().c_str(),&x, &y, &n, 4);
std::uint32_t x2 = x;
std::uint32_t y2 = y;
std::vector<char> data(sizeof(std::uint32_t)+sizeof(std::uint32_t)+(x*y*4));
memcpy(&data[0], &x2, sizeof(std::uint32_t));
memcpy(&data[sizeof(std::uint32_t)], &y2, sizeof(std::uint32_t));
memcpy(&data[sizeof(std::uint32_t)*2], imageData, x*y*4);
return data;
}
},
{
"OBJ:Crafter.Graphics.Mesh",
[](fs::path path) {
std::ifstream file(path);
std::string line;
std::vector<Float3> positions;
std::vector<std::uint32_t> indices;
while (std::getline(file, line)) {
if (line.substr(0, 1) == "v") {
std::istringstream iss(line.substr(2));
Float3 pos{0,0,0};
iss >> pos.x >> pos.y >> pos.z;
positions.push_back(pos);
}
else if (line.substr(0, 1) == "f") {
std::vector<std::string> tokens;
SplitString(line.substr(2), ' ', tokens);
for (size_t i = 0; i < 3; ++i) {
indices.push_back(std::stoul(tokens[i])-1);
}
}
}
file.close();
std::vector<char> buffer((positions.size()*sizeof(Float3))+(indices.size()*sizeof(std::uint32_t))+sizeof(std::uint32_t)+sizeof(std::uint32_t));
std::uint32_t vertSize = positions.size();
std::uint32_t indexSize = indices.size();
memcpy(&buffer[0], &vertSize, sizeof(std::uint32_t));
memcpy(&buffer[sizeof(std::uint32_t)], &indexSize, sizeof(std::uint32_t));
memcpy(&buffer[sizeof(std::uint32_t)*2], positions.data(), positions.size()*sizeof(Float3));
memcpy(&buffer[positions.size()*sizeof(Float3)+sizeof(std::uint32_t)+sizeof(std::uint32_t)], indices.data(), indices.size()*sizeof(std::uint32_t));
return buffer;
}
},
{
"OBJ:Crafter.Graphics.MeshUV",
[](fs::path path) {
std::ifstream file(path);
std::string line;
std::vector<Float3> positions;
std::vector<Float2> uvs;
std::vector<VertexUV> verticies;
std::vector<uint32_t> indicies;
while (std::getline(file, line)) {
if (line.substr(0, 2) == "vt") {
std::istringstream iss(line.substr(3));
Float2 uv{0,0};
iss >> uv.x >> uv.y;
uvs.push_back(uv);
}
else if (line.substr(0, 1) == "v") {
std::istringstream iss(line.substr(2));
Float3 pos{0,0,0};
iss >> pos.x >> pos.y >> pos.z;
positions.push_back(pos);
}
else if (line.substr(0, 1) == "f") {
std::vector<std::string> tokens;
SplitString(line.substr(2), ' ', tokens);
for (size_t i = 0; i < 3; ++i) {
size_t slashPos = tokens[i].find('/');
indicies.push_back(ProcessVertex(verticies, positions, uvs, std::stoul(tokens[i].substr(0, slashPos)), std::stoul(tokens[i].substr(slashPos + 1))));
}
}
}
file.close();
std::vector<char> buffer((verticies.size()*sizeof(VertexUV))+(indicies.size()*sizeof(std::uint32_t))+sizeof(std::uint32_t)+sizeof(std::uint32_t));
std::uint32_t vertSize = verticies.size();
std::uint32_t indexSize = indicies.size();
memcpy(&buffer[0], &vertSize, sizeof(std::uint32_t));
memcpy(&buffer[sizeof(std::uint32_t)], &indexSize, sizeof(std::uint32_t));
memcpy(&buffer[sizeof(std::uint32_t)*2], verticies.data(), verticies.size()*sizeof(VertexUV));
memcpy(&buffer[verticies.size()*sizeof(VertexUV)+sizeof(std::uint32_t)+sizeof(std::uint32_t)], indicies.data(), indicies.size()*sizeof(std::uint32_t));
return buffer;
}
}
};
std::unordered_map<std::string, std::function<void(fs::path, const void*)>> extractors = {
{
"Crafter.Graphics.TextureR8B8G8A8:PNG",
[](fs::path dst, const void* data) {
stbi_write_png(dst.generic_string().c_str(), reinterpret_cast<const std::uint32_t*>(data)[0], reinterpret_cast<const std::uint32_t*>(data)[1], 4, reinterpret_cast<const char*>(data)+(sizeof(std::uint32_t)*2), reinterpret_cast<const std::uint32_t*>(data)[0]*4);
}
}
};
class AssetEntry {
public:
std::string name;
std::string type;
std::vector<char> data;
AssetEntry();
AssetEntry(std::string name, std::string type, std::vector<char> data);
AssetEntry(std::string name, std::string type, void* data);
};
class Asset {
public:
std::vector<AssetEntry> entries;
Asset();
Asset(std::vector<AssetEntry> entries);
void LoadFull(fs::path path);
void LoadHeaders(fs::path path);
void LoadSpecific(fs::path path, std::string entry);
void LoadSpecific(fs::path path, std::vector<std::string> entries);
void LoadSpecific(fs::path path, std::uint32_t entry);
void LoadSpecific(fs::path path, std::vector<std::uint32_t> entries);
void Save(fs::path path);
};
}

2
Crafter.Asset.cppm Normal file
View file

@ -0,0 +1,2 @@
export module Crafter.Asset;
export import :Asset;

BIN
bin/crafter-asset Executable file

Binary file not shown.

BIN
build/Crafter.Asset-Asset.o Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
build/Crafter.Asset.o Normal file

Binary file not shown.

BIN
build/Crafter.Asset.pcm Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
build/main_source.o Normal file

Binary file not shown.

162
main.cpp Normal file
View file

@ -0,0 +1,162 @@
#include <iostream>
#include <string>
#include <filesystem>
#include <unordered_map>
#include <vector>
#include <functional>
import Crafter.Asset;
using namespace Crafter;
namespace fs = std::filesystem;
int main(int argc, char* argv[]) {
if(argc == 1) {
std::cout << "No arguments provided, use help for help" << std::endl;
return -1;
}
std::string command = std::string(argv[1]);
if(command == "help") {
std::cout << "help: Displays this help message.\nadd {file} {entry file} {entry type} {serializer} {entry name}: Adds an entry to an existing assset file or creates a new file if the file in {file} does not exist, {entry file} is the path to the file you want to add to the asset, {entry type} is the type of the resulting entry, {serializer} is the serializer to use to add the entry file to the asset, {entry name} is the name of the resulting entry\nremove {file} {entry}\nextract {file} {entry name} {extractor} {extracted path}: Extracts a entry with the name {entry name} from the asset file in {file}, using the extractor in {extractor} writes the result to the file {extracted path}\nlist serialize: Shows a list of the support serializers\nlist {file}: Lists all entries in the asset file\nlist extract: Shows a list of supported extractors" << std::endl;
} else if(command == "list") {
if(argc == 2) {
std::cout << "Unkown command, use help for help" << std::endl;
return -1;
}
std::string command2 = std::string(argv[2]);
if(command2 == "serialize") {
for (const auto& pair : serializers) {
std::cout << pair.first << std::endl;
}
} else if(command2 == "extract") {
for (const auto& pair : extractors) {
std::cout << pair.first << std::endl;
}
} else {
if(!fs::exists(argv[2])) {
std::cout << "Asset file does not exist" << std::endl;
return -1;
}
Asset asset;
asset.LoadFull(argv[2]);
std::cout << "<name>\t<type>\t<size>" << std::endl;
for(const AssetEntry& entry : asset.entries) {
std::cout << std::format("{}\t{}\t{}", entry.name, entry.type, entry.data.size()) << std::endl;
}
}
} else if(command == "remove") {
if(argc == 2) {
std::cout << "Missing argument {file}. Run help for help" << std::endl;
return -1;
} else if(argc == 3) {
std::cout << "Missing argument {enty name}. Run help for help" << std::endl;
return -1;
}
if(!fs::exists(argv[2])) {
std::cout << "Asset file does not exist" << std::endl;
return -1;
}
Asset asset;
asset.LoadFull(argv[2]);
if(asset.entries.size() == 0) {
std::cout << "Nothing to remove, asset file empty" << std::endl;
}
for (auto it = asset.entries.begin(); it != asset.entries.end(); std::advance(it, 1)) {
if (it->name == argv[3]) {
asset.entries.erase(it);
fs::remove(argv[2]);
asset.Save(argv[2]);
return 0;
}
std::cout << std::format("Entry {} not found", argv[3]) << std::endl;
return -1;
}
} else if(command == "add") {
if(argc == 2) {
std::cout << "Missing argument {file}. Run help for help" << std::endl;
return -1;
} else if(argc == 3) {
std::cout << "Missing argument {entry file}. Run help for help" << std::endl;
return -1;
} else if(argc == 4) {
std::cout << "Missing argument {entry type}. Run help for help" << std::endl;
return -1;
} else if(argc == 5) {
std::cout << "Missing argument {serializer}. Run help for help" << std::endl;
return -1;
} else if(argc == 6) {
std::cout << "Missing argument {entry name}. Run help for help" << std::endl;
return -1;
}
Asset asset;
if(fs::exists(argv[2])) {
asset.LoadFull(argv[2]);
fs::remove(argv[2]);
}
AssetEntry entry;
entry.name = std::string(argv[6]);
entry.type = std::string(argv[4]);
std::unordered_map<std::string, std::function<std::vector<char>(fs::path)>>::const_iterator pos = serializers.find(argv[5]);
if (pos == serializers.end()) {
std::cout << std::format("Unkown serializer {}", argv[5]) << std::endl;
return -1;
} else {
entry.data = pos->second(argv[3]);
}
asset.entries.push_back(entry);
asset.Save(argv[2]);
} else if(command == "extract") {
if(argc == 2) {
std::cout << "Missing argument {file}. Run help for help" << std::endl;
return -1;
} else if(argc == 3) {
std::cout << "Missing argument {entry name}. Run help for help" << std::endl;
return -1;
} else if(argc == 4) {
std::cout << "Missing argument {extractor}. Run help for help" << std::endl;
return -1;
} else if(argc == 5) {
std::cout << "Missing argument {extracted path}. Run help for help" << std::endl;
return -1;
}
if(!fs::exists(argv[2])) {
std::cout << "Asset file does not exist" << std::endl;
return -1;
}
Asset asset;
asset.LoadFull(argv[2]);
auto pos = extractors.find(argv[4]);
if (pos == extractors.end()) {
std::cout << std::format("Unkown extractor {}", argv[4]) << std::endl;
return -1;
} else {
for(const AssetEntry& entry : asset.entries) {
if(entry.name == std::string(argv[3])) {
pos->second(argv[5], entry.data.data());
return 0;
}
}
std::cout << std::format("Entry {} not found", argv[3]) << std::endl;
return -1;
}
} else {
std::cout << "Unkown command, use help for help" << std::endl;
}
// Asset test;
// AssetEntry entry;
// entry.name = "Logo";
// entry.type = "Crafter.Graphics.TextureR8B8G8A8";
// //entry.data = serializers.find("PNG:Crafter.Graphics.TextureR8B8G8A8")->second("/home/jorijn/Pictures/3dfortslogo.png");
// test.entries.push_back(entry);
// test.Save("test.cras");
// Asset test2;
// test2.LoadFull("test.cras");
//extractors.find(std::format("{}:PNG", test2.entries[0].type))->second("/home/jorijn/repos/Crafter/Crafter.Asset/test.png", test2.entries[0].data.data());
}

39
project.json Normal file
View file

@ -0,0 +1,39 @@
{
"name": "crafter-asset",
"configurations": [
{
"name": "base",
"standard": "c++26",
"source_files": ["Crafter.Asset-Asset"],
"module_files": ["Crafter.Asset-Asset", "Crafter.Asset"],
"build_dir": "./build",
"output_dir": "./bin"
},
{
"name": "executable",
"extends": ["base"],
"type":"executable",
"source_files": ["main"]
},
{
"name": "executable-windows",
"extends": ["executable"],
"target": "x86_64-w64-mingw64"
},
{
"name": "executable-linux-debug",
"extends": ["executable"],
"debug": true
},
{
"name": "lib",
"extends": ["base"],
"type":"library"
},
{
"name": "lib-debug",
"extends": ["lib"],
"debug": true
}
]
}

7988
stb_image.h Normal file

File diff suppressed because it is too large Load diff

1724
stb_image_write.h Normal file

File diff suppressed because it is too large Load diff