Skip to content

Latest commit

 

History

History
1669 lines (1151 loc) · 20.1 KB

File metadata and controls

1669 lines (1151 loc) · 20.1 KB

DSP408 Script Syntax

This file describes the syntax of .dspd scripts in tracks-dsp-raider.

The script language is intentionally small and pragmatic. It is designed for:

  • connecting to the proxy
  • reading and sending DSP commands
  • evaluating GUI captures
  • simple conditions and loops
  • byte/hex inspection
  • diff-based decode workflows
  • saving results

Modern Syntax (Preferred)

The DSL supports a modern C/Java-like style and the legacy syntax. Both can be mixed in the same script.

connect();
ensureSession();
handshake();

let pin = "1234";
login(pin);

let resp = readBlock(0x04);
print(resp.payloadHex);

if (resp.command == 0x24) {
    print("read block ok");
} else {
    print("unexpected response");
}

for (let ch in 0x04..0x07) {
    let block = readBlock(ch);
    print(block.commandHex);
}

Legacy syntax (end, kebab-case commands, no ;) remains supported.

Examples:

connect
ensure-session
handshake

let resp = read-block 0x04
print $resp.payloadHex

Naming Style

Most commands/functions can be used in:

  • modern camelCase, for example readBlock(), ensureSession(), saveDiffReport()
  • legacy kebab-case, for example read-block(), ensure-session(), save-diff-report()

Examples:

readBlock(0x00)
read-block(0x00)

ensureSession()
ensure-session()

File Format

  • scripts are plain text files
  • recommended extension: .dspd
  • encoding: UTF-8

Comments

Comments start with # or //.

# This is a comment
connect
// another comment

Comments inside strings are not treated as comments.

print("Hello # not a comment");

Statements

A statement can be separated by:

  • newline
  • ;

Examples:

connect
status
connect(); status(); handshake();
connect
status
handshake

Strings

Strings use double quotes:

print("Hello World");

Supported escape sequences inside strings include:

  • \"
  • \\
  • \n
  • \r
  • \t

Variables

Variables are created with let:

let x = 123;
let name = "Test";
let block = 0x10;

The last evaluated expression is also stored in _.

Example:

status();
print(_);

Values and Literals

Supported base values:

  • null
  • true
  • false
  • integers, for example 123
  • hex integers, for example 0x2C
  • decimals, for example 12.5
  • strings, for example "abc"

Variable Access

Direct access

Variables can be used directly by name:

let x = 10;
print(x);

Access with $

Access can also start with $:

let x = 10;
print($x);

Property access

Properties can be read from objects:

let s = status();
print(s.sessionActive);
print($s.injectReady);

Index access

Lists, strings, and byte arrays can be indexed:

let cap = guiEndCapture();
print(cap.frames[0]);
print(cap.frames[0].commandHex);

String Interpolation

Tokens and strings can use placeholders with ${...}.

let i = 5;
print("Block ${i}");
save-text("out/block-${i}.txt", "Hello");

Paths/properties are also possible:

let s = status();
print("ready=${s.injectReady}");

Blocks

Blocks can be closed with } (preferred) or end (legacy).

Used by:

  • if
  • for

Examples:

if (true) {
    print("ok");
}
if true
    print "ok"
end

Control Flow

if / else if / else

Modern style

if (x == 1) {
    print("one");
} else if (x == 2) {
    print("two");
} else {
    print("other");
}

Legacy style

if $x == 1
    print "one"
else if $x == 2
    print "two"
else
    print "other"
end

Comparison Operators

Supported:

  • ==
  • !=
  • >
  • >=
  • <
  • <=

Examples:

if (x == 10) {
    print("ok");
}
if ($gain > 0)
    print "positive"
end

Logical Operators

Supported:

  • and
  • or
  • not

Examples:

if ($a == 1 and $b == 2)
    print "both match"
end
if ($a == 1 or $b == 2)
    print "at least one matches"
end
if not ($x == 0)
    print "not zero"
end

Parentheses in Conditions

Conditions may be parenthesized:

