Introduction
When I first tried disassembling 180, I was confused with the games
entrypoint. It looked to be … nonsense!
Usually, this is indicative of subsequent code decrypting the
instructions you’re looking at (so it’s actually different to what you
initially see). But, 180 appeared to have a pattern of sorts … a routine
would be called, and later there would be an RST $38
… eventually this
gave the game away and helped me to decipher exactly what the code is for, as
soon as I realised that the hex value of RST $38
is actually $FF
this clue
helped me recognise it as a data terminator.
What I’d thought was encrypted code was actually text data terminated with an
$FF
. It confused me at first, just as I’d never seen anything like it before.
It’s such an elegant way to handle printing data that I couldn’t wait to write
it up!
Subroutine: Messaging: Bust
; Messaging: Bust
@label=Messaging_Bust
c$AD3B CALL $AC52 ; Call #R$AC52.
$AD3E CALL $964C ; Call PrintString_Loop.
$AD41 DEFB $16,$17,$10 ; PRINT AT: 17, 10.
$AD44 DEFM "BUST[[" ; #FONT#(:(#STR(#PC,$04,$06)))$8D75,attr=$47(bust)
$AD4A DEFB $FF ; Terminator.
$AD4B CALL $AE04 ; Call Pause_Long.
...etc.
There’s a CALL PrintString_Loop
but immediately afterward there is character
data! Such a clever idea! And once the terminator is reached, the code just
continues on - so elegant!!! I don’t know if other games use this technique as
well, but it’s the first time I’ve seen it done like this, and I’m so incredibly
impressed at the ingenuity of it! Maybe by 1986 everyone was writing printing
routines like this - who knows?!?! But it’s my first time seeing it, and I’m
suitably impressed 😆
Subroutine: Print String
This isn’t that the routine is complex - it actually isn’t at all, but it is truly a wonderfully clever idea!
Here is what the print routine looks like:
; Print String
;
; Prints the string data from the stack pointer to the screen buffer.
;
@label=PrintString_Loop
c$964C EX (SP),HL ; Exchange HL with the address at the top of the
; stack.
$964D LD A,(HL) ; Get the character from string.
$964E INC HL ; Increment the string pointer by one.
$964F EX (SP),HL ; Update the return address on the stack.
$9650 CP $FF ; {Return if the terminator has been reached.
$9652 RET Z ; }
$9653 CALL $9736 ; Call PrintCharacter.
$9656 JR $964C ; Jump back to PrintString_Loop.
In case you’re not familiar with how the Z80 CPU works; the program counter
(PC
) holds the memory address of the current instruction being executed. When
a subroutine is CALL
ed, the processor adds 3 to the program counter and
pushes it onto the stack (3 bytes being the length of a CALL
instruction).
When the RET
urn from the subroutine is issued, the program counter is updated
with the popped address from the stack and so, execution continues from where
it left off. You can’t directly modify the PC
register … but you can fudge
with the values on the stack!
This is vital to understanding how this subroutine works and why it’s so special!
So, breaking it down from the Messaging_Bust
subroutine above:
$AD3E CALL $964C ; Call PrintString_Loop.
At this point; PC is $AD3E
and the CPU polls the memory location and has a
CALL
to action. So, as we’ve learnt above - the processor adds 3 to the
program counter (the length of a CALL
instruction) and pushes this address
onto the stack.
The PrintString_Loop
has been called and there’s some magic on the very first
line:
@label=PrintString_Loop
c$964C EX (SP),HL ; Exchange HL with the address at the top of the
; stack.
HL
now contains the address $AD41
($AD3E
+$03
) and now points at the
string data for printing!
Next, it fetches the string data, and advances the return address on the stack:
$964D LD A,(HL) ; Get the character from string.
$964E INC HL ; Increment the string pointer by one.
$964F EX (SP),HL ; Update the return address on the stack.
Then it does a check to see if this is the last character in the string:
$9650 CP $FF ; {Return if the terminator has been reached.
$9652 RET Z ; }
And if it isn’t, calls the PrintCharacter
subroutine:
$9653 CALL $9736 ; Call PrintCharacter.
Lastly, it simply loops back to the beginning to examine the next character. So, continues to print each character until it hits a terminator:
$9656 JR $964C ; Jump back to PrintString_Loop.