Table of opcodes - AtomCrafty/MajiroTools GitHub Wiki

Table of opcodes

A-B C-O P-Z

add. operator +
alloca
and operator &
andl operator &&
argcheck
bge.case case <
bgt.case case <=
ble.case case >
blt.case case >=
bne.case case ==
br goto
brfalse if()
brtrue if(!)
br.case switch()
bsel.1
bsel.2
bsel.3
bsel.4
bsel.5
bsel.clr
bsel.x
bsel.jmp.4

call func()
callp func()
ceq. operator ==
cge. operator >=
cgt. operator >
cle. operator <=
clt. operator <
cne. operator !=
conv. type cast
ctrl \a \a()
div. operator /
ld load var
ldc. literal
ldelem load var[]
ldstr literal
line
mul. operator *
neg. unary -
not unary ~
notl unary !
or operator |
orl operator ||

pop
pos. unary +
proc
rem operator %
ret return()
shl operator <<
shr operator >>
st. assign =
st._. compound _=
stp. assign =
stp._. compound _=
stelem. assign[] =
stelem._. compound[] _=
stelemp. assign[] =
stelemp._. compound[] _=
sub. operator -
switch
syscall builtin()
syscallp builtin()
text
xor operator ^


Opcode Definitions


Operators

click to hide/show section

All float (.r) operators accept int values for any/all operands. A cast to float with conv.r is not required. As such, float operators are described as accepting numeric values. The only instance where the behavior changes between int and float is for Divide by zero.

All string comparisons == != < <= > >= are case-insensitive. Use syscall $5c823701 $strcmp(_a$, _b$) for case-sensitive comparisons.


Arithmetic Operators

click to hide/show subsection

add. ^ 

add.i, add.r, add.s (a + b)

Add values. Concatenation is performed for strings.

sub. ^ 

sub.i, sub.r (a - b)

Subtract values.

mul. ^ 

mul.i, mul.r (a * b)

Multiply the last two operands on the stack, and push the result.

mul. examples
; (2 * 30) -> 60
ldc.i       2               ; left operand   (2)
ldc.i       30              ; right operand  (30)
mul.i                       ; multiply   (2 * 30) -> 60
; (3.5 * 2) -> 7.0
ldc.r       3.5             ; left operand   (3.5)
ldc.i       2               ; right operand  (2)
mul.r                       ; multiply   (3.5 * 2) -> 7.0

div. ^ 

div.i, div.r (a / b)

Divide the last two operands on the stack, and push the result.

div. examples
; (47 / 3) -> 15
ldc.i       47              ; left operand   (47)
ldc.i       3               ; right operand  (3)
div.i                       ; divide     (47 / 3) -> 15 (truncated)
; (10.5 / 2) -> 5.25
ldc.r       10.5            ; left operand   (10.5)
ldc.i       2               ; right operand  (2)
div.r                       ; divide     (10.5 / 2) -> 5.25

Divide by zero

Dividing by zero will not crash, but will produce extremely unexpected results.

If the divisor is 0 and an int: (dividend * 10000) (equivalent of (dividend / 0.0001))
If the divisor is 0 and a float: (dividend / 0.00001) (equivalent of (dividend * 100000))

div by 0 examples
Divide by zero with int divisor
; (23 / 0) -> 230000
ldc.r       23              ; left operand   (23)
ldc.i       0               ; right operand  (0)
div.r                       ; divide     (23 * 10000) -> 230000
; (5.45 / 0) -> 54500.0
ldc.r       5.45            ; left operand   (5.45)
ldc.i       0               ; right operand  (0)
div.r                       ; divide     (5.45 * 10000) -> 54500.0
Divide by zero with float divisor
; (5.45 / 0.0) -> 545000.0
ldc.r       5.45            ; left operand   (5.45)
ldc.r       0.0             ; right operand  (0.0)
div.r                       ; divide     (5.45 / 0.00001) -> 545000.0

rem ^ 

rem, [ mod ] (a % b)

Remainder of dividing one integer value by another. Will return negative values as normal with negative operands (signed modulus).

neg. ^ 

neg.i, neg.r (-a)

Arithmetic negate a numeric value. (- unary operator)

pos. ^ 

nop.1a8, nop.1a9, [ pos.i, pos.r ] (+a)

Removed opcodes that appear directly after neg.. It's theorized that these were the useless positive unary operator to compliment the negative unary operator.


Bitwise Operators

click to hide/show subsection

and ^ 

and (a & b)

Bitwise AND of two values.

or (a | b)

Bitwise OR of two values.

xor ^ 

xor (a ^ b)

Bitwise XOR of two values.

shl ^ 

shl (a << b)

Shift left.

shr ^ 

shr (a >> b)

Shift right.

not ^ 

not (~a)

Bitwise NOT of a value.


Boolean Operators

click to hide/show subsection

ceq. ^ 

ceq.i, ceq.r, ceq.s, ceq.iarr, ceq.rarr, ceq.sarr (a == b)

Compare equals.