if (($a == 1 and $b == 2) or $c == 3) {
    print("ok");
}

for Loop

Syntax:

for <variable> in <start>..<end>
    ...
end

Modern style:

for (let i in 0..5) {
    print(i);
}

Legacy style:

for i in 0..5
    print $i
end

Descending ranges are also supported:

for i in 5..0
    print $i
end

Commands

Connection

connect

Connects to the default proxy:

connect
connect()

Or with explicit parameters:

connect("127.0.0.1", 19081, "127.0.0.1", 19082);

Legacy style:

connect "127.0.0.1" 19081 "127.0.0.1" 19082

disconnect

disconnect
disconnect()

Status / Session

status

Reads the proxy status.

status
let s = status()
print($s.sessionActive)
print($s.injectReady)

attach-session / attachSession

Attaches to an existing GUI/DSP session without rebuilding it via reset-session. This is the recommended mode when the original GUI should stay connected.

connect();
attachSession();
handshake();

reset-session / resetSession

reset-session
resetSession()

ensure-session / ensureSession

ensure-session
ensureSession()

clear-frames / clearFrames

clear-frames
clearFrames()

handshake

Sends:

  • handshake_init
  • device_info
  • system_info
handshake
handshake()

handshake-init / handshakeInit

let r = handshakeInit();
print(r.payloadHex);

device-info / deviceInfo

let r = deviceInfo();
print(r.payloadHex);

system-info / systemInfo

let r = systemInfo();
print(r.payloadHex);

Login

login

login 1234
login("1234")

The PIN must be exactly 4 digits.


Read Block

read-block / readBlock

let b = readBlock(0x00);
print(b.payloadHex);

Allowed range:

  • 0x00 to 0x1C

Send Payload

Legacy Syntax

send-payload "00 01 03 35 04 01"
tx "00 01 03 35 04 01"
write "00 01 03 35 04 01"

With expected response:

send-payload "00 01 02 27 00" expect 0x24

Function Style

write("00 01 03 35 04 01")
write("00 01 02 27 00", 0x24)

If an expected command is specified, response matching is strict.


GUI Capture

gui-connect / guiConnect

Connects the GUI sniffer to the stream.

gui-connect
guiConnect()

or:

guiConnect("127.0.0.1", 19081);

gui-disconnect / guiDisconnect

gui-disconnect
guiDisconnect()

gui-capture / guiCapture

Starts capture without interactive input. The engine arms the sniffer, waits a configurable time window for the GUI action, and then automatically collects until the quiet phase.

Preferred modern style:

let cap = guiCapture("Please perform the GUI action", 15000, 1500, 12000);
print(cap);

Parameters:

  • optional note text
  • optional actionWindowMs
  • optional quietMs
  • optional maxWaitMs

Examples:

let cap = guiCapture();
let cap = guiCapture(15000);
let cap = guiCapture("Move one slider now");
let cap = guiCapture("Move one slider now", 15000, 1500, 12000);

Legacy style is also supported:

let cap = gui-capture "Move one slider now" 15000 1500 12000

gui-begin-capture / guiBeginCapture

guiBeginCapture();
sleep(5000);
let cap = guiEndCapture(1500, 12000);

gui-end-capture / guiEndCapture

let cap = guiEndCapture();
let cap = guiEndCapture(1500, 12000);

Assertions

assert

assert $x == 10
assert not ($resp == null)

If the condition fails, the script aborts with an error.


Waiting

sleep

sleep 500
sleep(500)

Unit: milliseconds


Output

print

print("Hello");
print(resp);
print(resp.payloadHex);

Save to File

save-text / saveText

Syntax:

save-text <path> <expr>
saveText(path, expr)

Examples:

saveText("out/result.txt", "Hello World");
save-text "out/block.txt" $resp.payloadHex

save-diff-report / saveDiffReport

Writes a formatted diff report to a file and returns the absolute path.

saveDiffReport("out/diff.txt", beforeBytes, afterBytes);

Legacy style:

save-diff-report "out/diff.txt" $before $after

