vb6decompiler 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {vb6decompiler-0.2.0/vb6decompiler.egg-info → vb6decompiler-0.2.1}/PKG-INFO +305 -279
  2. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/README.md +27 -1
  3. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/pyproject.toml +1 -1
  4. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/setup.cfg +4 -4
  5. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/tests/test_core.py +1 -1
  6. vb6decompiler-0.2.1/tests/test_decompiler_scripts.py +119 -0
  7. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1/vb6decompiler.egg-info}/PKG-INFO +305 -279
  8. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler.egg-info/SOURCES.txt +1 -0
  9. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/LICENSE +0 -0
  10. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/tests/test_blob.py +0 -0
  11. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/tests/test_cli.py +0 -0
  12. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/tests/test_datareport.py +0 -0
  13. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/tests/test_frm.py +0 -0
  14. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/tests/test_hygiene.py +0 -0
  15. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/tests/test_target.py +0 -0
  16. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler/__init__.py +0 -0
  17. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler/blob.py +0 -0
  18. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler/cli.py +0 -0
  19. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler/datareport.py +0 -0
  20. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler/frm.py +0 -0
  21. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler/literals.py +0 -0
  22. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler/mask.py +0 -0
  23. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler/pe.py +0 -0
  24. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler/procs.py +0 -0
  25. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler/sqlstitch.py +0 -0
  26. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler.egg-info/dependency_links.txt +0 -0
  27. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler.egg-info/entry_points.txt +0 -0
  28. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler.egg-info/requires.txt +0 -0
  29. {vb6decompiler-0.2.0 → vb6decompiler-0.2.1}/vb6decompiler.egg-info/top_level.txt +0 -0
