My Tandy CoCo stuff.

29/03/2018.

Glove - Tandy Color Computer.

3D Deathchase - Tandy Color Computer.

The Hobbit - Dragon 64.


CoCoCode.

Here I will place a series of snippets from the routines I've written in the process of making my games.  They will not be ideal for the absolute beginner but, hopefully, they will come in useful to others and help spread cococoding!

I personally use the A09 assembler a lot and the code shown here will certainly work on that.  Other assemblers may require some modification to the code for it to assemble it.

One thing that is certainly worth explaining is the "Tandy disk prefix" and "suffix".  These are two sections of five bytes which surround any code file that you save onto a CoCo.  In the examples shown below, I include these in the code so that they are ready-made for insertion into a CoCo disk, however there are various reasons why you may not want them to be there.  You may be using a different DOS, for example Dragon DOS, which uses a completely different header.  In such a case, you should just comment out the two sections.


Detecting Dragon or Tandy CoCo...

You've got your universal loader (below), but how can you detect if you've loaded it on a Dragon? One simple way is just to search for the string "DRAGON" in the ROM! Take it a bit further and search for "BASIC 2" to determine if you're on a CoCo 3. Just call DETECT_MACHINE and get the result in A.

MT_COCO			EQU	0
MT_DRAGON		EQU	1
MT_COCO3		EQU	2

ROM_START		EQU	$8000
ROM_LEN			EQU	$4000

STRING_ASCII_DRAGON
	FCB "DRAGON",0
STRING_BASIC_2
	FCB "BASIC 2",0

MEMSTR
; Y = String to search for (zero terminated).
; X = Start address of search space.
; U = Length of search space.

	PSHS U

	CLRB
1
	LDA B,Y
	BNE 21F

; Found!
	LEAS 2,S
; Returns Z set.
	RTS
21
	CMPA B,X
	BNE 22F
; Match.
	TSTB
	BNE 211F
; Keep U count.
	STU 0,S
211
	INCB
	BRA 23F
22
	TSTB
	BEQ 221F
; Get U back.
	LDU 0,S
221
	CLRB
	LEAX 1,X
23
	LEAU -1,U
	CMPU #0
	BNE 1B

; NOT FOUND!
	LEAS 2,S
	CLRA
	INCA
; A = 0 (Z) - Found.
; A = 1 (NZ) - Not Found.
; X = Address of string in search space.
	RTS

DETECT_MACHINE
	LDY #STRING_ASCII_DRAGON
	LDX #ROM_START
	LDU #ROM_LEN
	BSR MEMSTR

	BNE 1F
; Dragon
	LDA #MT_DRAGON
	RTS

1
; Try and find the difference between a Coco1/2 and 3 here.

	LDY #STRING_BASIC_2
	LDX #ROM_START
	LDU #ROM_LEN
	BSR MEMSTR

	BNE 1F
	
	LDA #MT_COCO3
	RTS

; A = 0 - Tandy CoCo 1/2.
; A = 1 - Dragon.
; A = 2 - Tandy CoCo 3.

1
	LDA #MT_COCO
	RTS
		

Detecting PAL/NTSC models.

You don't need to, but you like the idea of changing your game speed so that it's the same on PAL (50hz) or NTSC (60hz) models. Alternatively, you might be thinking of using an "artifact" mode, which only looks OK on an NTSC television.  If you know that you're on a 60hz machine then it's a pretty good guess (although, the CoCo 3 can run with an RGB monitor, which shows no "artifacts" at all). For this routine to work, you must detect whether it's running on a Dragon first, so use the above routine to do that, then put the result in MachineFlag before calling DETECT_TV.

MachineFlag
	FCB 0	; Put result of "DETECT_MACHINE" in here.

HSYNCCOUNTER
	PSHS CC
	ORCC #$10|$40
	LDX #0
	TST $FF02
1
	TST $FF03
	BPL 1B
	TST $FF02
2
	TST $FF00
3
	TST $FF01
	BPL 3B
	LEAX 1,X
	TST $FF03
	BPL 2B
	PULS CC,PC

VSYNCTIMER
	PSHS CC
	ORCC #$10|$40
	LDX #0
	TST $FF02
1
	TST $FF03
	BPL 1B
	TST $FF02
2
	LEAX 1,X
	TST $FF03
	BPL 2B
	PULS CC,PC

DETECT_TV
	LDA MachineFlag
	RORA	; Test for Dragon.
	BCS 1F

; Tandy.
	LDX #((312-262)/2)+262	; 287
	PSHS X
	BSR HSYNCCOUNTER
	BRA 11F
1
	LDX #((1193-993)/2)+993	; 1093
	PSHS X
	BSR VSYNCTIMER
	BSR VSYNCTIMER	; Called twice for the benefit of the T3 emulator!
11
; A = 0 - PAL.
; A = 1 - NTSC.
; X = Number of lines/Arbritary number (Dragon).
	CLRA
	CMPX 0,S
	BHS 1F
	INCA
1
	PULS X,PC	; X = junk.
;	RTS

STRLENY
; Y = Address
	CLRB
	PSHS Y
1
	LDA ,Y+
	BEQ 2F

	INCB
	BRA 1B

2
	PULS Y,PC
;	RTS
		

