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/.resfile (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 A .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 -failmissingsets exit code 1 if any resource in the baseline is absent from the new build.-parsestringtablebreaksSTRINGTABLEbundles down to individual strings.-failonmissingstringsets 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.
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
-
Ignore resources that change every build. If you stamp a build number into an RCDATA block or a custom type, exclude it:
-ignoretype BUILDINFO. You can list several,-ignoretype BUILDINFO,SIGNATURE. -
Only care about presence, not content. Add
-nochangesto report added and removed resources and skip content comparison entirely. - Updating the baseline is a deliberate act. When a resource change is intended, regenerate the baseline the same way you first created it and commit it in the same change that made the edit, so a reviewer sees both.
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.