Control files

A control file contains a list of start addresses of code and data blocks. This information can be used by sna2skool.py to organise a skool file into corresponding code and data blocks.

Each block address in a control file is marked with a ‘control directive’, which is a single letter that indicates what the block contains:

  • b indicates a data block

  • c indicates a code block

  • g indicates a game status buffer entry

  • i indicates a block that will be ignored

  • s indicates a block containing bytes that are all the same value (typically unused zeroes)

  • t indicates a block containing text

  • u indicates an unused block of memory

  • w indicates a block containing words (two-byte values)

(If these letters remind you of the valid characters that may appear in the first column of each line of a skool file, that is no coincidence.)

For example:

c 24576 Do stuff
b 24832 Important data
t 25088 Interesting messages
u 25344 Unused

This control file declares that:

  • Everything before 24576 will be ignored

  • There is a routine at 24576-24831 titled ‘Do stuff’

  • There is data at 24832-25087

  • There is text at 25088-25343

  • Everything from 25344 onwards is unused (but will still be disassembled as data)

Addresses may be written as hexadecimal numbers, too; the equivalent example control file using hexadecimal notation would be:

c $6000 Do stuff
b $6100 Important data
t $6200 Interesting messages
u $6300 Unused

Besides the declaration of block types, addresses and titles, the control file syntax also supports the declaration of the following things:

  • Block descriptions

  • Register values

  • Block start comments

  • Mid-block comments

  • Block end comments

  • Sub-block types and comments

  • DEFB/DEFM/DEFW/DEFS statement lengths in data, text and unused sub-blocks

  • ASM directives (except block directives)

The syntax for declaring these things is described in the following sections.

Block descriptions

To provide a description for a code block at 24576 (for example), use the D directive thus:

c 24576 This is the title of the routine at 24576
D 24576 This is the description of the routine at 24576.

If the description consists of two or more paragraphs, declare each one with a separate D directive:

D 24576 This is the first paragraph of the description of the routine at 24576.
D 24576 This is the second paragraph of the description of the routine at 24576.

Register values

To declare the values of the registers upon entry to or exit from the routine at 24576, add one line per register with the R directive:

R 24576 A An important value in the accumulator
R 24576 O:DE Display file address

See the documentation on entry headers for more details on how to format a register description line.

Block start comments

To declare a block start comment that will appear above the instruction at 24576, use the N directive thus:

N 24576 And so this routine begins.

If the start comment consists of two or more paragraphs, declare each one with a separate N directive:

N 24576 This is the first paragraph of the start comment.
N 24576 This is the second paragraph of the start comment.

Mid-block comments

To declare a mid-block comment that will appear above the instruction at 24592, use the N directive thus:

N 24592 The next section of code does something really important.

If the mid-block comment consists of two or more paragraphs, declare each one with a separate N directive:

N 24592 This is the first paragraph of the mid-block comment.
N 24592 This is the second paragraph of the mid-block comment.

Block end comments

To declare a comment that will appear at the end of the routine at 24576, use the E directive thus:

E 24576 And so the work of this routine is done.

If the block end comment consists of two or more paragraphs, declare each one with a separate E directive:

E 24576 This is the first paragraph of the end comment for the routine at 24576.
E 24576 This is the second paragraph of the end comment for the routine at 24576.

Sub-block syntax

Sometimes a block marked as one type (code, data, text, or whatever) may contain instructions or statements of another type. For example, a word (w) block may contain the odd non-word here and there. To declare such sub-blocks whose type does not match that of the containing block, use the following syntax:

w 32768 A block containing mostly words
B 32800,3 But here's a sub-block of 3 bytes at 32800
T 32809,8 And an 8-byte text string at 32809
C 32821,10 And 10 bytes of code at 32821
S 32831,17 Followed by 17 zeroes at 32831

The directives (B, T, C and S) used here to mark the sub-blocks are the upper case equivalents of the directives used to mark top-level blocks (b, t, c and s). The comments at the end of these sub-block declarations are taken as instruction-level comments and will appear as such in the resultant skool file.

If an instruction-level comment spans a group of two or more sub-blocks, it must be declared with an M directive:

