ASM modes and directives

The two main skool files (sd.skool and bts.skool) contain directives that are processed when parsing in ASM mode. Exactly how a directive is processed (and whether it is executed) depends on the ‘substitution mode’ and ‘bugfix mode’ in which the skool file is being parsed.

Substitution modes

There are three substitution modes: @isub, @ssub, and @rsub. These modes are described in the following subsections.

@isub mode

In @isub mode, @isub directives are executed, but @ssub, and @rsub directives are not. The main purpose of @isub mode is to make the minimum number of instruction substitutions necessary to produce an ASM file that assembles.

For example (from sd.skool):

; @isub=LD A,(32512)
 25396 LD A,(m)      ; Pick up the byte holding the signal flag for this event

This @isub directive ensures that LD A,(m) is replaced by the valid instruction LD A,(32512).

@isub mode is invoked by default when running skool2asm.py.

@ssub mode

In @ssub mode, @isub and @ssub directives are executed, but @rsub directives are not. The main purpose of @ssub mode is to replace LSBs, MSBs and full addresses in the operands of instructions with labels, to make the code amenable to some degree of relocation, but without actually removing or inserting any code.

For example (from sd.skool):

; @ssub=LD (27015+1),A
*25091 LD (27016),A  ; Change the instruction at #R27015 from SET n,(HL) to
                     ; SET n+1,(HL) or SET 0,(HL)

This @ssub directive replaces LD (27016),A with LD (27015+1),A; the 27015 will be replaced by the label for that address before rendering. (27016 cannot be replaced by a label, since it is not the address of an instruction.)

@ssub mode is invoked by passing the -s option to skool2asm.py.

@rsub mode

In @rsub mode, @isub, @ssub and @rsub directives are executed. The main purpose of @rsub mode is to make code unconditionally relocatable, even if that requires the removal of existing code or the insertion of new code.

For example (from sd.skool):

 25313 LD L,99       ; {Change the address in bytes 99 and 100 of the
; @ssub=LD (HL),25317%256
 25315 LD (HL),229   ; character's buffer from #R25303 to #R25317 (below)
; @rsub+begin
       INC L
       LD (HL),25317/256
; @rsub+end
                     ; }

This @rsub block directive inserts two instructions that ensure that the address in bytes 99 and 100 of the character’s buffer has the correct MSB as well as the correct LSB, regardless of where the code originally at 25317 now lives.

@rsub mode is invoked by passing the -r option to skool2asm.py. @rsub mode also implies @ofix mode; see below for a description of @ofix mode and the other bugfix modes.

Bugfix modes

There are three bugfix modes: @ofix, @bfix and @rfix. These modes are described in the following subsections.

@ofix mode

In @ofix mode, @ofix directives are executed, but @bfix and @rfix directives are not. The main purpose of @ofix mode is to fix instructions that have faulty operands.

For example (from sd.skool):

; @ofix-begin
; @nowarn
 27872 CALL 27633    ; This should be CALL #R27634 - is ERIC beside a chair?
; @ofix+else
       CALL 27634    ; Is ERIC beside a chair?
; @ofix+end

These @ofix block directives fix the faulty operand of the CALL instruction at 27872.

@ofix mode is invoked by passing the -f1 option to skool2asm.py.

@bfix mode

In @bfix mode, @ofix and @bfix directives are executed, but @rfix directives are not. The main purpose of @bfix mode is to fix bugs by replacing instructions, but without changing the start address of any routines, routine entry points, or data blocks.

For example (from bts.skool):

 32203 BIT 2,(HL)    ; Is ERIC sitting (facing left) or lying down?