@@ -1,279 +1,305 @@
1
- Metadata-Version: 2.4
2
- Name: vb6decompiler
3
- Version: 0.2.0
4
- Summary: Evidence-based recovery of native-compiled VB6 programs: procedures, literals, SQL, form blobs, DataReports
5
- Author: vb6decompiler authors
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/47Hunter47/vb6decompiler
8
- Keywords: vb6,reverse-engineering,decompiler,pe,sql-server
9
- Requires-Python: >=3.11
10
- Description-Content-Type: text/markdown
11
- License-File: LICENSE
12
- Provides-Extra: pe
13
- Requires-Dist: pefile>=2023.2.7; extra == "pe"
14
- Provides-Extra: asm
15
- Requires-Dist: capstone>=5; extra == "asm"
16
- Provides-Extra: dev
17
- Requires-Dist: pytest>=8; extra == "dev"
18
- Dynamic: license-file
19
-
20
- # vb6decompiler
21
-
22
- Recovering what a native-compiled VB6 program actually does, with evidence.
23
-
24
- Commercial VB6 decompilers stop at one of two walls: they give you forms without
25
- code, or code without the semantics that make it portable. This toolkit was built
26
- while recovering a large native-compiled VB6 application (111 MB, ~34,000
27
- procedures) whose source was lost. Every claim below is a measurement, and every
28
- measurement has a command that reproduces it.
29
-
30
- The point is not another disassembler. VB6 is not plain x86 at the level that
31
- matters: strings are length-prefixed `BSTR`s stored inside `.text`, procedures are
32
- entered through a fixed runtime prologue, most calls go through `MSVBVM60` vtable
33
- dispatch, forms are serialised into the executable as binary property blobs, and
34
- reports live in separate VB6 DLLs as `DataReport` blobs. Off-the-shelf tools read
35
- none of that. These modules do.
36
-
37
- Nothing here points at the program it was developed against: no target name, no
38
- host, no database name, no account, no credential, no absolute install path.
39
- **You supply every path.**
40
-
41
- ## What it does
42
-
43
- | Stage | Command | Evidence it produces |
44
- |---|---|---|
45
- | PE surface of an install | `vb6d pe <dir>` | imports, exports, VB6 runtime dependency, ActiveX/COM classification |
46
- | Procedure inventory | `vb6d procs <exe>` | object table: objects and method counts, entry points verified by prologue |
47
- | String / SQL harvest | `vb6d literals <exe or listing dir>` | `BSTR` (UTF-16LE) literals the ASCII scanners miss, with code addresses |
48
- | SQL reconstruction | `vb6d stitch <dir or listing>` | fragments joined into whole statements, with explicit "not provable statically" buckets |
49
- | Form design | `vb6d frm <dir>` | controls, geometry, `Visible`, control arrays, `SSTab` pages from `.frm` text |
50
- | Form geometry from binary | `vb6d blob <exe> --validate` | geometry for forms that have no `.frm`, calibrated against `.frm` ground truth |
51
- | DataReport layout | `vb6d datareport <dll or dir>` | report sections, controls, captions recovered from the runtime blobs |
52
- | Form icons from `.frx` | `scripts/frx_icon.py <frx dir> <out> [--validate <frm dir>]` | standalone `.ico` per form, extracted from the leading ICON record without the executable; coverage cross-checked against `.frm` Icon references |
53
- | Credential gate | `vb6d mask <dir>` | files rewritten in place: connection strings redacted, then an audit counting what is still live |
54
-
55
- ## You fill these in
56
-
57
- Every entry point takes a path; nothing is baked in. Real-target tests read five
58
- variables and skip when they are unset.
59
-
60
- | Variable | What you put in it |
61
- |---|---|
62
- | `VB6D_TARGET` | your VB6 executable, e.g. `D:/App/App.exe` or `/opt/app/App.exe` |
63
- | `VB6D_LISTINGS` | a directory of decompiler output: listing text carrying `'VA` address comments (the shape `docs/formats.md` §4 reads), plus `.frm` and `.frx` files |
64
- | `VB6D_FRM_DIR` | a directory holding `.frm` design text — ground truth for `--validate` |
65
- | `VB6D_REPORT_DIR` | the install directory holding the report DLLs |
66
- | `VB6D_SCRUB_DENYLIST` | optional: a private file, one name per line — names you refuse to publish. The hygiene test fails if any of them appears in this repository. |
67
-
68
- Set them for one command, or export them for a session:
69
-
70
- ```bash
71
- # bash / zsh
72
- export VB6D_TARGET="/opt/app/App.exe"
73
- export VB6D_LISTINGS="$HOME/work/decompile"
74
- export VB6D_FRM_DIR="$HOME/work/decompile"
75
- export VB6D_REPORT_DIR="/opt/app"
76
- ```
77
-
78
- ```powershell
79
- # PowerShell
80
- $env:VB6D_TARGET = "D:/App/App.exe"
81
- $env:VB6D_LISTINGS = "D:/work/decompile"
82
- $env:VB6D_FRM_DIR = "D:/work/decompile"
83
- $env:VB6D_REPORT_DIR = "D:/App"
84
- ```
85
-
86
- ## Why not an existing tool
87
-
88
- Measured on the same target (`docs/tool-matrix.md` has the full table, including
89
- how each number was obtained):
90
-
91
- * **Semi-VB-Decompiler** — the only free tool that recovers procedure *bodies*. It
92
- is single-threaded, overflows its `Integer` procedure counter above 32,767
93
- procedures (`Error 6`), needs a PE `LAA` bit patch to use more than 2 GB, and
94
- writes one event name for every body in a form. Bodies are pseudo-source
95
- (`var_64`, `arg_C`), not compilable code.
96
- * **VBReFormer** — forms and structure, expression bodies withheld without a
97
- licence; cannot even accept the 111 MB executable (transport limit).
98
- * **Ghidra / Rizin / IDA** — no VB6 knowledge: no `BSTR` literals, no procedure
99
- table, no form blobs, no `DataReport` blobs.
100
- * **Generic RE engines** — correct for C/C++/Rust/Go, blind to VB6 layout. One such
101
- engine found 6,875 functions of 34,159 (20 %, stopped at its own instruction cap)
102
- and 59 strings with zero SQL, because its string scanner does not read UTF-16LE
103
- literals.
104
-
105
- None of these is wrong; they are aimed at C/C++/Rust/Go binaries. The gap this
106
- repository fills is VB6-specific structure, and it fills it with reproducible
107
- counts rather than vendor percentages.
108
-
109
- ## Install
110
-
111
- ```
112
- git clone <this repo> && cd vb6decompiler
113
- python -m pip install -e .
114
- ```
115
-
116
- Python 3.11+. **Nothing outside the standard library is required** — every parser
117
- here is hand-written, which is the point (a library wrapper would hide the layout).
118
- `pefile` and `capstone` are optional and used only as a second opinion while
119
- developing:
120
-
121
- ```
122
- python -m pip install -e ".[pe,asm]"
123
- ```
124
-
125
- ## Coding agents
126
-
127
- The repository ships as a plugin for coding agents as well as a pip package, so
128
- an agent can drive `vb6d` without a tutorial:
129
-
130
- ```
131
- claude plugin marketplace add 47Hunter47/vb6decompiler
132
- claude plugin install vb6decompiler@vb6decompiler
133
- ```
134
-
135
- One skill lives at `.agents/skills/vb6decompiler/SKILL.md` — the location both
136
- Claude Code and Codex read — and says when the tool applies, how to parse its
137
- output and which traps to avoid; the plugin manifests point at that single copy.
138
- Agents editing the repository itself follow `AGENTS.md`.
139
-
140
- ## Quick start
141
-
142
- ```
143
- vb6d pe <install dir> # what is this install made of
144
- vb6d procs <app.exe> # object table: objects, method counts
145
- vb6d procs <app.exe> --validate # are the entry points real prologues
146
- vb6d procs <app.exe> --names <listing dir> # attach decompiler names/addresses
147
- vb6d literals <app.exe> # BSTR/UTF-16 literals with addresses
148
- vb6d literals <listing dir> --sql-only # literals a decompiler listing produced
149
- vb6d stitch <listing dir> # join fragments, classify what stayed open
150
- vb6d frm <listing dir> # .frm controls, geometry, arrays, SSTab
151
- vb6d blob <app.exe> --validate --frm-dir <frm dir>
152
- vb6d datareport <install dir> # report layouts from the report DLLs
153
- vb6d mask . # mask credentials in your artefacts
154
- vb6d mask . --dry-run # report only; exit 1 if anything is live
155
- ```
156
-
157
- Exit codes: 0 = completed, whatever it found; 1 = mask audit not clean; 2 =
158
- unusable input (one line on stderr, no traceback — set `VB6D_DEBUG=1` for the
159
- traceback when reporting a bug).
160
-
161
- What the reference build (111 MB, 802 forms) produced while this toolkit was
162
- developed, so you can tell at a glance whether your own numbers look sane:
163
-
164
- | Stage | Result |
165
- |---|---|
166
- | object table | 834 objects, 34,415 methods, 0.4 s |
167
- | literals from the image | 167,564 in 4.6 s (6,171 with a SQL verb) |
168
- | literals from a listing | 441,607 in 12,480 procedures |
169
- | statements | 10,691 starts → 8,745 closed in 4,109 procedures, 494 tables |
170
- | `.frm` design | 369 forms, 30,000 controls, 126 forms with control arrays |
171
- | form blobs | 801 blobs, 24,967/24,967 controls (100 %), 367/369 forms |
172
- | DataReports | 437 layouts (413 in report DLLs + 24 embedded in the executable) |
173
-
174
- A different program legitimately gives different numbers. The tests assert
175
- invariants and lower bounds, not these totals.
176
-
177
- ## Using it well
178
-
179
- Run the stages in pipeline order; each one tells you whether the next is worth
180
- running. `docs/pipeline.md` states what every stage may claim and what it must
181
- never be used to claim.
182
-
183
- 1. `vb6d pe <install dir>` first. It costs seconds and says what you have: which
184
- binaries carry VB6 code, which are redistributables, where the report DLLs are.
185
- 2. `vb6d procs`, then `--validate` before trusting anything downstream. Method
186
- counts are cheap; entry points proven by their runtime prologue are evidence.
187
- Addresses and names only exist once a decompiler listing is merged in with
188
- `--names`.
189
- 3. `vb6d literals` on the executable gives UTF-16 text with addresses; on a
190
- listing it gives procedure ownership, and that ownership is what makes
191
- `vb6d stitch` possible at all.
192
- 4. `vb6d stitch` reports what stayed open as open. The gap buckets are not noise:
193
- `OPEN_QUOTE` means a run-time value is missing, and no smarter regex closes
194
- it.
195
- 5. `vb6d frm` reads the design text; `vb6d blob --validate` calibrates the binary
196
- reader against it. Trust blob geometry for forms that have no `.frm` exactly
197
- as far as that calibration holds.
198
- 6. Count embedded reports when you run `vb6d datareport`. A scan limited to
199
- `rpt*.dll` misses 24 of the 437 layouts the reference build carries inside its
200
- own executable.
201
- 7. `vb6d mask . --dry-run` before sharing anything you produced. A non-zero exit
202
- means the artefacts are not publishable, not that you should retry quietly.
203
-
204
- Things that cost days to learn:
205
-
206
- * Procedure names do not exist in a native build. A decompiler's names are
207
- guesses and `.frm` event names repeat; treat every name as a hint and every
208
- prologue-verified address as the better fact.
209
- * If a string scanner reports zero SQL on a VB6 binary, ask whether it reads
210
- UTF-16LE at all. One engine reported 59 strings and zero SQL on a build that
211
- holds 167,564 `BSTR` literals, 6,171 of them with a SQL verb.
212
- * Compare shapes, not totals: literals per procedure, closed versus open
213
- statements, controls per form. Totals scale with the program; the tests assert
214
- invariants for the same reason.
215
- * When a free decompiler refuses a large executable, run `scripts/semi_laa.py`
216
- on a copy. The Large Address Aware bit is a property of the file, not of the
217
- tool's competence.
218
- * Three-state flags such as `Visible` and `Locked` have defaults that look like
219
- explicit values, and `docs/pitfalls.md` lists that class of trap with the
220
- measurement that proved each one.
221
-
222
- ## Honest limits
223
-
224
- * Procedure **names** are not recoverable from a native build; only addresses and
225
- the names a decompiler guessed.
226
- * Bodies recovered by a third-party decompiler are pseudo-source. Expression-level
227
- fidelity does not exist in any free tool measured here.
228
- * SQL that is built at run time (concatenated from variables) cannot be closed
229
- statically. `vb6d stitch` reports those gaps as gaps instead of guessing.
230
- * Geometry recovered from the binary is calibrated, not exact for forms that
231
- changed after the `.frm` in hand was written; `--validate` says so per form.
232
- * The toolkit is calibrated on native-compiled builds; a P-Code build is outside
233
- that calibration and was not tested here. If `vb6d procs --validate` reports no
234
- real prologues in your build, suspect a P-Code compile before suspecting the
235
- toolkit.
236
- * The parsers know the grammar of the reference build. Another program ships
237
- different controls, property sets and report sections, so expect more `unknown`
238
- lines than the reference numbers show. An un-calibrated construct is reported as
239
- unknown, never guessed (`docs/formats.md` §7), and no stage drops a file
240
- silently: failures are counted and named.
241
-
242
- ## Layout
243
-
244
- ```
245
- vb6decompiler/ the library (one module per stage, stdlib only except pefile/capstone)
246
- scripts/ standalone helpers (semi_laa.py: set the LAA bit on a copy;
247
- frx_icon.py: extract the leading ICON record of a .frx;
248
- win32_gui_probe.py: drive vendor GUI tools headlessly when the
249
- measurement only exists in a window)
250
- tests/ unit tests + target tests that skip unless VB6D_TARGET is set
251
- docs/ formats.md (structures), pitfalls.md (traps), tool-matrix.md,
252
- pipeline.md (stages and what each may claim), RULES.md
253
- .agents/skills/ the vb6decompiler skill — what a coding agent installs
254
- .claude-plugin/ Claude Code plugin + marketplace manifests (skills point at it)
255
- .codex-plugin/ Codex plugin manifest (same single skill copy)
256
- ```
257
-
258
- Run the tests:
259
-
260
- ```
261
- pip install -e ".[dev]"
262
- pytest -q # unit tests, no target needed
263
- VB6D_TARGET="C:/path/App.exe" pytest -q # + real-target invariants
264
- VB6D_LISTINGS="C:/path/decompile" pytest -q # + listing/stitching tests
265
- VB6D_FRM_DIR="C:/path/decompile" pytest -q # + blob calibration
266
- VB6D_REPORT_DIR="C:/path/install" pytest -q # + DataReport parsing
267
- ```
268
-
269
- `docs/formats.md` is the structure reference: every offset, every grammar, and the
270
- fields that look documented but are not what they claim (`lpMethods` in a native
271
- build, `lpGuiTable`). `docs/pitfalls.md` is the second most useful file: it lists
272
- the traps that cost days (double-`Integer` overflow, `OriginalFirstThunk` vs
273
- `FirstThunk`, CP1254 vs UTF-16, `Visible` defaults, blob property sorting) with the
274
- measurement that
275
- proved each one.
276
-
277
- ## Licence
278
-
279
- MIT. See `LICENSE`.
1
+ Metadata-Version: 2.4
2
+ Name: vb6decompiler
3
+ Version: 0.2.1
4
+ Summary: Evidence-based recovery of native-compiled VB6 programs: procedures, literals, SQL, form blobs, DataReports
5
+ Author: vb6decompiler authors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/47Hunter47/vb6decompiler
8
+ Keywords: vb6,reverse-engineering,decompiler,pe,sql-server
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Provides-Extra: pe
13
+ Requires-Dist: pefile>=2023.2.7; extra == "pe"
14
+ Provides-Extra: asm
15
+ Requires-Dist: capstone>=5; extra == "asm"
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=8; extra == "dev"
18
+ Dynamic: license-file
19
+
20
+ # vb6decompiler
21
+
22
+ Recovering what a native-compiled VB6 program actually does, with evidence.
23
+
24
+ Commercial VB6 decompilers stop at one of two walls: they give you forms without
25
+ code, or code without the semantics that make it portable. This toolkit was built
26
+ while recovering a large native-compiled VB6 application (111 MB, ~34,000
27
+ procedures) whose source was lost. Every claim below is a measurement, and every
28
+ measurement has a command that reproduces it.
29
+
30
+ The point is not another disassembler. VB6 is not plain x86 at the level that
31
+ matters: strings are length-prefixed `BSTR`s stored inside `.text`, procedures are
32
+ entered through a fixed runtime prologue, most calls go through `MSVBVM60` vtable
33
+ dispatch, forms are serialised into the executable as binary property blobs, and
34
+ reports live in separate VB6 DLLs as `DataReport` blobs. Off-the-shelf tools read
35
+ none of that. These modules do.
36
+
37
+ Nothing here points at the program it was developed against: no target name, no
38
+ host, no database name, no account, no credential, no absolute install path.
39
+ **You supply every path.**
40
+
41
+ ## What it does
42
+
43
+ | Stage | Command | Evidence it produces |
44
+ |---|---|---|
45
+ | PE surface of an install | `vb6d pe <dir>` | imports, exports, VB6 runtime dependency, ActiveX/COM classification |
46
+ | Procedure inventory | `vb6d procs <exe>` | object table: objects and method counts, entry points verified by prologue |
47
+ | String / SQL harvest | `vb6d literals <exe or listing dir>` | `BSTR` (UTF-16LE) literals the ASCII scanners miss, with code addresses |
48
+ | SQL reconstruction | `vb6d stitch <dir or listing>` | fragments joined into whole statements, with explicit "not provable statically" buckets |
49
+ | Form design | `vb6d frm <dir>` | controls, geometry, `Visible`, control arrays, `SSTab` pages from `.frm` text |
50
+ | Form geometry from binary | `vb6d blob <exe> --validate` | geometry for forms that have no `.frm`, calibrated against `.frm` ground truth |
51
+ | DataReport layout | `vb6d datareport <dll or dir>` | report sections, controls, captions recovered from the runtime blobs |
52
+ | Form icons from `.frx` | `scripts/frx_icon.py <frx dir> <out> [--validate <frm dir>]` | standalone `.ico` per form, extracted from the leading ICON record without the executable; coverage cross-checked against `.frm` Icon references |
53
+ | Second-opinion decompiler run | `scripts/ghidra_bridge.py --ghidra <dir> --bin <exe> --targets <csv> --out <dir>` | one pseudo-C file per procedure: our bounds and names, engine ceilings as flags, the JDK requirement read from the install before anything is imported |
54
+ | Literal visibility check | `scripts/literal_visibility.py --corpus <dir> --literals <csv> --out <csv>` | measured visible / catalogued per procedure — an address token (`DAT_`, `PTR_PTR_`) **or** inlined text counts, with a denominator instead of a hunch |
55
+ | Pseudo-C inventories | `scripts/pseudoc_inventory.py --corpus <dir> --out-dir <dir>` | literal order, indirect-call object+slot, ordered calls with line numbers, branch conditions, unnamed-global census |
56
+ | Credential gate | `vb6d mask <dir>` | files rewritten in place: connection strings redacted, then an audit counting what is still live |
57
+
58
+ The `scripts/` helpers are in the repository, not in the pip install — take a checkout
59
+ for those. The three Ghidra-side helpers additionally expect a **local** Ghidra install
60
+ and `pip install pyghidra`; the library and the `vb6d` CLI stay standard-library only.
61
+
62
+ ## You fill these in
63
+
64
+ Every entry point takes a path; nothing is baked in. Real-target tests read five
65
+ variables and skip when they are unset.
66
+
67
+ | Variable | What you put in it |
68
+ |---|---|
69
+ | `VB6D_TARGET` | your VB6 executable, e.g. `D:/App/App.exe` or `/opt/app/App.exe` |
70
+ | `VB6D_LISTINGS` | a directory of decompiler output: listing text carrying `'VA` address comments (the shape `docs/formats.md` §4 reads), plus `.frm` and `.frx` files |
71
+ | `VB6D_FRM_DIR` | a directory holding `.frm` design text — ground truth for `--validate` |
72
+ | `VB6D_REPORT_DIR` | the install directory holding the report DLLs |
73
+ | `VB6D_SCRUB_DENYLIST` | optional: a private file, one name per line — names you refuse to publish. The hygiene test fails if any of them appears in this repository. |
74
+
75
+ Set them for one command, or export them for a session:
76
+
77
+ ```bash
78
+ # bash / zsh
79
+ export VB6D_TARGET="/opt/app/App.exe"
80
+ export VB6D_LISTINGS="$HOME/work/decompile"
81
+ export VB6D_FRM_DIR="$HOME/work/decompile"
82
+ export VB6D_REPORT_DIR="/opt/app"
83
+ ```
84
+
85
+ ```powershell
86
+ # PowerShell
87
+ $env:VB6D_TARGET = "D:/App/App.exe"
88
+ $env:VB6D_LISTINGS = "D:/work/decompile"
89
+ $env:VB6D_FRM_DIR = "D:/work/decompile"
90
+ $env:VB6D_REPORT_DIR = "D:/App"
91
+ ```
92
+
93
+ ## Why not an existing tool
94
+
95
+ Measured on the same target (`docs/tool-matrix.md` has the full table, including
96
+ how each number was obtained):
97
+
98
+ * **Semi-VB-Decompiler** — the only free tool that recovers procedure *bodies*. It
99
+ is single-threaded, overflows its `Integer` procedure counter above 32,767
100
+ procedures (`Error 6`), needs a PE `LAA` bit patch to use more than 2 GB, and
101
+ writes one event name for every body in a form. Bodies are pseudo-source
102
+ (`var_64`, `arg_C`), not compilable code.
103
+ * **VBReFormer** — forms and structure, expression bodies withheld without a
104
+ licence; cannot even accept the 111 MB executable (transport limit).
105
+ * **Ghidra / Rizin / IDA** — no VB6 knowledge: no `BSTR` literals, no procedure
106
+ table, no form blobs, no `DataReport` blobs.
107
+ * **Generic RE engines** — correct for C/C++/Rust/Go, blind to VB6 layout. One such
108
+ engine found 6,875 functions of 34,159 (20 %, stopped at its own instruction cap)
109
+ and 59 strings with zero SQL, because its string scanner does not read UTF-16LE
110
+ literals.
111
+
112
+ None of these is wrong; they are aimed at C/C++/Rust/Go binaries. The gap this
113
+ repository fills is VB6-specific structure, and it fills it with reproducible
114
+ counts rather than vendor percentages.
115
+
116
+ ## Install
117
+
118
+ ```
119
+ pip install vb6decompiler
120
+ ```
121
+
122
+ Or work from a checkout:
123
+
124
+ ```
125
+ git clone <this repo> && cd vb6decompiler
126
+ python -m pip install -e .
127
+ ```
128
+
129
+ Python 3.11+. **Nothing outside the standard library is required** — every parser
130
+ here is hand-written, which is the point (a library wrapper would hide the layout).
131
+ `pefile` and `capstone` are optional and used only as a second opinion while
132
+ developing:
133
+
134
+ ```
135
+ python -m pip install -e ".[pe,asm]"
136
+ ```
137
+
138
+ ## Coding agents
139
+
140
+ The repository ships as a plugin for coding agents as well as a pip package, so
141
+ an agent can drive `vb6d` without a tutorial:
142
+
143
+ ```
144
+ claude plugin marketplace add 47Hunter47/vb6decompiler
145
+ claude plugin install vb6decompiler@vb6decompiler
146
+ ```
147
+
148
+ One skill lives at `.agents/skills/vb6decompiler/SKILL.md` — the location both
149
+ Claude Code and Codex read — and says when the tool applies, how to parse its
150
+ output and which traps to avoid; the plugin manifests point at that single copy.
151
+ Agents editing the repository itself follow `AGENTS.md`.
152
+
153
+ ## Quick start
154
+
155
+ ```
156
+ vb6d pe <install dir> # what is this install made of
157
+ vb6d procs <app.exe> # object table: objects, method counts
158
+ vb6d procs <app.exe> --validate # are the entry points real prologues
159
+ vb6d procs <app.exe> --names <listing dir> # attach decompiler names/addresses
160
+ vb6d literals <app.exe> # BSTR/UTF-16 literals with addresses
161
+ vb6d literals <listing dir> --sql-only # literals a decompiler listing produced
162
+ vb6d stitch <listing dir> # join fragments, classify what stayed open
163
+ vb6d frm <listing dir> # .frm controls, geometry, arrays, SSTab
164
+ vb6d blob <app.exe> --validate --frm-dir <frm dir>
165
+ vb6d datareport <install dir> # report layouts from the report DLLs
166
+ vb6d mask . # mask credentials in your artefacts
167
+ vb6d mask . --dry-run # report only; exit 1 if anything is live
168
+ ```
169
+
170
+ Exit codes: 0 = completed, whatever it found; 1 = mask audit not clean; 2 =
171
+ unusable input (one line on stderr, no traceback — set `VB6D_DEBUG=1` for the
172
+ traceback when reporting a bug).
173
+
174
+ What the reference build (111 MB, 802 forms) produced while this toolkit was
175
+ developed, so you can tell at a glance whether your own numbers look sane:
176
+
177
+ | Stage | Result |
178
+ |---|---|
179
+ | object table | 834 objects, 34,415 methods, 0.4 s |
180
+ | literals from the image | 167,564 in 4.6 s (6,171 with a SQL verb) |
181
+ | literals from a listing | 441,607 in 12,480 procedures |
182
+ | statements | 10,691 starts → 8,745 closed in 4,109 procedures, 494 tables |
183
+ | `.frm` design | 369 forms, 30,000 controls, 126 forms with control arrays |
184
+ | form blobs | 801 blobs, 24,967/24,967 controls (100 %), 367/369 forms |
185
+ | DataReports | 437 layouts (413 in report DLLs + 24 embedded in the executable) |
186
+
187
+ A different program legitimately gives different numbers. The tests assert
188
+ invariants and lower bounds, not these totals.
189
+
190
+ ## Using it well
191
+
192
+ Run the stages in pipeline order; each one tells you whether the next is worth
193
+ running. `docs/pipeline.md` states what every stage may claim and what it must
194
+ never be used to claim.
195
+
196
+ 1. `vb6d pe <install dir>` first. It costs seconds and says what you have: which
197
+ binaries carry VB6 code, which are redistributables, where the report DLLs are.
198
+ 2. `vb6d procs`, then `--validate` before trusting anything downstream. Method
199
+ counts are cheap; entry points proven by their runtime prologue are evidence.
200
+ Addresses and names only exist once a decompiler listing is merged in with
201
+ `--names`.
202
+ 3. `vb6d literals` on the executable gives UTF-16 text with addresses; on a
203
+ listing it gives procedure ownership, and that ownership is what makes
204
+ `vb6d stitch` possible at all.
205
+ 4. `vb6d stitch` reports what stayed open as open. The gap buckets are not noise:
206
+ `OPEN_QUOTE` means a run-time value is missing, and no smarter regex closes
207
+ it.
208
+ 5. `vb6d frm` reads the design text; `vb6d blob --validate` calibrates the binary
209
+ reader against it. Trust blob geometry for forms that have no `.frm` exactly
210
+ as far as that calibration holds.
211
+ 6. Count embedded reports when you run `vb6d datareport`. A scan limited to
212
+ `rpt*.dll` misses 24 of the 437 layouts the reference build carries inside its
213
+ own executable.
214
+ 7. `vb6d mask . --dry-run` before sharing anything you produced. A non-zero exit
215
+ means the artefacts are not publishable, not that you should retry quietly.
216
+
217
+ Things that cost days to learn:
218
+
219
+ * Procedure names do not exist in a native build. A decompiler's names are
220
+ guesses and `.frm` event names repeat; treat every name as a hint and every
221
+ prologue-verified address as the better fact.
222
+ * If a string scanner reports zero SQL on a VB6 binary, ask whether it reads
223
+ UTF-16LE at all. One engine reported 59 strings and zero SQL on a build that
224
+ holds 167,564 `BSTR` literals, 6,171 of them with a SQL verb.
225
+ * Compare shapes, not totals: literals per procedure, closed versus open
226
+ statements, controls per form. Totals scale with the program; the tests assert
227
+ invariants for the same reason.
228
+ * When a free decompiler refuses a large executable, run `scripts/semi_laa.py`
229
+ on a copy. The Large Address Aware bit is a property of the file, not of the
230
+ tool's competence.
231
+ * Three-state flags such as `Visible` and `Locked` have defaults that look like
232
+ explicit values, and `docs/pitfalls.md` lists that class of trap with the
233
+ measurement that proved each one.
234
+
235
+ ## Honest limits
236
+
237
+ * Procedure **names** are not recoverable from a native build; only addresses and
238
+ the names a decompiler guessed.
239
+ * Bodies recovered by a third-party decompiler are pseudo-source. Expression-level
240
+ fidelity does not exist in any free tool measured here.
241
+ * SQL that is built at run time (concatenated from variables) cannot be closed
242
+ statically. `vb6d stitch` reports those gaps as gaps instead of guessing.
243
+ * An indirect call through an object global (`(*DAT_x)[slot]`) has no statically
244
+ resolvable target either: the bytes stored at such a global are 0 or a small
245
+ non-address token, never a vtable pointer, and a VB6 symbol record carries no
246
+ name for the global. `scripts/pseudoc_inventory.py` counts them and ranks them
247
+ for a run-time trace; nothing in this repo closes them statically.
248
+ * Geometry recovered from the binary is calibrated, not exact for forms that
249
+ changed after the `.frm` in hand was written; `--validate` says so per form.
250
+ * The toolkit is calibrated on native-compiled builds; a P-Code build is outside
251
+ that calibration and was not tested here. If `vb6d procs --validate` reports no
252
+ real prologues in your build, suspect a P-Code compile before suspecting the
253
+ toolkit.
254
+ * The parsers know the grammar of the reference build. Another program ships
255
+ different controls, property sets and report sections, so expect more `unknown`
256
+ lines than the reference numbers show. An un-calibrated construct is reported as
257
+ unknown, never guessed (`docs/formats.md` §7), and no stage drops a file
258
+ silently: failures are counted and named.
259
+
260
+ ## Layout
261
+
262
+ ```
263
+ vb6decompiler/ the library (one module per stage, stdlib only except pefile/capstone)
264
+ scripts/ standalone helpers (semi_laa.py: set the LAA bit on a copy;
265
+ frx_icon.py: extract the leading ICON record of a .frx;
266
+ win32_gui_probe.py: drive vendor GUI tools headlessly when the
267
+ measurement only exists in a window; ghidra_bridge.py +
268
+ literal_visibility.py + pseudoc_inventory.py: run a generic
269
+ decompiler over a validated procedure list, measure what its
270
+ output actually shows, and harvest order/slot/call inventories)
271
+ tests/ unit tests + target tests that skip unless VB6D_TARGET is set
272
+ docs/ formats.md (structures), pitfalls.md (traps), tool-matrix.md,
273
+ pipeline.md (stages and what each may claim), RULES.md
274
+ .agents/skills/ the vb6decompiler skill — what a coding agent installs
275
+ .claude-plugin/ Claude Code plugin + marketplace manifests (skills point at it)
276
+ .codex-plugin/ Codex plugin manifest (same single skill copy)
277
+ ```
278
+
279
+ Run the tests:
280
+
281
+ ```
282
+ pip install -e ".[dev]"
283
+ pytest -q # unit tests, no target needed
284
+ VB6D_TARGET="C:/path/App.exe" pytest -q # + real-target invariants
285
+ VB6D_LISTINGS="C:/path/decompile" pytest -q # + listing/stitching tests
286
+ VB6D_FRM_DIR="C:/path/decompile" pytest -q # + blob calibration
287
+ VB6D_REPORT_DIR="C:/path/install" pytest -q # + DataReport parsing
288
+ VB6D_SCRUB_DENYLIST="C:/path/names.txt" pytest -q # + scan for your target own names
289
+ ```
290
+
291
+ The hygiene test needs no target: it looks for private addresses, home paths, e-mail and
292
+ UNC shares, and it adds a name scan only when you point `VB6D_SCRUB_DENYLIST` at a list
293
+ kept **outside** the repository (the names are yours, they do not belong in public).
294
+
295
+ `docs/formats.md` is the structure reference: every offset, every grammar, and the
296
+ fields that look documented but are not what they claim (`lpMethods` in a native
297
+ build, `lpGuiTable`). `docs/pitfalls.md` is the second most useful file: it lists
298
+ the traps that cost days (double-`Integer` overflow, `OriginalFirstThunk` vs
299
+ `FirstThunk`, CP1254 vs UTF-16, `Visible` defaults, blob property sorting) with the
300
+ measurement that
301
+ proved each one.
302
+
303
+ ## Licence
304
+
305
+ MIT. See `LICENSE`.