save-capture-read-blocks / saveCaptureReadBlocks

Writes all detected read_block responses from a capture to files.

Generated files:

  • block-XX.hex.txt
  • block-XX.ascii.txt

Example:

saveCaptureReadBlocks(cap, "out/capture-blocks");

Legacy style:

save-capture-read-blocks $cap "out/capture-blocks"

Expressions

The script language supports two styles:

  • legacy style
  • function style

Both can be mixed.


Legacy Style

Examples:

status
read-block 0x00
login 1234
len $cap.frames
hex $resp.payload

Function Style

Examples:

status()
readBlock(0x00)
login("1234")
len(cap.frames)
hex(resp.payload)

Decode / Reverse-Engineering Helper Functions

These helpers are especially useful for fast DSP decoding.

read-block-index / readBlockIndex

Returns the read-block index if the value is a read-block response, otherwise null.

Works on:

  • ProxyResponse
  • SniffedFrame
  • payload byte[]
let r = readBlock(0x04);
print(readBlockIndex(r));

read-block-payload / readBlockPayload

Looks up the last captured read-block response for the given block index and returns its payload bytes.

Input:

  • GuiCaptureResult
  • block index
let payload = readBlockPayload(cap, 0x04);
print(hex(payload));

Returns null if not found.


cmd

Returns the command byte as integer.

Works on:

  • ProxyResponse
  • SniffedFrame
  • payload byte[]
print(cmd(resp));
print(cmd(frame));

payload

Returns payload bytes.

Works on:

  • ProxyResponse
  • SniffedFrame
  • payload byte[] passthrough
let p = payload(resp);
print(hex(p));

raw

Returns raw frame bytes.

Works on:

  • ProxyResponse
  • SniffedFrame
  • payload byte[] passthrough
let r = raw(resp);
print(hex(r));

payload-hex / payloadHex

Shortcut for payload bytes as hex text.

print(payloadHex(resp));
print(payloadHex(frame));

payload-ascii / payloadAscii

Shortcut for payload bytes as ASCII preview.

print(payloadAscii(resp));
print(payloadAscii(frame));

diff-bytes / diffBytes

Formats byte-level differences between two byte arrays.

print(diffBytes(before, after));

Example output:

0x12: 01 -> 02
0x13: 10 -> 11

diff-u16le / diffU16le

Formats little-endian 16-bit diff candidates between two byte arrays.

print(diffU16le(before, after));

Example output:

u16le@0x12: 280 -> 308 (0x0118 -> 0x0134)

diff-report / diffReport

Builds a full decode-oriented report:

  • before/after length
  • byte diffs
  • u16le candidates
print(diffReport(before, after));

changed-offsets / changedOffsets

Returns a list of integer offsets that changed.

let offsets = changedOffsets(before, after);
print(offsets);
print(len(offsets));

Helper Functions

len

len("abc")
len($bytes)
len($cap.frames)

contains

contains("abcdef", "cd")
contains($resp.payloadHex, "2C")

starts-with / startsWith

startsWith("abcdef", "abc")

ends-with / endsWith

endsWith("abcdef", "def")

upper / lower / trim

upper("abc")
lower("ABC")
trim("  test  ")

join

join(split("a,b,c", ","), " | ")

at

at("Hello", 1)
at($list, 0)
at($bytes, 3)

split

split("a,b,c", ",")

replace

replace("Hello World", "World", "DSP")

Byte/Hex Functions

bytes

Converts hex to byte[].

let p = bytes("00 01 03 35 04 01")

hex

Outputs bytes as hex.

hex($resp.payload)
hex($resp.payload, 0, 4)

slice

slice($resp.payload, 0, 8)

ascii

ascii($resp.payload, 0, 8)
ascii($resp.payload, 0, 8, true)

In legacy style, trimzero is also possible:

ascii $resp.payload 0 8 trimzero

u8 / u16le / u32le

u8($resp.payload, 0)
u16le($resp.payload, 4)
u32le($resp.payload, 8)

