Skip to content

Latest commit

 

History

History
266 lines (191 loc) · 8.02 KB

README.md

File metadata and controls

266 lines (191 loc) · 8.02 KB

res2cpp

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.

Introduction

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

Configuration

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"

Command line arguments

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.  

--config

The path to the configuration file. When none is set, the configuration is read from stdin.

--source and --header

The source and header filenames are deduced from configuration filename unless specified explicitly.

--data

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 and --include

--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

--alias

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

--xor

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;
}

--native

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.

Building

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

License

res2cpp is released under the GNU GPLv3. It comes with absolutely no warranty. Please see LICENSE for license details.