Application resources are easy to break without noticing. A merge resolves the wrong way and an icon reverts. A refactor of the build script drops the manifest. A localization round-trip loses three menu captions. The linker does not warn you, the app still runs, and the problem ships.

A resource regression check catches this. The idea is small: keep a known-good snapshot of your resources in version control, compare every build against it, and fail the build when something that used to be there is gone. Resource Builder’s console tool, sircc64.exe, does the comparison and returns a real exit code, so wiring it into CI is a few lines.

What you need for a Resource Regression Check

  • sircc64.exe, the console compiler and resource tool installed with Resource Builder. Run it on a build machine or agent that has Resource Builder installed.
  • A baseline: a .rc/.res file (or an .exe/.dll) that represents the resources you expect every build to contain, committed alongside your source.

Step 1: choose a baseline

If you already build resources from an .rc script

If your project’s resources are authored as an .rc file that lives in version control, you are done: that script is the baseline. -diff accepts an .rc directly and compiles it in memory before comparing, so there is nothing to extract or commit separately, and any intended change to the resources shows up as a normal reviewed diff of the script. Skip to Step 2 and point -diff at src\App.rc.

Otherwise, extract a baseline from a known-good build

Extract the resources from your last known-good binary into a .res file:

sircc64.exe -e ALL LastGood.exe ci\baseline.res

.res holds only the resources, not the whole binary, so it stays small. It is a binary format, though, so a version-control diff of baseline.res will only tell you that it changed, not what changed. That is fine for the check itself: the -diff step below is what produces the readable report, and the build fails regardless of what your VCS shows for the file.

If you would rather the extracted baseline be reviewable in a pull request, extract it as an RC script instead. sircc64.exe cannot do this (it only writes binary .res); formatted RC-script extraction is a feature of the main application in console mode:

Resbldr5.exe -e ALL LastGood.exe ci\baseline.rc -style P -nowait

Structural changes (a resource added or removed, a string edited, a dialog control moved) then show up as readable text in the diff. Embedded icons and bitmaps are still inlined as long hex blocks, so expect noise there. -diff accepts an .rc baseline just as readily as a .res one.

Either way, commit the baseline and treat it as a contract: it changes only when someone intends to change the resources, in a reviewed commit.

Step 2: compare each build against the baseline

After your build produces the binary, diff it against the baseline. Substitute your baseline path below: src\App.rc if you use the source script, or ci\baseline.res / ci\baseline.rc if you extracted one.

sircc64.exe -diff src\App.rc build\App.exe -failmissing -parsestringtable -failonmissingstring > ci\resource-diff.txt
  • -failmissing sets exit code 1 if any resource in the baseline is absent from the new build.
  • -parsestringtable breaks STRINGTABLE bundles down to individual strings.
  • -failonmissingstring sets exit code 1 if an individual string was dropped from a bundle.

Added resources and in-place content changes do not fail the check by themselves. That is the right default: CI should block on “something went missing”, not on every edit. The full report is written to ci\resource-diff.txt either way, so you can read what changed.

The report looks like this:

~ VERSION 1 (lang 1033, 1096 -> 1088 bytes)
- BITMAP BITMAP_SPLASH (lang 0, 65576 bytes)
- STRING 4067 (lang 0, 96 bytes)
    string 65071: removed (was "Some string value")
~ STRING 4068 (lang 0, 556 -> 548 bytes)
    string 65072: changed ("Invalid stream operation 1234" -> "Invalid stream operation")
~ STRING 4069 (lang 0, 986 -> 988 bytes)
~ STRING 4071 (lang 0, 2902 -> 2904 bytes)
~ STRING 4073 (lang 0, 702 -> 704 bytes)
~ STRING 4078 (lang 0, 986 -> 988 bytes)
+ GROUP_ICON MAINICON (lang 1033, 188 bytes)
+ ICON 1 (lang 1033, 49135 bytes)
+ ICON 10 (lang 1033, 1128 bytes)
+ ICON 11 (lang 1033, 872 bytes)
+ ICON 12 (lang 1033, 1384 bytes)
+ ICON 13 (lang 1033, 296 bytes)
+ ICON 2 (lang 1033, 9640 bytes)
+ ICON 3 (lang 1033, 7336 bytes)
+ ICON 4 (lang 1033, 3752 bytes)
+ ICON 5 (lang 1033, 1640 bytes)
+ ICON 6 (lang 1033, 4264 bytes)
+ ICON 7 (lang 1033, 3240 bytes)
+ ICON 8 (lang 1033, 2216 bytes)
+ ICON 9 (lang 1033, 744 bytes)
Added: 14, Removed: 2, Changed: 6
Removed strings: 1

- is removed, + is added, ~ is changed. Here the build lost a bitmap and a string, so the check fails.

Hint

The STRINGTABLE byte-difference quirk

A bundle built by a different toolchain can show as changed by a couple of bytes with no visible string difference. That is a trailing-slot encoding difference, not lost content. -parsestringtable reports the real per-string picture, and -failonmissingstring keys off actual dropped strings, so the check stays reliable. (More on this in the resource-comparison guide.

Exit codes

0 means the baseline is fully present. 1 means either a real error (a file that is not a valid binary, for example) or a triggered -failmissing / -failonmissingstring. Both should stop the pipeline.

Stricter: fail on any difference

If you want the build to stop on any change, added or edited included, check the summary line:

findstr /C:"Added: 0, Removed: 0, Changed: 0" ci\resource-diff.txt >nul || exit /b 1

Step 3: wire it into CI

Plain batch or PowerShell

sircc64.exe -diff src\App.rc build\App.exe -failmissing -parsestringtable -failonmissingstring > ci\resource-diff.txt
if errorlevel 1 (
    echo Resource regression detected. See ci\resource-diff.txt
    type ci\resource-diff.txt
    exit /b 1
)

(src\App.rc here stands for whichever baseline you chose in Step 1.)

GitHub Actions

A non-zero exit fails the step automatically. Upload the report so it is available whether the step passed or failed:

- name: Resource regression check
  run: sircc64.exe -diff src\App.rc build\App.exe -failmissing -parsestringtable -failonmissingstring | Tee-Object ci\resource-diff.txt
  shell: pwsh

- name: Upload resource diff
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: resource-diff
    path: ci/resource-diff.txt

The runner needs Resource Builder installed, so use a self-hosted Windows runner or an earlier setup step that installs it.

Tuning the check

Bonus: archive every build's resources

Alongside the check, keep a per-build snapshot of the actual resource files as a CI artifact:

sircc64.exe -unpack -types ALL -o artifacts\resources build\App.exe

This writes every icon, bitmap, cursor, manifest, and data blob out as an individual file. When something does slip through, you can walk back through build artifacts and see exactly when it changed.

If your pipeline also injects resources

If a later stage patches resources into the binary with -la-lr, or -ls, and the target is already Authenticode-signed, sircc64.exe refuses by default. It proceeds only with an explicit -forcesign, which invalidates the signature. Keep -forcesign out of an unattended pipeline unless a re-signing step runs immediately after, so a build can never silently ship a binary with a broken signature.

Try it

Resource Builder is a commercial tool with a free 30-day trial. Download it from the Download page, extract a baseline with sircc64.exe -e ALL, and add the -diff check to your next pipeline run.

Copyright © 1999-2026 Igor Siticov, SiComponents. All rights reserved.

TsiLang®, Resource Builder® and SiComponents® are registered trademarks of Igor Siticov.