Introduction | Configuration | Command line arguments | Building | Changelog
res2cpp
allows to embed the data of resource files in C++ source code. The generation of a header file containing a declaration for each embedded resource has the following advantages over other popular solutions:
- there are no filenames in the binary.
- there are no string lookups at runtime.
- the compiler checks, that all required files are embedded.
- the IDE can provide autocompletion and find all references of a resource.
Building res2cpp
and integrating it in any build chain should be easy, since it is distributed as a single .cpp file without dependencies. Care has been taken, that the generated header and source files are only updated when the inputs change.
The resources to embed are defined in a configuration file. In its most simple form, it just contains a list of paths to the files.
# comments start with a hash
resources/resource_0.ext
resources/resource_1.ext
When running the tool with res2cpp -c resources.conf
, a header and a source file are generated, with a constant std::pair
declaration/definition per resource, each containing the file's data and size. The generated source can be customized by the command line arguments.
The generated header file resources.h
looks like this:
#pragma once
#include <cstddef>
#include <utility>
namespace resources {
extern const std::pair<const unsigned char*, size_t> resource_0;
extern const std::pair<const unsigned char*, size_t> resource_1;
} // namespace resources
and the source file resources.cpp
like this:
#include "resources.h"
#include <cstdint>
namespace resources {
const uint8_t ressource_0_data_[] { $DATA };
const std::pair<const unsigned char*, size_t> resource_0{
reinterpret_cast<const unsigned char*>(resource_0_data_), $SIZE };
const uint8_t ressource_1_data_[] { $DATA };
const std::pair<const unsigned char*, size_t> resource_1{
reinterpret_cast<const unsigned char*>(resource_1_data_), $SIZE };
} // namespace resources
The configuration file specifies which files to embed and by which id they should be accessible.
There are a few ways to specify id to filename mappings. In the rest of the section the comments
visualize the resulting mappings.
When only a resource filename is provided, the id is automatically deduced from it, by:
- removing the extension.
- replacing not allowed characters with an underscore.
- collapsing multiple consecutive underscores.
- adding a namespace per folder.
# resources::filename = resources/filename.ext
resources/filename.ext
The id can also be explicitly set:
# resources::id = resources/filename.ext
resources::id = resources/filename.ext
Group headers, which set a common prefix for the following mappings, can be defined:
# resources::type::id = resources/type/filename.ext
[resources/type]
id = filename.ext
Separate prefixes for id and filename can be provided:
# resources::type::id = resources/type/filename.ext
[resources::type = resources/type]
id = filename.ext
Both prefixes in a header can be left empty:
# id = resources/type/filename.ext
[ = resources/type]
id = filename.ext
# resources::type::id = filename.ext
[resources::type = ]
id = filename.ext
Paths need to be enclosed in quotes, when they contain special characters:
id = "filename containing #.ext"
Usage: res2cpp [-options]
-c, --config <file> sets the path of the config file (stdin when not set).
-s, --source <file> sets the path of the source file.
-h, --header <file> sets the path of the header file.
-d, --data <type> use type for data (e.g. uint8_t, std::byte, void)
-t, --type <type> use type for resource (e.g. std::span<const uint8_t>).
-a, --alias <type> declare an alias for resource type.
-i, --include <file> add #include to generated header.
-x, --xor <key> encrypt the data using a simple XOR cipher.
-n, --native optimize for native endianness to improve compile-time.
The path to the configuration file. When none is set, the configuration is read from stdin
.
The source and header filenames are deduced from configuration filename unless specified explicitly.
Replaces the default data type unsigned char
with another one. With the arguments res2cpp -c resources.conf --data std::byte
the generated header looks like:
#pragma once
#include <cstddef>
#include <utility>
namespace resources {
extern const std::pair<const std::byte*, size_t> resource_0;
extern const std::pair<const std::byte*, size_t> resource_1;
} // namespace resources
--type
replaces the default resource type std::pair
with another one. It can be either a compatible standard type like std::span
or std::string_view
or a custom type.
--include
allows to add an #include
directive in the generated header, so the type declaration is found.
Running the tool with res2cpp -c resources.conf --data std::byte --type std::span<const std::byte> --include "<span>"
results in the following header:
#pragma once
#include <cstddef>
#include <utility>
#include <span>
namespace resources {
extern const std::span<std::byte> resource_0;
extern const std::span<std::byte> resource_1;
} // namespace resources
A custom type resources::Resource
, defined in a custom header file Resource.h
, can be used with res2cpp -c resources.conf --include Resource.h --type resources::Resource
. The generated header looks like:
#pragma once
#include "Resource.h"
namespace resources {
extern const Resource resource_0;
extern const Resource resource_1;
} // namespace resources
The custom Resource.h
could contain a type definition like:
#pragma once
#include <cstddef>
namespace resources {
struct Resource {
const unsigned char* data;
size_t size;
};
} // namespace
An alternative for defining a custom data type, is defining only a type alias. res2cpp -c resources.conf --alias resources::Resource
results in the following header:
#pragma once
#include <cstddef>
#include <utility>
namespace resources {
using Resource = std::pair<const unsigned char*, size_t>;
extern const Resource resource_0;
extern const Resource resource_1;
} // namespace resources
Applies a simple XOR cipher to the resource data, which can be decoded like this:
template<typename T>
std::vector<T> xor_cipher(const T* data, size_t size, std::string_view key) {
std::vector<T> decoded(size, T{ });
if (!key.empty())
for (size_t i = 0; i < size; ++i)
decoded[i] = data[i] ^ static_cast<T>(key[i % key.size()]);
return decoded;
}
By default each byte of a resource file is encoded separately in hex notation, e.g. 0xFF,
. So each byte of a file results in 5 bytes in the generated source.
In order to improve the compile time, this option allows to encode 8 bytes per hex number, which in total is much shorter. Since this makes the generated source dependent on the current processor's byte order, it is not enabled by default.
A C++17 conforming compiler is required. A script for the CMake build system is provided.
Checking out the source:
git clone https://github.com/houmain/res2cpp
Building:
cd res2cpp
cmake -B build
cmake --build build
res2cpp is released under the GNU GPLv3. It comes with absolutely no warranty. Please see LICENSE
for license details.