Deeper Native Code Intelligence in VB Decompiler v26.2
We are proud to announce the release of VB Decompiler version 26.2. This is a technically dense update focused on deep improvements to Native Code decompilation accuracy: a three-tier Variant variable detection system, full read/write support for multidimensional SafeArray access, snapshot-based state isolation for conditional branches, a significantly expanded COM/IID heuristics database, and numerous engine-level fixes and refactors that make the decompiler more reliable on complex real-world binaries.
Three-Tier Variant Variable Detection in Native Code
One of the core challenges in decompiling VB6 Native Code is correctly identifying Variant variables on the stack during function calls. Variants are VB6's universal container type and can hold integers, strings, objects, or any other subtype at runtime. In compiled native code, they are passed as raw memory structures with no explicit type annotation, making accurate reconstruction exceptionally difficult.
Version 26.2 introduces a comprehensive three-tier detection pipeline that approaches the problem from multiple angles simultaneously:
- Heuristic detection, based on stack analysis. The decompiler inspects stack layout and calling conventions to infer where Variant structures are being constructed and passed, even in the absence of any other contextual information.
- Signature-based detection, using a pre-prepared property and method database. A curated database of known object properties and methods - covering the most frequently called COM interfaces in VB6 programs - allows the decompiler to recognize Variant parameters by matching the call target against known signatures.
- On-the-fly type determination by parsing the called object's interface via its IID. When the decompiler encounters a late-bound call and can resolve the Interface ID of the target object, it parses the object's interface description at decompilation time and uses the resulting type information to determine parameter types precisely.
Together, these three mechanisms dramatically reduce the number of cases where Variant parameters are left unresolved or misidentified, particularly in code that makes heavy use of COM automation.
Parsing of called function parameters during both early binding (resolved at compile time via TypeLib) and late binding (resolved at runtime via
IDispatch), as well as direct Win32 API calls, has also been made significantly more accurate and consistent.
Expanded Variant Subtype Parsing
Alongside the new detection tiers, the Variant type parser itself has been extended. Previously, the parser recognized only five subtypes with dedicated handling:
vbBoolean, vbSingle, vbDouble, vbCurrency, and vbDate. All other subtypes were treated generically and their types could only be inferred heuristically.
Version 26.2 adds dedicated parsing support for four additional subtypes:
vbInteger- 16-bit signed integer values.vbLong- 32-bit signed integer values.vbByte- unsigned 8-bit values.vbString- BSTR string values embedded in a Variant.vbObject- object references held inside a Variant.
TypeLibrary parsing and the object database construction pipeline have both been updated to account for these new subtypes. This allows large sections of code that were previously resolved only through guesswork to now be typed precisely and consistently, resulting in cleaner and more accurate decompiled output.
Support most cases of SafeArray: Multidimensional Array Read and Write
This is one of the most impactful decompilation improvements in version 26.2. VB6 stores most arrays internally as SafeArray structures - a COM-defined descriptor that carries element type, dimensionality, bounds, and a pointer to the underlying data. Correctly reconstructing array access from native code requires the decompiler to understand this structure in full: parse the descriptor, track the number of dimensions and their bounds, compute element addresses from multi-index expressions, and map all of that back to readable VB6 subscript notation.
Prior versions of VB Decompiler had only partial SafeArray awareness. The decompiler could recognize array creation and resizing operations - calls to
__vbaRedim, __vbaRedimPreserve, and similar VB runtime APIs - and emit the corresponding ReDim statements correctly. However, the actual reading and writing of array elements at runtime was not understood. The raw compiled code that accessed array memory - loading the SafeArray pointer, reading the dimension descriptors, computing the flat memory offset from multi-dimensional indices, and dereferencing the element - was left unresolved. The decompiler emitted it as a tangled sequence of register-level expressions, pointer arithmetic, and partially-evaluated stack operations that bore no resemblance to the original source.
The results were particularly destructive in methods that performed heavy array work. Array element accesses would be rendered as grotesque compound expressions that mixed raw memory offsets with partially recognized structure fields, all concatenated with equals signs, parentheses and index values in ways that had no syntactic validity in any language. Any meaningful reconstruction of the method's logic was effectively impossible from such output.
What Was Happening Before
To understand the severity of the problem, consider a fragment from the
Before output of a real-world decompilation of a method that works with a four-dimensional array. The following lines appeared in the output:
loc_00412DEE: If var_7C(152)(156) = var_7C(152)(160) Then
loc_00412DF8: If var_7C(152)(156) = var_7C(152)(160) = 4 Then
loc_00412E15: If (eax - var_7C(152)(156) = var_7C(152)(160)(44)) >= var_7C(152)(156) = var_7C(152)(160)(40) Then
loc_00412E62: var_20(24)*(edi - var_7C(152)(156) = var_7C(152)(160)(20)) = ...
loc_00412E68: var_20(24)*(edi - ...)+(ebx - ...)*var_7C(152)(156) = ... + var_BC
loc_00412E72: var_20(24)*(edi - ...)+(ebx - ...)*...+var_BC*... = ... + (eax - ...)
These lines represent what the decompiler produced when it encountered SafeArray element reads and writes without understanding the SafeArray structure. Each "expression" is actually a collapsed rendering of raw pointer arithmetic against the
SAFEARRAY and SAFEARRAYBOUND descriptor fields - the cElements and lLbound members of each dimension's bound record, together with the computed flat offset into the data buffer. The output was syntactically invalid, semantically meaningless, and completely unreadable.
The New SafeArray Engine
Version 26.2 introduces comprehensive SafeArray analysis that covers the full lifecycle of array operations in compiled VB6 Native Code:
- SafeArray descriptor parsing. The decompiler now fully parses the in-memory
SAFEARRAYstructure: element size (cbElements), dimension count (cDims), data pointer (pvData), and the per-dimensionSAFEARRAYBOUNDrecords containing element count and lower bound. In most cases this gives the decompiler a complete model of the array's shape at decompilation time. - Multidimensional index reconstruction. VB6 compiles multi-dimensional subscript access (
arr(i, j, k)) into a flat memory offset computed as a sequence of multiply-and-add operations against the dimension sizes and lower bounds. The decompiler now recognizes most of these patterns, reverses the offset calculation, and reconstructs the original index expressions for each dimension. - Read access reconstruction. Array element reads - where compiled code loads a value from the computed element address - are now in most cases recognized and emitted as proper subscript expressions:
var_20(i, j, k, l). - Write access reconstruction. Array element writes, including indexed assignment targets on the left-hand side of assignment statements, are handled symmetrically in most cases.
- VarPtr on array elements. Calls to
VarPtrwith an array element as the argument - used to obtain a raw pointer to a specific element, a common pattern in VB6 code that passes array data to API functions - are now correctly reconstructed asVarPtr(arr(i, j, ...))with proper index expressions. - Bounds-checking pattern recognition. VB6 compiled code inserts explicit bounds checks before each array access, comparing the requested index against the lower bound and element count stored in the
SAFEARRAYBOUNDrecord. The decompiler recognizes these generated check sequences and suppresses them from the output - they are compiler-inserted boilerplate, not original source logic, and their presence in prior output was responsible for much of the surrounding noise. - Variable type annotation. Variables identified as SafeArray references are now declared with the
SafeArraytype in the decompiled output, making the relationship between declarations and usage explicit.
The Result
The same method that previously produced dozens of lines of unreadable register-level noise is now decompiled to clean, immediately recognizable VB6 source. The critical section, which previously collapsed into the multi-line catastrophe shown above, is now rendered as:
Dim var_20 As SafeArray
loc_00412DE3: var_20 = global_160
loc_00412EA7: global_176 = VarPtr(var_20(1, 1, 1, 1))
loc_00412EB6: global_180 = VarPtr(global_108(3))
Three lines instead of over twenty. A four-dimensional array access expressed as
var_20(1, 1, 1, 1) instead of an arithmetic explosion of descriptor field offsets. The variable var_20 is correctly declared as Dim var_20 As SafeArray at the top of the procedure. Bounds-check boilerplate is gone. The method is readable.
This improvement affects most VB6 Native Code application that uses arrays in non-trivial ways - image processing code, numerical algorithms, data transformation routines, game logic, or any other domain where multidimensional arrays are a natural fit. The quality improvement on such methods is among the most visually dramatic of any change in recent versions.
Snapshot-Based State Isolation for Conditional Branches
A long-standing source of decompilation errors in complex VB6 Native Code methods was state pollution between If/Else branches. When the decompiler analyzed the
If branch of a conditional construct, any changes it made to its internal model - register values, CPU/FPU stack state, variable assignments - would carry over into the analysis of the Else branch, as if the two paths shared execution context. This led to incorrect variable assignments, phantom references, and malformed output, especially in user-defined class methods with complex logic.
Version 26.2 resolves this fundamentally. When the decompiler encounters an
If/Else construct, it now:
- Captures a full state snapshot at the branch point, including all register values, the CPU and FPU stack state, and the complete variable assignment map.
- Analyzes the
Ifbranch using a working copy of this snapshot. - Restores the saved snapshot when entering the
Elseblock, ensuring the two branches are analyzed in complete isolation from each other.
This change has a measurably positive impact on decompilation quality for any binary that contains non-trivial conditional logic - which, in practice, means the vast majority of real-world VB6 Native Code programs.
Rewritten Temporary and Unused Variable Analysis
The analysis and filtering of temporary and unused variables in decompiled VB6 Native Code has been completely rewritten from scratch. The previous implementation accumulated technical debt over time and produced inconsistent results on optimized or compiler-generated intermediate code patterns.
The new implementation applies a clean, structured analysis pass that correctly identifies variables that exist solely as compiler-generated temporaries and eliminates them from the output - without incorrectly removing variables that, while short-lived, carry semantic meaning. This results in significantly less noise in the decompiled output and more readable reconstructed source code.
Significantly Expanded Late-Binding IID Heuristics
VB6 programs that use late binding - that is, object variables declared as
Object rather than a specific type - leave almost no type information in the compiled binary. The decompiler's late-binding heuristic analyzer identifies the runtime interface being called by matching the Interface ID (IID) of the target against a known database.
This database has been massively expanded in version 26.2 and now covers the following interface families:
- Core COM/OLE interfaces -
ITypeLib,ICreateTypeInfo,ITypeInfo, and related type system objects. - Microsoft Office automation - interfaces from Word, Excel, Access, Outlook, and PowerPoint.
- Data access - ADO (ActiveX Data Objects) and DAO (Data Access Objects).
- XML and HTTP - MSXML, WinHTTP, and WinInet interfaces.
- Scripting and shell -
FileSystemObject(Scripting runtime), Shell objects, and WScript. - Web browser - Internet Explorer and WebBrowser control interfaces.
- Messaging - CDO and MAPI interfaces for email automation.
- Directory services - ADSI (Active Directory Service Interfaces).
- Scripting -
VBScript.RegExpand related objects.
Each interface entry in the database also carries its parent library GUID. If the corresponding TypeLib is registered in the Windows registry on the machine running VB Decompiler, the tool will automatically pull the full TypeLib information from it and use it to enhance decompilation of that interface's methods and properties - without any manual configuration required.
Reworked __vbaNew2 Object Creation Emulation
Object creation in VB6 Native Code is handled internally by the
__vbaNew2 function in msvbvm60.dll (and equivalent variants in other runtime versions). Decompilation of this pattern has been completely reworked. The new implementation more accurately reconstructs set object to the new ClassName and handles edge cases that the previous version would either skip or misrepresent in the output.
Redesigned __vbaPrintObj Decompilation
The
__vbaPrintObj runtime API, used internally by VB6 for Print method calls on various output objects, has been completely redesigned. The new implementation correctly identifies and distinguishes between the different print object types that can appear as the content of a print operation - such as strings, chars, tabs, and debug output - and represents each one accurately in the decompiled output.
SAR Instruction Emulation for Signed Division
The VB6 compiler frequently uses the
sar (shift arithmetic right) assembly instruction as an optimization for dividing signed integers by powers of two. For example, sar eax, 1 is equivalent to dividing eax by 2, and sar eax, 2 divides by 4. Version 26.2 adds full emulation of this instruction in the Native Code analysis engine. Code that previously appeared as a raw bit-shift operation is now correctly reconstructed as an integer division expression, matching the original source intent.
Improved Code Analyzer: Conditional Jump Pre-Detection
The code analyzer now performs a pre-pass over all lines that contain conditional jumps before the main analysis begins. When such a line is identified, the analyzer will no longer collapse its address even if that location contains temporary variables that would normally be candidates for elimination. This prevents a class of edge-case errors where important branch targets were incorrectly merged or removed during optimization, causing structural defects in the reconstructed control flow.
Refactored and Faster Code Analyzer and Optimizer
The code analyzer and optimizer engine has undergone significant internal refactoring. Beyond correctness improvements, the refactoring has made the post-processing pipeline - the stage that transforms raw decompiled output into clean, structured VB6 source - substantially faster. Users working with large binaries or batch-processing multiple files will notice a meaningful reduction in analysis time.
Global Variable Highlighting
Version 26.1 introduced click-to-highlight for local string variables, making all occurrences of a selected
str_XX variable visible at a glance. Version 26.2 extends this feature to global variables: clicking any global_XX variable in the code view will now highlight all its occurrences throughout the current procedure, making it easy to trace the flow of global state through complex methods.
String Reference Dialog: Over 32,000 Strings
The String Reference dialog has been updated to support binaries containing over 32,000 string references - a limit that was previously hard-coded and would cause truncation or display issues in very large programs. In addition, string searching and navigation are now fully supported within the dialog, allowing users to quickly locate specific strings in programs with large string tables.
Memory handling for string list operations has also been optimized, reducing memory consumption when working with large string tables.
Updated API Signatures: rtcGetSetting and rtcDeleteSetting
The signatures for the
rtcGetSetting and rtcDeleteSetting VB runtime API functions have been updated to correctly reflect their use of Variant type parameters. This correction ensures that calls to these functions - used for reading and deleting application settings stored in the registry - are decompiled with properly typed arguments rather than raw pointer expressions.
Completely Revised Spanish Help System
The Spanish-language help system has been completely rewritten and revised by a native speaker. The previous translation contained numerous inaccuracies and outdated sections that did not reflect the current feature set. The new translation covers the full help content with accurate, professional Spanish technical writing. Special thanks to Leonardo Donaire for his extensive work on this translation.
Bug Fixes
- AI Helper tab resize on maximize: When maximizing the VB Decompiler window to full screen, the AI Helper tab did not resize correctly. The tab and its contents now resize properly along with the rest of the window.
- String Reference dialog navigation (.NET): Attempting to navigate to a hidden compiler-generated function from the String Reference dialog in a .NET binary no longer causes an error.
- Trace window residual lines: When reopening the trace window after a previous session, lines of code from the prior trace would in some cases remain visible. The trace window now correctly clears its content on reopen.
- Missing End If in Else branch detection: The Else branch detection feature in the Native Code analyzer and optimizer failed to insert an
End Ifclosing block in certain rare code patterns. This has been corrected. - P-Code / Native Code cross-contamination: If a program compiled in P-Code was decompiled immediately after decompiling a program compiled in Native Code, the
thispointer was incorrectly added to the basic properties of the Global object in the P-Code result. The decompiler now correctly resets all state between sessions. - Missing Else construct before conditional jump: The
Elseconstruct was not being generated when a conditional jump appeared immediately before the Else block in VB6 Native Code. This structural omission has been fixed.
April 5, 2026
© Sergey Chubchenko, VB Decompiler main developer
Visual Basic, Visual Studio, and Windows are registered trademarks of Microsoft Corporation.