dsh-labview
Open password-protected LabVIEW block diagrams — without LabVIEW, without Python.
Inherited a LabVIEW project whose front panels open but whose block diagrams are
locked? This plugin removes that protection from *.vi, *.ctl, *.vit and
*.ctt files by patching a few bytes in place. No gigabytes of NI runtime, no
pylabview, no network — just a small, dependency-free JavaScript engine.
lv_scan what is locked, and which LabVIEW version saved each file
lv_unlock back up, patch, verify, write
lv_restore put the originals back
Why this exists
LabVIEW "password protection" is not encryption. The diagram is stored in the file in the clear; what stops you from opening it is a small bookkeeping block:
| Block | Meaning |
|---|---|
BDPW |
md5(password) + hash_1 + hash_2 — the password record |
LVSR |
version block; execFlags bit 13 (LibProtected, 0x2000) marks a locked library |
Give LabVIEW an empty password and matching hashes and it opens the diagram without asking. That is exactly what this plugin writes:
password_md5→md5("")=d41d8cd98f00b204e9800998ecf8427ehash_1→md5(md5("") ‖ LIBN ‖ LVSR ‖ salt)hash_2→md5(hash_1 ‖ contentHash(BDH))- clear
VI_EXEC_FLAGS.LibProtectedinLVSR
The only unknown is the 12-byte salt, which LabVIEW derives from the counts of
terminal types in the connector pane. Rather than parse the type descriptors, the
engine brute-forces the three small integers against the hashes already stored in
the file — a typical count is under 20, so most files resolve in a few
milliseconds.
Nothing else in the file is touched: no re-serialization, no re-compression, no length changes. A file that was not locked is left byte-identical.
Install
dsh plugin --profile web add dsh-labview # from npm, after it is published
dsh plugin --profile web add link:/path/to/dsh-labview # local checkout
Restart the harness (or toggle the plugin in the market page) and the three tools appear in every session.
Use
Ask in plain language — "unlock the LabVIEW files in this project" — or call the tools directly.
lv_scan(dir?, lockedOnly?)
Read-only survey. Reports, per file, the LabVIEW version, whether the block diagram is protected, whether it can be unlocked, and why not when it cannot. Uses no writes, so it is safe on a production copy.
lv_unlock(dir?, dryRun?, hashSource?, limit?)
Backs up every original into <dir>/_lv_unlock_backup/…, patches the file, and
verifies the patched bytes in memory before touching the disk. Already-open
files are counted and skipped. dryRun: true reports what would happen and
writes nothing — always worth doing first on a large tree.
lv_restore(dir?, dryRun?, limit?)
Copies the backups back over the working files, undoing an unlock.
A typical session
Scan
D:\SCADA\FC MAS V1for locked LabVIEW files, then unlock them and tell me which LabVIEW version I need to open the project.
The model runs lv_scan, sees 140 × LabVIEW 2015 / 83 × LabVIEW 8.5, runs
lv_unlock, and reports both. Originals stay in _lv_unlock_backup/ until you
delete them.
Safety model
- Backup first. The original is copied to the backup tree before the first
byte is written. An existing backup is never overwritten, so a second
lv_unlockcannot destroy the pristine copy. - Verify, then write. The patched buffer is re-parsed and must report "no password" before it reaches the file. Anything unexpected is refused.
- Atomic writes. Bytes land through
<file>.lvunlock.tmp+ rename, so an interrupted run cannot leave a half-written VI. - Only two edits.
lv_unlockreports the exact byte ranges it changed for every file; a file that needs no change is never rewritten. - No LabVIEW required. The engine parses the RSRC container directly. It never runs the VI, never decompiles the diagram, and never needs a license.
Supported range
Files saved by LabVIEW 8.0 through 2019 (and older files that were last saved by those releases). LabVIEW 2020+ changed the block layout; those files are reported as unsupported instead of being written.
Version handling has one subtlety worth knowing: removing a password also clears
LibProtected, and LabVIEW hashes the version block after that change — so
hash_1 covers the cleared flags. VIs last saved by old releases keep a legacy
version-block layout where the flag never made it into the hash. The engine
detects this (hashSource: "auto") and reproduces the matching behaviour;
"final" or "original" force one or the other.
Evidence
The engine is not a guess: it was validated byte-for-byte against a reference implementation on real industrial code.
| Check | Result |
|---|---|
| Files re-unlocked from pristine originals and compared byte-for-byte | 223 / 223 identical |
| Random spot audits of a previous 601-file project | 597 unlocked, 0 remaining locked |
| Password-salt recovery proved correct (re-derives the hashes already in every file) | 223 / 223 |
| Files modified by mistake | 0 — every diff was the password block plus one flag byte |
Average 35 ms per file; the slowest file (a large VI with an unlucky salt) takes about 0.9 s.
Config
Every option has a working default; override only if you want to.
| Option | Default | Meaning |
|---|---|---|
backupDir |
_lv_unlock_backup |
Backup directory, relative to the scanned root |
extensions |
.vi .ctl .vit .ctt .xctl |
File types to process |
maxFiles |
20000 |
Safety cap on a single scan |
concurrency |
4 |
Files processed in parallel |
hashSource |
auto |
Which version block the new hash covers (auto/final/original) |
# profile cordis.patch.yml
- id: labview-unlock
config:
backupDir: _lv_backup
concurrency: 8
Limitations
- No in-app verification. The plugin cannot open LabVIEW, so "unlocked" means the file's own password record says so. Open one file in LabVIEW before trusting a bulk run on an irreplaceable project.
- Salt brute force has a ceiling. Counts are searched up to 255 each (≈16.7 M combinations). Pathological connector panes beyond that are reported as not unlockable rather than silently mishandled.
- Type definitions. Unlocking a
*.ctlexposes its diagram too — as with any password removal, types the project used as opaque boxes can now be edited. - Ethics. Use this only on software you own or are authorized to maintain. Recovering access to a project your company paid for is maintenance; defeating a vendor's licensing is not.
Development
The harness ships an Electron binary that runs as Node, which is handy on machines without a separate Node install:
ELECTRON_RUN_AS_NODE=1 "…/DSH Desktop.exe" test/verify-223.mjs <project dir>
ELECTRON_RUN_AS_NODE=1 "…/DSH Desktop.exe" test/smoke.mjs <dir with locked VIs>
ELECTRON_RUN_AS_NODE=1 "…/DSH Desktop.exe" test/harness-swap.mjs
test/verify-223.mjs compares the engine against reference output byte-for-byte;
test/smoke.mjs drives the three plugin tools end to end on a temp copy;
test/harness-swap.mjs temporarily points the vendored defineTool/Schema
stubs at the real modules inside the installed harness and re-runs the smoke
test, which is what proves the Host accepts the parameter specs, output schemas
and config schema. It restores the stubs afterwards, even on failure.
assets/make-icon.mjs renders the icon with no image dependency.
Publishing steps (npm, GitHub, marketplace PR) are in PUBLISHING.md.
lib/engine.js is standalone — import it from dsh-labview/engine to use
the parser and patcher without the harness.
License
MIT.
No comments yet. Be the first to write one.