M 40000,21 This comment covers the following 3 sub-blocks
B 40000,3
W 40003,10
T 40013,8

An M directive with no length parameter covers all sub-blocks from the given start address to either the next mid-block comment or the end of the containing block (whichever is closer).

To apply the same instruction-level comment to each instruction in a sub-block or group of sub-blocks, use the M directive with a third parameter set to 1:

c 32768 A routine with careful timing
M 32768,,1 This instruction at #PC takes #TSTATES(#PC) T-states

If a sub-block directive is left blank, then it is assumed to be of the same type as the containing block. So in:

c 24576 A great routine
  24580,8 A great section of code at 24580

the sub-block at 24580 is assumed to be of type C.

If the length parameter is omitted from a sub-block directive, then it is assumed to end where the next sub-block starts. So in:

c 24576 A great routine
  24580 A great section of code at 24580
  24588,10 Another great section of code at 24588

the sub-block at 24580 has length 8, because it is implicitly terminated by the following sub-block at 24588.

Sub-block lengths

Normally, a B sub-block declared thus:

B 24580,12 Interesting data

would result in something like this in the corresponding skool file:

24580 DEFB 1,2,3,4,5,6,7,8 ; {Interesting data
24588 DEFB 9,10,11,12      ; }

But what if you wanted to split the data in this sub-block into groups of 3 bytes each? That can be achieved with:

B 24580,12,3 Interesting data

which would give:

24580 DEFB 1,2,3    ; {Interesting data
24583 DEFB 4,5,6
24586 DEFB 7,8,9
24589 DEFB 10,11,12 ; }

That is, in a B directive, the desired DEFB statement lengths may be given as a comma-separated list of “sublengths” following the main length parameter, and the final sublength in the list is used for all remaining data in the block. So, for example:

B 24580,12,1,2,3 Interesting data

would give:

24580 DEFB 1        ; {Interesting data
24581 DEFB 2,3
24583 DEFB 4,5,6
24586 DEFB 7,8,9
24589 DEFB 10,11,12 ; }

Note that even if sublengths are specified, the main length parameter can be omitted (by leaving it blank) if the sub-block is implicitly terminated by the next sub-block. For example:

B 24580,,1,2,3 No need to specify the main length parameter here...
B 24592,10 ...because this sub-block implies that it must be 12

If the sublength list contains sequences of two or more identical lengths, as in:

B 24580,21,2,2,2,2,2,2,1,1,1,3

then it may be abbreviated thus:

B 24580,21,2*6,1*3,3

Sublengths can be used on C, S, T and W directives too (though on C directives they are really only useful for specifying number bases). For example:

S 32768,100,25 Four 25-byte chunks of zeroes

would give:

32768 DEFS 25 ; {Four 25-byte chunks of zeroes
32793 DEFS 25
32818 DEFS 25
32843 DEFS 25 ; }

The dot and colon directives

The dot (.) directive provides an alternative method of specifying a comment for a top-level or sub-block directive. For example, instead of:

c 30000 This is the title of the entry

you could write:

c 30000
. This is the title of the entry

At first glance this does not appear to be an improvement. But one advantage of the dot directive is that a comment can be split over multiple lines, and the line breaks are preserved when restored. This makes it much easier to read and write a long comment, especially if it contains a #LIST or #TABLE macro. For example:

D 30000 #TABLE(default) { =h Header 1 | =h Header 2 } { Cell 1      | Cell 2 } TABLE#

can be recast like this:

D 30000
. #TABLE(default)
. { =h Header 1 | =h Header 2 }
. { Cell 1      | Cell 2 }
. TABLE#

In addition, a sequence of D, N, E or R directives at the same address (one for each paragraph or register description) can be reduced to just one of those directives followed by a sequence of dot directives:

N 30000
. Paragraph 1.
. .
. Paragraph 2.

In fact, the dot directive can be used instead of D, R and N directives when specifying an entry header. For example:

c 30000
. This is the title of the entry.
.
. This is the description.
.
.   A Input
. O:B Output
.
. This is the start comment.

Note, however, that this works only if the entry header contains no ASM directives.

