PBRScript is a scripting language designed to simplify the process of extending Pokémon Battle Revolution's code. PBRScript provides a high-level syntax for implementing new game logic while still allowing for direct interaction with the game's existing assembly.
PBRScript files are text files with the extension .pbr
. To compile a .pbr
file, run build.py
and call build(path, address)
, where path
is the filepath to your .pbr
file and address
is the memory address the resulting assembly code will be inserted at (note that this insertion is not handled by PBRScript). The program will create two output files, a .asm
file containing the resulting assembly code and a .bin
file containing the corresponding machine code, each with the same name as the original .pbr
file.
Included in the repository is pbrscript-npp.xml
, a User-Defined Language file for use with Notepad++ that provides syntax-highlighting for the language:
Jump links: Metadata tags | Comments | Imports | Function definitions | Numeric literals | Variable assignment | Array allocation | Pointer | Expressions | Conditions | Function calls | If-Elif-Else blocks | For loops | While loops | Switch blocks | Memory Reading/Writing
<region="ntsc-u">
All PBRScript files must start with a region tag indicating which region the script targets. The value must be one of ntsc-j
, ntsc-u
, or pal
.
// this is a comment
Comments are preceded by //
followed by a space. They may appear at the end of lines or stand alone on their own line.
import "path/to/file.pbr"
Imports provide the ability to break up programs across multiple files. An import statement consists of the import
keyword followed by the path to the .pbr
file to be imported, surrounded by quotes.
def FOO(int <intParam>, float <floatParam>):
// function body
return <returnValue>
Functions are defined using the def
keyword, followed by a function name and a list of comma-separated parameters in parentheses. Parameters should be preceded by either the int
or float
keywords to indicate the type of the incoming parameter. Function definitions end with the return
keyword, which may optionally be followed by a variable to be returned by the function.
Note: function names must begin with an upper-case letter and can contain a combination of numbers, letters, and underscores.
PBRScript supports both decimal and hexadecimal int
literals. Hexadecimal literals should be prepended by 0x
(e.g. 0xabcdef
).
Note: float
literals are not currently supported.
set <intVar> = <expression>
fset <floatVar> = <expression>
Values may be assigned to variables using the keyword set
(for int variables) or fset
(for float variables). See the Expressions section for more information on what may appear on the right side of a set
/fset
statement.
Note: variable names must begin with a lower-case letter and can contain a combination of numbers, letters, and underscores.
alloc <arrayName> = (int|float)[<size>]
(set|fset) <arrayName>[<index>] = <expression>
Arrays are defined using the alloc
keyword, followed by a name for the array and a =
symbol. On the right of the =
symbol should be a type keyword (int
or float
) followed by the size of the array in square brackets (e.g. int[10]
, float[8]
). Array elements can be accessed or assigned to the array name followed by the zero-based index of the element in square brackets (e.g. my_array[0]
).
Note: array names follow the same rules as variable names (see above).
&<variable|function|array>
Pointers can be created by prepending a function or variable/array name with a &
symbol. This can be used to pass array and function references as arguments to calls. Function pointers can also be assigned to variables, in which case the variable can be used as the target of a call itself (e.g. call &<variable>(...)
).
<var1> + 2 * <var2>
<var1> mask 0x3f0 insert <var2>
Expressions consist of a string of variables and numbers connected by mathematical operations. Beyond the basic arithmetic operations +
, -
, *
, and /
, the following int
-only operations are avaiable.
Symbol | Description |
---|---|
mod |
modular division |
mask |
bitwise "and" |
lshift |
left bit-shift |
rshift |
right bit-shift |
insert |
when combined with mask , inserts bits at a given position in a number or variable |
((int|float))<variable>
Variables can be cast between int
and float
types by preceding the variable name with the desired type in parentheses.
<a> gt <b> and <c> le <d>
<a> eq <b> or <c> ne <d>
Conditions are used in if
, elif
, and while
constructs. They consist of a comparison followed optionally by either the and
or or
keywords and a second comparison. Comparisons are of the form <expr1> <mnemonic> <expr2>
. See the Expressions section for more information on the expressions that can appear inside conditions.
The list of mnemonics available for use in comparisons are listed in the following table.
Mnemonic | Name |
---|---|
eq |
equal |
ne |
not equal |
gt |
greater than |
ge |
greater than or equal |
lt |
less than |
le |
less than or equal |
call <FUNCTION_NAME>(<param1>, <param2>, ...)
Functions can be called using the call
keyword, followed by the function name and a list of comma-separated arguments. Arguments can be variables, numbers, or pointers. Functions from PBR's existing assembly can be called by prepending the function's memory address (in hexadecimal) by FUN_
(e.g. FUN_80004000
).
if <condition>:
// do something
elif <condition>:
// do something
else:
// do something
end
An if-elif-else
block consists of an if
statement, any number of optional elif
statements, an optional else
statement, and a terminating end
keyword. See the Conditions section for more information on the conditions that can be used with if
/elif
blocks.
for <var> in range(<count>):
// do something
end
A for
block consists of the for
keyword, an iterand variable, the in
keyword, and a range
whose argument must be either a number or a variable. The same variable cannot be used both as the iterand and the argument to range
. The block is terminated by the end
keyword. The break
and continue
keywords can be used to exit a loop or skip to the next iteration, respectively.
Note: nested loops are not currently supported.
while <condition>:
// do something
end
A while
block consists of the while
keyword followed by a conditional statement. See the Conditions section for more information on the conditions that can be used with while
blocks. The block is terminated by the end
keyword. The break
and continue
keywords can be used to exit a loop or skip to the next iteration, respectively.
Note: nested loops are not currently supported.
switch <var>:
case <val1>:
// do something
break
case <val2>:
case <val3>:
// do something
break
default:
// do something
break
end
A switch
block consists of the switch
keyword followed by a variable to switch on. A switch
can contain any number of case
blocks along with a single optional default
block; each block should be terminated using the break
keyword. switch
blocks support case fall-through, so multiple cases can trigger the same block of code.
Note: nested switch
-statements are not currently supported.
lwz <var>, <offset>(<base>)
sth <var>, <offset>(<base>)
Memory accesses are performed using a read/write mnemonic followed by a variable name, a comma, and an address construct. This expression consists of either a number or variable followed by a variable in parentheses, with the address read from/wrote to calculated as (base) + (offset).
The full list of supported mnemonics can be found in data/ops.py. To learn more about these mnemonics, visit https://www.ibm.com/docs/en/aix/7.3?topic=reference-appendix-f-powerpc-instructions.