All multi-byte values are little endian.


Return Objects and Properties

ProxyStatus

Properties:

  • sessionActive
  • injectReady
  • rawResponse

Example:

let s = status()
print $s.sessionActive
print $s.injectReady

ProxyResponse

Properties:

  • raw
  • payload
  • checksumOk
  • command
  • commandHex
  • readBlockIndex
  • rawHex
  • payloadHex
  • payloadAscii
  • rawLen
  • payloadLen

Example:

let r = readBlock(0x00);
print(r.commandHex);
print(r.readBlockIndex);
print(r.payloadHex);
print(r.payloadLen);

GuiCaptureResult

Properties:

  • frames
  • totalFrames
  • writeCount
  • responseCount
  • isEmpty
  • firstFrame
  • lastFrame
  • firstWrite
  • lastWrite
  • firstResponse
  • lastResponse
  • writes
  • responses
  • readBlockResponses

Example:

let cap = guiEndCapture();
print(cap.totalFrames);
print(cap.lastWrite);
print(cap.readBlockResponses);

SniffedFrame

Properties:

  • direction
  • frame
  • payload
  • frameHex
  • payloadHex
  • payloadAscii
  • command
  • commandHex
  • readBlockIndex
  • payloadLen
  • checksumOk

Example:

let f = cap.lastWrite;
print(f.commandHex);
print(f.payloadHex);
print(f.frameHex);

byte[]

A byte array also supports property access in paths:

  • hex
  • ascii
  • len
  • command
  • commandHex
  • readBlockIndex

Example:

let p = bytes("00 01 02 24 04");
print(p.hex);
print(p.commandHex);
print(p.readBlockIndex);

Useful Capture Functions

first-write / last-write

firstWrite(cap)
lastWrite(cap)

first-response / last-response

firstResponse(cap)
lastResponse(cap)

writes / responses

writes(cap)
responses(cap)

capture-count / capture-write-count / capture-response-count

captureCount(cap)
captureWriteCount(cap)
captureResponseCount(cap)

capture-frame

captureFrame(cap, 0)

last-write-excluding

lastWriteExcluding(cap, 0x40)

recent-writes

recentWrites(cap, 10)

recent-writes-excluding

recentWritesExcluding(cap, 10, 0x40)

Truthiness

The following values are considered false:

  • null
  • false
  • numeric 0
  • empty string
  • empty byte array
  • empty list

Everything else is true.

Example:

if $resp
    print "Response present"
end

Complete Examples

Example 1: Prepare session and read a block

connect();
resetSession();
ensureSession();
handshake();

let resp = readBlock(0x00);
print(resp);
print(resp.payloadHex);
print(resp.payloadAscii);

Example 2: Login and write

connect();
resetSession();
ensureSession();
handshake();
login("1234");

let resp = write("00 01 03 35 04 01");
print(resp);

Example 3: Decode a GUI action

guiConnect();

let cap = guiCapture("Please perform the action in the original GUI now", 15000, 1800, 12000);
print(cap);
print(cap.lastWrite);
print(cap.writes);

Example 4: Read multiple blocks

connect();
resetSession();
ensureSession();
handshake();

for (let i in 0..5) {
    let r = readBlock(i);
    print("Block ${i}");
    print(r.payloadHex);
}

Example 5: Conditions

connect();
let s = status();

if (s.sessionActive and s.injectReady) {
    print("Proxy is ready");
} else {
    print("Proxy is not ready");
}

Example 6: Diff two payloads

let before = bytes("00 01 04 34 04 18 01");
let after  = bytes("00 01 04 34 04 34 01");

print(diffBytes(before, after));
print(diffU16le(before, after));
print(diffReport(before, after));
print(changedOffsets(before, after));

Example 7: Save a decode diff report

let before = bytes("00 01 04 34 04 18 01");
let after  = bytes("00 01 04 34 04 34 01");

let path = saveDiffReport("out/gain-diff.txt", before, after);
print(path);

Example 8: Extract captured read-block payload