The dot directive also makes it simpler to preserve @*sub and @*fix directives that replace part of an instruction-level comment. For example, consider the following skool file snippet:

 49155 LD A,(HL)     ; {Increase the sprite's x-coordinate by
@bfix=ADD A,3        ; three}
 49156 ADD A,2       ; two (which is a bug)}

When preserved without dot directives, this becomes:

@ 49156 bfix=ADD A,3        ; three}
C 49155,3 Increase the sprite's x-coordinate by two (which is a bug)

which is restored incorrectly by sna2skool.py (using the default line width of 79 characters) as:

 49155 LD A,(HL)     ; {Increase the sprite's x-coordinate by two (which is a
@bfix=ADD A,3        ; three}
 49156 ADD A,2       ; bug)}

This problem could be addressed by recasting the comment lines in the skool file and adding a @bfix directive for ‘LD A,(HL)’:

@bfix=               ; {Increase the sprite's x-coordinate by three
 49155 LD A,(HL)     ; {Increase the sprite's x-coordinate by two (which is a
@bfix=ADD A,3        ; }
 49156 ADD A,2       ; bug)}

which would be preserved without dot directives as:

@ 49155 bfix=               ; {Increase the sprite's x-coordinate by three
@ 49156 bfix=ADD A,3        ; }
C 49155,3 Increase the sprite's x-coordinate by two (which is a bug)

But this solution requires two @bfix directives instead of one, repeats the part of the comment that doesn’t change, and could still be restored incorrectly if sna2skool.py is used with a line width other than the default.

It is much easier and more robust to use dot directives to preserve the original form in a way that will always be restored correctly:

@ 49156 bfix=ADD A,3        ; three}
C 49155,3
. Increase the sprite's x-coordinate by
. two (which is a bug)

Finally, the colon (:) directive can be used alongside the dot directive to force an instruction comment continuation line where there would not otherwise be one. For example:

B 31995,2,1
. The first two comment lines
: belong to the first DEFB.
. And this comment line belongs to the second DEFB.

would be restored as:

b31995 DEFB 0        ; {The first two comment lines
                     ; belong to the first DEFB.
 31996 DEFB 0        ; And this comment line belongs to the second DEFB.}

The colon directive is rarely needed, but it is useful in cases like the one above where an @*sub or @*fix directive is used to replace all or part of the comment of the second instruction only:

 49155 LD A,(HL)     ; {Having adjusted the sprite's y-coordinate, we now
                     ; increase its x-coordinate by
@bfix=ADD A,3        ; three}
 49156 ADD A,2       ; two (which is a bug)}

This can be preserved as:

@ 49156 bfix=ADD A,3        ; three}
C 49155,3
. Having adjusted the sprite's y-coordinate, we now
: increase its x-coordinate by
. two (which is a bug)

If a dot directive were used instead of the colon directive here, it would restore incorrectly as:

 49155 LD A,(HL)     ; {Having adjusted the sprite's y-coordinate, we now
@bfix=ADD A,3        ; three}
 49156 ADD A,2       ; increase its x-coordinate by
                     ; two (which is a bug)}

Loops

Sometimes the instructions and statements in a code or data block follow a repeating pattern. For example:

b 30000 Two bytes and one word, times ten
B 30000,2
W 30002
B 30004,2
W 30004
...
B 30036,2
W 30038

Repeating patterns like this can be expressed more succinctly as a loop by using the L directive, which has the following format:

L start,length,count[,flags]

where:

  • start is the loop start address

  • length is the length of the loop (the size of the address range to repeat)

  • count is the number of times to repeat the loop (only values of 2 or more make sense)

  • flags controls which elements in the loop are repeated (see below)

flags may have one of the following values:

  • 0 - repeat sub-block elements only (this is the default)

  • 1 - repeat block-level elements and sub-block elements

  • 2 - repeat sub-block elements only, except any mid-block comment at the loop start address

So using the L directive, the body of the data block above can be expressed in three lines instead of 20:

b 30000 Two bytes and one word, times ten
B 30000,2
W 30002
L 30000,4,10

The L directive can also be used to repeat entire blocks, by setting flags to 1. For example:

b 40000 A block of five pairs of bytes
B 40000,10,2
L 40000,10,3,1

