-- set cpu to 6502 cpu = require "6502" setmetatable(_ENV, cpu) vcs = { -- TIA write only VSYNC = 0x00, -- ......1. vertical sync set-clear VBLANK = 0x01, -- 11....1. vertical blank set-clear WSYNC = 0x02, -- wait for leading edge of horizontal blank RSYNC = 0x03, -- reset horizontal sync counter NUSIZ0 = 0x04, -- ..111111 number-size player-missile 0 NUSIZ1 = 0x05, -- ..111111 number-size player-missile 1 COLUP0 = 0x06, -- 1111111. color-lum player 0 and missile 0 COLUP1 = 0x07, -- 1111111. color-lum player 1 and missile 1 COLUPF = 0x08, -- 1111111. color-lum playfield and ball COLUBK = 0x09, -- 1111111. color-lum background CTRLPF = 0x0a, -- ..11.111 control playfield ball size & collisions REFP0 = 0x0b, -- ....1... reflect player 0 REFP1 = 0x0c, -- ....1... reflect player 1 PF0 = 0x0d, -- 1111.... playfield register byte 0 PF1 = 0x0e, -- 11111111 playfield register byte 1 PF2 = 0x0f, -- 11111111 playfield register byte 2 RESP0 = 0x10, -- reset player 0 RESP1 = 0x11, -- reset player 1 RESM0 = 0x12, -- reset missile 0 RESM1 = 0x13, -- reset missile 1 RESBL = 0x14, -- reset ball AUDC0 = 0x15, -- ....1111 audio control 0 AUDC1 = 0x16, -- ....1111 audio control 1 AUDF0 = 0x17, -- ...11111 audio frequency 0 AUDF1 = 0x18, -- ...11111 audio frequency 1 AUDV0 = 0x19, -- ....1111 audio volume 0 AUDV1 = 0x1a, -- ....1111 audio volume 1 GRP0 = 0x1b, -- 11111111 graphics player 0 GRP1 = 0x1c, -- 11111111 graphics player 1 ENAM0 = 0x1d, -- ......1. graphics (enable) missile 0 ENAM1 = 0x1e, -- ......1. graphics (enable) missile 1 ENABL = 0x1f, -- ......1. graphics (enable) ball HMP0 = 0x20, -- 1111.... horizontal motion player 0 HMP1 = 0x21, -- 1111.... horizontal motion player 1 HMM0 = 0x22, -- 1111.... horizontal motion missile 0 HMM1 = 0x23, -- 1111.... horizontal motion missile 1 HMBL = 0x24, -- 1111.... horizontal motion ball VDELP0 = 0x25, -- .......1 vertical delay player 0 VDELP1 = 0x26, -- .......1 vertical delay player 1 VDELBL = 0x27, -- .......1 vertical delay ball RESMP0 = 0x28, -- ......1. reset missile 0 to player 0 RESMP1 = 0x29, -- ......1. reset missile 1 to player 1 HMOVE = 0x2a, -- apply horizontal motion HMCLR = 0x2b, -- clear horizontal motion registers CXCLR = 0x2c, -- clear collision latches= 0x20 -- TIA read only CXM0P = 0x30, -- 11...... read collision M0-P1, M0-P0 (Bit 7 --6) CXM1P = 0x31, -- 11...... read collision M1-P0, M1-P1 CXP0FB = 0x32, -- 11...... read collision P0-PF, P0-BL CXP1FB = 0x33, -- 11...... read collision P1-PF, P1-BL CXM0FB = 0x34, -- 11...... read collision M0-PF, M0-BL CXM1FB = 0x35, -- 11...... read collision M1-PF, M1-BL CXBLPF = 0x36, -- 1....... read collision BL-PF, unused CXPPMM = 0x37, -- 11...... read collision P0-P1, M0-M1 INPT0 = 0x38, -- 1....... read pot port INPT1 = 0x39, -- 1....... read pot port INPT2 = 0x3a, -- 1....... read pot port INPT3 = 0x3b, -- 1....... read pot port INPT4 = 0x3c, -- 1....... read input INPT5 = 0x3d, -- 1....... read input -- PIA SWCHA = 0x280, -- 11111111 Port A= 0x280 -- input or output (read or write) SWACNT = 0x281, -- 11111111 Port A DDR, 0= input, 1=output SWCHB = 0x282, -- 11111111 Port B= 0x280 -- console switches (read only) SWBCNT = 0x283, -- 11111111 Port B DDR (hardwired as input) INTIM = 0x284, -- 11111111 Timer output (read only) INSTAT = 0x285, -- 11...... Timer Status (read only, undocumented) TIM1T = 0x294, -- 11111111 set 1 clock interval (838 nsec/interval) TIM8T = 0x295, -- 11111111 set 8 clock interval (6.7 usec/interval) TIM64T = 0x296, -- 11111111 set 64 clock interval (53.6 usec/interval) T1024T = 0x297, -- 11111111 set 1024 clock interval (858.2 usec/interval) } do local symbols = cpu.symbols for k,v in pairs(vcs) do symbols[k] = v end end nusiz = { MSL_SIZE_1 = 16*0, MSL_SIZE_2 = 16*1, MSL_SIZE_4 = 16*2, MSL_SIZE_8 = 16*3, ONE_COPY = 0, TWO_COPIES_CLOSE = 1, TWO_COPIES_MEDIUM = 2, THREE_COPIES_CLOSE = 3, TWO_COPIES_WIDE = 4, DOUBLE_SIZED_PLAYER = 5, THREE_COPIES_MEDIUM = 6, QUAD_SIZED_PLAYER = 7, } ctrlpf = { BALL_SIZE_1 = 16*0, BALL_SIZE_2 = 16*1, BALL_SIZE_4 = 16*2, BALL_SIZE_8 = 16*3, PF_MIRRORED = 1, PF_SCOREMODE = 2, PF_PRIO_BALLABOVE = 4, } enable = { DISABLE = 0, ENABLE = 2, } vdel = { DISABLE = 0, ENABLE = 1, } hm = { LEFT_7 = 7*16, LEFT_6 = 6*16, LEFT_5 = 5*16, LEFT_4 = 4*16, LEFT_3 = 3*16, LEFT_2 = 2*16, LEFT_1 = 1*16, NO_MOTION = 0, RIGHT_1 = 15*16, RIGHT_2 = 14*16, RIGHT_3 = 13*16, RIGHT_4 = 12*16, RIGHT_5 = 11*16, RIGHT_6 = 10*16, RIGHT_7 = 9*16, RIGHT_8 = 8*16, C74_LEFT_15 = 7*16, C74_LEFT_14 = 6*16, C74_LEFT_13 = 5*16, C74_LEFT_12 = 4*16, C74_LEFT_11 = 3*16, C74_LEFT_10 = 2*16, C74_LEFT_9 = 1*16, C74_LEFT_8 = 0*16, C74_LEFT_7 = 15*16, C74_LEFT_6 = 14*16, C74_LEFT_5 = 13*16, C74_LEFT_4 = 12*16, C74_LEFT_3 = 11*16, C74_LEFT_2 = 10*16, C74_LEFT_1 = 9*16, C74_NO_MOTION = 8*16, } NTSC = { TIM_OVERSCAN = 38, -- TIM64T, 2432 cycles = 32 scanlines TIM_VBLANK = 45, -- TIM64T, 2880 cycles = ~ 38 scanlines TIM_KERNEL = 15, -- T1024T, 15360 cycles = ~202 scanlines } PAL = { TIM_OVERSCAN = 50, -- TIM64T, 3200 cycles = ~ 42 scanlines TIM_VBLANK = 61, -- TIM64T, 3904 cycles = ~ 51 scanlines TIM_KERNEL = 17, -- T1024T, 17408 cycles = ~229 scanlines } TV = PAL init = function() cld ldx#0 txa local l=label() dex txs pha bne l end wait = function() local l=label() lda INTIM bne l end overscan_begin = function() sta WSYNC lda#2 sta VBLANK lda#TV.TIM_OVERSCAN sta TIM64T end overscan_end = wait overscan = function(f) overscan_begin() if f then f() end overscan_end() end vblank_begin = function() lda#0b1110 local l=label() sta WSYNC sta VSYNC lsr bne l lda#2 sta VBLANK lda#TV.TIM_VBLANK sta TIM64T end vblank_end = function() wait() sta WSYNC sta VBLANK end vblank = function(f) vblank_begin() if f then f() end vblank_end() end screen_begin = function() lda#TV.TIM_KERNEL sta T1024T end screen_end = wait screen = function(f) screen_begin() if f then f() end screen_end() end setptr = function(f, add) if not add then add=tmp end lda#op_resolve(f)&0xff sta add lda#op_resolve(f)>>8 sta add+1 end xcall = function(f) setptr(f, mappers.tmp) jsr jmpfar end rtximp = function() jmp rtsfar end -- for mappers that swap banks in place -- call an asm function into another bank and generate the stubs if needed local far_stubs = {} _ENV.far_stubs=far_stubs farcall = function(dst) local loc_src = location_current local create_stubs = function() if type(dst) == 'function' then dst = dst() end if type(dst) == 'table' and dst.type == 'section' then dst = dst.instructions[1] end -- put labels only local stub,loc_dst = far_stubs[dst],dst.section.location if not (stub and stub[loc_src]) then location(loc_src) local stub_caller = section() switchrom(loc_dst.rom) skip(6) rts location(loc_dst) local stub_callee = section() jsr dst switchrom(loc_src.rom) relate(stub_caller, stub_callee, 3) if not stub then far_stubs[dst] = {} end far_stubs[dst][loc_src] = stub_caller end end -- create stubs at the end to get to the referenced labels table.insert(before_link, create_stubs) jsr far_stubs[dst][loc_src] end mappers = { tmp=0x8a } mappers['2K'] = function() rom0 = location(0xf800, 0xffff) section{"vectors", org=0xfffc} dc.w main if mappers.irq then dc.w main end end mappers.CV = mappers['2K'] mappers['4K'] = function() rom0 = location(0xf000, 0xffff) section{"vectors", org=0xfffc} dc.w main if mappers.irq then dc.w main end end local bank_stubs = function(count, hotspot, entry) function switchrom(i) assert(i>=0 and i0 and bi or '')) lda mappers.tmp+1 lsr lsr lsr lsr lsr tax if mappers.noillegal then and 0x1000+hotspot,x else nop 0x1000+hotspot,x end jmp (mappers.tmp) local rtsfar = section("rtsfar"..(bi>0 and bi or '')) tsx lda 2,x lsr lsr lsr lsr lsr tax if mappers.noillegal then and 0x1000+hotspot,x else nop 0x1000+hotspot,x end rts if bi==0 then jmpfar0=jmpfar rtsfar0=rtsfar else relate(jmpfar0, jmpfar) relate(rtsfar0, rtsfar) end end end end mappers.FA = function() bank_stubs(3, 0xff8) end mappers.F8 = function() bank_stubs(2, 0xff8) end mappers.F6 = function() bank_stubs(4, 0xff6) end mappers.F4 = function() bank_stubs(8, 0xff4) end mappers.EF = function() bank_stubs(16, 0xfe0, 0xffc-6) end mappers.FE = function() rom1 = location(0xd000, 0xdfff) section{"vectors1", org=0xdffc} dc.w main if mappers.irq then dc.w main end location{0xe000, 0xefff, nofill=true} rom0 = location(0xf000, 0xffff) section{"vectors0", org=0xfffc} dc.w main if mappers.irq then dc.w main end end mappers.E0 = function(map) function switchrom0(i) assert(map[i]==0) if mappers.noillegal then bit 0x1fe0+i else nop 0x1fe0+i end end function switchrom1(i) assert(map[i]==1) if mappers.noillegal then bit 0x1fe8+i else nop 0x1fe8+i end end function switchrom2(i) assert(map[i]==2) if mappers.noillegal then bit 0x1ff0+i else nop 0x1ff0+i end end map[7]=3 for bi=0,7 do local o = bi*0x400 _ENV["rom"..bi] = location{0xe000+o, 0xe3ff+o, rorg=0xf000+map[bi]*0x400} end section{"switchtab", org=0xffe0} for i=1,8*3 do byte(0) end section{"vectors", org=0xfffc} dc.w main if mappers.irq then dc.w main end end -- rom0 refers to the last one in ROM, ie the one always mapped to 0xF800-0xFFFF, -- such that changing the rom count does not change its index mappers['3F'] = function(count) function switchrom(i) assert(i>=0 and i=0 and i<7) if mappers.noillegal then bit 0x1fe0+i else nop 0x1fe0+i end end function enableram() if mappers.noillegal then bit 0x1fe7 else nop 0x1fe7 end end function switchram(i) assert(i>=0 and i<4) if mappers.noillegal then bit 0x1fe8+i else nop 0x1fe8+i end end for bi=0,6 do local o = bi*0x800 _ENV['rom' .. bi] = location{o+0xc000, o+0xc7ff, rorg=0xf000} end rom7 = location{0xf800, 0xffff, rorg=0xf800} section{"switchtab", org=0xffe0} for i=1,12 do byte(0) end section{"vectors", org=0xfffc} dc.w main if mappers.irq then dc.w main end end mappers.F0 = function() function switchrom() lda 0x1ff0 end for bi=0,15 do local o = bi*0x1000 + 0x1000 _ENV['rom' .. bi] = location{o, o+0xfff, rorg=0xf000} local start=section{"entry"..bi, org=o+0xffc-8} local l=label() switchrom() bne l if bi==0 then jmp main end section{"switchtab"..bi, org=o+0xff0} byte(bi) section{"vectors"..bi, org=o+0xffc} dc.w start if mappers.irq then dc.w start end end end local bank_stubs2 = function(hotspot0, hotspot1) function switchrom(i) if i==0 then if mappers.noillegal then bit hotspot0 else nop hotspot0 end elseif i==1 then if mappers.noillegal then bit hotspot1 else nop hotspot1 end else error("invalid rom index: " .. i) end end local base = 0xe000 for bi=0,1 do local o = base+(bi<<12) _ENV['rom' .. bi] = location{o, o+0xfff, rorg=0xf000} local start=section{"entry"..bi, org=o+0xffc-6} switchrom(0) if bi==0 then jmp main end section{"vectors"..bi, org=o+0xffc} dc.w start if mappers.irq then dc.w start end end end mappers.UA = function() bank_stubs2(0x220, 0x240) end mappers['0840'] = function() bank_stubs2(0x800, 0x840) end mappers.SB = function(count) function switchrom(i) assert(i>=0 and i=0 and i=0 and i<128) lda#i sta 0x3c+i end _ENV['switchram'..i] = function(i) assert(i>=0 and i<128) lda#i+0x80 sta 0x3c+i end end for bi=0,rom_count-1 do local o = bi*0x400 + 0x1000 _ENV["rom"..(count-1-bi)] = location{o, 0x3ff+o, rorg=0xf000+(o&0xfff)} end local start=section("entry") switchrom3(0) jmp main section{"vectors", org=0xfffc} dc.w start if mappers.irq then dc.w start end end mappers.X07 = function() function switchrom(i) assert(i>=0 and i<16) if mappers.noillegal then bit 0x80b+(i<<4) else nop 0x80b+(i<<4) end end -- map TIA also to 0x40-0x7F: use VSYNC for bank 14, and VSYNC2 for bank 15 vcs.VSYNC2=0x40 cpu.symbols.VSYNC2=0x40 for bi=0,count-1 do local o = bi*0x1000 + 0x1000 _ENV['rom' .. bi] = location{o, o+0xfff, rorg=0xf000} local start=section{"entry"..bi, org=o+0xffc-6} switchrom(0) if bi==0 then jmp main end section{"vectors"..bi, org=o+0xffc} dc.w start if mappers.irq then dc.w start end end end -------------------------------------------------------------------------------- -- Data converters -------------------------------------------------------------------------------- image_scan = function(img, opt) local r, b, flipx, flipy, xinc, yinc, x0, y0, x1, y1, width, height = {}, {} xinc=opt.xinc or 1 x0=opt.x0 or 0 x1=opt.x1 or img.width-1 yinc=opt.yinc or 1 y0=opt.y0 or 0 y1=opt.y1 or img.height-1 if x1=y0 and x1>=x0 and x1 %d] y[%d -> %d] for img[%d, %d]", x0, x1, y0, y1, img.width, img.height)) width = 1 + (x1 - x0) // xinc height = 1 + (y1 - y0) // yinc for y=y0,y1,yinc do for x=x0,x1,xinc do b[#b+1] = img[y*img.width+x+1] end end if flipx then for y=0,height-1 do for x=0,width//2-1 do local i0, i1 = y*width+x+1, y*height+width b[i0], b[i1] = b[i1], b[i0] end end end if flipy then for y=0,height//2-1 do for x=0,width-1 do local i0, i1 = y*width+x+1, (height-1-y)*width+x+1 b[i0], b[i1] = b[i1], b[i0] end end end return b, width, height end linecol = function(opt) if type(opt) ~= 'table' then opt = { opt } end local image = opt.image or assert(l65.image(opt.filename or opt[1])) local b,w,h = image_scan(image, opt) local lc = {} for y=0,h-1 do lc[#lc+1] = 0 for x=1,w do local col = b[y*w + x] if col ~= 0 then lc[#lc]=col break end end end return lc end playfield = function(opt) if type(opt) ~= 'table' then opt = { opt } end local image = opt.image or assert(l65.image(opt.filename or opt[1])) local b,w,h = image_scan(image, opt) local pf={} for i=1,6 do pf[i]={} end for y=1,h do local x = \o,s((b[(y-1)*w+o+1]==0 and 0 or 1)<= w and w-1 or o+7 local i,color = 7,0 for x=o,e do if b[y*w+x+1] ~= 0 then color = color | 1< instrument.releaseStart+1 and instrument.frequencies[sz] == 0 and instrument.volumes[sz] == 0 do sz = sz-1 end local oldsz = #t.insfreqvoltable for i=1,sz do ins(t.insfreqvoltable, instrument.frequencies[i]+8 << 4 | instrument.volumes[i]) end ins(t.insfreqvoltable, instrument.releaseStart + 1 + oldsz, 0) ins(t.insfreqvoltable, 0) for i = 1, instrument.waveform == 16 and 2 or 1 do ins(t.insadindexes, insEnvelopeIndex) ins(t.inssustainindexes, insEnvelopeIndex + instrument.sustainStart) ins(t.insreleaseindexes, insEnvelopeIndex + instrument.releaseStart) end insEnvelopeIndex = insEnvelopeIndex + sz + 2 if instrument.waveform == 16 then -- PURE_COMBINED ins(t.insctrltable, 4) -- PURE_HIGH ins(t.insctrltable, 12) -- PURE_LOW else ins(t.insctrltable, instrument.waveform) end end local valueIns = instrumentmap[note.number] + 1 if s.instruments[note.number+1].waveform == 16 and note.value > 31 then valueIns=valueIns+1 end ins(pattern, valueIns<<5 | note.value&0x1f) elseif note.type == 3 then -- Percussion if not percussionmap[note.number] then percussionmap[note.number] = percussioncount percussioncount = percussioncount + 1 local percussion = s.percussion[note.number+1] local sz = percussion.envelopeLength while sz > 0 and percussion.waveforms[sz] == 0 and percussion.volumes[sz] == 0 do sz = sz-1 end for i=1,sz do local freq = percussion.frequencies[i] if percussion.overlay and i == sz then freq = freq + 128 end ins(t.percfreqtable, freq) ins(t.percctrlvoltable, percussion.waveforms[i] << 4 | percussion.volumes[i]) end ins(t.percfreqtable, 0) ins(t.percctrlvoltable, 0) ins(t.percindexes, percEnvelopeIndex+1) percEnvelopeIndex = percEnvelopeIndex + sz + 1 if percussion.overlay then t.useoverlay = true end end ins(pattern, percussionmap[note.number] + 17) -- NoteFirstPerc elseif note.type == 0 then -- Hold ins(pattern, 8) -- NoteHold if sequenceindex == 1 and noteindex == 1 then t.startswithnotes = false end elseif note.type == 2 then -- Pause ins(pattern, 16) -- NotePause elseif note.type == 4 then -- Slide ins(pattern, 8 + note.value) -- NoteHold + note.value t.useslide = true end end ins(pattern, 0) if not t.globalspeed then ins(t.patternspeeds, t.usefunktempo and (spattern.evenspeed-1 << 4 | spattern.oddspeed-1) or spattern.evenspeed-1) end end ins(t.sequence[channelindex], patternmap[patternindex]) if sequence.gototarget ~= -1 then t.usegoto = true local value = 128 + sequence.gototarget + gotooffset gotooffset = gotooffset + 1 if channelindex == 2 then value = value + #t.sequence[1] end assert(value < 256, "goto target in channel " .. (channelindex-1) .. " is out of range: " .. value) ins(t.sequence[channelindex], value) end end end local i t.c0init,i = s.startpattern0,0 while i <= t.c0init do if t.sequence[1][i+1] > 127 then t.c0init = t.c0init + 1 end i = i+1 end t.c1init,i = s.startpattern1,0 while i <= t.c1init do if t.sequence[2][i+1] > 127 then t.c1init = t.c1init + 1 end i = i+1 end t.c1init = t.c1init + #t.sequence[1] return t end