Skip to content

Commit 3bef984

Browse files
committed
Update README.md
1 parent c8056b1 commit 3bef984

1 file changed

Lines changed: 244 additions & 42 deletions

File tree

README.md

Lines changed: 244 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# What is QScripts?
22

3-
QScripts is productivity tool and an alternative to IDA's "Recent scripts" (Alt-F9) and "Execute Scripts" (Shift-F2) facilities. QScripts allows you to develop and run any supported scripting language (\*.py; \*.idc, etc.) from the comfort of your own favorite text editor as soon as you save the active script, the trigger file or any of its dependencies.
3+
QScripts is productivity tool and an alternative to IDA's "Recent scripts" (Alt-F9) and "Execute Scripts" (Shift-F2) facilities. QScripts allows you to develop and run any supported scripting language (\*.py; \*.idc, etc.) from the comfort of your own favorite text editor as soon as you save the active script, the trigger file or any of its dependencies. QScripts also supports hot-reloading of native plugins (loaders, processor modules, and plugins) using trigger files, enabling rapid development of compiled IDA addons.
44

55
![Quick introduction](docs/_resources/qscripts-vid-1.gif)
66

@@ -12,7 +12,7 @@ Video tutorials on the [AllThingsIDA](https://www.youtube.com/@allthingsida) You
1212

1313
# Usage
1414

15-
Invoke QScripts from the plugins menu, press Ctrl-3 or its default hotkey Alt-Shift-F9.
15+
Invoke QScripts from the plugins menu, or its default hotkey Alt-Shift-F9.
1616
When it runs, the scripts list might be empty. Just press `Ins` and select a script to add, or press `Del` to delete a script from the list.
1717
QScripts shares the same scripts list as IDA's `Recent Scripts` window.
1818

@@ -22,33 +22,64 @@ An active script will then be monitored for changes. If you modify the script in
2222

2323
To deactivate the script monitor, just press `Ctrl-D` or right-click and choose `Deactivate script monitor` from the QScripts window. When an active script becomes inactive, it will be shown in *italics*.
2424

25-
There are few options that can be configured in QScripts. Just press `Ctrl+E` or right-click and select `Options`:
25+
## Keyboard shortcuts
26+
27+
Inside QScripts window:
28+
* `Alt-Shift-F9`: Open QScripts window
29+
* `ENTER` or double-click: Activate and execute selected script
30+
* `Shift-Enter`: Execute selected script without activating it
31+
* `Ins`: Add a new script to the list
32+
* `Del`: Remove a script from the list
33+
* `Ctrl-E`: Open options dialog
34+
* `Ctrl-D`: Deactivate script monitor
35+
36+
From anywhere in IDA:
37+
* `Alt-Shift-X`: Re-execute the last active script or notebook cell
38+
39+
## Configuration options
40+
41+
Press `Ctrl+E` or right-click and select `Options` to configure QScripts:
2642

2743
* Clear message window before execution: clear the message log before re-running the script. Very handy if you to have a fresh output log each time.
2844
* Show file name when execution: display the name of the file that is automatically executed
2945
* Execute the unload script function: A special function, if defined in the global scope (usually by your active script), called `__quick_unload_script` will be invoked before reloading the script. This gives your script a chance to do some cleanup (for example to unregister some hotkeys)
3046
* Script monitor interval: controls the refresh rate of the script change monitor. Ideally 500ms is a good amount of time to pick up script changes.
3147
* Allow QScripts execution to be undo-able: The executed script's side effects can be reverted with IDA's Undo.
3248

33-
## Executing a script without activating it
34-
35-
It is possible to execute a script from QScripts without having to activate it. Just press `Shift-Enter` on a script and it will be executed (disregarding if there's an active script or not).
36-
37-
## Managing Dependencies in QScripts
49+
# Managing Dependencies in QScripts
3850

3951
QScripts offers a feature that allows automatic re-execution of the active script when any of its dependent scripts, undergo modifications.
4052

41-
### Setting Up Automatic Dependencies
53+
## Setting Up Automatic Dependencies
4254

4355
To leverage the automatic dependency tracking feature, create a file named identically to your active script, appending `.deps.qscripts` to its name. This file should contain paths to dependent scripts, along with any necessary reload directives.
4456

45-
Optionally, you can place the `.deps.qscripts` file within a `.qscripts` subfolder, located alongside your active script.
57+
Alternatively, you can place a `.deps` file (without the `.qscripts` suffix) within a `.qscripts` subfolder, located alongside your active script.
58+
59+
**Example locations for `script.py`:**
60+
* `script.py.deps.qscripts` (same directory)
61+
* `.qscripts/script.py.deps.qscripts` (local folder)
62+
* `.qscripts/script.py.deps` (local folder, alternate name)
63+
64+
## Dependency Index File Syntax
65+
66+
The dependency index file supports comments and directives:
67+
68+
```txt
69+
# Python-style comments
70+
; Semicolon comments
71+
// C-style comments
72+
73+
/reload import importlib; import $basename$; importlib.reload($basename$);
74+
dependency1.py
75+
dependency2.py
76+
```
4677

47-
### Integrating Python Scripts
78+
## Integrating Python Scripts
4879

4980
For projects involving Python, QScripts can automatically [reload](https://docs.python.org/3/library/importlib.html#importlib.reload) any changed dependent Python scripts. Include a `/reload` directive in your `.deps.qscripts` file, followed by the appropriate Python reload syntax.
5081

51-
#### Example `.deps.qscripts` file for `t1.py`:
82+
### Example `.deps.qscripts` file for `t1.py`:
5283

5384
```txt
5485
/reload import importlib; import $basename$; importlib.reload($basename$);
@@ -70,23 +101,115 @@ See also:
70101
* [Simple dependency example](test_scripts/dependency-test/README.md)
71102
* [Package dependency example](test_scripts/pkg-dependency/README.md)
72103

73-
### Special variables in the dependency index file
104+
## Directive Reference
105+
106+
Directives must appear at the beginning of a line and start with `/`. Arguments follow the directive name.
107+
108+
### `/reload <code>`
109+
110+
Executes the specified code snippet when a dependency is modified, before re-executing the main script. The code is executed in the context of the dependency's language interpreter.
111+
112+
```txt
113+
/reload import importlib; import $basename$; importlib.reload($basename$);
114+
```
115+
116+
### `/pkgbase <path>`
117+
118+
Specifies a package base directory for Python package dependencies. This path is used in conjunction with `$pkgmodname$` and `$pkgparentmodname$` variables.
119+
120+
```txt
121+
/pkgbase C:\projects\mypackage
122+
```
123+
124+
### `/triggerfile [/keep] <filepath>`
125+
126+
Configures QScripts to execute the script when the specified trigger file is created or modified, rather than when the script itself changes. This is particularly useful for hot-reloading compiled native plugins.
127+
128+
Options:
129+
* `/keep`: Preserve the trigger file after execution (default behavior is to delete it)
130+
131+
```txt
132+
/triggerfile /keep C:\temp\build_done.flag
133+
```
134+
135+
### `/notebook [<title>]`
136+
137+
Enables notebook mode where a directory of scripts is treated as a collection of cells. When any file matching the cell pattern is saved, that cell is executed.
138+
139+
```txt
140+
/notebook My Analysis Notebook
141+
```
142+
143+
### `/notebook.cells_re <regex>`
144+
145+
Specifies a regular expression pattern to identify notebook cell files. The default pattern is `\d{4}.*\.py$`.
146+
147+
```txt
148+
/notebook.cells_re ^\d{4}_.+\.py$
149+
```
150+
151+
### `/notebook.activate <action>`
152+
153+
Controls the behavior when the notebook is activated:
154+
* `exec_none`: Display the notebook title but do not execute any scripts
155+
* `exec_main`: Execute the main script file
156+
* `exec_all`: Execute all notebook cells in order
157+
158+
```txt
159+
/notebook.activate exec_all
160+
```
161+
162+
## Special Variables
74163

75-
* `$basename$`: This variable is expanded to the base name of the current dependency line
76-
* `$env:EnvVariableName$`: `EnvVariableName` is expanded to its environment variable value if it exists or left unexpanded otherwise
77-
* `$pkgbase$`: Specify a package base directory. Can be used as part of a dependency file path.
78-
* `$pkgparentmodname$` and `$pkgmodname$`: These are mainly used inside the `reload` directive. They help with proper [package dependency](test_scripts/pkg-dependency/README.md) reloading.
79-
* `$ext$`: This resolves to the plugin suffix and extension ("64.dll", ".so", "64.dylib", etc.). See the trigger native deps files for reference.
164+
Variables are expanded when encountered in file paths or reload directives. Use the syntax `$variable$`.
80165

81-
## Using QScripts like a Jupyter notebook
166+
* `$basename$`: The base name (without extension) of the current dependency file
167+
* `$env:VariableName$`: The value of the environment variable `VariableName`
168+
* `$pkgbase$`: The package base directory (set via `/pkgbase`)
169+
* `$pkgmodname$`: The module name derived from the dependency file path relative to `$pkgbase$`, with path separators replaced by dots (e.g., `pkg.submodule.file`)
170+
* `$pkgparentmodname$`: The parent module name (e.g., `pkg.submodule`)
171+
* `$ext$`: The platform-specific plugin extension (e.g., `64.dll`, `.so`, `64.dylib`)
82172

83-
It is possible to use QScripts as if you were working in a regular Jupiter notebook. Your `.deps.qscripts` file should have the `/notebook` keyword. This allows you to monitor a folder, where each file in that folder is considered a cell in the notebook. When you save a file, the last saved cell will be re-executed.
173+
### Examples
174+
175+
```txt
176+
# Reload a Python module
177+
/reload import importlib; import $basename$; importlib.reload($basename$);
178+
179+
# Use environment variable in path
180+
$env:SCRIPTS_DIR$/helper.py
181+
182+
# Package reloading
183+
/pkgbase C:\myproject\src
184+
/reload import importlib; import $pkgmodname$; importlib.reload($pkgmodname$);
185+
src/utils/helper.py
186+
```
187+
188+
# Using QScripts like a Jupyter notebook
189+
190+
QScripts can monitor a directory of script files as if they were notebook cells. When you save any file matching the cell pattern, that cell is executed.
191+
192+
**Example notebook configuration for `notebook.py.deps.qscripts`:**
193+
194+
```txt
195+
/notebook Data Analysis Notebook
196+
/notebook.cells_re ^\d{4}_.+\.py$
197+
/notebook.activate exec_none
198+
/reload import importlib; import $basename$; importlib.reload($basename$);
199+
shared_utils.py
200+
```
201+
202+
With this configuration:
203+
* Files like `0010_load_data.py`, `0020_process.py`, `0030_visualize.py` are recognized as cells
204+
* Saving any cell executes only that cell
205+
* The notebook title is displayed when activated
206+
* Changes to `shared_utils.py` cause the last-executed cell to re-run
84207

85208
See also:
86209

87210
* [Notebooks dependency example](test_scripts/notebooks/README.md)
88211

89-
## Using QScripts with trigger files
212+
# Using QScripts with trigger files
90213

91214
Sometimes you don't want to trigger QScripts when your scripts are saved, instead you want your own trigger condition.
92215
One way to achieve a custom trigger is by using the `/triggerfile` directive:
@@ -100,38 +223,80 @@ dep1.py
100223

101224
This tells QScripts to wait until the trigger file `createme.tmp` is created (or modified) before executing your script. Now, any time you want to execute the active script, just create (or modify) the trigger file.
102225

103-
104-
You may pass the `/keep` option so QScripts does not delete your trigger file, for example:
226+
You may pass the `/keep` option so QScripts does not delete your trigger file:
105227

106228
```
107229
/triggerfile /keep dont_del_me.info
108230
```
109231

110-
## Using QScripts programmatically
232+
# Using QScripts programmatically
111233

112-
It is possible to invoke QScripts from a script. For instance, in IDAPython, you can execute the last selected script with:
234+
QScripts can be controlled programmatically from scripts or other plugins.
235+
236+
## Plugin Arguments
113237

114238
```python
115-
load_and_run_plugin("qscripts", 1);
239+
# Open QScripts window
240+
idaapi.load_and_run_plugin("qscripts", 0)
241+
242+
# Execute the last selected script
243+
idaapi.load_and_run_plugin("qscripts", 1)
244+
245+
# Activate the script monitor
246+
idaapi.load_and_run_plugin("qscripts", 2)
247+
248+
# Deactivate the script monitor
249+
idaapi.load_and_run_plugin("qscripts", 3)
116250
```
117251

118-
(note the run argument `1`)
252+
## Action IDs
253+
254+
QScripts registers the following actions that can be invoked programmatically:
255+
256+
* `qscripts:deactivatemonitor` - Deactivate the script monitor
257+
* `qscripts:execselscript` - Execute the selected script without activating it
258+
* `qscripts:execscriptwithundo` - Re-execute the last active script or notebook cell
259+
* `qscripts:executenotebook` - Execute all cells in the active notebook
119260

120-
If the script monitor is deactivated, you can programmatically activate it by running the plugin with argument `2`. To deactivate again, use run argument `3`.
261+
```python
262+
# Re-execute the active script
263+
idaapi.process_ui_action("qscripts:execscriptwithundo")
264+
265+
# Execute all notebook cells
266+
idaapi.process_ui_action("qscripts:executenotebook")
267+
```
121268

122-
## Using QScripts with compiled code
269+
# Hot-Reloading Native Plugins
123270

124-
QScripts is not designed to work with compiled code, however using a combination of tricks, we can use QScripts for such cases:
271+
QScripts supports hot-reloading of compiled native plugins (IDA plugins, loaders, and processor modules) using trigger files. This enables rapid iterative development of native IDA addons without restarting IDA.
125272

126273
![Compiled code](docs/_resources/trigger_native.gif)
127274

128-
What you just saw was the `hello` sample from the IDA SDK. This plugin has the `PLUGIN_UNL` flag. This flag tells IDA to unload the plugin after each invocation.
129-
We can then use the trigger files option and specify the compiled binary path as the trigger file. Additionally, we need to write a simple script that loads and runs that newly compiled plugin in IDA.
275+
## Requirements
130276

131-
First, let's start with the script that we need to activate and run:
277+
The native plugin must be designed to support unloading:
278+
* **Plugins**: Set the `PLUGIN_UNL` flag to allow IDA to unload the plugin after each invocation
279+
* **Loaders**: Loaders are naturally unloadable
280+
* **Processor modules**: Use appropriate cleanup in module termination
281+
282+
## Workflow
283+
284+
The hot-reload workflow uses trigger files to detect when a new binary is available:
285+
286+
1. Create a loader script that invokes your native plugin
287+
2. Configure a dependency file with `/triggerfile` pointing to the compiled binary
288+
3. Build your native plugin
289+
4. QScripts detects the binary change and executes the loader script
290+
5. The loader script invokes the newly compiled plugin
291+
292+
## Example: Hot-Reloading a Plugin
293+
294+
For a plugin with the `PLUGIN_UNL` flag (like the IDA SDK `hello` sample):
295+
296+
**Step 1**: Create a loader script `load_hello.py`:
132297

133298
```python
134-
# Optionally clear the screen:
299+
# Optionally clear the screen
135300
idaapi.msg_clear()
136301

137302
# Load your plugin and pass any arg value you want
@@ -141,21 +306,58 @@ idaapi.load_and_run_plugin('hello', 0)
141306
# ...
142307
```
143308

144-
Then let's create the dependency file with the proper trigger file configuration:
309+
**Step 2**: Create the dependency file `load_hello.py.deps.qscripts`:
310+
311+
```
312+
/triggerfile /keep C:\<ida_dir>\plugins\hello$ext$
313+
```
314+
315+
**Step 3**: Activate `load_hello.py` in QScripts (press `ENTER` on it)
316+
317+
**Step 4**: Build or rebuild the plugin in your IDE
318+
319+
The moment the compilation succeeds, the new binary will be detected (since it is the trigger file) and your loader script will use IDA's `load_and_run_plugin()` to run the plugin again.
320+
321+
## Example: Hot-Reloading a Loader
322+
323+
For loaders, the workflow is similar but uses `load_file()` instead:
324+
325+
**Loader script `test_loader.py`:**
326+
327+
```python
328+
idaapi.msg_clear()
329+
330+
# Unload the old loader if needed
331+
# (IDA handles this automatically for loaders)
332+
333+
# Load a test file with your loader
334+
idaapi.load_file("C:\\testfiles\\myformat.bin", 0)
335+
```
336+
337+
**Dependency file `test_loader.py.deps.qscripts`:**
145338

146339
```
147-
/triggerfile /keep C:\<ida_dir>\plugins\hello.dll
340+
/triggerfile /keep C:\<ida_dir>\loaders\myloader$ext$
148341
```
149342

150-
Now, simply use your favorite IDE (or terminal) and build (or rebuild) the `hello` sample plugin.
343+
## Using $ext$ Variable
344+
345+
The `$ext$` variable automatically expands to the platform-specific plugin extension:
346+
* Windows 64-bit: `64.dll`
347+
* Windows 32-bit: `.dll`
348+
* Linux 64-bit: `64.so`
349+
* Linux 32-bit: `.so`
350+
* macOS: `64.dylib` or `.dylib`
351+
352+
This allows your dependency files to work across platforms without modification.
151353

152-
The moment the compilation succeeds, the new binary will be detected (since it is the trigger file) then your active script will use IDA's `load_and_run_plugin()` to run the plugin again.
354+
## Additional Examples
153355

154-
Please check the native addons examples in [test_addons](test_addons/).
356+
Please check the native addons examples in [test_addons](test_addons/) for complete working examples of hot-reloading plugins, loaders, and processor modules.
155357

156358
# Building
157359

158-
QScripts uses [idax](https://github.com/0xeb/idax) and is built using [ida-cmake](https://github.com/0xeb/ida-cmake).
360+
QScripts uses [idacpp](https://github.com/allthingsida/idacpp) and is built using [ida-cmake](https://github.com/allthingsida/ida-cmake).
159361

160362
If you don't want to build from sources, then there are release pre-built for MS Windows.
161363

@@ -166,10 +368,10 @@ QScripts is written in C++ with IDA's SDK and therefore it should be deployed li
166368
* `<IDA_install_folder>/plugins`
167369
* `%APPDATA%\Hex-Rays/plugins`
168370

169-
Since the plugin uses IDA's SDK and no other OS specific functions, the plugin should be compilable for macOS and Linux just fine. I only provide MS Windows binaries. Please check the [releases page](https://github.com/0xeb/ida-qscripts/releases).
371+
Since the plugin uses IDA's SDK and no other OS specific functions, the plugin should be compilable for macOS and Linux just fine. I only provide MS Windows binaries. Please check the [releases page](https://github.com/allthingsida/ida-qscripts/releases).
170372

171373
# BONUS
172374

173375
## Snippet Manager
174376

175-
QScripts ships with a simple [Snippet Manager](snippet_manager/README.md) plugin to allow you to manage script snippets.
377+
QScripts ships with a simple [Snippet Manager](snippet_manager/README.md) plugin to allow you to manage script snippets.

0 commit comments

Comments
 (0)