You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
@@ -12,7 +12,7 @@ Video tutorials on the [AllThingsIDA](https://www.youtube.com/@allthingsida) You
12
12
13
13
# Usage
14
14
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.
16
16
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.
17
17
QScripts shares the same scripts list as IDA's `Recent Scripts` window.
18
18
@@ -22,33 +22,64 @@ An active script will then be monitored for changes. If you modify the script in
22
22
23
23
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*.
24
24
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:
26
42
27
43
* 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.
28
44
* Show file name when execution: display the name of the file that is automatically executed
29
45
* 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)
30
46
* 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.
31
47
* Allow QScripts execution to be undo-able: The executed script's side effects can be reverted with IDA's Undo.
32
48
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
38
50
39
51
QScripts offers a feature that allows automatic re-execution of the active script when any of its dependent scripts, undergo modifications.
40
52
41
-
###Setting Up Automatic Dependencies
53
+
## Setting Up Automatic Dependencies
42
54
43
55
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.
44
56
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.
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.
### 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.
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
74
163
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$`.
80
165
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`)
82
172
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.
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`:**
Sometimes you don't want to trigger QScripts when your scripts are saved, instead you want your own trigger condition.
92
215
One way to achieve a custom trigger is by using the `/triggerfile` directive:
@@ -100,38 +223,80 @@ dep1.py
100
223
101
224
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.
102
225
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:
105
227
106
228
```
107
229
/triggerfile /keep dont_del_me.info
108
230
```
109
231
110
-
##Using QScripts programmatically
232
+
# Using QScripts programmatically
111
233
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
113
237
114
238
```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)
116
250
```
117
251
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
119
260
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`.
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.
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
130
276
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`:
132
297
133
298
```python
134
-
# Optionally clear the screen:
299
+
# Optionally clear the screen
135
300
idaapi.msg_clear()
136
301
137
302
# Load your plugin and pass any arg value you want
**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:
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.
151
353
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
153
355
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.
155
357
156
358
# Building
157
359
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).
159
361
160
362
If you don't want to build from sources, then there are release pre-built for MS Windows.
161
363
@@ -166,10 +368,10 @@ QScripts is written in C++ with IDA's SDK and therefore it should be deployed li
166
368
*`<IDA_install_folder>/plugins`
167
369
*`%APPDATA%\Hex-Rays/plugins`
168
370
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).
170
372
171
373
# BONUS
172
374
173
375
## Snippet Manager
174
376
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