## Overview
duckyScript is a simple language for **automating keyboard/mouse inputs**.
## Comments
### `//`
C-style comment. Anything after `//` is ignored.
```
// This is a comment
```
### `REM_BLOCK` and `END_REM`
Comment block. Everything in-between is ignored.
```
REM_BLOCK
Put as much comment here
as you want!
END_REM
```
## Typing
### `STRING` and `STRINGLN`
`STRING` types out whatever after it **`AS-IS`**.
```
STRING Hello world!
// types out "Hello world!"
```
`STRINGLN` also presses **enter key** at the end.
### `STRINGLN_BLOCK` and `END_STRINGLN`
Type out everything inside as-is.
Also presses **enter key** at the **end of each line**.
```
STRINGLN_BLOCK
According to all known laws of aviation,
there is no way a bee should be able to fly.
END_STRINGLN
```
### `STRING_BLOCK` and `END_STRING`
Similar to above, but does **NOT** press enter on new lines.
## Pressing Keys
### Special Keys
duckyScript supports many special keys.
They can be used on their own:
```
WINDOWS
```
...or combined with a character to form shortcuts:
```
WINDOWS s
```
...or chained even longer:
```
WINDOWS SHIFT s
```
------
* Type the key name **as-is** in **`ALL CAPS`**.
* Keys are pressed in sequence from **left-to-right**, then released **right-to-left**.
------
List of Special Keys:
```
CTRL / RCTRL | (media keys)
SHIFT / RSHIFT | MK_VOLUP
ALT / RALT | MK_VOLDOWN
WINDOWS / RWINDOWS | MK_MUTE
GUI | MK_PREV
COMMAND / RCOMMAND | MK_NEXT
OPTION / ROPTION | MK_PP (play/pause)
ESC | MK_STOP
ENTER |
UP/DOWN/LEFT/RIGHT |
SPACE | (numpad keys)
BACKSPACE | NUMLOCK
TAB | KP_SLASH
CAPSLOCK | KP_ASTERISK
PRINTSCREEN | KP_MINUS
SCROLLLOCK | KP_PLUS
PAUSE | KP_ENTER
BREAK | KP_0 to KP_9
INSERT | KP_DOT
HOME | KP_EQUAL
PAGEUP / PAGEDOWN |
DELETE | (Japanese input method)
END | ZENKAKUHANKAKU
MENU | HENKAN
POWER | MUHENKAN
F1 to F24 | KATAKANAHIRAGANA
```
### `KEYDOWN` / `KEYUP`
Hold/release a key.
Allows more fine-grained control.
Can be used to input [Alt Codes](https://en.wikipedia.org/wiki/Alt_code) for special characters:
```
// types out ¼
KEYDOWN ALT
KP_1
KP_7
KP_2
KEYUP ALT
```
### `REPEAT`
Repeats the **last line** **`n`** times.
```
STRING Hello world
REPEAT 10
// types out "Hello world" 11 times (1 original + 10 repeats)
```
## Timing
### `DELAY n`
Pause execution for `n` **milliseconds**.
Useful for **waiting for UI to catch up**.
```
WINDOWS r
DELAY 1000 // 1000ms = 1 second
STRING cmd
```
### `DEFAULTDELAY n`
How long to wait between each **`NON-LETTER input actions`**.
* Default: 20ms
* Applies to:
* `MOUSE_MOVE` and `MOUSE_SCROLL`
* `KEYDOWN` and `KEYUP` (including combo keys)
* Pressing ENTER at end of `STRINGLN`
```
DEFAULTDELAY 50
// Waits 50ms between pressing each key
CTRL ALT DELETE
```
### `DEFAULTCHARDELAY n`
How long to wait between **`each letter`** when **`typing text`**.
* Default: 20ms
* To type faster, set to around 10.
* Applies to:
* `STRING` and `STRINGLN`
* `RANDCHR()`
* `PUTS()`
```
DEFAULTCHARDELAY 10
// Waits 10ms between each letter
STRING Hello World!
```
### `CHARJITTER n`
Adds an **`additional`** random delay between 0 and `n` milliseconds after **`each letter`** when **`typing text`**.
* Can make typing more human-like
* Set to 0 to disable
* Applies to:
* `STRING` and `STRINGLN`
* `RANDCHR()`
* `PUTS()`
## Mouse
### Mouse Buttons
* `LMOUSE`: Click `LEFT` mouse button
* `RMOUSE`: Click `RIGHT` mouse button
* `MMOUSE`: Click `MIDDLE` mouse button
* `FMOUSE`: Click `FORWARD` mouse side-button
* `BMOUSE`: Click `BACKWARD` mouse side-button
* Can be used with `KEYDOWN` / `KEYUP` commands.
### `MOUSE_MOVE x y`
Move mouse cursor `x` pixels horizontally, and `y` pixels vertically.
* `x`: Positive moves RIGHT, negative moves LEFT.
* `y`: Positive moves UP, negative moves DOWN.
* Set to 0 if no movement needed
* **Disable mouse acceleration** for **pixel-accurate** results
### `MOUSE_SCROLL h v`
Scroll mouse wheel **Horizontal** `h` lines, and **Vertical** `v` lines.
* `h`: Positive scrolls RIGHT, negative scrolls LEFT.
* `v`: Positive scrolls UP, negative scrolls DOWN.
* Set to 0 for no scroll
## Multiple Actions
`LOOP` command lets you to **assign multiple actions to one key**.
You can use it to toggle / cycle through several actions like this:
```
LOOP0:
STRINGLN first action
LOOP1:
STRINGLN second action
LOOP2:
STRINGLN third action
```
* Start from `LOOP0`
* When pressed, a counter increments, and the script at the corresponding loop is executed.
* Keep the code inside simple!
* For more complex needs, see [`WHILE` Loops](#loops) section below.
## Profile Switching
### `PREV_PROFILE` / `NEXT_PROFILE`
Switch to the previous / next profile.
### `GOTO_PROFILE`
Jump to a profile by name. **Case sensitive!**
This ends the current script execution.
Works with [Advanced Printing](#advanced-printing).
```
GOTO_PROFILE NumPad
```
## OLED
### `OLED_CURSOR x y`
Set where to print on screen.
`x y`: Pixel coordinates between `0` and `127`.
Characters are **7 pixels wide, 10 pixels tall.**
Max **18 Characters Per Line**.
Characters print from **top-left** corner.
### `OLED_PRINT`
`OLED_PRINT hello world!`
Print the message into display buffer at **current cursor location**.
Works with [Advanced Printing](#advanced-printing).
### `OLED_CPRINT`
Same as `OLED_PRINT`, but prints message **center-aligned**.
### `OLED_CLEAR`
Clear the display buffer.
### `OLED_CIRCLE x y radius options`
* `x y`: Origin
* `radius`: In Pixels
* `options`:
* `0`: White, outline.
* `1`: White, filled.
* `2`: Black, outline.
* `3`: Black, filled.
### `OLED_LINE x1 y1 x2 y2`
* `x1, y1`: Start Point
* `X2, y2`: End Point
### `OLED_RECT x1 y1 x2 y2 options`
* `x1, y1`: Start Corner
* `X2, y2`: End Corner
* `options`:
* `0`: White, outline.
* `1`: White, filled.
* `2`: Black, outline.
* `3`: Black, filled.
### `OLED_UPDATE`
Actually update the OLED.
You should use the other commands to set up the buffer, then call `OLED_UPDATE` to write to display.
This is **much faster** than updating the whole screen for every change.
### `OLED_RESTORE`
Restore the default profile/key name display.
* `OLED_UPDATE` **NOT NEEDED**.
## Per-Key RGB
### `SWC_SET n r g b`
Change LED color of a switch
Set `n` to 0 for current key.
Set `n` between 1 to 20 for a particular key.
`r, g, b` must be between 0 and 255.
### `SWC_FILL r g b`
Change color of **ALL** LEDs.
`r, g, b` must be between 0 and 255.
### `SWC_RESET n`
Reset the key back to default color.
Set `n` to 0 for current key.
Set `n` from 1 to 20 for a particular key.
Set `n` to 99 for all keys.
## Constants
You can use `DEFINE` to, well, define a constant.
The content is **replaced AS-IS** during preprocessing, similar to `#define` in C.
```
DEFINE MY_EMAIL example@gmail.com
STRING My email is MY_EMAIL!
```
## Variables
You can declare a variable using `VAR` command, and assign values to it.
Assignment can be:
* Decimal: Normal numbers
* Hexadecimal: Starts with `0x`
* Character: **A single character** inside **single or double quote**
* Will be converted to its 8-bit ASCII value
```
// Declaration
VAR spam = 42
VAR eggs = 0xff
VAR foo = 'a'
// Assignment
spam = 20
eggs = spam*2
```
* Variables are **SIGNED 32-bit Integers**
* Can hold values between **−2,147,483,648 and 2,147,483,647**
* Variables declared at top level have **global scope** and can be accessed **anywhere**.
* Variables declared **inside a function** have **local scope** and is only accessible **within that function**.
### Persistent Global Variables
There are 32 pre-defined global variables that provides **non-volatile** data storage.
* `_GV0` to `_GV31`
* Available across **all profiles**
* Persists over reboots
### Reserved Variables
Some variables are **always available**. They all start with an underscore `_`.
You can read them to obtain information, or write to adjust settings.
```
VAR status = _IS_NUMLOCK_ON
_CHARJITTER = 10
```
[Click me for full list](#reserved-variables-list)
## Operators
You can perform operations on constants and variables.
All ops are **Signed** by default. Use explicit [**unsigned calls**](#unsigned-operators) to treat variables as unsigned.
### Mathematics
```
= Assignment
+ Add
- Subtract
* Multiply
/ SIGNED Integer Division
% SIGNED Modulus
** Exponent
```
Example:
```
spam = 2+3
spam = eggs * 10
```
### Comparison
All comparisons evaluate to **either 0 or 1**.
```
== Equal
!= Not equal
> SIGNED Greater than
< SIGNED Less than
>= SIGNED Greater than or equal
<= SIGNED Less than or equal
```
### Logical
| Operator | Name | Comment |
|:--------:|:---------------------:|--------------------------------------------------------|
| `&&` | Logical AND | Evaluates to 1 if BOTH side are non-zero, otherwise 0. |
| `\|\|` | Logical OR | Evaluates to 1 if ANY side is non-zero, otherwise 0. |
| `!` | Logical NOT | **Single (Unary) Operand**
Evaluates to 1 if expression is 0
Evaluates to 0 if expression is **Non-Zero**|
### Bitwise
```
& Bitwise AND
| Bitwise OR
^ Bitwise XOR
~ Bitwise NOT
<< Left Shift
>> Arithmetic Right Shift (sign-extend)
```
### Augmented Assignments
* E.g. `+=`, `-=`, `*=`, etc.
* Available for all **2-operand operators**
* `x += 1` same as `x = x + 1`, etc.
### UNSIGNED Operators
Call built-in functions below to perform **unsigned** operations on variables.
```
ULT(lhs, rhs) Unsigned Less Than
ULTE(lhs, rhs) Unsigned Less Than or Equal
UGT(lhs, rhs) Unsigned Greater Than
UGTE(lhs, rhs) Unsigned Greater Than or Equal
UDIV(val, n) Unsigned Division (val / n)
UMOD(val, n) Unsigned Modulo (val % n)
LSR(val, n) Logical Right Shift (Zero-Extend)
```
## Advanced Printing
You can print the **value of a variable** by adding a **dollar symbol ($) before its name**.
```
VAR foo = -10
STRING Value is $foo
```
```
Value is -10
```
* Works with `STRING`, `STRINGLN`, `OLED_PRINT`, `OLED_CPRINT` and `GOTO_PROFILE`.
### Format Specifiers
You can use **optional C-Style Format Specifiers** to adjust **print format** and **padding**.
To add a specifier: **Immediately after the variable name**, type `%`, then a **data-type indicator letter**.
* `%d` to print variable as **Signed Decimal**
* **DEFAULT**, same as no specifier.
* `%u` to print variable as **Unsigned Decimal**
* `%x` to print variable as **Lowercase Hexadecimal**
* `%X` to print variable as **Uppercase Hexadecimal**
```
VAR foo = -10
STRING Value is: $foo%d
STRING Value is: $foo%u
STRING Value is: $foo%x
STRING Value is: $foo%X
```
```
Value is: -10
Value is: 4294967286
Value is: fffffff6
Value is: FFFFFFF6
```
### Numerical Padding
* To pad with **SPACE**
* Add a **width number** just **after `%`** and **before the letter**
* The output will be **at least that** wide
* Any extra space are padded with **space characters**
```
VAR foo = 5
STRING I have $foo%10d apples!
```
```
I have 5 apples!
```
* To pad with **LEADING-ZERO**
* Right **after `%`**, add a `0`, then **width number**, then the letter.
* The output will be **at least that** wide
* Any extra space are padded with `0`
* Useful for printing **dates** and **hex numbers**
```
VAR foo = 5
STRING I have $foo%010d apples!
```
```
I have 0000000005 apples!
```
## Real-time Clock (RTC)
duckyPad can keep track of **current date and time** for use in scripts.
### Setting RTC
On cold-boot, duckyPad doesn't know what time it is.
It must be set once, after which it will keep time **as long as it is powered-on**.
* RTC is **automatically set** when using the [Autoswitcher](https://github.com/duckyPad/duckyPad-Profile-Autoswitcher)
* A **clock icon** appears when RTC is valid

* You can also set it manually
* [HID Commands](https://github.com/duckyPad/duckyPad-Profile-Autoswitcher/blob/master/HID_details.md)
* [Sample Script](https://github.com/duckyPad/duckyPad-Profile-Autoswitcher/blob/master/hid_example/ex3_set_rtc.py)
### Reading RTC
#### Validity Check
**ALWAYS check** `_RTC_IS_VALID` **first**!
* **Do not proceed** if value is 0.
```
IF _RTC_IS_VALID == 0
// RTC is uninitialised, do not proceed.
HALT
END_IF
```
#### UTC Offset
The RTC always runs in **UTC**.
Local time is obtained by adding an **UTC Offset in `MINUTES`**
* It is **set automatically** to your **local timezone** when using the [Autoswitcher](https://github.com/duckyPad/duckyPad-Profile-Autoswitcher).
* You can check (and manually adjust) the offset by reading/writing `_RTC_UTC_OFFSET` variable
* Can be positive, 0, or negative.
#### Time and Date
With **valid RTC** and **correct UTC offset**, you can now read from the variables below:
| Name | Comment | Range |
| ------------- | -------------------------- | --------------- |
| `_RTC_YEAR` | **4-digit** Year | e.g. `2025` |
| `_RTC_MONTH` | Month | `1–12` |
| `_RTC_DAY` | Day | `1–31` |
| `_RTC_HOUR` | Hour | `0–23` |
| `_RTC_MINUTE` | Minute | `0–59` |
| `_RTC_SECOND` | Second | `0–60` |
| `_RTC_WDAY` | Day of Week (`0 = Sunday`) | `0–6` |
| `_RTC_YDAY` | Day of Year (`0 = Jan 1`) | `0–365` |
#### Example Usage
```
STRING $_RTC_YEAR%04d-$_RTC_MONTH%02d-$_RTC_DAY%02d $_RTC_HOUR%02d:$_RTC_MINUTE%02d:$_RTC_SECOND%02d
```
```
2025-09-18 09:07:23
```
See [Advanced Printing](#advanced-printing) for formatting tips.
## Conditional Statements
`IF` statements can be used to **conditionally execute code**.
At simplest, it involves `IF` and `END_IF`:
```
IF expression
code to execute
END_IF
```
The code inside is executed if the **expression evaluates to non-zero**.
Indent doesn't matter, feel free to add them for a cleaner look.
----
You can use `ELSE IF` and `ELSE` for additional checks.
If the first `IF` evaluate to 0, `ELSE IF`s are checked.
If none of the conditions are met, code inside `ELSE` is executed.
```
VAR temp = 25
IF temp > 30
STRING It's very hot!
ELSE IF temp > 18
STRING It's a pleasant day.
ELSE
STRING It's quite chilly!
END_IF
```
## Loops
You can use `WHILE` statement to **repeat actions** until a **certain condition is met**.
```
WHILE expression
code to repeat
END_WHILE
```
* If `expression` evaluates to **non-zero**, code inside is repeated. Otherwise, the code is skipped.
This simple example loops 3 times.
```
VAR i = 0
WHILE i < 3
STRINGLN Counter is $i!
i = i + 1
END_WHILE
```
```
Counter is 0!
Counter is 1!
Counter is 2!
```
### `LBREAK`
Use `LBREAK` to **exit a loop** immediately.
```
VAR i = 0
WHILE 1
STRINGLN Counter is $i!
i = i + 1
IF i == 3
LBREAK
END_IF
END_WHILE
```
```
Counter is 0!
Counter is 1!
Counter is 2!
```
### `CONTINUE`
Use `CONTINUE` to **jump to the start of loop** immediately.
```
VAR i = 0
WHILE i < 5
i = i + 1
IF i == 3
CONTINUE
END_IF
STRINGLN Counter is $i!
END_WHILE
```
Here when `i` is 3, it skips printing and starts from the top instead.
```
Counter is 1!
Counter is 2!
Counter is 4!
Counter is 5!
```
### Infinite Loop
To exit an infinite loop, you can [check button status](#reading-inputs), or turn on `Allow Abort` in configurator settings.
## Functions
A function is a **block of organized code** that you can call to **perform a task**.
It makes your script **more modular** and **easier to maintain** compared to copy-pasting same code multiple times.
### Plain Functions
* Declare a function with `FUN name()` and `END_FUN`
* Put the code you want to execute inside
* Call it with `name()`
* Code inside are executed
```
FUN print_addr()
STRINGLN 123 Ducky Lane
STRINGLN Pond City, QU 12345
END_FUN
print_addr() // call it
```
### Arguments and Returns
You can also **pass arguments** into a function and specify a **return value**.
* Ideal for performing calculations
```
FUN add_number(a, b)
RETURN a + b
END_FUN
VAR total = add_number(10, 20)
```
### Variable Scoping
Variables declared **outside functions** have **global scope**, they can be **accessed anywhere**.
Variables declared **inside functions** have **local scope**, they are only accessible **within that function**.
* If a local variable has the **same name** as a global variable, the **local var takes priority** within that function.
```
// Both global scope
VAR x = 10
VAR y = 20
FUN scope_demo()
VAR x = 5 // This x is local, will shadow the global x.
x = x + y
STRINGLN Local x is: $x
END_FUN
```
```
Local x is: 25
```
### Nested / Recursive Calls
You can also:
* Call other functions from inside a function
* Including **calling itself**!
```
FUN factorial(n)
IF n <= 1
RETURN 1
END_IF
RETURN n * factorial(n - 1)
END_FUN
VAR fact = factorial(5)
```
## duckyPad Standard Library
The **DPDS StdLib** provides handy helper functions to simplify duckyScript coding.
To use them, add `USE_STDLIB` in your code.
[More Info / Contribute](https://github.com/duckyPad/DPDS-Standard-Library/blob/master/README.md)
```
USE_STDLIB
STRINGLN Press Key 3 to continue...
WAITKEY(3)
VAR high_score = MAX(100, 500)
STRINGLN The high score is: $high_score
```
## User Headers
You can also **create your own header** for custom helper functions and more.
* Click `Edit Headers` Button
* Write code
* Add `USE_UH` to your script to include them
* The header is added to your source code **AS-IS** during preprocessing
## Built-in Functions
A few built-in functions are available. They are intended for **low-level tinkering**.
You might want to get familiar with [VM's memory map](https://duckypad.github.io/DuckStack/)
All multi-byte values are **little-endian**
### `PEEK8(addr)` / `PEEK16(addr)` / `PEEK32(addr)`
Read **SIGNED** value at memory address.
* High bits are **SIGN-extended**
### `PEEKU8(addr)` / `PEEKU16(addr)`
Read **UNSIGNED** value at memory address.
* High bits are **ZERO-extended**
### `POKE8(addr, val)` / `POKE16(addr, val)` / `POKE32(addr, val)`
Write value at memory address.
* `val` can be numbers or characters
* `POKE8(0xf400, 'c')`
* `POKE32(0xf410, 0xabcd)`
### `RANDCHR(value)`
Generate a **random character**.
* `value` is checked as a bitfield:
* `Bit 0`: Letter Lowercase (A-Z)
* `Bit 1`: Letter Uppercase (a-z)
* `Bit 2`: Digits (0-9)
* `Bit 3`: Symbols (\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\})
* `Bit 8`: Type via Keyboard
* `Bit 9`: Print to OLED at current cursor position
* For `Bit 0-3`, if any bit is `1`, its pool of characters will be included for random selection.
* If `Bit 8` is 1, it will type the character via keyboard.
* If `Bit 9` is 1, it will print the character to screen buffer
* Don't forget to use `OLED_UPDATE` to actually refresh the screen.
### `RANDINT(lower, upper)` / `RANDUINT(lower, upper)`
Returns a **Signed/Unsigned** random number between `lower` and `upper` **INCLUSIVE**.
```
VAR value = RANDINT(-100, 100)
VAR value = RANDUINT(3000000000, 4000000000)
```
### `PUTS(value)`
**Print string** at memory address.
* `value` contains:
* `Bit 0-15`: Address
* `Bit 16-23`: `n`
* `Bit 29`: Print to OLED at current cursor position
* `Bit 30`: Print to OLED center-aligned
* `Bit 31`: Type via Keyboard
* If `n = 0`, print until zero-termination.
* Else, print max `n` characters.
### `HIDTX(addr)`
Send a **raw HID message**
* Pick an address `addr` in **scratch memory area**
* Use `POKE8()` to write **9 bytes** starting from `addr`
* Follow the format below
* Call `HIDTX(addr)` to send the HID message
* Include a short delay (10-20ms) to allow computer to register the input
* Don't forget to **release the key** after pressing it
* Set `Byte 1-8` to 0 to release
#### Keyboard
|Byte|Value|Description|
|:-------:|:----------:|:---------:|
|`addr`|1|Usage ID|
|`addr+1`|Modifier
Bitfield|`Bit 0`: Left Control
`Bit 1`: Left Shift
`Bit 2`: Left Alt
`Bit 3`: Left GUI (Win/Cmd)
`Bit 4`: Right Control
`Bit 5`: Right Shift
`Bit 6`: Right Alt (AltGr)
`Bit 7`: Right GUI (Win/Cmd)|
|`addr+2`|0|Reserved|
|`addr+3`
-
`addr+8`|HID Keyboard
Scan Code|[See list](https://gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2)
Max 6 keys at once (6KRO)
Write `0` for released / unused|
#### Media Keys
|Byte|Value|Description|
|:-------:|:----------:|:---------:|
|`addr`|2|Usage ID|
|`addr+1`|Key Status
Bitfield|`Bit 0`: Next Track
`Bit 1`: Previous Track
`Bit 2`: Stop
`Bit 3`: Eject
`Bit 4`: Play / Pause
`Bit 5`: Mute
`Bit 6`: Volume Up
`Bit 7`: Volume Down|
|`addr+2`
-
`addr+8`|0|0|
#### Mouse
|Byte|Value|Description|
|:-------:|:----------:|:---------:|
|`addr`|3|Usage ID|
|`addr+1`|Buttons
Status
Bitfield|`Bit 0`: Left
`Bit 1`: Right
`Bit 2`: Middle
`Bit 3`: Backward
`Bit 4`: Forward|
|`addr+2`|X Movement|-127 - 127|
|`addr+3`|Y Movement|-127 - 127|
|`addr+4`|Vertical
Scroll|-127 - 127|
|`addr+5`|Horizontal
Scroll|-127 - 127|
|`addr+6`
-
`addr+8`|0|0|
## Reading Inputs
You can **read the status of switches / encoders** to perform actions.
### Blocking Read
Simplest method.
Just read `_BLOCKING_READKEY` reserved variable.
It will block until a key is pressed.
```
VAR this_key = _BLOCKING_READKEY
// Blocks here until a key is pressed
IF this_key == 1
// do something here
ELSE IF this_key == 2
// do something else
END_IF
```
### Non-Blocking Read
Read `_READKEY`, **returns immediately**.
Returns 0 if no key is pressed. `Key ID` otherwise.
Check this **in a loop** to perform work even when no key is pressed.
```
WHILE TRUE
VAR this_key = _READKEY
IF this_key == 1
// handling button press
END_IF
// otherwise do work here
END_WHILE
```
### Switch Status Bitfield
Read `_SW_BITFIELD`, **returns immediately**.
Each bit position stores the status of the corresponding key.
* E.g. `bit 1` = `key ID 1`, `bit 13` = `key ID 13`, etc.
If that bit is 1, the key is currently pressed.
You can use bitmasks to **check multiple keys at once**.
### Key ID
This is the number returned by methods above.
```
duckyPad Pro (2024):
1-20:
* Built-in keys
* Top left is 1
* Bottom right is 20
21: Upper Rotary Encoder Clockwise
22: Upper Rotary Encoder Counterclockwise
23: Upper Rotary Encoder Push-down
24: Lower Rotary Encoder Clockwise
25: Lower Rotary Encoder Counterclockwise
26: Lower Rotary Encoder Push-down
27: Plus Button
28: Minus Button
37+: External Switches
```
```
duckyPad (2020):
1-15:
* Top left is 1
* Bottom right is 15
* Plus button 16, Minus button 17.
```
## Randomization
### Random Number
Call `RANDINT(lower, upper)` for a random number between `lower` and `upper` **INCLUSIVE**.
```
VAR value = RANDINT(0, 1000)
```
### Random Character
Use one of below to **type a random character**:
```
RANDOM_LOWERCASE_LETTER RANDOM_NUMBER
RANDOM_UPPERCASE_LETTER RANDOM_SPECIAL
RANDOM_LETTER RANDOM_CHAR
```
```
RANDOM_NUMBER
REPEAT 7
// types 8 random numbers
```
For more granular control, see `RANDCHR()` in [Built-in Functions](#built-in-functions).
## Miscellaneous
### `DP_SLEEP`
Make duckyPad go to sleep. Terminates execution.
Backlight and screen are turned off.
Press any key to wake up.
### `HALT`
Stop execution immediately
### `BCLR`
Clears the internal keypress event queue. Can be used:
* **At end of a long script**: Prevents "buffered" presses from triggering it again.
* **Before reading button status**: Ensures **new presses** are returned instead of old ones in the queue.
### `PASS`
Does nothing. Can be used as **placeholders** or empty statements.
## Reserved Variables List
There are some **reserved variables** that are always available.
You can read or write (RW) to adjust settings. Some are read-only (RO).
| Name | Access | Description |
| --------------------------------------------------------------------------- | :-----: |:----:|
| **`_TIME_S`**
**`_TIME_MS`** | RO | Elapsed time since power-on|
| **`_READKEY`**
**`_BLOCKING_READKEY`**
**`_SW_BITFIELD`** | RO | See [Reading Inputs](#reading-inputs)|
| **`_KBLED_BITFIELD`** | RO |Keyboard LED Status
`Bit 0`: Num Lock
`Bit 1`: Caps Lock
`Bit 2`: Scroll Lock
Bit is set if LED is on.
Certain OS may not have all LEDs|
| **`_IS_NUMLOCK_ON`**
**`_IS_CAPSLOCK_ON`**
**`_IS_SCROLLLOCK_ON`** | RO | Aliases|
| **`_DEFAULTDELAY`**
**`_DEFAULTCHARDELAY`**
**`_CHARJITTER`** | RW | Aliases|
| **`_ALLOW_ABORT`**
**`_DONT_REPEAT`** | RW | Write `1` to enable
`0` to disable. |
| **`_THIS_KEYID`** | RO | Returns the [Key ID](#key-id) for the **current script** |
| **`_DP_MODEL`** | RO | Device model. Returns:
`1` for duckyPad (2020)
`2` for duckyPad Pro (2024) |
| **`_KEYPRESS_COUNT`** | RW | How many times **current key**
has been pressed in the **current profile**
Assign **0 to reset** |
| **`_LOOP_SIZE`** | RO | Used by `LOOP` command.
Do not modify |
| **`_NEEDS_EPILOGUE`** | RO | Internal use only
Do not modify |
|**`_RTC_IS_VALID`**
**`_RTC_YEAR`**
**`_RTC_MONTH`**
**`_RTC_DAY`**
**`_RTC_HOUR`**
**`_RTC_MINUTE`**
**`_RTC_SECOND`**
**`_RTC_WDAY`**
**`_RTC_YDAY`**|RO|See [Real-time Clock](#real-time-clock-rtc)|
|**`_RTC_UTC_OFFSET`**|RW|See [Real-time Clock](#real-time-clock-rtc)|
-----------------
# DuckStack Bytecode Virtual Machine
DuckStack is a simple **stack-based bytecode VM** for executing compiled **duckyScript** binaries.
## Architecture Overview
duckStack uses **32-bit** variables, arithmetics, and stack width.
Addressing is **16-bit**, executable 64KB max.
* Single **Data Stack**
* Flat memory map
* Byte-addressed
* Program Counter (PC)
* 16-bit byte-addressed
* Stack Pointer (SP)
* 16-bit byte-addressed
* Points to **the next free stack slot**
* Frame Pointer (FP)
* Points to current function base frame
### Memory Map
|Address|Purpose|Size|Comment|`PEEK` and
`POKE`-able|
|:-:|:--:|:--:|:--:|:--:|
|`0000`
`EFFF`|Shared
**Executable**
and **Stack**
|61440 Bytes|See Notes Below|✅|
|`F000`
`F3FF`|User-defined
Global
Variables|1024 Bytes
4 Bytes/Entry
256 Entries|ZI Data|✅|
|`F400`
`F7FF`|Scratch
Memory|1024 Bytes|General-purpose|✅|
|`FC00`
`FDFF`|Persistent
Global
Variables|512 Bytes
4 Bytes/Entry
128 Entries|Non-volatile Data
Saved on SD card|✅|
|`FE00`
`FEFF`|VM
Internal
Variables|256 Bytes
4 Bytes/Entry
64 Entries|Read/Adjust
VM Settings|❌|
|`FF00`
`FFFF`|Device-specific
MMIO|256 Bytes||✅|
* Binary executable is loaded at `0x0`
* Stack grows from `0xEFFF` towards **smaller address**
* Each stack item **4 bytes long**
* SP can be **4-byte aligned** for better performance.
* Smaller executable allows larger stack, vise versa.
* Over/underflow are checked before every push/pop.
## Instruction Set
**Variable-length** between **1 to 5 bytes**.
* First byte (Byte 0): **Opcode**.
* Byte 1 - 4: **Optional payload**.
* ⚠️ All multi-byte payloads are **Little-endian**
### CPU Instructions
* **1 stack item** = 4 **bytes**
* `PUSHR` / `POPR` **Offset** is a **byte-addressed signed 16-bit integer**
* Positive: Towards larger address / Base of Stack
* Negative: Towards smaller address / Top of Stack (TOS)
|Name|Inst.
Size|Opcode
Byte 0|Comment|Payload
Byte 1-4|
|:-:|:-:|:-:|:-:|:-:|
|`NOP`|1|`0`/`0x0` |Do nothing|None|
|`PUSHC16`|3|`1`/`0x1` |Push **unsigned 16-bit (0-65535)** constant on stack
For negative numbers, push abs then use `USUB`.|2 Bytes:
`CONST_LSB`
`CONST_MSB` |
|`PUSHI`|3|`2`/`0x2` |Read **4 Bytes** at `ADDR`
Push to stack as one **32-bit** number|2 Bytes:
`ADDR_LSB`
`ADDR_MSB`|
|`PUSHR`|3|`3`/`0x3`|Read **4 Bytes** at **offset from FP**
Push to stack as one **32-bit** number|2 Bytes:
`OFFSET_LSB`
`OFFSET_MSB`|
|`POPI`|3|`4`/`0x4` |Pop one item off TOS
Write **4 bytes** to `ADDR`|2 Bytes:
`ADDR_LSB`
`ADDR_MSB`|
|`POPR`|3|`5`/`0x5`|Pop one item off TOS
Write as **4 Bytes** at **offset from FP**|2 Bytes:
`OFFSET_LSB`
`OFFSET_MSB`|
|`BRZ`|3|`6`/`0x6` |Pop one item off TOS
If value is zero, jump to `ADDR` |2 Bytes:
`ADDR_LSB`
`ADDR_MSB`|
|`JMP`|3|`7`/`0x7` |Unconditional Jump|2 Bytes:
`ADDR_LSB`
`ADDR_MSB`|
|`ALLOC`|3|`8`/`0x8` |Push `n` blank entries to stack
Used to allocate local variables
on function entry|2 Bytes:
`n_LSB`
`n_MSB`|
|`CALL`|3|`9`/`0x9` |Construct 32b value `frame_info`:
Top 16b `current_FP`,
Bottom 16b `return_addr (PC+3)`.
Push `frame_info` to TOS
Set **FP** to TOS
Jump to `ADDR`|2 Bytes:
`ADDR_LSB`
`ADDR_MSB`|
|`RET`|3|`10`/`0xa` |`return_value` on TOS
Pop `return_value` into temp location
Pop items until TOS is `FP`
Pop `frame_info`, restore **FP** and **PC**.
Pop off `ARG_COUNT` items
Push `return_value` back on TOS
Resumes execution at PC|2 Bytes:
`ARG_COUNT`
`Reserved`|
|`HALT`|1|`11`/`0xb` |Stop execution|None|
|`PUSH0`|1|`12`/`0xc` |Push `0` to TOS|None|
|`PUSH1`|1|`13`/`0xd` |Push `1` to TOS|None|
|`DROP`|1|`14`/`0xe` |Discard **ONE** item off TOS|None|
|`DUP`|1|`15`/`0xf` |**Duplicate the item** on TOS|None|
|`RANDINT`|1|`16`/`0x10` |Pop **TWO** item off TOS
First `Upper`, then `Lower`.
Push a **SIGNED** random number inbetween (**inclusive**) on TOS|None|
|`RANDUINT`|1|`17`/`0x11` |Pop **TWO** item off TOS
First `Upper`, then `Lower`.
Push an **UNSIGNED** random number inbetween (**inclusive**) on TOS|None|
|`PUSHC32`|5|`18`/`0x12` |Push **32-bit** constant on stack|4 Bytes
`CONST_LSB`
`CONST_B1`
`CONST_B2`
`CONST_MSB`|
|`PUSHC8`|2|`19`/`0x13` |Push **unsigned 8-bit (0-255)** constant on stack
For negative numbers, push abs then use `USUB`.|1 Byte|
|`VMVER`|3|`255`/`0xff`| VM Version Check
Abort if mismatch |2 Bytes:
`VM_VER`
`Reserved`|
### Memory Access
* All **single-byte** instructions
#### PEEK Instructions
* Pop **ONE** item off TOS as `ADDR`
* Then...
|Name|Opcode
Byte 0|Comment|
|:-:|:-:|:-:|
|`PEEK8`|`24`/`0x18`|Read **ONE byte** at `ADDR`
Push on stack **SIGN-extended**|
|`PEEKU8`|`25`/`0x19`|Read **ONE byte** at `ADDR`
Push on stack **ZERO-extended**|
|`PEEK16`|`26`/`0x1a`|Read **TWO bytes** at `ADDR`
Push on stack **SIGN-extended**|
|`PEEKU16`|`27`/`0x1b`|Read **TWO bytes** at `ADDR`
Push on stack **ZERO-extended**|
|`PEEK32`|`28`/`0x1c`|Read **FOUR bytes** at `ADDR`
Push on stack **AS-IS**|
#### POKE Instructions
* Pop **TWO** item off TOS
* First `ADDR`, then `VAL`
* Then...
|Name|Opcode
Byte 0|Comment|
|:-:|:-:|:-:|
|`POKE8`|`29`/`0x1d`|Write **low 8 bits** of `VAL` to `ADDR`|
|`POKE16`|`30`/`0x1e`|Write **low 16 bits** of `VAL` to `ADDR`|
|`POKE32`|`31`/`0x1f`|Write `VAL` to `ADDR` **as-is**|
### Binary Operators
Binary as in **involving two operands**.
* All **single-byte** instructions
* Pop **TWO** items off TOS
* First item: Left-hand-side
* Second item: Right-hand-side
* Perform operation
* Push result back on TOS
-----
|Name|Opcode
Byte 0|Comment|
|:--:|:--:|:--:|
|`EQ`|`32`/`0x20`|Equal|
|`NOTEQ`|`33`/`0x21`|Not Equal|
|`LT`|`34`/`0x22`|**SIGNED** Less Than|
|`LTE`|`35`/`0x23`|**SIGNED** Less Than or Equal|
|`GT`|`36`/`0x24`|**SIGNED** Greater Than|
|`GTE`|`37`/`0x25`|**SIGNED** Greater Than or Equal|
|`ADD`|`38`/`0x26`|Add|
|`SUB`|`39`/`0x27`|Subtract|
|`MULT`|`40`/`0x28`|Multiply|
|`DIV`|`41`/`0x29`|**SIGNED** Integer Division|
|`MOD`|`42`/`0x2a`|**SIGNED** Modulus|
|`POW`|`43`/`0x2b`|Power of|
|`LSL`|`44`/`0x2c`|Logical Shift Left|
|`ASR`|`45`/`0x2d`|**Arithmetic** Shift Right
(Sign-extend)|
|`BITOR`|`46`/`0x2e`|Bitwise OR|
|`BITXOR`|`47`/`0x2f`|Bitwise XOR|
|`BITAND`|`48`/`0x30`|Bitwise AND|
|`LOGIAND`|`49`/`0x31`|Logical AND|
|`LOGIOR`|`50`/`0x32`|Logical OR|
|`ULT`|`51`/`0x33`|**UNSIGNED** Less Than|
|`ULTE`|`52`/`0x34`|**UNSIGNED** Less Than or Equal|
|`UGT`|`53`/`0x35`|**UNSIGNED** Greater Than|
|`UGTE`|`54`/`0x36`|**UNSIGNED** Greater Than or Equal|
|`UDIV`|`55`/`0x37`|**UNSIGNED** Integer Division|
|`UMOD`|`56`/`0x38`|**UNSIGNED** Modulus|
|`LSR`|`57`/`0x39`|**Logical** Shift Right
(Zero-extend)|
### Unary Operators
* All **single-byte** instructions
* Pop **ONE** items off TOS
* Perform operation
* Push result back on TOS
|Name|Opcode
Byte 0|Comment|
|:--:|:--:|:--:|
|`BITINV`|`60`/`0x3c`|Bitwise Invert|
|`LOGINOT`|`61`/`0x3d`|Logical NOT|
|`USUB`|`62`/`0x3e`|Unary Minus|
### duckyScript Commands
* All **single-byte** instructions
|Name|Opcode
Byte 0|Comment|
|:-------:|:----------:|:---------:|
|`DELAY`|`64`/`0x40`| **Delay**
Pop **ONE** item
Delay amount in **milliseconds**|
|`KDOWN`|`65`/`0x41`| **Press Key**
Pop **ONE** item
`\|MSB\|B2\|B1\|LSB`
`\|Unused\|Unused\|KeyType\|KeyCode\|`|
|`KUP`|`66`/`0x42`|**Release Key**
Pop **ONE** item
`\|MSB\|B2\|B1\|LSB`
`\|Unused\|Unused\|KeyType\|KeyCode\|`|
|`MSCL`|`67`/`0x43`| **Mouse Scroll**
Pop **TWO** items
First `hline`, then `vline`
Scroll `hline` horizontally
`(Positive: RIGHT, Negative: LEFT)`
Scroll `vline` vertically
`(Positive: UP, Negative: DOWN)`|
|`MMOV`|`68`/`0x44`|**Mouse Move**
Pop **TWO** items: `x` then `y`
`x`: Positive RIGHT, Negative LEFT.
`y`: Positive UP, Negative DOWN.|
|`SWCF`|`69`/`0x45`| **Switch Color Fill**
Pop **THREE** items
`Red, Green, Blue`
Set ALL LED color to the RGB value|
|`SWCC`|`70`/`0x46`| **Switch Color Change**
Pop **FOUR** item
`N, Red, Green, Blue`
Set N-th switch to the RGB value
If N is 0, set current switch.|
|`SWCR`|`71`/`0x47`| **Switch Color Reset**
Pop **ONE** item
If value is 0, reset color of current key
If value is between 1 and 20, reset color of that key
If value is 99, reset color of all keys.|
|`STR`|`72`/`0x48`|**Type String**
Pop **ONE** item as `ADDR`
Print zero-terminated
string at `ADDR`|
|`STRLN`|`73`/`0x49`|**Type Line**
Pop **ONE** item as `ADDR`
Print zero-terminated
string at `ADDR`
**Press ENTER at end**|
|`OLED_CUSR`|`74`/`0x4a`|**OLED Set Cursor**
Pop **TWO** items: `x` then `y`|
|`OLED_PRNT`|`75`/`0x4b`|**OLED Print**
Pop **TWO** items: `OPTIONS` then `ADDR`
Print zero-terminated
string at `ADDR` to OLED
`OPTIONS` Bit 0: If set, print center-aligned.|
|`OLED_UPDE`|`76`/`0x4c`|**OLED Update**|
|`OLED_CLR`|`77`/`0x4d`|**OLED Clear**|
|`OLED_REST`|`78`/`0x4e`| **OLED Restore**|
|`OLED_LINE`|`79`/`0x4f`|**OLED Draw Line**
Pop **FOUR** items
`x1, y1, x2, y2`
Draw single-pixel line in-between|
|`OLED_RECT`|`80`/`0x50`|**OLED Draw Rectangle**
Pop **FIVE** items
`opt, x1, y1, x2, y2`
Draw rectangle between two points
`opt`Bit 0: Fill, Bit 1: Color|
|`OLED_CIRC`|`81`/`0x51`|**OLED Draw Circle**
Pop **FOUR** items
`opt, radius, x, y`
Draw circle with `radius` at `(x,y)`
`opt`Bit 0: Fill, Bit 1: Color|
|`BCLR`|`82`/`0x52`|**Clear switch event queue**|
|`SKIPP`|`83`/`0x53`| **Skip Profile**
Pop **ONE** item as `n`
If `n` is **positive**, go to **next** profile
If `n` is **negative**, go to **prev** profile|
|`GOTOP`|`84`/`0x54`| **Goto Profile**
Pop **ONE** item as `ADDR`
Retrieve zero-terminated string at `ADDR`
If resolves into an **integer `n`**
Go to `n`th profile.
Otherwise jump to profile name|
|`SLEEP`|`85`/`0x55`| **Sleep**
Put duckyPad to sleep
Terminates execution|
|`RANDCHR`|`86`/`0x56`| **Random Character**
Pop **ONE** item as bitmask.
Bit 0: Letter Lowercase
Bit 1: Letter Uppercase
Bit 2: Digits
Bit 3: Symbols
Bit 8: Type via Keyboard
Bit 9: OLED Print-at-cursor|
|`PUTS`|`87`/`0x57` |**Print String**
Pop **ONE** item off TOS
------
Bit 0-15: `ADDR`
Bit 16-23: `n`
Bit 29: OLED Print-at-cursor
Bit 30: OLED Print-Center-Aligned
Bit 31: Type via Keyboard
Print string starting from `ADDR`
------
If `n=0`, print until zero-termination.
Else, print max `n` chars (or until `\0`).
|
|`HIDTX`|`88`/`0x58`| Pop **ONE** item off TOS as `ADDR`
Read **9 bytes** from `ADDR`
Construct & send raw HID message
[See `HIDTX()` in duckyScript doc](https://github.com/dekuNukem/duckyPad-Pro/blob/master/doc/duckyscript_info.md#hidtxaddr)|
## String Encoding
The following commands involves user-provided strings:
* `STRING`/ `STRINGLN`
* `OLED_PRINT` / `OLED_CPRINT`
* `GOTO_PROFILE`
* `PUTS()`
Strings are **zero-terminated** and appended at the **end of the binary executable**.
The **starting address** of a string is **pushed onto stack** before calling one of those commands, who pops off the address and fetch the string there.
Identical strings are deduplicated and share the same address.
```
STRING Hello World!
STRINGLN Hello World!
OLED_PRINT Hi there!
```
```
3 PUSHC16 16 0x10 ;STRING Hello World!
6 STR ;STRING Hello World!
7 PUSHC16 16 0x10 ;STRINGLN Hello World!
10 STRLN ;STRINGLN Hello World!
11 PUSHC16 29 0x1d ;OLED_PRINT Hi there!
14 OLED_PRNT ;OLED_PRINT Hi there!
15 HALT
16 DATA: b'Hello World!\x00'
29 DATA: b'Hi there!\x00'
```
## Printing Variables
When printing a variable, its info is embedded into the string between **two separator bytes**.
* `0x1f` for **Global Variables**
* Contains: **Little-endian** memory address
* `[0x1f][ADDR_LSB][ADDR_MSB][Format Specifiers][0x1f]`
* `0x1e` for **Local variables & arguments inside functions**
* Contains: **FP-Relative Offset**
* `[0x1e][OFFSET_LSB][OFFSET_MSB][Format Specifiers][0x1e]`
```
VAR foo = 255
STRING Count is: $foo%02x
```
```
3 PUSHC16 255 0xff ;VAR foo = 255
6 POPI 63488 0xf800 ;VAR foo = 255
9 PUSHC16 14 0xe ;STRING Count is: $foo%02x
12 STR ;STRING Count is: $foo%02x
13 HALT
14 DATA: b'Count is: \x1f\x00\xf8%02x\x1f\x00'
```
## Run-time Exceptions
Exceptions such as Division-by-Zero, Stack Over/Underflow, etc, result in immediate termination of the VM execution.
## Calling Convention
* Multiple arguments, one return value.
* Supports nested and recursive calls
* **TOS** grows towards **smaller address**
### Stack Set-up
Outside function calls, FP points to **base of stack.**
||...|
|:--:|:--:|
||...|
|`FP ->`|Base (`EFFF`)|
When calling a function: **`foo(a, b, c)`**
* **Caller** pushes 32-bit arguments **right to left** to stack
* Don't push if no args.
|||
|:--:|:--:|
||`a`|
||`b`|
||`c`|
||...|
|`FP ->`|Base (`EFFF`)|
Caller then executes `CALL` instruction, which:
* Constructs a 32b value `frame_info`
* Top 16b: `return_FP`
* Bottom 16b: `return_address`
* Pushes `frame_info` to TOS
* Sets **FP** to TOS
* Jumps to the function address
|||
|:--:|:--:|
|`FP ->`|`Prev_FP \| Return_addr`|
||`a`|
||`b`|
||`c`|
||...|
||Base (`EFFF`)|
### Arguments / Locals
Once in function, callee uses `ALLOC n` to make space for local variables.
To reference arguments and locals, **FP + Byte_Offset** is used.
* **Negative** offset towards **smaller address / TOS / locals**.
* `FP - 4` points to **first local**, etc
* **Positive** offset towards **larger address / base of stack / args**.
* `FP + 4` points to **leftmost argument**, etc
* Use `PUSHR + Offset` and `POPR + Offset` to read/write to args and locals.
|||
|:--:|:--:|
||...|
|`FP - 8`|`localvar_2`|
|`FP - 4`|`localvar_1`|
|`FP ->`|`Prev_FP \| Return_addr`|
|`FP + 4`|`a`|
|`FP + 8`|`b`|
|`FP + 12`|`c`|
||...|
||Base (`EFFF`)|
### Stack Unwinding
At end of a function, `return_value` is on TOS.
* If no explicit `RETURN` statement, **0 is returned**.
|||
|:--:|:--:|
||`return_value`|
||`temp data`|
|`FP - 8`|`localvar_2`|
|`FP - 4`|`localvar_1`|
|`FP ->`|`Prev_FP \| Return_addr`|
|`FP + 4`|`a`|
|`FP + 8`|`b`|
|`FP + 12`|`c`|
||...|
||Base (`EFFF`)|
**Callee** executes `RET n` instruction, which:
* Pops off `return_value` into temp location
* Pop off items until `frame_info` is on **TOS**
* AKA `SP + 4 == FP`
* Pops off `frame_info`
* Loads `previous FP` into **FP**
* Loads `return address` into **PC**
* Pops off `n` arguments
* Pushes `return_value` back on TOS
* Resumes execution at PC
* Return value now on TOS for caller to use
|||
|:--:|:--:|
||`return_val`|
||...|
|`FP ->`|Base (`EFFF`)|