is equivalent to:

b 40000 A block of five pairs of bytes
B 40000,10,2
b 40010 A block of five pairs of bytes
B 40010,10,2
b 40020 A block of five pairs of bytes
B 40020,10,2

By default, an N directive at the beginning of a loop is repeated. To avoid that, set flags to 2. For example:

b 50000 Three groups of bytes and words
N 50000 The three groups are as follows:
B 50000 Byte
W 50001 Word
L 50000,3,3,2

is equivalent to:

b 50000 Three groups of bytes and words
N 50000 The three groups are as follows:
B 50000 Byte
W 50001 Word
B 50003 Byte
W 50004 Word
B 50006 Byte
W 50007 Word

Note that ASM directives in the address range of an L directive loop are not repeated.

Number bases

Numeric values in instruction operands and DEFB, DEFM, DEFS and DEFW statements are normally rendered in either decimal or hexadecimal, depending on the options passed to sna2skool.py. To render a numeric value in a specific base, as a negative number, or as a character, attach a b (binary), c (character), d (decimal), h (hexadecimal) or m (minus) prefix to the relevant length or sublength parameter on the B, C, S, T or W directive.

For example:

C 30000,b
C 30002,c

will result in something like this:

30000 LD A,%10001111
30002 LD B,"?"

and:

B 40000,8,b1:d2:h1,m1,b1,h2
S 40008,8,8:c

will result in something like this:

40000 DEFB %10101010,23,43,$5F
40004 DEFB -1
40005 DEFB %11110000
40006 DEFB $2B,$80
40008 DEFS 8,"!"

Note that attaching a prefix to the main length parameter sets the default base for any sublength parameters that follow. So:

B 40000,b,1:d2,1
B 40004,h4,1:b1:d1,1

will result in something like this:

40000 DEFB %01010101,32,57
40003 DEFB %00001111
40004 DEFB $0F,%11110000,93
40007 DEFB $A0

Some instructions have two numeric operands. To specify a different base for each one, use two prefixes:

C 30000,hb4

which will result in something like this:

30000 LD (IX+$0A),%10000001

To use the default base for one operand, and a specific base for the other, use the n (none) prefix to denote the default base. So if the default base is decimal, then:

C 30000,,nb4,hn4

will result in something like this:

30000 LD (IX+10),%10000001
30004 LD (IX+$0B),130

DEFB and DEFM statements may contain both bytes and strings; for example:

40000 DEFM "Hi ",5
40004 DEFB 4,"go"

Such statements can be encoded in a control file thus:

T 40000,,3:n1
B 40004,3,1:c2

That is, the length of a string in a DEFB statement is prefixed by c, the length of a sequence of bytes in a DEFM statement is prefixed by n, and the lengths of all strings and byte sequences are separated by colons. This notation can also be combined with the ‘*’ notation; for example:

T 50000,8,2:n2*2

which is equivalent to:

T 50000,8,2:n2,2:n2

A character code may be ‘inverted’ (i.e. have bit 7 set), typically to indicate the end of a string:

49152 DEFM "Hell","o"+128

This can be encoded thus:

T 49152,5,4:1

and the terminal character will be restored in the same format.

ASM directives

To declare an ASM directive for a block or an individual instruction, use the @ directive thus:

@ address directive[=value]

where:

  • directive is the directive name

  • address is the address of the block or instruction to which the directive applies

  • value is the value of the directive (if it requires one)

For example, to declare a @label directive for the instruction at 32768:

@ 32768 label=LOOP

When declaring an @ignoreua directive for anything other than an instruction-level comment, a suffix must be appended to the directive to specify the type of comment it applies to:

@ address ignoreua:X[=addr1[,addr2...]]

where X is one of:

  • d - entry description

  • e - block end comment

  • i - instruction-level comment (default)

  • m - block start comment or mid-block comment

  • r - register description section

  • t - entry title

For example, to declare an @ignoreua directive for the description of the routine at 49152:

@ 49152 ignoreua:d
D 49152 This is the description of the routine at 49152.

Instruction-level comments

One limitation of storing instruction-level comments as shown so far is that there is no way to distinguish between a blank comment that spans two or more instructions and no comment at all. For example, both:

