A cross-platform Windows resource-definition script (.rc) to resource file (.res) compiler. It has been merged into the Zig compiler (#17069, #17412), but it is also maintained as a separate standalone tool.
- This is a fully from-scratch and clean-room implementation, using fuzz testing as the primary method of determining how
rc.exe
works and how compatibleresinator
is with its implementation.- See this talk for a deeper dive into this
resinator
can successfully compile every.rc
file in the Windows-classic-samples repo byte-for-byte identically to the Windows RC compiler when using the includes from MSVC/the Windows SDK. This is tested via win32-samples-rc-tests.resinator
has zero external dependencies at runtime (it embeds Aro for preprocessing) and can be used to cross-compile from any system out-of-the-box (if Windows system include paths are not found, a complete set of MinGW include files are extracted on-demand).- Documentation (really just a dumping ground for documentation-adjacent stuff; to-be-improved-upon)
A Windows resource-definition file (.rc
) is made up of both C/C++ preprocessor commands and resource definitions.
- The preprocessor commands are evaluated first via Aro
- The preprocessed
.rc
file is then compiled into a.res
file - The
.res
file can then be linked into an executable by a linker
resinator
is similar to llvm-rc
and GNU's windres
, in that it aims to be a cross-platform alternative to the Windows rc.exe
tool.
However, unlike llvm-rc
and windres
(see this section), resinator
aims to get as close to 1:1 compatibility with the Windows rc.exe
implementation as possible. That is, the ideal would be:
- The
.res
output ofresinator
should match the.res
output of the Windowsrc.exe
in as many cases as possible (if not exactly, then functionally). However,resinator
will not support all valid.rc
files (i.e.#pragma code_page
support will be limited to particular code pages). resinator
should fail to compile.rc
files that the Windowsrc.exe
tool fails to compile.
In practice, though, 1:1 compatibility is not actually desirable, as there are quirks/bugs in the rc.exe
implementation that resinator
attempts to handle better.
resinator
is a drop-in replacement for rc.exe
with some improvements, some known exceptions, and some caveats.
resinator
avoids some miscompilations of the Win32 RC compiler and generally handles edge cases better (e.g. resource data size overflow). See this exhaustive list of intentional differences between resinator
and rc.exe
.
Also, resinator
will emit helpful warnings/errors for many of these differences:
This is a limitation of the preprocessor that is used by resinator
(see this issue).
.rc
files can #include
files from MSVC/Windows SDKs (most commonly, windows.h
). The paths for these files are typically provided via the INCLUDE
environment variable or the /i
command line option. resinator
supports the same options, but it will also try to auto-detect system include paths on Windows, or extract a full set of MinGW includes on-demand (meaning #include "windows.h"
in a .rc
file will work out-of-the-box on Linux/MacOS/etc).
This behavior can be controlled with the /:auto-includes
CLI option.
Feature | resinator |
windres |
llvm-rc |
rc.exe |
---|---|---|---|---|
Cross-platform | ✅ | ✅ | ✅ | ❌ |
Identical win32-samples-rc-tests outputs as rc.exe |
✅ | ❌ | ❌ | ✅ |
Support for UTF-16 encoded .rc files |
❌ (TODO) | ❌ | ❌ | ✅ |
CLI compatibility with rc.exe |
✅ | ❌ | ✅ | ✅ |
Includes preprocessor | ✅ | ❌ | ❌ | ✅ |
Support for outputting .rc files |
❌ | ✅ | ❌ | ❌ |
Support for outputting COFF object files | ❌ (TODO) | ✅ | ❌ | ❌ |
Here is an example .rc
script that is handled differently by each of windres
, llvm-rc
, and the canonical Windows rc.exe
implementation:
// <id> <resource type> { <data> }
1 "FOO" { "bar" }
rc.exe
compiles this to a.res
file with a resource of ID1
that has the type"FOO"
(the quotes are part of the user-defined resource type name)windres
compiles this to a.res
file with a resource of ID1
that has the typeFOO
(the"FOO"
is parsed as a quoted string)llvm-rc
errors on this file with:Error: expected int or identifier, got "FOO"
resinator
matches therc.exe
behavior exactly in this case
This particular example is mostly inconsequential in terms of real-world .rc
files, but it is indicative of how closely the different implementations conform with the rc.exe
behavior. See win32-samples-rc-tests for a more wide-ranging comparison on real-world .rc
files.
See the version of Zig used in CI to determine which version of Zig should be used to build resinator
.
git clone https://github.com/squeek502/resinator
cd resinator
zig build
zig build test
The 'fuzzy' tests are a collection of tests that may be similar to either fuzz testing or property testing. With default settings, the more fuzzing-like tests will run for 1000 iterations. These tests rely on rc.exe
being available on PATH
since each test uses rc.exe
as an oracle to determine if resinator
behavior is expected or not.
zig build test_fuzzy
will run all of the 'fuzzy' tests.
Each 'fuzzy' test can be run individually, too. For example, this will run the fuzzy_numbers
test infinitely:
zig build test_fuzzy_numbers -Diterations=0
- Requires winafl to be installed/built.
- Requires
rc.exe
to be available on thePATH
To build the fuzzer:
zig build fuzz_winafl
To run the fuzzer, from within the winafl/build64/bin
directory (replace C:\path\to\
with the actual path to the relevant directories):
set PATH_TO_INPUTS_DIR=C:\path\to\resinator\test\inputs
set PATH_TO_OUTPUTS_DIR=C:\path\to\resinator\test\outputs
set PATH_TO_DYNAMORIO_BIN=C:\path\to\DynamoRIO-Windows\bin64
set PATH_TO_RESINATOR_FUZZER=C:\path\to\resinator\zig-out\bin\fuzz_winafl.exe
afl-fuzz.exe -i "%PATH_TO_INPUTS_DIR%" -o "%PATH_TO_OUTPUTS_DIR%" -D "%PATH_TO_DYNAMORIO_BIN%" -t 20000 -- -coverage_module fuzz_winafl.exe -target_module fuzz_winafl.exe -target_method fuzzMain -fuzz_iterations 5000 -nargs 2 -- "%PATH_TO_RESINATOR_FUZZER%" @@
Currently, Linux fuzz testing only tests the resinator
implementation for crashes/bugs, and does not check for correctness against rc.exe
.
- Requires afl++ with
afl-clang-lto
to be installed.
zig build fuzz_rc
To run the fuzzer:
afl-fuzz -i test/inputs -o test/outputs -- ./zig-out/bin/fuzz_rc
Ideally, this fuzzer would compare the outputs against rc.exe
but wine
was not able to perfectly emulate rc.exe
last I tried (need to investigate this more, I was using a rather old version of wine).