An autorunning cassette loader (for machine code programs) that works on all models of CoCo and Dragon, regardless of any disk DOS running.

The code hides in the section between $015E and $03FF, allowing you to load code just about anywhere from $0400 and upwards.  It replaces just about every vector used by BASIC to jump to our EXEC point.  The example here goes beyond that and loads in two block after the EXEC point.  The first one is assumed to be a screen and the second a code block, which have both been saved in the usual BASIC way.  The second code block should have its EXEC address set, which we jump to with JMP [$9D].

One thing to note here is the use of bCounterOn.  When this is non-zero, it will decrement an on-screen counter (located at COUNTADDR) each time it loads a block from cassette (the standard is that each cassette file is broken up into 255 byte chunks).  It does not know how many blocks are in the file, so you just have to figure out the value yourself and set it (in ASCII) into COUNTADDR, as the code below does.

; "Loader" by Jane McKay, 2006
VRAM		EQU     $0400
START		EQU     $015E
COUNTADDR	EQU     VRAM + $20	; The second line of the screen.

	ORG START-5

JMPEXEC MACRO
	FCB $7E	; JMP
	FCW EXEC
	ENDM

; Tandy disk prefix.
	FCB 0
	FCW ENDPC-START,START

; $015E
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC


; $0182 - read a line of input (called after c/loadm).
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
; $018E - user error trap (called after cloadm (syntax error)).
	JMPEXEC
; $0191 - system error trap.
	JMPEXEC

; $0194 - other stuff... (called by some machines).
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC
	JMPEXEC

; $01A9 (beyond the RAM hooks).

	RMB $0200-*

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; RAM

RAMStart

bCounterOn
	FCB 0

RAMEnd

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; CODE

EXEC
; This is the point of entry, we could clear everything else out, but instead the code will load in two blocks and EXEC the last one.

; Make sure ROM is paged in (especially for Dragon 64 in 64K RAM mode).
; AND set the stack pointer.
	ORCC #$10|$40
	LDS #$0400

	CLR $FFDE
; Select 32K ROM (Dragon 64) and set orange palette.
	LDA $FF22
	ORA #$04|$08
	STA $FF22

; Make sure RESET does a cold boot - as a very light form of protection.
	LDA #$00
	STA $0071

	CLR bCounterOn

	BSR CLOAD

	INC bCounterOn

	LDX #COUNTADDR
	LDA #$30	; 0
	STA ,X+
	LDA #$39	; 9
	STA ,X+
	LDA #$36	; 6
	STA ,X+

	BSR CLOAD
	JMP [$9D]

CAS_FNAME		EQU	0	; 8 ASCII bytes.
CAS_FTYPE		EQU	8
CAS_ASCII		EQU	9
CAS_GAP			EQU	10
CAS_EXEC		EQU	11	; WORD
CAS_ADDR		EQU	13	; WORD

CAS_LENGTH		EQU	15

CASHEAD
	FCB "TESTCAS",0	; Must be 8 bytes.
	FCB $02			; Binary.
	FCB $00			; Binary Data.
	FCB $00			; Continuous Data.
	FCW $0000		; EXEC.
	FCW $0500		; Load Address.

MOTOR_OFF
	LDB $FF21
	ANDB #~$08
	STB $FF21
;	ANDCC #~($10|$40)	; Only if we're returning to BASIC...
	ORCC #$10|$40	; Try to prevent any interrupts from kicking in...
	RTS

DECBLOCK
	LDA bCounterOn
	BNE 1F
	RTS
1
	LDX #COUNTADDR + $02
	LDB #$03
1
	DEC ,X
	LDA ,X
	CMPA #$30
	BHS 11F

	LDA #$39
	STA ,X

	LEAX -1,X
	DECB
	BNE 1B
11
	RTS

CLOAD
	JSR [$A004]		; Cass Leader Read.
	LDX #CASHEAD
	STX $7E
1
	BSR DECBLOCK
	JSR [$A006]		; Read Block.
	STX $7E

	LDA $81			; Check Error.
	BNE 3F
	LDA $7C			; Block Type ( 0 = Header, $FF = EOF ).
	BEQ 2F			; $00 = Header.
	INCA
	BEQ 3F			; $FF = EOF. (A = 0).
; Loop to next block.
	BRA 1B

2
; Header.
	LDX CASHEAD+CAS_ADDR
	STX $7E			; Start address.
	LDX CASHEAD+CAS_EXEC
	STX $9D			; EXEC address.

	JSR [$A004]		; Cass Leader Read.
	BRA 1B

3
; Error (if A is non-zero).
	BSR MOTOR_OFF
; A = 0 - OK.
; A != 0 - ERROR.
	RTS

;;;;;

MEMSET
; X = Address.
; Y = Length.
; A = Byte.
1
	STA ,X+
	LEAY -1,Y
	BNE 1B
	RTS

MEMCPY
; X = Dest Address.
; Y = Src Address.
; D = Length.

	PSHS U

	TFR X,U
	TFR D,X
1
	LDA ,Y+
	STA ,U+
	LEAX -1,X
	BNE 1B

	TFR U,X

	PULS U
	RTS

ENDPC

; Tandy disk suffix.
        FCB 255,0,0
        FCW EXEC
.end
		

(C) Jane McKay, 2018