cne. ^ 

cne.i, cne.r, cne.s, cne.iarr, cne.rarr, cne.sarr (a != b)

Compare not equal.

clt. ^ 

clt.i, clt.r, clt.s (a < b)

Compare lower (less) than.

cle. ^ 

cle.i, cle.r, cle.s (a <= b)

Compare lower (less) or equal.

cgt. ^ 

cgt.i, cgt.r, cgt.s (a > b)

Compare greater than.

cge. ^ 

cge.i, cge.r, cge.s (a >= b)

Compare greater or equal.

andl ^ 

andl (a && b)

Logical boolean and of two values.

orl ^ 

orl (a || b)

Logical boolean or of two values.

notl ^ 

notl, nop.191, [ notl.r ] (!a)

Logical boolean not of a value.


Cast Operators

click to hide/show subsection

conv. ^ 

conv.i, conv.r ((type)a)

Convert a numeric value to an integer or real.

Unlike other operators, conv must be passed a value of the opposite numeric type from the stack. conv.i accepts floats and conv.r accepts ints.


Store

click to hide/show section

Assign Store

click to hide/show subsection

st. ^ 

st.i, st.r, st.s, st.iarr, st.rarr, st.sarr (var = a)
stp.i, stp.r, stp.s, stp.iarr, stp.rarr, stp.sarr (var = a  //and pop)

Store a value in a variable.

stelem. ^ 

stelem.i, stelem.r, stelem.s (var[i] = a)
stelemp.i, stelemp.r, stelemp.s (var[i] = a  //and pop)

Store an element in an array type variable.


Compound Store

click to hide/show subsection

st._. ^ 

stelem._. ^ 

st._.i, st._.r, st._.s, stp._.i, stp._.r, stp._.s (var _= a)
stelem._.i, stelem._.r, stelem._.s, stelemp._.i, stelemp._.r, stelemp._.s (var[i] _= a)

add., sub., mul., div., rem, and, or, xor, shl, shr
(int-only operators that exclude .i should do the same with compound stores)

st._.i, st._.r, st._.s (var _= a)
stp._.i, stp._.r, stp._.s (var _= a  //and pop)
stelem._.i, stelem._.r, stelem._.s (var[i] _= a)
stelemp._.i, stelemp._.r, stelemp._.s (var[i] _= a  //and pop)


Load

click to hide/show section

ldc. ^ 

ldc.i, ldc.r (literal #, #.f)

Load consant value (literal)

ldc. examples
; push int value: 33
ldc.i       33
; push float value: 4.2
ldc.r       4.2

ldstr ^ 

ldstr, [ ldc.s ] (literal "")

Load string value (literal)

ldstr examples
; push string value: Hello world
ldstr       "Hello world"

ld (var)

Load a variable.

ldelem ^ 

ldelem (var[i])

Load an element from an array type variable.


Branching

click to hide/show section

Standard Branching

click to hide/show subsection

br, [ jmp ]

brfalse ^ 

brfalse, [ brnull, brzero ]

brtrue ^ 

brtrue, [ brinst ]

switch ^ 

switch (switch(){case...})

Switch statement.


Branch with Case

click to hide/show subsection

br.case, bne.case, bge.case, bgt.case, ble.case, blt.case
[ br.v, bne.v, bge.v, bgt.v, ble.v, blt.v ]

br.case ^ 

br.case, [ br.v ] (switch(a))

Branch with case register. These opcodes usually appear in-place of the switch opcode when there are non-sequential cases. br.case is the initial switch condition, which stores the condition in a variable register. And generally only bne.case is used for case statements.

bne.case ^ 

bne.case, [ bne.v ] (case b:)

Branch if case register is not equal.

bge.case ^ 

bge.case, [ bge.v ] (case < b:)

Branch if case register is greater or equal.

bgt.case ^ 

bgt.case, [ bgt.v ] (case <= b:)

Branch if case register is greater than.

ble.case ^ 

ble.case, [ ble.v ] (case > b:)

Branch if case register is lower (less) or equal.

blt.case ^ 

blt.case, [ blt.v ] (case >= b:)

Branch if case register is lower (less) than.

br.case example
; switch(...) {
; case <10:
; case 10:
; case 11:
; case 12:
; case >19:
; }
 br.case     @case_lt10
case_lt10:
 ldc.i       10
 bge.case    @case_eq10
case_eq10:
 ldc.i       10
 bne.case    @case_eq11
case_eq11:
 ldc.i       11
 bne.case    @case_eq12
case_eq12:
 ldc.i       12
 bne.case    @case_gt19
case_gt19:
 ldc.i       19
 ble.case    @switch_end

Branch Selectors

click to hide/show subsection

bsel. ^ 

bsel.1, bsel.2, bsel.3, bsel.x, bsel.4, bsel.5

Branch selector instructions. These are special-behavior instructions for special syntax blocks like setskip {}, UNK, and destructor {}. Internally these instructions will set AIP's (alternative instruction pointers), but do not actually perform a jump. [citation needed]

bsel.clr ^ 

bsel.clr

Branch selector clear. Clears all assigned AIP's (alternative instruction pointers) from bsel.# instructions.

bsel.jmp.4 ^ 

bsel.jmp.4

Branch selector jump to AIP 4. Jump to AIP registered by bsel.4. Seems to be an old instruction.


General

click to hide/show section

call ^ 

call, callp (func() with return, func() pop return)

User-defined function call. Also used for library functions provided by other .mjs scripts.

syscall ^ 

syscall, syscallp (sys() with return, sys() pop return)

System function call.

ret ^ 

ret, [ return ] (return a)

Return from function with a value (required).

alloca ^ 

alloca (declarations)

Allocate local variables and assign types.

argcheck ^ 

argcheck, [ sigchk ]

Check function arguments for correct types.

pop ^ 

pop

Pop top value on the stack.


VN-related

click to hide/show section

line ^ 

line (script line number)

Line number. States the current line number in the script. This is used by both the debugger for branching, and by story scripts for marking line numbers as read. (yes it's by line, not by actual number of messages)

Lines are only used to mark read content if the script was compiled with the preprocessor setting: #use_readflg on. This changes the second field after the signature in .mjo sciprts to that of the last line number.

Interesting observations: The line opcode may appear multiple times in a row. It seems that MajiroCompile.exe will include line instructions for every non-empty line (including comments). This means lines with opening or closing { } will appear as their own line, even if nothing else is done. This makes it easier to more-accurately analyze and reproduce some of the original code in a script.

text ^ 

text

Push text to the message buffer. This is used to display character names and message text.

proc ^ 

proc

Process text in the message buffer. Called after a message has been populated by text instruction and is ready to begin (and then wait for user input).

ctrl ^ 

ctrl (\a())

Control sequence. Special escapes for the message buffer. These can change font, color, produce newlines, etc. Their syntax in-script looks like \o(10, 20) for escapes with arguments, and \x for escapes without.

Note: The engine only checks the first character to determine the subcode type, meaning sequences such as \nFooBarBaz would still be treated as \n.

The internal representation of each issued control sequence is appended to the global text buffer, presumably so their effect can be reproduced in history mode.

  • \c(color, flags)
    Internal: \c0x{color:x8}0x{flags:x8}
    Sets the text color and some flags (?) of the current text box.

  • \d(value*)
    Internal: \d{value}
    Appends a string representation of the operand to the text buffer. Value can be of type int, float or string.

  • \f(width, height, lineSkip, isProportional, fontname$)
    Internal: \f0x{width:x8}0x{height:x8}0x{lineSkip:x8}0x{isProportional:x8}{fontname}
    Sets various font parameters.
    The integer values can be set to -1, which means "keep current value".

  • \g(a1, a2, a3, a4, a5, a6)
    Internal: \g0x{a1:x8}0x{a2:x8}0x{a3:x8}0x{a4:x8}0x{a5:x8}0x{a6:x8}
    Takes 6 integer arguments and passes them to callback d3ca70e5 (GAIZI_OUT), removing all with the value -99.

  • \l(x, y)
    Internal: \l0x{x:x8}0x{y:x8}
    Overwrites the text cursor position.

  • \n
    Internal: \n
    Issues a line break. If the text is currently inside of an unbalanced quote, \n will resolve to the internal representation N instead.
    Invokes callback 04ae36bd (CRLF).

  • \N
    Internal: \N
    Issues a line break and indents the next line.
    Invokes callback 04ae36bd (CRLF).

  • \o(x, y)
    Internal: \o0x{x:x8}0x{y:x8}
    Enters "offset text" mode. This saves the current text parameters and moves the cursor to the specified location. Offset text mode can be reset with \r.

  • \p
    Internal: \p
    Waits for input. Invokes callback 20ce505d (PAUSE).

  • \P
    Internal: \P
    Clears \z and sets \s(-1), then invokes callback 7ee67053 (PAUSE_IN_HISTORY).

  • \r
    Internal: \r
    Resets "offset text" mode. This restores the previous text settings saved by the last \o sequence.

  • \s(speed)
    Internal: \s0x{speed:x8}
    Sets the current moji speed (whatever that is).

  • \t(time)
    Internal: \t0x{time:x8}
    Sets the wait timer to time ms in the future, effectively a delay command.

  • \vFclipname
    Internal: \vFclipname
    Plays a voice clip with flags F (a single decimal digit, possibly even a boolean value).
    This control sequence does not accept any parameters, the clip name has to be hard coded into the instruction itself.

  • \w
    Internal: \w
    Clears (wipes) the console and invokes callback 93b38a0b (CONSOLE_CLS).
    This also resets various other settings like \o and \z.

  • \x(command$)
    Internal: \x{command}
    Calls a user defined control code handler, callback ec771b85 (X_CONTROL).

  • \z
    Internal: \z
    Probably switches to "immediate" text display mode.
    Functionally equivalent to syscall 30fa2a29 ($console_setzerotime()).

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