The VG Language™ - Electry/VitaGrafix GitHub Wiki

To facilitate user-made game patches without having to recompile the whole plugin, a domain specific language was created. Similar, but somewhat simplified to that of VitaCheat.

Written in patchlist.txt, patches are parsed and applied during game boot-up.


Section header

A patch for each game starts with a header. Header identifies SKU/region (TITLE ID), the executable name (because several “collections” use the same TITLE ID for multiple games in their series) and the game version (indirectly, in a form of module NID).


[PCSA00001, eboot.bin, 0x12345678]
PCSA00001 game's TITLE ID required
eboot.bin a part of executable's full path recommended
0x12345678 main module NID, 8 hex digits with '0x' prefix recommended

Executable name/path is used to differentiate between games that share a single TITLE ID (e.g. collections).

NID is used to inform user about potentional version mismatch (e.g. when game gets a new update), instead of applying patches to wrong/old memory addresses and then crashing.

If multiple regions/SKUs share the same offsets, having to duplicate patches would be wasteful. You can freely stack multiple headers after each other, like shown below. Patches will then be applied for all matching games/regions.

[PCSA00001, eboot.bin, 0x12345678]
[PCSB00023, eboot.bin, 0x87654321]
...

Feature type

Header is followed by patch feature/type marking. This indicates that subsequent patches shall only be applied if user has enabled said feature in their configuration file.

Use one of the following:

@FB
@IB
@FPS
@MSAA

Patches

The rules for patches are simple. Each patched memory location is on a separate line, starts with a relative address (segment:offset) followed by the new value.

0:0x1234 0xDEADBEEF

results in 4 bytes at seg000 start + 0x1234 address being patched to EF BE AD DE.



Datatypes

VitaGrafix internally always works with one of the following datatypes:

  • Primitive types:
    • int (32-bit signed integer)
    • uint (32-bit unsigned integer)
    • float (32-bit floating point number)
  • Raw type:
    • bytes (size up to 32 B)

You can cast between those types freely using casting functions (see Explicit type conversion).

Immediate values are always parsed as

  • int – when + or - sign precedes 1 or more decimal digits (with no space inbetween)
    • OK - +1, -1
    • KO - + 1
  • uint – when:
    • hex number is prefixed with 0x or 0X
    • octal number is prefixed with 0
    • decimal integer does not have prefix or suffix
    • OK - 0xCAFE, 0876, 123
    • KO - CAFE
  • float – when 1 or more decimal digits (optionally with preceding + or - sign) are followed by:
    • . and optionally:
      • 1 or more digits after the decimal point and optionally f
      • f
    • f
    • OK - 1., 1.2, 1.2f, 1f, 1.f, -1.0, -2f, +123.4f
    • KO - .1, .1f, 1 . 0 f
  • bytes – when 1 or more (up to 32) 8-bit hex values (without the 0x prefix) are followed by r
    • OK - DEADr, DE AD r
    • KO - DEAD, 0xDEADr, 0xDE 0xAD r

Implicit type conversion

The interpreter makes use of implicit conversion when necessary. This is done when applying infix operator or math function (arity >= 2) to following operand types (in no particular order):

operand 1 operand 2, 3, ... result example
float int or uint all casted to float float 100 / 2.0 equals 50.0
int uint all casted to int int 100 / -2 equals -50
uint uint ONLY if result is < 0 int 1 - 2 equals -1
any any ONLY when applying . (concat) bytes 1 . 2 equals 0100000002000000r

Explicit type conversion

While working with values, changing the datatype manually might sometimes be desirable.

To convert value of any type to one of the primitive types use following functions:

function argument type result type example
int(value) any int int(-2.0) equals -2
uint(value) any uint uint(FFr) equals 255
float(value) any float float(2) equals 2.0

To convert value of any type to raw type use:

function argument type result type result size (B) example
int8(value) any bytes 1 int8(-128) equals 80 r
int16(value) any bytes 2 int16(-32768) equals 00 80 r
int32(value) any bytes 4 int32(-2147483648) equals 00 00 00 80 r
uint8(value) any bytes 1 uint8(255) equals FF r
uint16(value) any bytes 2 uint16(65535) equals FF FF r
uint32(value) any bytes 4 uint32(4294967295) equals FF FF FF FF r
fl32(value) any bytes 4 fl32(12345.0) equals 00 E4 40 46 r

If you don't wish to cast the value, but rather just reinterpret bytes, use:

function value type result type result size (B) example
raw(value) any bytes sizeof(value) raw(1.0 >> 16) equals 80 3F 00 00 r
rawn(value, n) any bytes n rawn(1.0 >> 16, 2) equals 80 3F r

Infix operators

Operator precedence is honored, order is similar to C-like languages. Brackets (parentheses) are allowed and evaluated first.

