Windows resource file: PHP 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.

PHP source code to parse Windows resource file

WindowsResourceFile.php

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

/**
 * 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.
 */

namespace {
    class WindowsResourceFile extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \Kaitai\Struct\Struct $_parent = null, \WindowsResourceFile $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_resources = [];
            $i = 0;
            while (!$this->_io->isEof()) {
                $this->_m_resources[] = new \WindowsResourceFile\Resource($this->_io, $this, $this->_root);
                $i++;
            }
        }
        protected $_m_resources;
        public function resources() { return $this->_m_resources; }
    }
}

/**
 * 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.
 */

namespace WindowsResourceFile {
    class Resource extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \WindowsResourceFile $_parent = null, \WindowsResourceFile $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            $this->_m_valueSize = $this->_io->readU4le();
            $this->_m_headerSize = $this->_io->readU4le();
            $this->_m_type = new \WindowsResourceFile\UnicodeOrId($this->_io, $this, $this->_root);
            $this->_m_name = new \WindowsResourceFile\UnicodeOrId($this->_io, $this, $this->_root);
            $this->_m_padding1 = $this->_io->readBytes(\Kaitai\Struct\Stream::mod((4 - $this->_io()->pos()), 4));
            $this->_m_formatVersion = $this->_io->readU4le();
            $this->_m_flags = $this->_io->readU2le();
            $this->_m_language = $this->_io->readU2le();
            $this->_m_valueVersion = $this->_io->readU4le();
            $this->_m_characteristics = $this->_io->readU4le();
            $this->_m_value = $this->_io->readBytes($this->valueSize());
            $this->_m_padding2 = $this->_io->readBytes(\Kaitai\Struct\Stream::mod((4 - $this->_io()->pos()), 4));
        }
        protected $_m_typeAsPredef;

        /**
         * 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.
         */
        public function typeAsPredef() {
            if ($this->_m_typeAsPredef !== null)
                return $this->_m_typeAsPredef;
            if ( ((!($this->type()->isString())) && ($this->type()->asNumeric() <= 255)) ) {
                $this->_m_typeAsPredef = $this->type()->asNumeric();
            }
            return $this->_m_typeAsPredef;
        }
        protected $_m_valueSize;
        protected $_m_headerSize;
        protected $_m_type;
        protected $_m_name;
        protected $_m_padding1;
        protected $_m_formatVersion;
        protected $_m_flags;
        protected $_m_language;
        protected $_m_valueVersion;
        protected $_m_characteristics;
        protected $_m_value;
        protected $_m_padding2;

        /**
         * Size of resource value that follows the header
         */
        public function valueSize() { return $this->_m_valueSize; }

        /**
         * Size of this header (i.e. every field except `value` and an
         * optional padding after it)
         */
        public function headerSize() { return $this->_m_headerSize; }
        public function type() { return $this->_m_type; }
        public function name() { return $this->_m_name; }
        public function padding1() { return $this->_m_padding1; }
        public function formatVersion() { return $this->_m_formatVersion; }
        public function flags() { return $this->_m_flags; }
        public function language() { return $this->_m_language; }

        /**
         * Version for value, as specified by a user.
         */
        public function valueVersion() { return $this->_m_valueVersion; }

        /**
         * Extra 4 bytes that can be used by user for any purpose.
         */
        public function characteristics() { return $this->_m_characteristics; }
        public function value() { return $this->_m_value; }
        public function padding2() { return $this->_m_padding2; }
    }
}

namespace WindowsResourceFile\Resource {
    class PredefTypes {
        const CURSOR = 1;
        const BITMAP = 2;
        const ICON = 3;
        const MENU = 4;
        const DIALOG = 5;
        const STRING = 6;
        const FONTDIR = 7;
        const FONT = 8;
        const ACCELERATOR = 9;
        const RCDATA = 10;
        const MESSAGETABLE = 11;
        const GROUP_CURSOR = 12;
        const GROUP_ICON = 14;
        const VERSION = 16;
        const DLGINCLUDE = 17;
        const PLUGPLAY = 19;
        const VXD = 20;
        const ANICURSOR = 21;
        const ANIICON = 22;
        const HTML = 23;
        const MANIFEST = 24;
    }
}

/**
 * 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.
 */

namespace WindowsResourceFile {
    class UnicodeOrId extends \Kaitai\Struct\Struct {
        public function __construct(\Kaitai\Struct\Stream $_io, \WindowsResourceFile\Resource $_parent = null, \WindowsResourceFile $_root = null) {
            parent::__construct($_io, $_parent, $_root);
            $this->_read();
        }

        private function _read() {
            if ($this->savePos1() >= 0) {
                $this->_m_first = $this->_io->readU2le();
            }
            if (!($this->isString())) {
                $this->_m_asNumeric = $this->_io->readU2le();
            }
            if ($this->isString()) {
                $this->_m_rest = [];
                $i = 0;
                do {
                    $_ = $this->_io->readU2le();
                    $this->_m_rest[] = $_;
                    $i++;
                } while (!($_ == 0));
            }
            if ( (($this->isString()) && ($this->savePos2() >= 0)) ) {
                $this->_m_noop = $this->_io->readBytes(0);
            }
        }
        protected $_m_savePos1;
        public function savePos1() {
            if ($this->_m_savePos1 !== null)
                return $this->_m_savePos1;
            $this->_m_savePos1 = $this->_io()->pos();
            return $this->_m_savePos1;
        }
        protected $_m_savePos2;
        public function savePos2() {
            if ($this->_m_savePos2 !== null)
                return $this->_m_savePos2;
            $this->_m_savePos2 = $this->_io()->pos();
            return $this->_m_savePos2;
        }
        protected $_m_isString;
        public function isString() {
            if ($this->_m_isString !== null)
                return $this->_m_isString;
            $this->_m_isString = $this->first() != 65535;
            return $this->_m_isString;
        }
        protected $_m_asString;
        public function asString() {
            if ($this->_m_asString !== null)
                return $this->_m_asString;
            if ($this->isString()) {
                $_pos = $this->_io->pos();
                $this->_io->seek($this->savePos1());
                $this->_m_asString = \Kaitai\Struct\Stream::bytesToStr($this->_io->readBytes((($this->savePos2() - $this->savePos1()) - 2)), "UTF-16LE");
                $this->_io->seek($_pos);
            }
            return $this->_m_asString;
        }
        protected $_m_first;
        protected $_m_asNumeric;
        protected $_m_rest;
        protected $_m_noop;
        public function first() { return $this->_m_first; }
        public function asNumeric() { return $this->_m_asNumeric; }
        public function rest() { return $this->_m_rest; }
        public function noop() { return $this->_m_noop; }
    }
}