by Richard F. Drushel (drushel@apk.net)
[Administrative note: this article is appearing a week late because of the observance of my wife's birthday on 26 January and family illness this week. Thanks for bearing with me. The regularly-scheduled article for 2 February is appearing on time as a separate article.]
I mentioned in my article on disassembling the ColecoVision game Pitfall by Activision (TWWMCA 9701.19) that, in addition to an unused sound, I discovered several complete code subroutines which were not used. I'm presenting them here with some comments. They are general-purpose routines which are called through the 1-byte RST xxH subroutine mechanism (designed so that frequently-used subroutines could be called using only one byte of machine code rather than the three required by CALL nnnn.
Since RST xxH causes a CALL to address xxH, and these addresses 08H-38H lie in the ColecoVision OS7 ROM, there are JP nnnn statements at each vector to defined locations in cartridge ROM address space. These defined locations are organized as a jump table (i.e., a series of consecutive JP nnnn statements). It is the responsibility of the cartridge programmer to make sure that each jump table entry points to his own routines. If a particular RST xxH is not used by the game, the jump table entry for that RST must point to a RET.
Although Pitfall(tm) makes no use of RST xxH commands, I presume that other Activision games do; I haven't looked, though.
Here follows the entire low memory setup of the Pitfall(tm) game cartridge, taken from my commented disassembly PITFALL.ASM. Some of the comments have been improved from the version I originally put up download as PITFALL.ZIP; I have now put up a fixed version, PITFALL2.ZIP, which supersedes the former. I indicate the division between the code that is common to (i.e., *required* by) all ColecoVision game cartridges, and the Pitfall-specific code. Symbols that begin with MY_ and disassembler-generated symbols of the form J$nnnn or D.nnnn are Pitfall-specific; all the rest are OS7 public globals (see OS7SYM.ASM).
;***********************************************
;* *
;* start of Pitfall(tm) game cartridge ROM *
;* *
;* the initial pointers and jump table *
;* are required for every game cartridge *
;* *
;***********************************************
CARTRIDGE:
DB 55H,0AAH ;magic identifier for "test" cartridge
;(i.e., don't use ColecoVision boot screen)
LOCAL_SPR_TBL:
DW MY_LOCAL_SPR_TBL ;copy of sprite name table (mirror of VRAM)
SPRITE_ORDER:
DW MY_SPRITE_ORDER ;sprite order table
WORK_BUFFER:
DW MY_WORK_BUFFER ;scratch RAM for OS-7 function calls
CONTROLLER_MAP:
DW MY_CONTROLLER_MAP ;data area to store state of game controller
START_GAME:
DW MY_START_GAME ;first executable code of the game
; -----------------
;***********************************************
;* *
;* low-memory RST, INT, and NMI jump table *
;* *
;* the OS-7 ROM has JPs to this code *
;* *
;***********************************************
RST_8H_RAM:
JP MY_RST_8H_RAM ;for RST 08H
;
; -----------------
RST_10H_RAM:
JP MY_RST_10H_RAM ;for RST 10H
;
; -----------------
RST_18H_RAM:
JP MY_RST_18H_RAM ;for RST 18H
;
; -----------------
RST_20H_RAM:
JP MY_RST_20H_RAM ;for RST 20H
;
; -----------------
RST_28H_RAM:
JP MY_RST_28H_RAM ;for RST 28H
;
; -----------------
RST_30H_RAM:
JP MY_RST_30H_RAM ;for RST 30H
;
; -----------------
IRQ_INT_VECT:
JP MY_IRQ_INT_VECT ;for RST 38H or INT interrupt
;
; -----------------
NMI_INT_VECT:
JP MY_NMI_VECT ;for NMI interrupt
;
; -----------------
GAME_NAME:
;This is where the title string is stored for cartridges
;which use the ColecoVision boot screen (magic number
;AA55 hex at addresses 0-1). Pitfall(tm) doesn't need a
;title string, since it's a "test" cartridge (55AA). Thus,
;the game specific code begins here.
;**************************************
;* *
;* begin cartridge-specific code *
;* *
;**************************************
MY_RST_8H_RAM:
;not used in Pitfall(tm). Routine to pause for a certain
;number of interrupt cycles. On entry, the stack contains
;a pointer to a count (nominally the return address of the
;CALL or RST, but in this case the data byte is in-line
;with the code, immediately after the CALL or RST. This
;count is fetched, and the CPU put into the HALT state
;for the specified number of counts. HALT disables the
;CPU until an external interrupt (INT or NMI) occurs;
;execution then begins at the first statement after the
;HALT (which here is a DJNZ loop). On exit, return is to
;the address immediately after the in-line count byte.
;This code, if useful at all on the ColecoVision/ADAM, can
;only be used with INT interrupts. The TMS9928 VDP, which
;generates NMIs, must receive a handshake after each NMI in
;order to keep them coming (namely, a read of VDP register 8).
;This code doesn't do the handshake after the HALT. INT
;interrupts, however, will keep coming as long as they have
;been enabled with EI.
;calling code has the form:
; RST 08H
; DB COUNT
; [next opcode]
EX (SP),HL ;get HL=return address off stack
PUSH BC ;save a scratch register
LD B,(HL) ;get count byte
INC HL ;point ahead one to real return address
J$8028:
HALT ;halt the CPU and wait for an interrupt
DJNZ J$8028 ;interrupt occurred, so go back and wait again
;
POP BC ;all done, restore BC
EX (SP),HL ;put return address back on stack
RET ;and exit
;
; -----------------
MY_RST_10H_RAM:
;not used in Pitfall(tm). Routine to get a vector from
;a table and JP there. On entry, HL=address of table,
;and A=index of vector. On exit, a JP is performed to
;the desired vector.
ADD A,A ;A=A*2 for word offset
ADD A,L ;add it to lobyte of table base
LD L,A ;and back into L; was there a carry?
JR NC,J$8034 ;no
;
INC H ;yes, so we have to INC H
J$8034:
LD A,(HL) ;get lobyte of vector
INC HL ;point to hibyte
LD H,(HL) ;get hibyte of vector
LD L,A ;now HL=vector
JP (HL) ;go there
;
; -----------------
MY_RST_18H_RAM:
;not used in Pitfall(tm). Looks like a custom routine to
;return a random number. The built-in RAND_GEN routine
;doesn't give the greatest sequence of random numbers. On
;entry, A=count of how many times to call RAND_GEN, and
;HL=seed value. On exit, A=random number.
PUSH HL
PUSH DE
PUSH BC
PUSH AF ;save count
LD B,A ;count in B
J$803E:
CALL RAND_GEN ;OS7 call, return A=random number
;
DJNZ J$803E ;keep going until done
;
POP BC ;restore count
XOR A ;A=0
LD D,A ;DE=0
LD E,A
EX DE,HL ;HL=0, DE=entry seed
J$8048:
ADD HL,DE ;add seed
ADC A,00H ;and add the carry (if set)
DJNZ J$8048 ;keep going until done
;
POP BC
POP DE
POP HL
RET
;
; -----------------
MY_RST_20H_RAM:
;not used in Pitfall(tm). Multiplies HL*A*2, returning
;product in HL. Could be another way to calculate an offset
;into a vector table.
PUSH DE
LD H,00H
LD D,H
LD E,H
EX DE,HL ;DE=entry L, HL=0
J$8057:
SLA E
RL D ;DE=DE*2
SRL A ;A=A/2; did DE*2 give a carry?
JR NC,J$8060 ;no
;
ADD HL,DE ;yes, so HL=HL+DE
J$8060:
OR A ;is A=0 yet?
JR NZ,J$8057 ;no, so keep going
;
POP DE
RET
;
; -----------------
MY_RST_28H_RAM:
;not implemented in Pitfall(tm).
RET
;
; -----------------
MY_IRQ_INT_VECT:
;This is for the maskable interrupt routine (for the spinners, trackball,
;and driving wheel). Not implemented in Pitfall(tm).
RET
;
; -----------------
MY_RST_30H_RAM:
;not implemented in Pitfall(tm).
RET
;
; -----------------
MY_NMI_VECT:
;NMI routine. Does a JP to address stored at D.7000.
PUSH HL ;save HL
LD HL,(D.7000) ;get the vector
EX (SP),HL ;put it on the stack and restore HL
RET ;go to the vector address
;
; -----------------
START_MAIN:
;starts the MAIN program.
[code omitted]
You may be somewhat disturbed by the lack of comments regarding the NMI routine at MY_NMI_VECTOR. That's because what happens during the NMI routine is extremely complicated (for Pitfall and for any other ColecoVision game). Stated briefly, the "main" program of a ColecoVision game is an idle loop,
IDLE_LOOP:
JR IDLE_LOOP
waiting endlessly for some kind of interrupt (maskable or non-maskable) to occur. The game itself is played only after an interrupt occurs. In the case of Pitfall, which uses the 60 Hz (or 50 Hz on European systems) NMI as the system clock, everything happens as part of the MY_NMI_VECT routine. There is additional complexity to insure that the proper handshaking is performed with the TMS9928/9929 VDP (which generates the NMI) so that the NMI is re-enabled before the RETN command (return from NMI) is executed.
The NMI handler in Pitfall is quite interesting, but too complex for me to cover adequately at this time. I intend to make it a topic of discussion in a future issue of TWWMCA; stay tuned.
See you next week!
*Rich*
Next Article
Previous Article
TWWMCA Archive Main Page