SkoolKit

Spectrum game disassembly toolkit

Simulation stimulation

SkoolKit 8.7 has been released. To get a copy, please head over to the download page, the Python Package Index, the Ubuntu PPA, or the Fedora copr repo.

The tradition in these release announcements is to start by listing the main new macros, commands, or parameters thereof that you can get your teeth sunk into. In this case, however, you may consider that tradition (slightly) bucked. The main bit of newness in version 8.7 is not a macro, or a command - or, indeed, a parameter thereof - but a Z80 instruction set simulator.

The question, then, is how this simulator has been applied to provide the new and exciting features you’ve come to expect from a SkoolKit release. Well, taking centre stage is the --sim-load option of tap2sna.py. It simulates a freshly booted 48K ZX Spectrum running LOAD "" (or LOAD ""CODE, if the first block on the tape is a ‘Bytes’ header). Which means it’s now much easier to convert a TAP/TZX file into a pristine snapshot. No more messing around with --ram load, --ram poke or any of the other --ram commands, and no more worrying about headerless blocks and turbo loaders. Give --sim-load a try and see how much cruft you can remove from your t2s file.

Next, taking stage right, is the #AUDIO macro. It converts a sequence of delays between changes in the Spectrum’s speaker state into a WAV file, and then presents it for playing via an HTML5 <audio> element. But how does the simulator fit into this picture? Good question. While you can provide the sequence of delays manually, a much easier option is to use the simulator to calculate them for you by running the code that produces the sound effect. For example, here’s how you might create an <audio> element that plays the theme tune for Manic Miner:

#AUDIO4(blue-danube.wav)(34351,34358)

Now, taking stage left, is the #TSTATES macro. As its name suggests, it computes the number of T-states it takes to execute a single instruction or a sequence of instructions. Where the simulator really helps here is if the code you want to time is a loop, or contains conditional instructions with variable timing. For example:

; This code creates a delay of #TSTATES(32768,32776,4) T-states.
 32768 LD DE,0
*32771 DEC DE
 32772 LD A,D
 32773 OR E
 32774 JR NZ,32771

And finally in the simulator department, what if none of the applications described so far scratches your itch? In that case, perhaps the #SIM macro will help. Instead of executing Z80 machine code for the specific purpose of loading a tape, calculating speaker flip delays, or timing instructions, it just executes Z80 machine code and lets you do whatever you like with the result - that result being changes to register values (available as replacement fields in the sim dictionary) or changes to the contents of the internal memory snapshot constructed from the contents of the skool file. For example, you could create an image of the Manic Miner title screen like this:

#SIM(start=34278,stop=34332)
#SCR

As for non-simulator-related new features, well, there are some, but they’re just not as interesting. If you really want to know what they are, you can consult the changelog. After that, why not take 8.7 for a spin and see what the simulator can do for you?

Once in a #WHILE

SkoolKit 8.6 has been released. To get a copy, please head over to the download page, the Python Package Index, the Ubuntu PPA, or the Fedora copr repo.

So far, the 8.x series of SkoolKit has been a big one for new macros - eight since 8.0 - and version 8.6 continues that theme by bringing along no fewer than three in the shape of #UDGS, #WHILE and #STR.

First up, #UDGS. This macro is an alternative to the venerable #UDGARRAY for creating an image of a rectangular array of UDGs. How is it different from #UDGARRAY? Well, where #UDGARRAY expects a sequence of UDG specifications (including UDG base address, attribute byte etc.) in its parameter string, #UDGS instead expects a single expression that takes a pair of coordinates (the coordinates of the UDG within the rectangular array) and expands to the name of a frame. This makes the typical #UDGS macro much shorter and more readable than the equivalent #UDGARRAY macro. For example:

#UDGS3,4(thing)(#UDGAT($x,$y))

This creates an image (thing.png) three UDGs wide and four high, where the UDG at (x,y) is produced by the #UDGAT macro (which you might have defined with #DEF).

Second up, #WHILE. In the interests of making skool macros more usable as a programming language, this one, as its name suggests, repeatedly expands other macros while a conditional expression is true. So, for example, if you wanted to translate a sequence of character codes starting at address 32768 until a zero-byte is reached, you could do something like this:

#LET(a=32768)
#WHILE(#PEEK({a})>0)(
  #CHR(#PEEK({a}))
  #LET(a={a}+1)
)

Third up, #STR. This one rather undermines the #WHILE example just given, because it expands to the text string at a specific address, by default terminating either just before the first byte that is zero or on the first byte that has bit 7 set. So the example above could be reduced to:

#STR32768

But never mind: #WHILE can no doubt be put to other uses. Meanwhile, #STR can also strip leading and trailing whitespace from strings, and handle strings that terminate somewhere other than the first byte whose value is 0 or 128+.

By now you’re probably feeling some macro fatigue, so let’s move on to a couple of other features. The first one, while not a macro itself, could be described as macro-related, but bear with me. Ever used the @expand directive and liked it, but wondered if there was an alternative that is specific to HTML mode? Wonder no more. The value of the Expand parameter in the [Config] section is expanded before anything else, and thus enables all your macro definitions to be placed in their own section, e.g. [Macros]:

[Config]
Expand=#INCLUDE(Macros)

[Macros]
...macro definitions here instead of in @expand directives...

Finally, a new feature that genuinely has nothing to do with macros. The --ram option of tap2sna.py now supports ‘call’ operations, which call an arbitrary Python function to perform arbitrary modification of the memory snapshot. This will come in handy when the stock ‘move’ and ‘poke’ operations just don’t quite cut it, for example if the RAM needs to be descrambled before it can be disassembled.

Of course, this is not an exhaustive account of all the new features in 8.6. For details on the others, I suggest heading over to the changelog. There you will find details on such things as defining dictionary variables with the #LET macro, and how the #CHR macro is now more friendly towards the ‘special’ members of the ZX Spectrum character set: ‘↑’, ‘£’ and ‘©’. When you’re done there, why not grab a copy of 8.6 and see whether #UDGS can reignite your passion for image macros?

Macros v. Python

SkoolKit 8.5 has been released. To get a copy, please head over to the download page, the Python Package Index, the Ubuntu PPA, or the Fedora copr repo.

As if the #PLOT macro (new in 8.3) weren’t enough, this release introduces the second and third new image macros to the 8.x series: #COPY and #OVER. The first of these (#COPY) enables you to copy all or part of an existing frame into a new frame, and change the scale, mask type and cropping specification (if desired) in the process. The second (#OVER) enables you to superimpose one frame (let’s call it the foreground) over another (the background, if you will), using the foreground’s mask or some other custom scheme for combining the graphic bytes and attributes of each frame.

So what do #COPY and #OVER make possible that was either very difficult or impossible before? One example is composing complex gameplay images consisting of sprites drawn on a background without having to write a SkoolKit extension module first. (Not that I’d want to discourage anyone from writing Python, but I understand that it’s not everyone’s bag.) Construct one big frame of the entire gameplay background, and one frame for each sprite in the game. Then use #COPY to copy the desired portion of that background to a new frame, and #OVER to sprinkle sprites onto that new frame. Finally, use the good old #UDGARRAY* macro to create an image file from the result. Bingo, as the saying goes.

Now, while you’re doing that, you might consider using the new #DEF macro to define custom macros that will simplify some of the repeated #COPYing and #OVERing. Why the new #DEF instead of the old #DEFINE? Well, #DEF has a more natural syntax and can create macros with optional integer and string parameters. And you can name the parameters to boot. In short, #DEF is what #DEFINE should have been from the start. For example, whereas you might define a #MAX macro using #DEFINE like this:

#DEFINE2(MAX,#IF({0}>{1})({0},{1}))

the same can be accomplished using #DEF like this:

#DEF(#MAX(a,b) #IF($a>$b)($a,$b))

If you’re not sold on #DEF yet by this particular example, think about how you’d define a new macro that has 10 arguments instead of just two. Then perhaps you’ll find that keeping track of them all by name instead of their position in a list is quite a bit easier and makes the macro definition much more readable.

At this point you’re probably sick of hearing about macros, so let’s talk about sna2skool.py instead. This workhorse of the SkoolKit suite now has the ability to disassemble an instruction that crosses the 64K boundary (i.e. has one of its bytes at address 65535 and the next one at address 0). Just set the new Wrap configuration parameter to 1, and off you go. Should be a boon to those of you who particularly enjoy disassembling custom interrupt routines.

While all that is perhaps the most interesting new stuff in 8.5, it is by no means everything. For details on the rest, I recommend consulting the changelog. There you will find details on such things as how to change the default snapshot disassembly start address (traditionally 16384), and how to add to the list of instructions that SkoolKit regards as identifying entry points (traditionally just calls, jumps and RSTs). After that, grab a copy of 8.5 and see just how complex an image you can compose without resorting to Python.