30000 DEFB 0 ; {
30001 DEFB 0 ; }

and:

30000 DEFB 0 ;
30001 DEFB 0 ;

would be preserved thus:

B 30000,2,1

To solve this problem, a special syntax is used to preserve blank multi-instruction comments:

B 30000,2,1 .

When restored, this comment is reduced to an empty string.

But how then to preserve a multi-instruction comment consisting of a single dot (.), or a sequence of two or more dots? In that case, another dot is prefixed to the comment. So:

30000 DEFB 0 ; {...
30001 DEFB 0 ; }

is preserved thus:

B 30000,2,1 ....

Note that this scheme does not apply to multi-instruction comments that contain at least one character other than a dot; such comments are preserved verbatim (that is, without a dot prefix).

Non-entry blocks

In addition to regular entries (routines and data blocks), a skool file may also contain blocks of lines that do not match the format of an entry, such as a header comment that appears before the first entry and contains copyright information. Blocks like this can be preserved by the > directive. For example, the copyright header in this skool file:

; Copyright 2018 J Smith

; Start
c24576 JP 32768

is preserved thus:

> 24576 ; Copyright 2018 J Smith

Note that the address of the > directive is the address of the next regular entry.

A non-entry block may also appear at the end of the skool file, after the last regular entry:

; The end
c65535 RET

; And that was the disassembly.

In this case the block is preserved by the > directive with the parameter 1 (indicating a ‘footer’) following the address of the last entry:

> 65535,1 ; And that was the disassembly.

Quick reference

Block directives

Every block directive has the format:

d address[ title]

where address is the address of the block, and title (optional) is its title. The block directive d controls how the contents of the block are disassembled by default, and must be one of the following:

  • b - data block (DEFB statements)

  • c - code block (assembly language instructions)

  • g - game status buffer entry (DEFB statements)

  • i - block that will be ignored

  • s - block containing bytes that are all the same value (DEFS statement)

  • t - block containing text (DEFM statements)

  • u - unused block of memory (DEFB statements)

  • w - block containing words (DEFW statements)

B directive

The B sub-block directive disassembles an address range as one or more DEFB statements:

B address[,length[,sublengths]][ comment]
  • address is the start address

  • length is the length of the address range; if not given, the range ends where the next declared sub-block starts

  • sublengths controls the DEFB statement lengths and byte value formats; see Sub-block lengths and Number bases for more details

  • comment is the comment applied to the sub-block

C directive

The C sub-block directive disassembles an address range as code (assembly language instructions):

C address[,length[,sublengths]][ comment]
  • address is the start address

  • length is the length of the address range; if not given, the range ends where the next declared sub-block starts

  • sublengths controls the instruction operand value formats; see Number bases for more details

  • comment is the comment applied to the sub-block

D directive

The D directive declares a description for a code or data block:

D address description
  • address is the address of the block

  • description is the description

See Block descriptions for more details.

E directive

The E directive declares a block end comment:

E address comment
  • address is the address of the block

  • comment is the comment

See Block end comments for more details.

L directive

The L directive defines a control file loop that repeats a sequence of other control directives:

L start,length,count[,flags]

See Loops for more details.

M directive

The M directive applies a comment to a contiguous group of sub-blocks:

M address[,length[,repeat]] comment
  • address is the start address of the group of sub-blocks

  • length is the length of the group; if not given, the directive covers all sub-blocks up to either the next mid-block comment or the end of the containing block (whichever is closer)

  • repeat is 1 to apply the comment to each instruction line in the group, or 0 to apply it to the group as a whole (default: 0)

  • comment is the comment

See Sub-block syntax for more details.

N directive

The N directive declares a block start comment or mid-block comment:

N address comment
  • address is the address of the instruction above which to place the comment

  • comment is the comment

See Block start comments and Mid-block comments for more details.

R directive

The R directive declares an input or output register value for a code block:

R address register
  • address is the address of the code block

  • register is a description of the register name and value

See Register values for more details.

S directive

The S sub-block directive disassembles an address range as one or more DEFS statements:

