Windows resource file: C++98/STL parsing library

Windows resource file (.res) are binary bundles of "resources". Resource has some sort of ID (numerical or string), type (predefined or user-defined), and raw value. Resource files can be seen standalone (as .res file), or embedded inside PE executable (.exe, .dll) files.

Typical use cases include:

  • providing information about the application (such as title, copyrights, etc)
  • embedding icon(s) to be displayed in file managers into .exe
  • adding non-code data into the binary, such as menus, dialog forms, cursor images, fonts, various misc bitmaps, and locale-aware strings

Windows provides special API to access "resources" from a binary.

Normally, resources files are created with rc compiler: it takes a .rc file (so called "resource-definition script") + all the raw resource binary files for input, and outputs .res file. That .res file can be linked into an .exe / .dll afterwards using a linker.

Internally, resource file is just a sequence of individual resource definitions. RC tool ensures that first resource (#0) is always empty.

File extension

res

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of Windows resource file using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C++98/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.res", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
    
  2. We need to wrap our input stream into Kaitai stream:
    #include "kaitai/kaitaistream.h"
    
    kaitai::kstream ks(&is);
    
  3. And finally, we can invoke the parsing:
    windows_resource_file_t data(&ks);
    

After that, one can get various attributes from the structure by invoking getter methods like:

data.resources() // => get resources

C++98/STL source code to parse Windows resource file

windows_resource_file.h

#ifndef WINDOWS_RESOURCE_FILE_H_
#define WINDOWS_RESOURCE_FILE_H_

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <vector>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * Windows resource file (.res) are binary bundles of
 * "resources". Resource has some sort of ID (numerical or string),
 * type (predefined or user-defined), and raw value. Resource files can
 * be seen standalone (as .res file), or embedded inside PE executable
 * (.exe, .dll) files.
 * 
 * Typical use cases include:
 * 
 * * providing information about the application (such as title, copyrights, etc)
 * * embedding icon(s) to be displayed in file managers into .exe
 * * adding non-code data into the binary, such as menus, dialog forms,
 *   cursor images, fonts, various misc bitmaps, and locale-aware
 *   strings
 * 
 * Windows provides special API to access "resources" from a binary.
 * 
 * Normally, resources files are created with `rc` compiler: it takes a
 * .rc file (so called "resource-definition script") + all the raw
 * resource binary files for input, and outputs .res file. That .res
 * file can be linked into an .exe / .dll afterwards using a linker.
 * 
 * Internally, resource file is just a sequence of individual resource
 * definitions. RC tool ensures that first resource (#0) is always
 * empty.
 */

class windows_resource_file_t : public kaitai::kstruct {

public:
    class resource_t;
    class unicode_or_id_t;

    windows_resource_file_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, windows_resource_file_t* p__root = 0);

private:
    void _read();
    void _clean_up();

public:
    ~windows_resource_file_t();

    /**
     * Each resource has a `type` and a `name`, which can be used to
     * identify it, and a `value`. Both `type` and `name` can be a
     * number or a string.
     * \sa https://learn.microsoft.com/en-us/windows/win32/menurc/resourceheader Source
     */

    class resource_t : public kaitai::kstruct {

    public:

        enum predef_types_t {
            PREDEF_TYPES_CURSOR = 1,
            PREDEF_TYPES_BITMAP = 2,
            PREDEF_TYPES_ICON = 3,
            PREDEF_TYPES_MENU = 4,
            PREDEF_TYPES_DIALOG = 5,
            PREDEF_TYPES_STRING = 6,
            PREDEF_TYPES_FONTDIR = 7,
            PREDEF_TYPES_FONT = 8,
            PREDEF_TYPES_ACCELERATOR = 9,
            PREDEF_TYPES_RCDATA = 10,
            PREDEF_TYPES_MESSAGETABLE = 11,
            PREDEF_TYPES_GROUP_CURSOR = 12,
            PREDEF_TYPES_GROUP_ICON = 14,
            PREDEF_TYPES_VERSION = 16,
            PREDEF_TYPES_DLGINCLUDE = 17,
            PREDEF_TYPES_PLUGPLAY = 19,
            PREDEF_TYPES_VXD = 20,
            PREDEF_TYPES_ANICURSOR = 21,
            PREDEF_TYPES_ANIICON = 22,
            PREDEF_TYPES_HTML = 23,
            PREDEF_TYPES_MANIFEST = 24
        };

        resource_t(kaitai::kstream* p__io, windows_resource_file_t* p__parent = 0, windows_resource_file_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~resource_t();

    private:
        bool f_type_as_predef;
        predef_types_t m_type_as_predef;
        bool n_type_as_predef;

    public:
        bool _is_null_type_as_predef() { type_as_predef(); return n_type_as_predef; };

    private:

    public:

        /**
         * Numeric type IDs in range of [0..0xff] are reserved for
         * system usage in Windows, and there are some predefined,
         * well-known values in that range. This instance allows to get
         * it as enum value, if applicable.
         */
        predef_types_t type_as_predef();

    private:
        uint32_t m_value_size;
        uint32_t m_header_size;
        unicode_or_id_t* m_type;
        unicode_or_id_t* m_name;
        std::string m_padding1;
        uint32_t m_format_version;
        uint16_t m_flags;
        uint16_t m_language;
        uint32_t m_value_version;
        uint32_t m_characteristics;
        std::string m_value;
        std::string m_padding2;
        windows_resource_file_t* m__root;
        windows_resource_file_t* m__parent;

    public:

        /**
         * Size of resource value that follows the header
         */
        uint32_t value_size() const { return m_value_size; }

        /**
         * Size of this header (i.e. every field except `value` and an
         * optional padding after it)
         */
        uint32_t header_size() const { return m_header_size; }
        unicode_or_id_t* type() const { return m_type; }
        unicode_or_id_t* name() const { return m_name; }
        std::string padding1() const { return m_padding1; }
        uint32_t format_version() const { return m_format_version; }
        uint16_t flags() const { return m_flags; }
        uint16_t language() const { return m_language; }

        /**
         * Version for value, as specified by a user.
         */
        uint32_t value_version() const { return m_value_version; }

        /**
         * Extra 4 bytes that can be used by user for any purpose.
         */
        uint32_t characteristics() const { return m_characteristics; }
        std::string value() const { return m_value; }
        std::string padding2() const { return m_padding2; }
        windows_resource_file_t* _root() const { return m__root; }
        windows_resource_file_t* _parent() const { return m__parent; }
    };

    /**
     * Resources use a special serialization of names and types: they
     * can be either a number or a string.
     * 
     * Use `is_string` to check which kind we've got here, and then use
     * `as_numeric` or `as_string` to get relevant value.
     */

    class unicode_or_id_t : public kaitai::kstruct {

    public:

        unicode_or_id_t(kaitai::kstream* p__io, windows_resource_file_t::resource_t* p__parent = 0, windows_resource_file_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~unicode_or_id_t();

    private:
        bool f_save_pos1;
        int32_t m_save_pos1;

    public:
        int32_t save_pos1();

    private:
        bool f_save_pos2;
        int32_t m_save_pos2;

    public:
        int32_t save_pos2();

    private:
        bool f_is_string;
        bool m_is_string;

    public:
        bool is_string();

    private:
        bool f_as_string;
        std::string m_as_string;
        bool n_as_string;

    public:
        bool _is_null_as_string() { as_string(); return n_as_string; };

    private:

    public:
        std::string as_string();

    private:
        uint16_t m_first;
        bool n_first;

    public:
        bool _is_null_first() { first(); return n_first; };

    private:
        uint16_t m_as_numeric;
        bool n_as_numeric;

    public:
        bool _is_null_as_numeric() { as_numeric(); return n_as_numeric; };

    private:
        std::vector<uint16_t>* m_rest;
        bool n_rest;

    public:
        bool _is_null_rest() { rest(); return n_rest; };

    private:
        std::string m_noop;
        bool n_noop;

    public:
        bool _is_null_noop() { noop(); return n_noop; };

    private:
        windows_resource_file_t* m__root;
        windows_resource_file_t::resource_t* m__parent;

    public:
        uint16_t first() const { return m_first; }
        uint16_t as_numeric() const { return m_as_numeric; }
        std::vector<uint16_t>* rest() const { return m_rest; }
        std::string noop() const { return m_noop; }
        windows_resource_file_t* _root() const { return m__root; }
        windows_resource_file_t::resource_t* _parent() const { return m__parent; }
    };

private:
    std::vector<resource_t*>* m_resources;
    windows_resource_file_t* m__root;
    kaitai::kstruct* m__parent;

public:
    std::vector<resource_t*>* resources() const { return m_resources; }
    windows_resource_file_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

#endif  // WINDOWS_RESOURCE_FILE_H_

windows_resource_file.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "windows_resource_file.h"

windows_resource_file_t::windows_resource_file_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, windows_resource_file_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_resources = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void windows_resource_file_t::_read() {
    m_resources = new std::vector<resource_t*>();
    {
        int i = 0;
        while (!m__io->is_eof()) {
            m_resources->push_back(new resource_t(m__io, this, m__root));
            i++;
        }
    }
}

windows_resource_file_t::~windows_resource_file_t() {
    _clean_up();
}

void windows_resource_file_t::_clean_up() {
    if (m_resources) {
        for (std::vector<resource_t*>::iterator it = m_resources->begin(); it != m_resources->end(); ++it) {
            delete *it;
        }
        delete m_resources; m_resources = 0;
    }
}

windows_resource_file_t::resource_t::resource_t(kaitai::kstream* p__io, windows_resource_file_t* p__parent, windows_resource_file_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_type = 0;
    m_name = 0;
    f_type_as_predef = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void windows_resource_file_t::resource_t::_read() {
    m_value_size = m__io->read_u4le();
    m_header_size = m__io->read_u4le();
    m_type = new unicode_or_id_t(m__io, this, m__root);
    m_name = new unicode_or_id_t(m__io, this, m__root);
    m_padding1 = m__io->read_bytes(kaitai::kstream::mod((4 - _io()->pos()), 4));
    m_format_version = m__io->read_u4le();
    m_flags = m__io->read_u2le();
    m_language = m__io->read_u2le();
    m_value_version = m__io->read_u4le();
    m_characteristics = m__io->read_u4le();
    m_value = m__io->read_bytes(value_size());
    m_padding2 = m__io->read_bytes(kaitai::kstream::mod((4 - _io()->pos()), 4));
}

windows_resource_file_t::resource_t::~resource_t() {
    _clean_up();
}

void windows_resource_file_t::resource_t::_clean_up() {
    if (m_type) {
        delete m_type; m_type = 0;
    }
    if (m_name) {
        delete m_name; m_name = 0;
    }
}

windows_resource_file_t::resource_t::predef_types_t windows_resource_file_t::resource_t::type_as_predef() {
    if (f_type_as_predef)
        return m_type_as_predef;
    n_type_as_predef = true;
    if ( ((!(type()->is_string())) && (type()->as_numeric() <= 255)) ) {
        n_type_as_predef = false;
        m_type_as_predef = static_cast<windows_resource_file_t::resource_t::predef_types_t>(type()->as_numeric());
    }
    f_type_as_predef = true;
    return m_type_as_predef;
}

windows_resource_file_t::unicode_or_id_t::unicode_or_id_t(kaitai::kstream* p__io, windows_resource_file_t::resource_t* p__parent, windows_resource_file_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_rest = 0;
    f_save_pos1 = false;
    f_save_pos2 = false;
    f_is_string = false;
    f_as_string = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void windows_resource_file_t::unicode_or_id_t::_read() {
    n_first = true;
    if (save_pos1() >= 0) {
        n_first = false;
        m_first = m__io->read_u2le();
    }
    n_as_numeric = true;
    if (!(is_string())) {
        n_as_numeric = false;
        m_as_numeric = m__io->read_u2le();
    }
    n_rest = true;
    if (is_string()) {
        n_rest = false;
        m_rest = new std::vector<uint16_t>();
        {
            int i = 0;
            uint16_t _;
            do {
                _ = m__io->read_u2le();
                m_rest->push_back(_);
                i++;
            } while (!(_ == 0));
        }
    }
    n_noop = true;
    if ( ((is_string()) && (save_pos2() >= 0)) ) {
        n_noop = false;
        m_noop = m__io->read_bytes(0);
    }
}

windows_resource_file_t::unicode_or_id_t::~unicode_or_id_t() {
    _clean_up();
}

void windows_resource_file_t::unicode_or_id_t::_clean_up() {
    if (!n_first) {
    }
    if (!n_as_numeric) {
    }
    if (!n_rest) {
        if (m_rest) {
            delete m_rest; m_rest = 0;
        }
    }
    if (!n_noop) {
    }
    if (f_as_string && !n_as_string) {
    }
}

int32_t windows_resource_file_t::unicode_or_id_t::save_pos1() {
    if (f_save_pos1)
        return m_save_pos1;
    m_save_pos1 = _io()->pos();
    f_save_pos1 = true;
    return m_save_pos1;
}

int32_t windows_resource_file_t::unicode_or_id_t::save_pos2() {
    if (f_save_pos2)
        return m_save_pos2;
    m_save_pos2 = _io()->pos();
    f_save_pos2 = true;
    return m_save_pos2;
}

bool windows_resource_file_t::unicode_or_id_t::is_string() {
    if (f_is_string)
        return m_is_string;
    m_is_string = first() != 65535;
    f_is_string = true;
    return m_is_string;
}

std::string windows_resource_file_t::unicode_or_id_t::as_string() {
    if (f_as_string)
        return m_as_string;
    n_as_string = true;
    if (is_string()) {
        n_as_string = false;
        std::streampos _pos = m__io->pos();
        m__io->seek(save_pos1());
        m_as_string = kaitai::kstream::bytes_to_str(m__io->read_bytes(((save_pos2() - save_pos1()) - 2)), std::string("UTF-16LE"));
        m__io->seek(_pos);
        f_as_string = true;
    }
    return m_as_string;
}