let cap = guiCapture("Trigger one GUI read action now", 15000, 1500, 12000);
let p = readBlockPayload(cap, 0x04);

if (p != null) {
    print(hex(p));
    print(p.readBlockIndex);
}

Example 9: Event-based GUI action capture

guiConnect();

let cap = guiActionCapture("Toggle one GUI value now", 45000, 1200, 6000);
print(cap);
print(lastWriteExcluding(cap, 0x40));

Example 10: Fader recorder helpers

guiConnect();

let cap = guiActionCapture("Move the InA gain fader now", 45000, 1200, 6000);
let series = writesByCommandAndChannel(cap, 0x34, 0x00);

print(payloadSeries(series));
print(changingOffsetsAcrossWrites(series));
print(u16Series(series, 5));

Example 11: Start Gate decoding with isolated GUI capture

Use the dedicated Gate capture scripts to identify the real GUI write command before readback localization:

script-example/64-auto-capture-ina-gate-threshold-clean.dspd
script-example/65-auto-capture-ina-gate-attack-clean.dspd
script-example/66-auto-capture-ina-gate-hold-clean.dspd
script-example/67-auto-capture-ina-gate-release-clean.dspd

Recommended workflow:

  1. Move exactly one InA gate control once.
  2. Inspect out/clean/.../last-write.txt.
  3. Inspect recent-interesting-writes.txt for related command series.
  4. If the GUI emitted read-block responses, inspect the saved blocks/.
  5. After the Gate write command is known, create focused before/after read-diff scripts like the existing mute, phase, and gain decode scripts.

Event-Based Capture And Fader Helpers

gui-action-capture / guiActionCapture

Event-based capture mode for live decoding.

It:

  • waits for the first real GUI write
  • ignores command 0x40
  • auto-stops after the action becomes quiet
let cap = guiActionCapture("Move one slider now", 45000, 1200, 6000);
print(lastWriteExcluding(cap, 0x40));

writes-by-command / writesByCommand

let gainWrites = writesByCommand(cap, 0x34);

writes-by-command-and-channel / writesByCommandAndChannel

let inAGain = writesByCommandAndChannel(cap, 0x34, 0x00);

payload-series / payloadSeries

print(payloadSeries(series));

u16-series / u16Series

print(u16Series(series, 5));

changing-offsets-across-writes / changingOffsetsAcrossWrites

print(changingOffsetsAcrossWrites(series));

Typical Errors

Missing end or }

if true
    print "ok"

Error: block not closed.


Invalid range expression

for i in 0-5
    print $i
end

Correct:

for i in 0..5
    print $i
end

Invalid hex data

write("00 01 0G")

Error: 0G is not valid hex.


Invalid variable path

print $resp.unknownField

Error: the property does not exist.


Quick Reference

Statements

  • let <var> = <expr>
  • connect
  • disconnect
  • gui-connect
  • gui-disconnect
  • status
  • attach-session
  • reset-session
  • ensure-session
  • clear-frames
  • handshake
  • assert <condition>
  • sleep <ms>
  • save-text <path> <expr>
  • save-diff-report <path> <before> <after>
  • save-capture-read-blocks <capture> <dir>
  • print <expr>

Control Flow

  • if ...
  • else if ...
  • else
  • for i in a..b
  • end
  • { ... }

Comparison

  • ==
  • !=
  • >
  • >=
  • <
  • <=

Logic

  • and
  • or
  • not

Decode Helpers

  • read-block-index
  • read-block-payload
  • cmd
  • payload
  • raw
  • payload-hex
  • payload-ascii
  • diff-bytes
  • diff-u16le
  • diff-report
  • changed-offsets
  • gui-action-capture
  • writes-by-command
  • writes-by-command-and-channel
  • payload-series
  • u16-series
  • changing-offsets-across-writes

Byte / Helpers

  • bytes
  • hex
  • slice
  • ascii
  • u8
  • u16le
  • u32le
  • len
  • contains
  • starts-with
  • ends-with
  • upper
  • lower
  • trim
  • join
  • at
  • split
  • replace