S address[,length[,sublengths]][ comment]
  • address is the start address

  • length is the length of the address range; if not given, the range ends where the next declared sub-block starts

  • sublengths controls the DEFS statement lengths and byte value formats; see Sub-block lengths and Number bases for more details

  • comment is the comment applied to the sub-block

T directive

The T sub-block directive disassembles an address range as one or more DEFM statements:

T address[,length[,sublengths]][ comment]
  • address is the start address

  • length is the length of the address range; if not given, the range ends where the next declared sub-block starts

  • sublengths controls the DEFM statement lengths; see Sub-block lengths for more details

  • comment is the comment applied to the sub-block

W directive

The W sub-block directive disassembles an address range as one or more DEFW statements:

W address[,length[,sublengths]][ comment]
  • address is the start address

  • length is the length of the address range; if not given, the range ends where the next declared sub-block starts

  • sublengths controls the DEFW statement lengths and word value formats; see Sub-block lengths and Number bases for more details

  • comment is the comment applied to the sub-block

‘ ‘ directive

The ‘ ‘ (space) sub-block directive is equivalent to a B, C, S, T or W directive, according to the default disassembly type of the containing block.

See Block directives for more details.

@ directive

The @ directive declares an ASM directive at a given address:

@ address directive[=value]

See ASM directives for more details.

. and : directives

The . and : directives provide an alternative method of specifying comments for block and sub-block directives that can be used to preserve line breaks.

See The dot and colon directives for more details.

> directive

The > directive declares a line of text that lies outside a regular entry (code or data block).

See Non-entry blocks for more details.

Control file comments

A comment may be added to a control file by starting a line with a hash character (#), a per cent sign (%), or a semicolon (;). For example:

# This is a comment
% This is another comment
; This is yet another comment

Control file comments are ignored by sna2skool.py, and will not show up in the skool file.

Limitations

Control files cannot preserve ASM block directives that occur inside a regular entry. If your skool file contains any such ASM block directives, they should be replaced before using skool2ctl.py.

An ASM block directive that adds, removes or modifies a sequence of instructions and their associated comments can be replaced by one or more plain @isub, @ssub, @rsub, @ofix, @bfix or @rfix directives.

An ASM block directive that modifies part of an entry header, mid-block comment or block end comment can be replaced by an #IF macro that checks the relevant substitution mode (asm) or fix mode (fix). For example:

@bfix-begin
; This is a bug.
@bfix+else
; This bug is fixed.
@bfix+end

could be replaced by:

; This #IF({mode[fix]}<2)(is a bug,bug is fixed).

Revision history

Version

Changes

9.0

Added support to the L directive for avoiding repetition of an N directive at the start of the loop

8.7

Added support to the M directive for applying its comment to each instruction in its range

7.2

Added the dot (.) and colon (:) directives for specifying comments over multiple lines

7.1

Added support for specifying that numeric values in instruction operands and DEFB, DEFM, DEFS and DEFW statements be rendered as negative numbers

7.0

Added the > directive for preserving non-entry blocks; added support for preserving ‘inverted’ characters (with bit 7 set); the byte value in an S directive may be left blank

4.5

Added support for specifying character values in DEFS statements

4.4

Added support for specifying that numeric values in instruction operands be rendered as characters or in a specific base; added support for specifying character values in DEFW statements

4.3

Added the @ directive, the N directive and support for block start comments

4.2

Added the L directive and support for preserving the location of @ignoreua directives

3.7

Added support for binary numbers; added support for specifying the base of numeric values in DEFB, DEFM, DEFS and DEFW statements; added the s and S directives and support for DEFS statements with non-zero byte values

3.6

Added support for preserving blank comments that span two or more instructions

3.1.4

Added support for DEFB and DEFM statements that contain both strings and bytes

2.4

Added support for non-block ASM directives

2.2

Added support for the * notation in DEFB, DEFM, DEFS and DEFW statement length lists

2.1.2

Added support for DEFM, DEFS and DEFW statement lengths

2.1.1

Added the M directive

2.1

Added support for DEFB statement lengths

2.0.6

Added support for hexadecimal numbers

1.0.7

Added support for block titles, block descriptions, register values, mid-block comments, block end comments, sub-block types and instruction-level comments