; @bfix-begin
 32205 JR Z,32232    ; Tell ERIC to sit facing the stage if not (this is a
                     ; #BUG#assemblySit)
; @bfix+else
       JR NZ,32232   ; Tell ERIC to sit facing the stage if so
; @bfix+end

@bfix mode is invoked by passing the -f2 option to skool2asm.py.

@rfix mode

In @rfix mode, @ofix, @bfix and @rfix directives are executed. The purpose of @rfix mode is to fix bugs that cannot be fixed without moving code around (to make space for the fix).

For example (from sd.skool):

; @rfix-begin
; ERIC has been hit by the pellet. Knock him over.
; @rfix+else
; ERIC is in the same location as the pellet. Can he be knocked over?
 28017 LD A,(44128)  ; #REGa=ERIC's animatory state
       AND 15        ; Keep only the 'action' bits (bits 0-3)
       CP 6          ; Is ERIC standing, midstride, or sitting in a chair?
       RET NC        ; Return if not (he can't be knocked over)
; @rfix+end

These @rfix block directives insert some instructions to fix the bug in Skool Daze where ERIC can be hit by BOY WANDER’s pellet even when he’s sitting on the floor or lying down.

@rfix mode is invoked by passing the -f3 option to skool2asm.py. @rfix mode implies @rsub mode (see @rsub mode).

ASM directives

The ASM directives recognised by SkoolKit are described in the following subsections.

@bfix

The @bfix directive is used to make an instruction substitution in @bfix mode.

; @bfix=INSTRUCTION
  • INSTRUCTION is the replacement instruction

For example (from sd.skool):

; @bfix=DEFM "Phosphorus"
t57532 DEFM "Phosphorous"

@bfix block directives

The @bfix block directives are used to define a block of lines that will be inserted or removed in @bfix mode.

The syntax for defining a block that will be inserted in @bfix mode is:

; @bfix+begin
...                  ; Lines to be inserted
; @bfix+end

The syntax for defining a block that will be removed in @bfix mode is:

; @bfix-begin
...                  ; Lines to be removed
; @bfix-end

Typically, though, it is desirable to define a block that will be removed in @bfix mode right next to the block that should be inserted in its place. That may be done thus:

; @bfix-begin
...                  ; Instructions to be removed
; @bfix+else
...                  ; Instructions to be inserted
; @bfix+end

which is equivalent to:

; @bfix-begin
...                  ; Instructions to be removed
; @bfix-end
; @bfix+begin
...                  ; Instructions to be inserted
; @bfix+end

For example (from bts.skool):

; @bfix-begin
 26211 SET 7,(HL)    ; Set bit 7 of ERIC's primary status flags, indicating that
                     ; he's been knocked to the floor; however, bit 7 is ignored
                     ; by the routine at #R63405 (because bit 2 is also set), so
                     ; ERIC actually stays in his seat (which is a #BUG#seat)
; @bfix+else
       LD (HL),128   ; Set bit 7 of ERIC's primary status flags, indicating that
                     ; he's been knocked to the floor
; @bfix+end

These @bfix block directives fix the bug where ERIC cannot be pushed out of his seat by changing the instruction at 26211 from SET 7,(HL) to LD (HL),128.

@ignoremrcua

The @ignoremrcua directive is used to suppress any warnings that would otherwise be reported concerning addresses not converted to labels in the mid-routine comment above the next instruction.

; @ignoremrcua

For example (from bts.skool):

; @ignoremrcua
; R=5, 6 or 7. The next section of code is intended to set the x-coordinate for
; the new free mouse to 10 provided that X>=56, or try again with a new random
; number (R) if X<56. However, as noted above, #REGhl may now hold 32522 or
; 32600 instead of #R32767, and so the contents of 32522 or 32600 (instead of X)
; may be compared with 56. Thus it is possible for the new free mouse to appear
; at x-coordinate 10 even when X<56, which means it could appear on-screen. This
; is a #BUG#rematerialisingMouse.

If the @ignoremrcua directive were not present, warnings would be printed (during the rendering phase) about the comment containing addresses (32522, 32600) that cannot be converted to labels.

@ignoreua

The @ignoreua directive is used to suppress any warnings that would otherwise be reported concerning addresses not converted to labels in the comment for the next instruction.

; @ignoreua

For example (from sd.skool):

; @ignoreua
c60139 LD HL,784     ; {These parameters were meant for the routine at #R65122;
 60142 LD DE,12896   ; however, the CALL below is to 62818 (98,245) instead of
 60145 LD A,1        ; #R65122 (98,254), which is a #BUG#jumpSound}

If the @ignoreua directive were not present, a warning would be printed (during the rendering phase) about the comment containing an address (62818) that cannot be converted to a label.

@isub

The @isub directive is used to make an instruction substitution in @isub mode.

; @isub=INSTRUCTION
  • INSTRUCTION is the replacement instruction

For example (in sd.skool):

; @isub=LD A,(32512)
 25396 LD A,(m)      ; Pick up the byte holding the signal flag for this event

@isub block directives

The @isub block directives are used to define a block of lines that will be inserted or removed in @isub mode.

The syntax is equivalent to that for the @bfix block directives.

@keep

The @keep directive is used to prevent the substitution of a label for the operand in the next instruction (but only when the instruction has not been replaced using an @isub or @ssub directive).

; @keep

For example (from sd.skool):

; @keep
 28328 LD BC,24576   ; #REGb=96, #REGc=0 (maximum and minimum bounds)

If the @keep directive were not present, the operand (24576) of the LD BC instruction would be replaced with a label (because there is a routine at 24576); however, the operand is meant to be a pure data value, not a variable or routine address.

@label

The @label directive sets the label for the next instruction.

; @label=LABEL
  • LABEL is the label to apply

For example (from sd.skool):

; @label=RSCROLL
c24576 EXX

This sets the label for the routine at 24576 to RSCROLL.

See also @rlabel.

@nolabel

The @nolabel directive is used to prevent the next instruction from having a label automatically generated.

; @nolabel

For example (from bts.skool):

; Determine where to release another mouse.
; @bfix+begin
; @label=NEXTR
; @bfix+end
 31607 LD HL,32767   ; #R32767 holds the column of the play area at the far left
                     ; of the screen
; @bfix+begin
; @nolabel
; @bfix+end
*31610 CALL 25233    ; {#REGa=R, a random number from 0 to 7; we'll use this to
 31613 AND 7         ; determine where the new free mouse will be placed}

The @nolabel directive here prevents the instruction at 31610 from being labelled in @bfix mode (because no label is required; instead, the previous instruction at 31607 will be labelled).

The output from this section of bts.skool in @bfix mode will be:

; Determine where to release another mouse.
NEXTR:
  LD HL,LEFTCOL           ; LEFTCOL holds the column of the play area at the
                          ; far left of the screen
  CALL GETRANDOM          ; A=R, a random number from 0 to 7; we'll use this to
  AND 7                   ; determine where the new free mouse will be placed

And the output when not in @bfix mode will be:

; Determine where to release another mouse.
  LD HL,LEFTCOL           ; LEFTCOL holds the column of the play area at the
                          ; far left of the screen
CATCHMORF_0:
  CALL GETRANDOM          ; A=R, a random number from 0 to 7; we'll use this to
  AND 7                   ; determine where the new free mouse will be placed

@nowarn

The @nowarn directive is used to suppress any warnings that would otherwise be reported for the next instruction concerning:

  • a LD operand being replaced with a routine label (if the instruction has not been replaced using @isub or @ssub)
  • an operand not being replaced with a label (because the operand address has no label)
; @nowarn

For example (from sd.skool):

; @nowarn
 25560 LD BC,25404   ; Point #REGbc at #R25404 (guide character to intermediate
                     ; destination)

If this @nowarn directive were not present, a warning would be printed (during the parsing phase) about the operand (25404) being replaced with a routine label (which would be inappropriate if 25404 were intended to be a pure data value).

For another example (also from sd.skool):

; @ofix-begin
; @nowarn
 27872 CALL 27633    ; This should be CALL #R27634 - is ERIC beside a chair?
; @ofix+else
       CALL 27634    ; Is ERIC beside a chair?
; @ofix+end

If this @nowarn directive were not present, a warning would be printed (during the parsing phase, if not in @ofix mode) about the operand (27633) not being replaced with a label (usually you would want the operand of a CALL instruction to be replaced with a label, but not in this case).

@ofix

The @ofix directive is used to make an instruction substitution in @ofix mode.

; @ofix=INSTRUCTION
  • INSTRUCTION is the replacement instruction (with a corrected operand)

The stock skool files distributed with SkoolKit do not make use of this directive; instead they use the @ofix block directives.

Anyway, an example usage would be:

; @ofix=JR NZ,26067
 25989 JR NZ,26068

which would replace the operand of the JR NZ instruction with 26067.

@ofix block directives

The @ofix block directives are used to define a block of lines that will be inserted or removed in @ofix mode.

The syntax is equivalent to that for the @bfix block directives.

@org

The @org directive inserts an ORG assembler directive.

; @org=ADDRESS
  • ADDRESS is the ORG address

@rem

The @rem directive is used to make an illuminating comment about a nearby section or other ASM directive in a skool file. The directive is ignored by the parser.

; @rem COMMENT
  • COMMENT is a suitably illuminating comment

For example (from bts.skool):

; @rem This table must be 256 bytes after the table for the skool gate when shut
; @org=64000
; UDG reference table for the skool gate when open

If this @rem directive were not present, I might one day be tempted to remove the @org directive that follows, having forgotten why it’s actually extremely important.

@rfix block directives

The @rfix block directives are used to define a block of lines that will be inserted or removed in @rfix mode.

The syntax is equivalent to that for the @bfix block directives.

@rlabel

The @rlabel directive sets the label for the next instruction, but only when parsing in @rsub mode.

; @rlabel=LABEL
  • LABEL is the label to apply

For example (from sd.skool):

; @rsub+begin
       INC L
       LD (HL),31861/256
       LD A,E
       CP 62
       JR Z,31892
       LD (HL),31895/256
; @rsub+end
                     ; }
; @rlabel=DINSTEP
 31892 JP 25404      ; Proceed to the other end of the dinner hall

This sets the label for the instruction at 31892 to DINSTEP, so that the operand of the JR Z,31892 instruction (which is included only in @rsub mode) a few lines up can be converted to that label.

See also @label.

@rsub

The @rsub directive is used to make an instruction substitution in @rsub mode.

; @rsub=INSTRUCTION
  • INSTRUCTION is the replacement instruction

For example (from sd.skool):

; @rsub=INC BC
 30143 INC C         ; Move #REGbc along in the graphic data table

@rsub block directives

The @rsub block directives are used to define a block of lines that will be inserted or removed in @rsub mode.

The syntax is equivalent to that for the @bfix block directives.

@ssub

The @ssub directive is used to make an instruction substitution in @ssub mode.

; @ssub=INSTRUCTION
  • INSTRUCTION is the replacement instruction

For example (from sd.skool):

; @ssub=LD HL,44128+3
 25864 LD HL,44131   ; Point #REGhl at byte 99 of ERIC's buffer

@start

The @start directive indicates where to start parsing the skool file for the purpose of generating ASM output. Everything before the @start directive is ignored.

; @start