VBReFormer is twenty years old. Its first version already decompiled native Visual Basic 6 at a time when we were being told that "native VB6 simply cannot be decompiled". Since then, every strange binary, every compiler idiom, every oddity of MSVBVM60 has eventually found its way into the product.
V7 keeps that knowledge and throws away everything else.
The engine has been rewritten in full, in .NET, from zero. Not a port: a rewrite, in which only the semantics — ten years of observing the VB6 runtime — were carried over. And for the first time, native and P-code are handled by the same product, to the same standard of finish.
What actually changes
1. Native, finally treated as a real decompilation problem
The old engine worked by pattern recognition: it knew what the code VB6 emits looks like, and it rendered it. That works beautifully — right up to the binary compiled with an unusual optimisation profile.
V7 works differently. It abstractly interprets the machine code: registers, virtual memory, FPU stack, tracking both the literal value and the expression that produced it. Disassembly goes through Iced. The result is an expression tree, not a recognised string.
The difference shows on the hard cases:
- all four VB6 optimisation profiles (Fast Code, Favor Small Code, No Optimization, Remove Safety Checks) are covered and measured separately — and, counter-intuitively, it is No Optimization that is hardest
- the jump tables of a
Select Caseare followed, including when aCasebody has no obvious incoming edge - the Variant model is reconstructed (tag, value,
ByRef) instead of guessed - UDTs on the stack, bounded arrays,
CurrencyandParamArrayare reconstructed as types, not as bytes
2. P-code, in the same product
A VB6 binary is either native or P-code, never both — and until now that meant two paths and two levels of quality. V7 decompiles P-code end to end: bytes → typed opcodes → stack machine → expression tree → control-flow graph → structuring → VB6 source.
It is the same structuring stage that serves both natures. A Do While … Loop rebuilt from P-code and the same one rebuilt from native go through the same code — and therefore through the same fixes.
3. Names
This is what separates a usable decompiler from an assembly dump. V7 restores:
- the members of
Me, publics, arguments,Staticvariables - calls into the
MSVBVM60runtime rendered as VB idioms (Left(s, 3), not__vbaStrLeft) — 635 ordinals catalogued - virtual calls (
VCall), including into theVB6.OLBintrinsics New→ the name of the class being instantiated- form inheritance (
Form→_Form) and control accessors - the bounds and element type of arrays, read from procedure descriptors and IIDs
Above all, V7 always says what it does not know. A name read from the binary is a fact; a proc_405630 is an unnamed location. The symbol inventory counts them separately — on SETUP1.EXE: 147 names read against 1,182 synthesised locations. So you know, line by line, what you can lean on.
4. Types
A complete type model, with a lattice, propagated through the body, the signature and the conditions. Properties are typed by aligning Property Get with Property Let. The return type of a TypeLib member is read from the TypeLib — never guessed from its name.
5. What the binary does not contain is written down
Reference=, the nine compilation options, StartMode=: none of these are in any structure of the VB6 format. The old product inferred them — sometimes well, sometimes not. V7 leaves a reasoned note in their place, saying why the value is missing.
Less pretty. True.
6. The reference tables
Ten years of reverse engineering MSVBVM60.DLL and VB6.EXE, frozen into the product — none of it derived from third-party work:
| Table | Entries |
|---|---|
| P-code machine opcodes | 1,536 |
| Control properties (31 types) | 1,212 |
| VB6 intrinsic constants | 711 |
MSVBVM60 runtime exports | 635 |
| Control members | 602 |
| Control events | 46 |
They are neither read from a CSV at startup nor downloaded: they are compiled into the assembly, indexed, and queried in constant time.
The tools
| Call graph | three scales (document, object, procedure), callers / callees filters, isolated-node hiding, drill-down navigation |
| Control flow | blocks, edges, loop headers, back edges, unreachable blocks dashed — and five representations of the content: shape, IR, VB6, assembly, P-code |
| Object and TypeLib explorer | answers "where is this defined?" when the answer is outside the binary: third-party OCX members, VBRUN constants, reconstructed signatures, the owning library |
| Win32 API browser | prototypes, aliases, notes — to fill in what a Declare lost |
| Symbol inventory | read vs synthesised names, filtering, global rename (call sites in other modules follow) |
| Designer | the GUI section of every form, 1,212 control property definitions across 31 types |
| Hex view | with a "bytes → code" path back |
| Resources | inventory, extraction |
| Binary patching | writing into the analysed executable |
| Project reconstruction | .vbp, .frm, .bas, .cls with the headers VB6 requires to reopen them |
| Bookmarks, global search, program and library finders |
The measurement bench
V7 is continuously held against three sets of ground truth: a corpus of 148 binaries we compiled ourselves together with their 377 original source files, the VB6 Setup Toolkit (whose sources Microsoft publishes), and a witness binary whose code we own.
A defect that has not been held against its source is not a defect: it is a hypothesis.
3,849 automated checks run on every change.
And above them, the one criterion that actually decides whether an export is worth anything: does the reconstructed project recompile? All 149 bench projects are re-exported and passed to VB6.EXE /make. As of 5 September 2026: 119 recompile as-is, or 80%.
SETUP1.EXE is not among them — it stops on an Argument not optional at line 119 of frmSetup1.frm. It is our most public witness; hiding that would have lasted one afternoon.
Legacy vs V7, on the same binaries
The figures below are measured, not estimated: both products ran on the same files, and their output was checked against the original VB6 source. The "legacy" column is VBReFormer 6.4 — the last version that handles both witnesses end to end.
SETUP1.EXE — 286 KB, native, 16 components, 238 procedures in the source:
| VBReFormer legacy | VBReFormer V7 | |
|---|---|---|
| Procedures written to the export | 136 | 227 |
| Empty body where the source is not empty | 1 | 0 |
| Lines of code, all files | 7,464 | 8,119 |
| Opening the binary (format + location) | — | 0.05 s |
| Decompiling all 227 bodies | — | 0.64 s (2.8 ms per body) |
| From bytes to a complete project | — | 0.95 s |
A 9.35 MB binary — 165 components, 1,718 procedures in the source:
| VBReFormer legacy | VBReFormer V7 | |
|---|---|---|
| Open → project written | ≈ 88 s | 12 s |
| Procedures written to the export | 1,799 | 1,976 |
| Empty body where the source is not empty | 229 | 0 |
| Procedures whose structuring succeeds | — | 1,976 / 1,976 |
| Error markers in the rendered code | 1,129 | 0 |
Signatures rendered as As Unknow | 75,014 | 0 |
And the test that does not reduce to a counter: 24 procedures picked at random and read line by line against their original source. V7 is more faithful in 22 cases, the legacy in 2.
The two places the legacy still wins
A comparison that loses nowhere is a comparison that was never run. Here are ours:
- Isolated blocks. When a procedure contains blocks that no edge connects to the entry — targets of
On Error GoTo,GoSub,Resume— V7 sees them, feeds them to the control-flow graph, and does not write them into the source. On the witness: 1,541 blocks lost, across 232 procedures (12%). The legacy abandons nothing: its linear emission writes everything, badly structured but present. It is our widest open defect - Resources. 28 files written by the legacy against 8 by V7 (no
.ctxat all)
The legacy also still carries six desktop features with no V7 equivalent — project-wide text search, imported-library search, VB program search on disk, resource view, form image export, options screen.
None of the figures in this section is an estimate. The protocol, the scripts and the 24 procedures read one by one ship with the product.
What V7 refuses to do
A short list, and a deliberate one.
- No reconstructed
With. The compiler dissolves it; putting it back would mean inventing a structure the binary does not carry. Output is fully qualified (Frame1.Caption) - No plausible value. When a value cannot be rendered correctly, V7 writes
?. A credible, wrong number costs more than a visible marker - No invented name for something that has none.
proc_405630tells the truth; a pretty name would not
Availability
VBReFormer Online is in beta as of today — vb.decompiler.tools — and its free tier goes a long way: complete disassembly, complete P-code, structure, resources, designer, reconstructed .vbp, and the source as a real preview.
VBReFormer V7 (desktop) follows in the coming weeks. A desktop+cloud licence opens both.