operator operand type description example
+ primitive addition 1 + 2 equals 3
- primitive subtraction 1 - 2 equals -1
* primitive multiplication 2 * 3 equals 6
/ primitive division 3 / 2.0 equals 1.5
% int or uint modulo (division remainder) 123 % 23 equals 8
| primitive bitwise OR 14 | 5 equals 15
^ primitive bitwise XOR 14 ^ 5 equals 11
& primitive bitwise AND 14 & 5 equals 4
<< primitive bitwise left shift 14 >> 2 equals 3
>> primitive bitwise right shift 14 << 2 equals 56
. raw concatenation DEr . ADr equals DE AD r
* primitive * raw raw repeat nop * 2 equals 00 BF 00 BF r

Math functions

function description example
abs(value) absolute value abs(-1.0) equals 1.0
acos(rad) arc cosine (inverse cosine) in radians acos(-0.5) equals 2.094395
align(value, al) aligns value to a multiple of al align(720, 32) equals 736
asin(rad) arc sine (inverse sine) in radians asin(0.5) equals 0.523599
atan(rad) arc tangent (inverse tangent) in radians atan(1.0) equals 0.785398
atan2(y, x) arc tangent of y/x in radians atan2(0.5, 0.4) equals 0.896055
ceil(value) nearest integer greater than value ceil(1.23) equals 2.0
cos(rad) cosine in radians cos(0.5) equals 0.877583
cosh(rad) hyperbolic cosine in radians cosh(0.5) equals 1.127626
exp(value) exponential (Euler's number) raised to value exp(2.0) equals 7.389056
floor(value) nearest integer lesser than value floor(1.23) equals 1.0
ln(value) natural logarithm of value ln(10.0) equals 2.302585
log10(value) base 10 logarithm of value log10(10.0) equals 1.0
min(a, b) the smaller of a and b min(123, 23) equals 23
max(a, b) the greater of a and b max(123, 23) equals 123
pow(base, power) base raised to the power pow(2, 4) equals 16
round(value) nearest integer to value (0.5 rounds up) round(1.23) equals 1.0
sin(rad) sine in radians sin(0.5) equals 0.479426
sinh(rad) hyperbolic sine in radians sinh(0.5) equals 0.521095
sqrt(value) square root of value sqrt(10.0) equals 3.162278
tan(rad) tangent in radians tan(0.5) equals 0.546302
tanh(rad) hyperbolic tangent in radians tanh(0.5) equals 0.462117

Math constants

constant description defined as
pi The number π 3.141593
e Euler's number 2.718282

User configuration macros

These return values based on user configuration entry (in config.txt) for current game.

macro description config option
fb_w Framebuffer width in pixels FB
fb_h Framebuffer height in pixels FB
ib_w Internal buf. width in pixels IB
ib_h Internal buf. height in pixels IB
ib_wi(index) Internal buf. width in pixels (indexed from 0) IB (multiple)
ib_hi(index) Internal buf. height in pixels (indexed from 0) IB (multiple)
vblank Vertical blanking interval (1 for 60Hz, 2 for 30, etc...) FPS
msaa SceGxmMultisampleMode (0 for no MSAA, 1 for 2x, 2 for 4x) MSAA

Instruction encoders

These encode few of the commonly used instructions. Result type is always raw, size depends on encoder. All are unconditional.

Register arguments are expected to be decimal integers.
ARM within 0 <= reg <= 14 range, where 0 denotes R0 or A1, 13 = SP and 14 = LR.
VFP within 0 <= reg <= 31 range, where 0 denotes S0.

encoder description instr. set
(enc.)
size (B)
t1_mov(reg, imm) Insert an immediate value to a register.

MOV <Rd>,#<imm8>

reg - destination
imm - immediate value (up to 8-bit)
Thumb
(T1)
2
t2_mov(sf, reg, imm) Insert an immediate value to a register.

MOV{S}.W <Rd>,#<const>

sf - set condition flags (0 = MOV.W, 1 = MOVS.W)
reg - destination
imm - immediate value (constant)
Thumb-2
(T2)
4
t3_mov(reg, imm) Insert an immediate value to a register.

MOVW <Rd>,#<imm16>

reg - destination
imm - immediate value (up to 16-bit)
Thumb-2
(T3)
4
t1_movt(reg, imm) Insert 16-bit immediate value to the top halfword of a register.
Bottom halfword is not affected.

MOVT <Rd>,#<imm16>

reg - destination
imm - immediate value (up to 16-bit)
Thumb-2
(T1)
4
mov32(reg, imm, gap) Pseduo-instruction, generates MOVW, MOVT pair,
allows you to load any 32-bit value into a register.

MOVW <Rd>,#<imm16>
...
MOVT <Rd>,#<imm16>

reg - destination
imm - immediate value (up to 32-bit)
gap - gap size (in bytes) between MOVW and MOVT (max. 24 B)
Thumb-2
(T3 + T1)
4 + gap + 4
a1_mov(sf, reg, imm) Insert an immediate value to a register.

MOV{S} <Rd>,#<const>

sf - set condition flags (0 = MOV, 1 = MOVS)
reg - destination
imm - immediate value (constant)
Arm
(A1)
4
a2_mov(reg, imm) Insert an immediate value to a register.

MOVW <Rd>,#<imm16>

reg - destination
imm - immediate value (up to 16-bit)
Arm
(A2)
4
t2_vmov(reg, imm) Insert a floating-point immediate value in a single-precision register.

VMOV.F32 <Sd>, #<imm>

reg - destination (VFP)
imm - float immediate (+/-n * 2^-r, 16 <= n <= 31, 0 <= r <= 7)
VFPv3
(T2/A2)
4
bkpt Breakpoint (enter debug state).

BKPT #0
Thumb
(T1)
2
nop No operation.

NOP
Thumb-2
(T1)
2

Comments

Comments always start with # character. They can, but don't need to be on a separate line.

# This is a comment
[PCSA00001, eboot.bin, 0x12345678] # This is also a valid comment
...

Backwards compatibility with v4.x

All older patches are compatible with v5.x, however, support for the old syntax/style might be dropped in future versions. It is highly advised to use the new syntax for new patches.

Changes:

v4.x: nop() and bkpt() encoders must have brackets
v5.x: nop and bkpt encoders are classified as constants, constants do not have brackets
example:

  • nop() => nop

v4.x: no support for math operators/fns/precedence, macros start with < and end with >
v5.x: infix operators and math functions are available
example:

  • <+,1,2> => 1 + 2
  • <*,<&,<+,<fb_w>,31>,0xFFFFFFE0>,4> => align(fb_w, 32) * 4

v4.x: config-option macros start with < and end with >
v5.x: config-option macros are used just like regular constants and functions
example:

  • <fb_w> => fb_w
  • <ib_w> => ib_w
  • <ib_w,2> => ib_wi(2)

v4.x: no extra spaces are allowed, comments have to be on a separate line
v5.x: spaces are ignored, anything after # too (until EOL)
example:

  • t2_mov(1,1,<fb_w>) => t2_mov(1, 1, fb_w)
  • # Comment
    0:0x1234 nop is valid just like 0:0x1234 nop # Comment

v4.x: bytes() is used to parse raw bytes
v5.x: r suffix is used to parse raw bytes, raw() is used for reinterpreting/conversion
example:

  • bytes(DE AD BE EF) => DEADBEEF r

and more...


Examples

Here are some examples of valid patches.

[PCSF00243,eboot.bin]            # Killzone Mercenary [EU 1.12]
[PCSF00403,eboot.bin]            # Killzone Mercenary [EU 1.12]
[PCSA00107,eboot.bin,0x0F9D3B7C] # Killzone Mercenary [US 1.12]
[PCSC00045,eboot.bin]            # Killzone Mercenary [JP 1.12]
[PCSD00071,eboot.bin]            # Killzone Mercenary [ASIA 1.12]
@IB
0:0x15A5C8 nop *4
1:0xD728 uint32(ib_w) . uint32(ib_h)
1:0xD730 uint32(ib_w) . uint32(ib_h)
# God of War Collection [EU 1.00]
[PCSF00438,GOW1.self,0x8638ffed]
@FB
0:0x9E212 t2_mov(1, 4, fb_w)
0:0x9E21A t2_mov(1, 2, fb_h)
0:0x9F0F0 t2_mov(1, 0, fb_w)
0:0x9F0F8 t2_mov(1, 1, fb_h)
0:0xA31C6 t2_mov(1, 7, fb_w)
0:0xA31CC t2_mov(1, 1, fb_h)
0:0xCEF06 t2_mov(1, 0, fb_w)
0:0xCEF0E t2_mov(1, 2, fb_h)
0:0xA1098 t2_mov(1, 14, fb_h)
@FPS
0:0x9E228 t1_mov(0, vblank)
# LEGO Star Wars: The Force Awakens [EU 1.00]
[PCSB00877,eboot.bin]
@IB
0:0x2241C4 t2_mov(1, 1, 0xA00000)
0:0x1F313E t2_mov(1, 4, ib_w)
0:0x1F3144 t2_mov(1, 5, ib_h)
1:0x4650 uint32(ib_w) . uint32(ib_h)
# Fix touchscreen/touchpad pos calc
0:0x223AF6 t2_mov(0, 0, 640) . nop *3
0:0x223B36 t2_mov(0, 0, 368) . nop *3

IO/patcher/syntax errors

The parser will try to warn you if you make a mistake. Nevertheless, it isn't perfect so you shouldn't rely on it.

Example:

⚠️ **GitHub.com Fallback** ⚠️