diff --git a/doc/notes.txt b/doc/notes.txt
index 9bfd6e1f..89cc4869 100644
--- a/doc/notes.txt
+++ b/doc/notes.txt
@@ -154,6 +154,7 @@ TODO:
- publishing Markdown file loads default file?
- better text/binary detection (e.g. 0xa9 copyright)
- going into repo chooses wrong file if republished with different main
+ - renaming main file doesn't change repo def.
- keyboard shortcuts
- ctrl+alt+l on ubuntu locks screen
- alt-D doesn't work anymore
diff --git a/electron.html b/electron.html
index 06cd6fa2..cf5b2473 100644
--- a/electron.html
+++ b/electron.html
@@ -362,6 +362,7 @@ body {
+
diff --git a/presets/vcs/skeleton.ca65 b/presets/vcs/skeleton.ca65
new file mode 100644
index 00000000..d5dbd42c
--- /dev/null
+++ b/presets/vcs/skeleton.ca65
@@ -0,0 +1,32 @@
+
+.include "vcs-ca65.h"
+
+.zeropage
+Temp: .byte 0
+
+
+.segment "CODE"
+
+Reset:
+ CLEAN_START
+
+NextFrame:
+ FRAME_START
+
+ lda #$80
+ sta COLUBK
+
+ KERNEL_START
+
+ KERNEL_END
+
+ FRAME_END
+
+ jmp NextFrame
+
+
+.segment "VECTORS"
+.word Reset
+.word Reset
+.word Reset
+
diff --git a/presets/vcs/vcs-ca65.h b/presets/vcs/vcs-ca65.h
new file mode 100644
index 00000000..85503054
--- /dev/null
+++ b/presets/vcs/vcs-ca65.h
@@ -0,0 +1,232 @@
+.setcpu "6502X"
+
+; TIA write registers
+
+VSYNC := $00 ; ---- --1- This address controls vertical sync time by writing D1 into the VSYNC latch.
+VBLANK := $01 ; 76-- --1- 1=Start VBLANK, 6=Enable INPT4, INPT5 latches, 7=Dump INPT1,2,3,6 to ground
+WSYNC := $02 ; ---- ---- This address halts microprocessor by clearing RDY latch to zero. RDY is set true again by the leading edge of horizontal blank.
+RSYNC := $03 ; ---- ---- This address resets the horizontal sync counter to define the beginning of horizontal blank time, and is used in chip testing.
+NUSIZ0 := $04 ; --54 -210 \ 0,1,2: player copys'n'size, 4,5: missile size: 2^x pixels
+NUSIZ1 := $05 ; --54 -210 /
+COLUP0 := $06 ; 7654 321- color player 0
+COLUP1 := $07 ; 7654 321- color player 1
+COLUPF := $08 ; 7654 321- color playfield
+COLUBK := $09 ; 7654 321- color background
+CTRLPF := $0A ; --54 -210 0=reflect playfield, 1=pf uses player colors, 2=playfield over sprites 4,5=ballsize:2^x
+REFP0 := $0B ; ---- 3--- reflect player 0
+REFP1 := $0C ; ---- 3--- reflect player 1
+PF0 := $0D ; DCBA ---- \ Playfield bits: ABCDEFGHIJKLMNOPQRST
+PF1 := $0E ; EFGH IJKL > normal: ABCDEFGHIJKLMNOPQRSTABCDEFGHIJKLMNOPQRST
+PF2 := $0F ; TSRQ PONM / reflect: ABCDEFGHIJKLMNOPQRSTTSRQPONMLKJIHGFEDCBA
+RESP0 := $10 ; ---- ---- \
+RESP1 := $11 ; ---- ---- \
+RESM0 := $12 ; ---- ---- > reset players, missiles and the ball. The object will begin its serial graphics at the time of a horizontal line at which the reset address occurs.
+RESM1 := $13 ; ---- ---- /
+RESBL := $14 ; ---- ---- /
+AUDC0 := $15 ; ---- 3210 audio control voice 0
+AUDC1 := $16 ; ---- 3210 audio control voice 1
+AUDF0 := $17 ; ---4 3210 frequency divider voice 0
+AUDF1 := $18 ; ---4 3210 frequency divider voice 1
+AUDV0 := $19 ; ---- 3210 audio volume voice 0
+AUDV1 := $1A ; ---- 3210 audio volume voice 1
+GRP0 := $1B ; 7654 3210 graphics player 0
+GRP1 := $1C ; 7654 3210 graphics player 1
+ENAM0 := $1D ; ---- --1- enable missile 0
+ENAM1 := $1E ; ---- --1- enable missile 1
+ENABL := $1F ; ---- --1- enable ball
+HMP0 := $20 ; 7654 ---- write data (horizontal motion values) into the horizontal motion registers
+HMP1 := $21 ; 7654 ---- write data (horizontal motion values) into the horizontal motion registers
+HMM0 := $22 ; 7654 ---- write data (horizontal motion values) into the horizontal motion registers
+HMM1 := $23 ; 7654 ---- write data (horizontal motion values) into the horizontal motion registers
+HMBL := $24 ; 7654 ---- write data (horizontal motion values) into the horizontal motion registers
+VDELP0 := $25 ; ---- ---0 delay player 0 by one vertical line
+VDELP1 := $26 ; ---- ---0 delay player 1 by one vertical line
+VDELBL := $27 ; ---- ---0 delay ball by one vertical line
+RESMP0 := $28 ; ---- --1- keep missile 0 aligned with player 0
+RESMP1 := $29 ; ---- --1- keep missile 1 aligned with player 1
+HMOVE := $2A ; ---- ---- This address causes the horizontal motion register values to be acted upon during the horizontal blank time in which it occurs.
+HMCLR := $2B ; ---- ---- This address clears all horizontal motion registers to zero (no motion).
+CXCLR := $2C ; ---- ---- clears all collision latches
+
+; TIA read registers
+
+CXM0P := $00 ; xx00 0000 Read Collision M0-P1 M0-P0
+CXM1P := $01 ; xx00 0000 M1-P0 M1-P1
+CXP0FB := $02 ; xx00 0000 P0-PF P0-BL
+CXP1FB := $03 ; xx00 0000 P1-PF P1-BL
+CXM0FB := $04 ; xx00 0000 M0-PF M0-BL
+CXM1FB := $05 ; xx00 0000 M1-PF M1-BL
+CXBLPF := $06 ; x000 0000 BL-PF -----
+CXPPMM := $07 ; xx00 0000 P0-P1 M0-M1
+INPT0 := $08 ; x000 0000 Read Pot Port 0
+INPT1 := $09 ; x000 0000 Read Pot Port 1
+INPT2 := $0A ; x000 0000 Read Pot Port 2
+INPT3 := $0B ; x000 0000 Read Pot Port 3
+INPT4 := $0C ; x000 0000 Read Input (Trigger) 0
+INPT5 := $0D ; x000 0000 Read Input (Trigger) 1
+
+; RIOT
+
+SWCHA := $0280
+SWACNT := $0281
+SWCHB := $0282
+SWBCNT := $0283
+INTIM := $0284 ; Timer output
+TIMINT := $0285
+
+TIM1T := $0294
+TIM8T := $0295
+TIM64T := $0296
+TIM1024T := $0297
+
+;-------------------------------------------------------------------------------
+; SLEEP duration
+; Original author: Thomas Jentzsch
+; Inserts code which takes the specified number of cycles to execute. This is
+; useful for code where precise timing is required.
+; ILLEGAL-OPCODE VERSION DOES NOT AFFECT FLAGS OR REGISTERS.
+; LEGAL OPCODE VERSION MAY AFFECT FLAGS
+; Uses illegal opcode (DASM 2.20.01 onwards).
+
+.macro SLEEP cycles
+.if cycles < 2
+.error "MACRO ERROR: 'SLEEP': Duration must be > 1"
+.endif
+.if cycles & 1
+.ifndef NO_ILLEGAL_OPCODES
+ nop 0
+.else
+ bit VSYNC
+.endif
+.repeat (cycles-3)/2
+ nop
+.endrep
+.else
+.repeat cycles/2
+ nop
+.endrep
+.endif
+.endmacro
+
+;-------------------------------------------------------------------------------
+; VERTICAL_SYNC
+; revised version by Edwin Blink -- saves bytes!
+; Inserts the code required for a proper 3 scanline vertical sync sequence
+; Note: Alters the accumulator
+
+; OUT: A = 0
+
+.macro VERTICAL_SYNC
+ lda #%1110 ; each '1' bits generate a VSYNC ON line (bits 1..3)
+.local VSLP1
+VSLP1: sta WSYNC ; 1st '0' bit resets Vsync, 2nd '0' bit exit loop
+ sta VSYNC
+ lsr
+ bne VSLP1 ; branch until VYSNC has been reset
+.endmacro
+
+;-------------------------------------------------------
+; Usage: TIMER_SETUP lines
+; where lines is the number of scanlines to skip (> 2).
+; The timer will be set so that it expires before this number
+; of scanlines. A WSYNC will be done first.
+
+.macro TIMER_SETUP lines
+.local cycles
+cycles = ((lines * 76) - 13)
+; special case for when we have two timer events in a line
+; and our 2nd event straddles the WSYNC boundary
+ .if (cycles .mod 64) < 12
+ lda #(cycles / 64) - 1
+ sta WSYNC
+ .else
+ lda #(cycles / 64)
+ sta WSYNC
+ .endif
+ sta TIM64T
+.endmacro
+
+;-------------------------------------------------------
+; Use with TIMER_SETUP to wait for timer to complete.
+; Performs a WSYNC afterwards.
+
+.macro TIMER_WAIT
+.local waittimer
+waittimer:
+ lda INTIM
+ bne waittimer
+ sta WSYNC
+.endmacro
+
+;-------------------------------------------------------------------------------
+; CLEAN_START
+; Original author: Andrew Davie
+; Standardised start-up code, clears stack, all TIA registers and RAM to 0
+; Sets stack pointer to $FF, and all registers to 0
+; Sets decimal mode off, sets interrupt flag (kind of un-necessary)
+; Use as very first section of code on boot (ie: at reset)
+; Code written to minimise total ROM usage - uses weird 6502 knowledge :)
+
+.macro CLEAN_START
+.local CLEAR_STACK
+ sei
+ cld
+ ldx #0
+ txa
+ tay
+CLEAR_STACK: dex
+ txs
+ pha
+ bne CLEAR_STACK ; SP=$FF, X = A = Y = 0
+.endmacro
+
+; assume NTSC unless PAL defined
+.ifndef PAL
+PAL = 0
+.endif
+
+; 192 visible scanlines for NTSC, 228 for PAL
+.if PAL
+SCANLINES = 228
+.else
+SCANLINES = 192
+.endif
+
+; start of frame -- vsync and set back porch timer
+.macro FRAME_START
+ VERTICAL_SYNC
+ .if PAL
+ TIMER_SETUP 44
+ .else
+ TIMER_SETUP 36
+ .endif
+.endmacro
+
+; end of back porch -- start kernel
+.macro KERNEL_START
+ TIMER_WAIT
+ lda #0
+ sta VBLANK
+ .if !PAL
+ TIMER_SETUP 194
+ .endif
+.endmacro
+
+; end of kernel -- start front porch timer
+.macro KERNEL_END
+ .if !PAL
+ TIMER_WAIT
+ .endif
+ lda #2
+ sta VBLANK
+ .if PAL
+ TIMER_SETUP 36
+ .else
+ TIMER_SETUP 28
+ .endif
+.endmacro
+
+; end of frame -- jump to frame start
+.macro FRAME_END
+ TIMER_WAIT
+.endmacro
diff --git a/src/platform/vcs.ts b/src/platform/vcs.ts
index 53ddc8be..60273b72 100644
--- a/src/platform/vcs.ts
+++ b/src/platform/vcs.ts
@@ -265,6 +265,7 @@ class VCSPlatform extends BasePlatform {
getToolForFilename(fn) {
if (fn.endsWith(".wiz")) return "wiz";
if (fn.endsWith(".bb") || fn.endsWith(".bas")) return "bataribasic";
+ if (fn.endsWith(".ca65")) return "ca65";
return "dasm";
}
getDefaultExtension() { return ".a"; }
@@ -434,6 +435,9 @@ class VCSMAMEPlatform extends BaseMAMEPlatform implements Platform {
getPresets = function() { return VCS_PRESETS; }
getToolForFilename = function(fn) {
+ if (fn.endsWith(".wiz")) return "wiz";
+ if (fn.endsWith(".bb") || fn.endsWith(".bas")) return "bataribasic";
+ if (fn.endsWith(".ca65")) return "ca65";
return "dasm";
}
getDefaultExtension = function() { return ".a"; };
diff --git a/src/worker/lib/vcs/atari2600.cfg b/src/worker/lib/vcs/atari2600.cfg
new file mode 100644
index 00000000..ed91fa85
--- /dev/null
+++ b/src/worker/lib/vcs/atari2600.cfg
@@ -0,0 +1,20 @@
+# Linker config file for targeting the Atari 2600.
+# From http://wiki.cc65.org/doku.php?id=cc65:atari_2600
+
+MEMORY {
+ RAM: start = $80, size=$80, type = rw, define = yes;
+ ROM: start = $F000, size=$1000, type = ro, file = %O, define = yes;
+ TIA: start = $00, size=$40, type = rw, define = yes;
+ RIOT: start = $280, size=$20, type = rw, define = yes;
+}
+
+SEGMENTS {
+ RODATA: load=ROM, type=ro, align = $100;
+ CODE: load=ROM, type=ro, define=yes;
+ DATA: load=ROM, run=RAM, type=rw, define=yes;
+ BSS: load=RAM, type=bss, define=yes;
+ VECTORS: load=ROM, type=ro, start=$FFFA;
+ ZEROPAGE: load=RAM, type=zp;
+ TIA: load=TIA, type=rw, define = yes, optional = yes;
+ RIOT: load=RIOT, type=rw, define = yes, optional = yes;
+}
diff --git a/src/worker/workermain.ts b/src/worker/workermain.ts
index 6e495584..f887e2bf 100644
--- a/src/worker/workermain.ts
+++ b/src/worker/workermain.ts
@@ -60,7 +60,9 @@ var PLATFORM_PARAMS = {
data_start: 0x80,
data_size: 0x80,
wiz_rom_ext: '.a26',
- wiz_inc_dir: '2600'
+ wiz_inc_dir: '2600',
+ extra_link_files: ['atari2600.cfg'],
+ cfgfile: 'atari2600.cfg',
},
'mw8080bw': {
arch: 'z80',
@@ -575,6 +577,7 @@ function setupFS(FS, name:string) {
if (name === '65-vector') name = '65-sim6502'; // TODO
if (name === '65-atari7800') name = '65-sim6502'; // TODO
if (name === '65-devel') name = '65-sim6502'; // TODO
+ if (name === '65-vcs') name = '65-sim6502'; // TODO
if (!fsMeta[name]) throw Error("No filesystem for '" + name + "'");
FS.mkdir('/share');
FS.mount(WORKERFS, {
@@ -915,6 +918,7 @@ function parseCA65Listing(code, symbols, params, dbg) {
var dbgLineMatch = /^([0-9A-F]+)([r]?)\s+(\d+)\s+[.]dbg\s+(\w+), "([^"]+)", (.+)/;
var funcLineMatch = /"(\w+)", (\w+), "(\w+)"/;
var insnLineMatch = /^([0-9A-F]+)([r]?)\s{1,2}(\d+)\s{1,2}([0-9A-Frx ]{11})\s+(.*)/;
+ var segMatch = /[.]segment\s+"(\w+)"/i;
var lines = [];
var linenum = 0;
// TODO: only does .c functions, not all .s files
@@ -945,28 +949,37 @@ function parseCA65Listing(code, symbols, params, dbg) {
}
} else {
var linem = insnLineMatch.exec(line);
- if (linem) linenum++;
- if (linem && linem[1]) {
+ var topfile = linem && linem[3] == '1';
+ if (topfile) linenum++;
+ if (topfile && linem[1]) {
var offset = parseInt(linem[1], 16);
var insns = linem[4].trim();
if (insns.length) {
- lines.push({
- line:linenum,
- offset:offset + segofs,
- insns:insns
- });
// take back one to honor the long .byte line
- if (linem[5].length == 0) linenum--;
+ if (linem[5].length == 0) {
+ linenum--;
+ } else {
+ lines.push({
+ line:linenum,
+ offset:offset + segofs,
+ insns:insns
+ });
+ }
} else {
var sym = linem[5];
- if (sym && sym.endsWith(':')) {
- sym = sym.substring(0, sym.length-1);
- var symofs = symbols[sym];
+ var segm = sym && segMatch.exec(sym);
+ if (segm && segm[1]) {
+ var symofs = symbols['__' + segm[1] + '_RUN__'];
if (typeof symofs === 'number') {
- offset = parseInt(linem[1], 16);
- segofs = symofs - offset;
+ segofs = symofs;
//console.log(sym, symofs, '-', offset);
}
+ } else if (sym.endsWith(':') && !sym.startsWith('@')) {
+ var symofs = symbols[sym.substring(0,sym.length-1)];
+ if (typeof symofs === 'number') {
+ segofs = symofs - offset;
+ //console.log(sym, segofs, symofs, offset);
+ }
}
}
}
@@ -2866,6 +2879,7 @@ var TOOL_PRELOADFS = {
'ca65-atari7800': '65-sim6502',
'cc65-devel': '65-sim6502',
'ca65-devel': '65-sim6502',
+ 'ca65-vcs': '65-sim6502',
'sdasz80': 'sdcc',
'sdcc': 'sdcc',
'sccz80': 'sccz80',