mirror of
https://github.com/g012/l65.git
synced 2024-11-15 19:09:19 +00:00
810 lines
32 KiB
Plaintext
810 lines
32 KiB
Plaintext
-- set cpu to 6502
|
|
cpu = require "6502"
|
|
setmetatable(_ENV, cpu)
|
|
|
|
nes = {
|
|
OAM = 0x200, -- 0x100 bytes
|
|
RAM = 0x300, -- 0x500 bytes + ZP 0x100 bytes + Stack 0x100 bytes + OAM 0x100 bytes = 0x800 bytes
|
|
|
|
-- 2C02 / 2C07 PPU
|
|
PPUCTRL = 0x2000,
|
|
PPUMASK = 0x2001,
|
|
PPUSTAT = 0x2002,
|
|
OAMADDR = 0x2003,
|
|
OAMDATA = 0x2004,
|
|
BGSCROL = 0x2005,
|
|
PPUADDR = 0x2006,
|
|
PPUDATA = 0x2007,
|
|
|
|
-- 2A03 / 2A07 CPU+APU
|
|
SQ1VOL = 0x4000,
|
|
SQ1SWEEP = 0x4001,
|
|
SQ1LO = 0x4002,
|
|
SQ1HI = 0x4003,
|
|
SQ2VOL = 0x4004,
|
|
SQ2SWEEP = 0x4005,
|
|
SQ2LO = 0x4006,
|
|
SQ2HI = 0x4007,
|
|
TRILINEAR = 0x4008,
|
|
TRILO = 0x400A,
|
|
TRIHI = 0x400B,
|
|
NOISEVOL = 0x400C,
|
|
NOISELO = 0x400E,
|
|
NOISEHI = 0x400F,
|
|
DMCFREQ = 0x4010,
|
|
DMCRAW = 0x4011,
|
|
DMCSTART = 0x4012,
|
|
DMCLEN = 0x4013,
|
|
OAMDMA = 0x4014,
|
|
SNDCNT = 0x4015,
|
|
SPECIO1 = 0x4016,
|
|
SPECIO2 = 0x4017,
|
|
|
|
SRAM = 0x6000, -- 0x2000 bytes
|
|
ROM = 0x8000, -- 0x8000 bytes
|
|
|
|
-- PPU Memory declarations
|
|
CHAR0 = 0x0000, -- 0x1000 bytes
|
|
CHAR1 = 0x1000, -- 0x1000 bytes
|
|
SCREEN0 = 0x2000, -- 0x400 bytes
|
|
SCREEN1 = 0x2400, -- 0x400 bytes
|
|
SCREEN2 = 0x2800, -- 0x400 bytes
|
|
SCREEN3 = 0x2C00, -- 0x400 bytes
|
|
BGPAL = 0x3F00, -- 0x10 bytes
|
|
OBJPAL = 0x3F10, -- 0x10 bytes
|
|
}
|
|
do
|
|
local symbols = cpu.symbols
|
|
for k,v in pairs(nes) do symbols[k] = v end
|
|
end
|
|
|
|
-- add some symbol file formats for NES debuggers
|
|
cpu.getsym_as.mesen = function() -- .mlb
|
|
local ins,fmt = table.insert,string.format
|
|
local s = getsym(function(a,l,lorg)
|
|
if a >= 0x10000 then return end
|
|
local prefix = {}
|
|
if a < 0x2000 then prefix[1]='R'
|
|
elseif a >= 0x6000 and a < 0x8000 then prefix[1]='S' prefix[2]='W' a=a-0x6000
|
|
elseif a >= 0x8000 then prefix[1]='P' a=(symbolsorg[lorg] or a)-0x8000
|
|
else prefix[1]='G' end
|
|
local s = {}
|
|
for _,p in ipairs(prefix) do ins(s, fmt("%s:%04x:%s", p, a, l)) end
|
|
return s
|
|
end)
|
|
return table.concat(s, '\n')
|
|
end
|
|
cpu.getsym_as.fceux = function(filename) -- .nl, multiple files
|
|
local ins,fmt = table.insert,string.format
|
|
local ram,rom = {},{}
|
|
local s = getsym(function(a,l,lorg)
|
|
local s = fmt("$%04x#%s#", a, l)
|
|
if a < 0x8000 then ins(ram, s)
|
|
elseif a < 0x10000 then
|
|
local a_org = symbolsorg[lorg] or a
|
|
local romstart = locations[2].start -- header location should always be defined first, skip it
|
|
if a_org >= romstart then
|
|
local bank = math.floor((a_org - romstart) / 0x4000)
|
|
if not rom[bank] then rom[bank] = {} end
|
|
ins(rom[bank], s)
|
|
end
|
|
end
|
|
end)
|
|
local fn = filename
|
|
if not fn:find('%.') then fn = fn .. '.nes' end
|
|
local fni = fn .. '.ram.nl'
|
|
local f = assert(io.open(fni, "wb"), "failed to open " .. fni .. " for writing")
|
|
f:write(table.concat(ram, '\n')) f:close()
|
|
for k,v in pairs(rom) do
|
|
fni = fn .. '.' .. k .. '.nl'
|
|
f = assert(io.open(fni, "wb"), "failed to open " .. fni .. " for writing")
|
|
f:write(table.concat(v, '\n')) f:close()
|
|
end
|
|
end
|
|
|
|
mappers = {}
|
|
|
|
vblank_waitbegin = function()
|
|
local l=label() bit PPUSTAT bpl l
|
|
end
|
|
vblank_waitend = function()
|
|
local l=label() bit PPUSTAT bmi l
|
|
end
|
|
|
|
ppu_addr = function(addr)
|
|
lda #addr>>8 sta PPUADDR
|
|
if addr&0xff ~= addr>>8 then lda #addr&0xff end
|
|
sta PPUADDR
|
|
end
|
|
|
|
oam_bytes = function(t)
|
|
return {
|
|
t.y - 1,
|
|
t.tile,
|
|
(t.palette or 0)&3 | ((t.behind or t.priority==1) and 0x20 or 0) | (t.flipx and 0x40 or 0) | (t.flipy and 0x80 or 0),
|
|
t.x
|
|
}
|
|
end
|
|
oam_set = function(t)
|
|
local b = oam_bytes(t)
|
|
lda #t[1]*4 sta OAMADDR
|
|
for _,v in ipairs(b) do lda #v sta OAMDATA end
|
|
end
|
|
|
|
oamcache = OAM -- change it to set other location
|
|
oamcache_clear = function()
|
|
local oam = oamcache
|
|
ldx #0 lda #0xff
|
|
local l=label() sta oam,x inx inx inx inx bne l
|
|
end
|
|
oamcache_flush = function()
|
|
local oam = oamcache
|
|
lda #0 sta OAMADDR lda #oam>>8 sta OAMDMA
|
|
end
|
|
oamcache_set = function(t)
|
|
local oam = oamcache
|
|
local b = oam_bytes(t)
|
|
ldx #t[1]*4 lda #b[1] sta oam,x
|
|
inx lda #b[2] sta oam,x
|
|
inx lda #b[3] sta oam,x
|
|
inx lda #b[4] sta oam,x
|
|
end
|
|
|
|
--[[ button state:
|
|
bit: 7 6 5 4 3 2 1 0
|
|
button: A B Select Start Up Down Left Right
|
|
https://wiki.nesdev.com/w/index.php/Controller_Reading
|
|
]]
|
|
-- fast reading of just the A button
|
|
-- return C if A is pressed, c otherwise
|
|
read_joy_a = function(joy_index)
|
|
local joy = joy_index == 2 and SPECIO2 or SPECIO1
|
|
lda #1 sta joy lsr sta joy lda joy lsr
|
|
end
|
|
-- read one joypad state into dst
|
|
read_joy = function(dst, joy_index)
|
|
local joy = joy_index == 2 and SPECIO2 or SPECIO1
|
|
lda #1 sta joy sta dst lsr sta joy
|
|
@_readbutton lda joy lsr rol dst bcc _readbutton
|
|
end
|
|
-- read both joypad states and Famicom's DA15 expansion port
|
|
read_joys = function(dst1, dst2)
|
|
lda #1 sta SPECIO1 sta dst2 lsr sta SPECIO1
|
|
@_readbuttons
|
|
lda SPECIO1 and #3 cmp #1 rol dst1
|
|
lda SPECIO2 and #3 cmp #1 rol dst2
|
|
bcc _readbuttons
|
|
end
|
|
-- read both joypad states on even cycles only, to safely work with DPCM
|
|
-- must be called right after oamcache_flush or any other sta OAMDMA
|
|
read_joys_even = function(dst1, dst2)
|
|
ldx #1 stx dst1 stx SPECIO1 dex stx SPECIO1
|
|
@_readbuttons
|
|
lda SPECIO2 and #3 cmp #1 rol dst2,x
|
|
lda SPECIO1 and #3 cmp #1 rol dst1
|
|
bcc _readbuttons
|
|
end
|
|
|
|
init = function()
|
|
sei -- cld not needed, no BCD support
|
|
ldx #0x40 stx SPECIO2 -- disable APU frame IRQ
|
|
ldx #0xff txs inx stx PPUCTRL stx PPUMASK stx DMCFREQ -- disable NMI, rendering, DMC IRQs
|
|
bit PPUSTAT -- clear remnant VBlank PPU status flag on reset
|
|
vblank_waitbegin()
|
|
lda #0 sta SNDCNT -- stop APU channels
|
|
-- clear CPU RAM
|
|
@_zeroram
|
|
sta 0x0000,x sta 0x0100,x sta 0x0200,x sta 0x0300,x
|
|
sta 0x0400,x sta 0x0500,x sta 0x0600,x sta 0x0700,x
|
|
inx bne _zeroram
|
|
vblank_waitbegin()
|
|
-- clear OAM
|
|
oamcache_clear() oamcache_flush()
|
|
-- clear PPU RAM
|
|
bit PPUSTAT ppu_addr(0x2000) tax ldy #0x10
|
|
@_zeroppu
|
|
sta PPUDATA dex bne _zeroppu dey bne _zeroppu
|
|
bit PPUSTAT -- reset latch
|
|
if mappers.init then mappers.init() end
|
|
end
|
|
|
|
-- NES 2.0 (backward compatible with iNES)
|
|
-- https://wiki.nesdev.com/w/index.php/NES_2.0
|
|
header = function(t)
|
|
if not t then t = {} end
|
|
local logsz = function(sz)
|
|
assert(sz >= 0 and sz <= 1048576, "invalid size: " .. sz .. ", expected [0, 1048576]")
|
|
if sz < 1 then return 0 end
|
|
if sz <= 128 then return 1 end
|
|
return math.ceil(math.log(sz/64, 2))
|
|
end
|
|
-- mapper
|
|
local mi1 = t.mapperid or 0
|
|
assert(mi1 >= 0 and mi1 < 4096, "invalid mapper id: " .. mi1 .. ", expected [0, 4095]")
|
|
local ms1 = t.submapperid or 0
|
|
assert(ms1 >= 0 and ms1 < 16, "invalid submapper id: " .. ms1 .. ", expected [0, 15]")
|
|
local mapper6 = (mi1 & 0xf) << 4
|
|
local mapper7 = mi1 & 0xf0
|
|
local mapper8 = (mi1 >> 8) | (ms1 << 4)
|
|
-- prgsize
|
|
local prgsize = math.tointeger((t.prgsize or 16384) / 16384)
|
|
assert(prgsize, "prgsize must be a multiple of 16384")
|
|
-- chrsize
|
|
local chrsize = math.tointeger((t.chrsize or 0) / 8192)
|
|
assert(chrsize, "chrsize must be a multiple of 8192")
|
|
-- wramsize (not battery-backed)
|
|
local wramsize = logsz(t.wramsize or 0)
|
|
-- bramsize (battery-backed)
|
|
local bramsize = logsz(t.bramsize or 0)
|
|
-- chrbramsize (battery-backed)
|
|
local chrbramsize = logsz(t.chrbramsize or 0)
|
|
-- chrramsize (not battery-backed)
|
|
local chrramsize = logsz(t.chrramsize or (chrbramsize==0 and chrsize==0 and 8192 or 0))
|
|
local battery_bit = bramsize == 0 and chrbramsize == 0 and 0 or 2
|
|
-- mirror: 'H' for horizontal mirroring, 'V' for vertical mirroring
|
|
-- '4' for four-screen VRAM, 218 for four-screen and vertical
|
|
local mirror = (t.mirror or 'h'):lower()
|
|
mirror = ({ h=0, v=1, ['4']=8, [218]=9 })[mirror]
|
|
assert(mirror, "invalid mirror mode: " .. mirror .. ", expected 'H', 'V', '4', or 218")
|
|
-- tv: 'N' for NTSC, 'P' for PAL, 'NP' for both preferring NTSC, 'PN' for both preferring PAL
|
|
local tv, tvm = 0, (t.tv or 'n'):lower()
|
|
assert(tvm=='n' or tvm=='p' or tvm=='np' or tvm=='pn', "invalid tv mode: " .. tostring(t.tv) .. ", expected 'N', 'P', 'NP' or 'PN'")
|
|
if tvm[1] == 'p' then tv = 1 end
|
|
if #tvm > 1 then tv = tv + 2 end
|
|
|
|
@@header -- size: 16 bytes
|
|
dc.b 0x4e, 0x45, 0x53 -- 'NES'
|
|
dc.b 0x1a
|
|
dc.b prgsize, chrsize
|
|
dc.b mapper6 | mirror | battery_bit
|
|
dc.b mapper7 | 8
|
|
dc.b mapper8
|
|
dc.b ((chrsize >> 4) & 0xF0) | ((prgsize >> 8) & 0x0F)
|
|
dc.b (bramsize << 4) | wramsize
|
|
dc.b (chrbramsize << 4) | chrramsize
|
|
dc.b tv, 0, 0, 0
|
|
|
|
-- update table with defaulted values
|
|
t.prgsize = prgsize * 16384
|
|
t.chrsize = chrsize * 8192
|
|
t.wramsize = math.tointeger(2^wramsize*64)
|
|
t.bramsize = math.tointeger(2^bramsize*64)
|
|
t.chrbramsize = math.tointeger(2^chrbramsize*64)
|
|
t.chrramsize = math.tointeger(2^chrramsize*64)
|
|
mappers.header=t
|
|
end
|
|
|
|
local n0ne = function(x) return not x or x == 0 end
|
|
local val0 = function(x) return x and x or 0 end
|
|
|
|
--[[ For all mappers, where relevant:
|
|
|
|
prg rom banks:
|
|
prgroms are numbered from last (0) to first (#-1), so that adding more does not change
|
|
prgrom0, which must contain the reset vector (main). The switchprgrom* functions expect
|
|
a bank index according to this reversed numbering (0 is last physical bank in ROM).
|
|
|
|
t.prgmap is an optional function taking a prgrom bank index and returning its rorg value.
|
|
Default is to map banks in sequence according to their size, eg.:
|
|
* for sizes 32kB: always 0x8000.
|
|
* for sizes 16kB+16kB: repeat sequence 0x8000, 0xa000.
|
|
* for sizes 16kB+8kB+8kB: repeat sequence 0x8000, 0xc000, 0xe000.
|
|
* for sizes 8kB+8kB+8kB+8kB: repeat sequence 0x8000, 0xa000, 0xc000, 0xe000.
|
|
If last bank is fixed, it is not part of the sequence and its mapping value is skipped:
|
|
* for sizes 8kB+8kB+ last fixed 16kB: repeat sequence 0x8000, 0xa000, set last bank to 0xc000.
|
|
|
|
chr rom banks:
|
|
chrroms are numbered in physical ROM address order, unlike prgroms.
|
|
|
|
Banks are created with the smallest switchable size, maximum being 4kB by default.
|
|
t.chrmap is an optional function taking a chrrom bank index and returning its offset in
|
|
ROM from the beginning of the first chrrom, its size (defaults to everything remaining),
|
|
and its relocation origin (defaults to 0).
|
|
So if a mapper allows switching chrroms in any of 1-1-1-1-1-1-1-1kB / 2-2-2-2kB, etc.,
|
|
the default is to create only 1kB chrroms. Conversely, with a mapper switching only in 8kB
|
|
slices, 4kB chrrom are created, not 8kB (but you can specify any size using t.chrmap).
|
|
]]
|
|
|
|
--[[
|
|
https://wiki.nesdev.com/w/index.php/NROM
|
|
]]
|
|
mappers.NROM = function(t)
|
|
if not t then t = {} end
|
|
if not t.prgsize then t.prgsize = 16384 end
|
|
assert(t.prgsize == 16384 or t.prgsize == 32768, "prgsize must be 16 or 32kB")
|
|
if n0ne(t.chrsize) and n0ne(t.chrramsize) and n0ne(t.chrbramsize) then t.chrsize = 8192 end
|
|
assert(val0(t.chrsize) + val0(t.chrramsize) + val0(t.chrbramsize) == 8192, "combined chrrom size must be 8kB")
|
|
assert(not t.mirror or ({ h=1, v=1 })[t.mirror:lower()], "only H and V mirroring are supported")
|
|
local prgstart = 0x10000 - t.prgsize
|
|
hdrrom = location{prgstart - 16, prgstart - 1, name='header'}
|
|
header(t)
|
|
prgrom0 = location{prgstart, 0xffff, name='prgrom'}
|
|
prgrom = prgrom0
|
|
section{"vectors", org=0xfffa} dc.w nmi, main, irq
|
|
if (t.chrsize > 0) then
|
|
local ci = 0
|
|
local chrmap = t.chrmap or function(ci) return ci*0x1000, 0x1000, (ci&1)*0x1000 end
|
|
repeat
|
|
local off, sz, rorg = chrmap(ci)
|
|
sz = sz or t.chrsize - off
|
|
local o = off + 0x10000
|
|
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
|
|
ci = ci+1
|
|
until off + sz >= t.chrsize
|
|
chrrom = chrrom0
|
|
end
|
|
end
|
|
mappers[0] = mappers.NROM
|
|
|
|
--[[
|
|
https://wiki.nesdev.com/w/index.php/UxROM
|
|
Has bus conflicts.
|
|
]]
|
|
mappers.UxROM = function(t)
|
|
if not t then t = {} end
|
|
t.mapperid = 2
|
|
if not t.prgsize then t.prgsize = 32768 end
|
|
assert(t.prgsize >= 0x8000 and t.prgsize <= 0x40000, "prgsize must be at least 32kB and at most 256kB")
|
|
if not t.chrsize then t.chrsize = 8192 end
|
|
assert(t.chrsize == 0x2000, "chrsize must be 8kB")
|
|
assert(not t.mirror or ({ h=1, v=1 })[t.mirror:lower()], "only H and V mirroring are supported")
|
|
hdrrom = location{0x7ff0, 0x7fff, name='header'}
|
|
header(t)
|
|
local bc = t.prgsize//0x4000
|
|
for bi=0,bc-2 do
|
|
local o,ix = 0x8000 + bi*0x4000, bc-1-bi
|
|
_ENV['prgrom'..ix] = location{o, o+0x3fff, rorg=0x8000, name='prgrom'..ix}
|
|
end
|
|
local prglast = 0x8000 + (bc-1)*0x4000
|
|
prgrom0 = location{prglast, prglast+0x3fff, rorg=0xc000, name='prgrom0'}
|
|
prgrom = prgrom0
|
|
section{"vectors", org=prglast+0x3ffa} dc.w nmi, main, irq
|
|
@@bankbytes -- for handling bus conflicts
|
|
samepage for i=bc-1,0,-1 do byte(i) end end
|
|
local ci, chrstart = 0, 0x8000 + bc*0x4000
|
|
local chrmap = t.chrmap or function(ci) return ci*0x1000, 0x1000, (ci&1)*0x1000 end
|
|
repeat
|
|
local off, sz, rorg = chrmap(ci)
|
|
sz = sz or t.chrsize - off
|
|
local o = off + chrstart
|
|
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
|
|
ci = ci+1
|
|
until off + sz >= t.chrsize
|
|
chrrom = chrrom0
|
|
function switchprgrom(bankix)
|
|
if bankix then assert(bankix < bc-1) lda #bankix end
|
|
-- lda to reverse map [n..0] to [0..n]
|
|
tax lda bankbytes,x sta bankbytes,x
|
|
end
|
|
mappers.init = function()
|
|
switchprgrom(1)
|
|
end
|
|
end
|
|
mappers[2] = mappers.UxROM
|
|
|
|
--[[
|
|
https://wiki.nesdev.com/w/index.php/CNROM
|
|
Has bus conflicts.
|
|
]]
|
|
mappers.CNROM = function(t)
|
|
if not t then t = {} end
|
|
t.mapperid = 3
|
|
if not t.prgsize then t.prgsize = 16384 end
|
|
assert(t.prgsize == 16384 or t.prgsize == 32768, "prgsize must be 16 or 32kB")
|
|
if not t.chrsize then t.chrsize = 8192 end
|
|
assert(t.chrsize >= 0x2000 and t.chrsize <= 0x8000, "chrsize must be at least 8kB and at most 32kB")
|
|
assert(not t.mirror or ({ h=1, v=1 })[t.mirror:lower()], "only H and V mirroring are supported")
|
|
local prgstart = 0x10000 - t.prgsize
|
|
hdrrom = location{prgstart - 16, prgstart - 1, name='header'}
|
|
header(t)
|
|
prgrom = location{prgstart, 0xffff, name='prgrom'}
|
|
section{"vectors", org=0xfffa} dc.w nmi, main, irq
|
|
@@bankbytes samepage -- for handling bus conflicts
|
|
dc.b 0x00, 0x01, 0x02, 0x03
|
|
dc.b 0x10, 0x11, 0x12, 0x13
|
|
dc.b 0x20, 0x21, 0x22, 0x23
|
|
dc.b 0x30, 0x31, 0x32, 0x33
|
|
end
|
|
local ci, cc = 0, t.chrsize//0x2000
|
|
local chrmap = t.chrmap or function(ci) return ci*0x1000, 0x1000, (ci&1)*0x1000 end
|
|
repeat
|
|
local off, sz, rorg = chrmap(ci)
|
|
sz = sz or t.chrsize - off
|
|
local o = off + 0x10000
|
|
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
|
|
ci = ci+1
|
|
until off + sz >= t.chrsize
|
|
chrrom = chrrom0
|
|
securitydiodes = 0 -- set to actual value, eg 0x20
|
|
function switchchrrom(bankix)
|
|
assert(securitydiodes < 0x40)
|
|
if bankix then
|
|
assert(bankix < cc)
|
|
ldx #bankix|securitydiodes>>2 lda #bankix|securitydiodes
|
|
else
|
|
ora #securitydiodes>>2 tax and #3 ora #securitydiodes
|
|
end
|
|
sta bankbytes,x
|
|
end
|
|
mappers.init = function()
|
|
switchchrrom(0)
|
|
end
|
|
end
|
|
mappers[3] = mappers.CNROM
|
|
|
|
--[[
|
|
https://wiki.nesdev.com/w/index.php/GxROM
|
|
Has bus conflicts.
|
|
]]
|
|
mappers.GxROM = function(t)
|
|
if not t then t = {} end
|
|
t.mapperid = 66
|
|
if not t.prgsize then t.prgsize = 32768 end
|
|
assert(t.prgsize >= 0x8000 and t.prgsize <= 0x20000, "prgsize must be at least 32kB and at most 128kB")
|
|
if not t.chrsize then t.chrsize = 8192 end
|
|
assert(t.chrsize >= 0x2000 and t.chrsize <= 0x8000, "chrsize must be at least 8kB and at most 32kB")
|
|
assert(not t.mirror or ({ h=1, v=1 })[t.mirror:lower()], "only H and V mirroring are supported")
|
|
hdrrom = location{0x7ff0, 0x7fff, name='header'}
|
|
header(t)
|
|
local bc = t.prgsize//0x8000
|
|
for bi=0,bc-1 do
|
|
local o,ix = 0x8000 + bi*0x8000, bc-1-bi
|
|
_ENV['prgrom'..ix] = location{o, o+0x7fff, rorg=0x8000, name='prgrom'..ix}
|
|
section{"vectors"..ix, org=o+0x8000-6} dc.w nmi, main, irq
|
|
section{"bankbytes"..ix, org=o+0x8000-6-16} samepage -- for handling bus conflicts
|
|
dc.b 0x00, 0x01, 0x02, 0x03
|
|
dc.b 0x10, 0x11, 0x12, 0x13
|
|
dc.b 0x20, 0x21, 0x22, 0x23
|
|
dc.b 0x30, 0x31, 0x32, 0x33
|
|
end
|
|
end
|
|
prgrom = prgrom0
|
|
local cc = t.chrsize//0x2000
|
|
if t.onechrrom then
|
|
chrrom0 = location{chrstart, chrstart + cc*0x2000 - 1, rorg=0, name='chrrom'}
|
|
else
|
|
for ci=0,cc-1 do
|
|
local o = chrstart + ci*0x2000
|
|
_ENV['chrrom'..ci] = location{o, o+0x2000-1, rorg=0, name='chrrom'..ci}
|
|
end
|
|
end
|
|
local ci, chrstart = 0, 0x8000 + bc*0x8000
|
|
local chrmap = t.chrmap or function(ci) return ci*0x1000, 0x1000, (ci&1)*0x1000 end
|
|
repeat
|
|
local off, sz, rorg = chrmap(ci)
|
|
sz = sz or t.chrsize - off
|
|
local o = off + chrstart
|
|
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
|
|
ci = ci+1
|
|
until off + sz >= t.chrsize
|
|
chrrom = chrrom0
|
|
-- RAM address of bank register copy, to switch using A instead of immediate
|
|
bankregister_shadow = -1
|
|
-- if bankregister_shadow is negative, only immediate bankswitching is available
|
|
bankregister = 0
|
|
-- otherwise, just set the value directly: xxPPxxCC in A, xxxxPPCC in X with sta bankbytes0,x
|
|
function switchprgrom(bankix)
|
|
if bankix then
|
|
assert(bankix < bc)
|
|
bankregister = (bankregister & ~0x30) | (bankix << 4)
|
|
ldx #bankregister&3|bankregister>>2 lda #bankregister sta bankbytes0,x
|
|
else
|
|
assert(bankregister_shadow >= 0, "no RAM slot assigned to bankregister_shadow")
|
|
tay lda bankregister_shadow and #3 sta bankregister_shadow
|
|
tya asl asl ora bankregister_shadow tax
|
|
and #0xc asl asl ora bankregister_shadow sta bankregister_shadow
|
|
sta bankbytes0,x
|
|
end
|
|
end
|
|
function switchchrrom(bankix)
|
|
if bankix then
|
|
assert(bankix < cc)
|
|
bankregister = (bankregister & ~3) | bankix
|
|
ldx #bankregister&3|bankregister>>2 lda #bankregister sta bankbytes0,x
|
|
else
|
|
assert(bankregister_shadow >= 0, "no RAM slot assigned to bankregister_shadow")
|
|
tay lda bankregister_shadow lsr lsr sta bankregister_shadow
|
|
tya ora bankregister_shadow tax and #0xc
|
|
asl asl sta bankregister_shadow tya ora bankregister_shadow sta bankregister_shadow
|
|
sta bankbytes0,x
|
|
end
|
|
end
|
|
mappers.init = function()
|
|
switchchrrom(0)
|
|
end
|
|
end
|
|
mappers[66] = mappers.GxROM
|
|
|
|
--[[
|
|
https://wiki.nesdev.com/w/index.php/MMC1
|
|
|
|
t.prgmap is an optional function taking a prgrom bank index and returning its rorg value.
|
|
Default is to create 16kB banks if prgswitchmode is not all, 32kB otherwise, and rorg them
|
|
accordingly.
|
|
Same goes for chrrom sizes.
|
|
|
|
t.prgswitchmode:
|
|
'first' makes 0x8000-0xbfff switchable (default)
|
|
'last' makes 0xc000-0xffff switchable
|
|
'all' makes 0x8000-0xffff switchable
|
|
t.chrswitchmode:
|
|
'all' switches whole 8kB at a time
|
|
'half' switches 2 separate 4kB banks (default)
|
|
]]
|
|
mappers.MMC1 = function(t)
|
|
if not t then t = {} end
|
|
t.mapperid = 1
|
|
if not t.prgswitchmode then t.prgswitchmode = 'first' end
|
|
if not t.chrswitchmode then t.chrswitchmode = 'half' end
|
|
if not t.wramsize then t.wramsize = 0 end
|
|
if not t.bramsize and t.wramsize == 0 then t.bramsize = 8192 end
|
|
local prgram = t.bramsize + t.wramsize
|
|
assert(prgram >= 0x2000 and prgram <= 0x8000, "bramsize or wramsize must be at least 8kB and at most 32kB")
|
|
if not t.prgsize then t.prgsize = 32768 end
|
|
assert(t.prgsize >= 0x8000 and t.prgsize <= 0x80000, "prgsize must be at least 32kB and at most 512kB")
|
|
if not t.chrsize then t.chrsize = 8192 end
|
|
assert(t.chrsize >= 0x2000 and t.chrsize <= 0x20000, "chrsize must be at least 8kB and at most 128kB")
|
|
hdrrom = location{0x7FF0, 0x7FFF, name='header'}
|
|
header(t)
|
|
local bsz = t.prgswitchmode=='all' and 0x8000 or 0x4000
|
|
local prgmap = t.prgmap or function(bi, bc) return t.prgswitchmode=='last' and 0xc000 or 0x8000 end
|
|
local bc = t.prgsize//bsz
|
|
for bi=0,bc-1 do
|
|
local o,ix = 0x8000 + bi*bsz, bc-1-bi
|
|
_ENV['prgrom'..ix] = location{o, o+bsz-1, rorg=prgmap(ix,bc), name='prgrom'..ix}
|
|
end
|
|
section{"vectors", org=0x8000+bc*bsz-6} dc.w nmi, main, irq
|
|
prgrom = prgrom0
|
|
local ci, chrstart = 0, 0x8000 + bc*bsz
|
|
local csz = t.chrswitchmode=='all' and 0x2000 or 0x1000
|
|
local cc = t.chrsize//csz
|
|
local chrmap = t.chrmap or function(ci) return ci*0x1000, 0x1000, (ci&1)*0x1000 end
|
|
repeat
|
|
local off, sz, rorg = chrmap(ci)
|
|
sz = sz or t.chrsize - off
|
|
local o = off + chrstart
|
|
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
|
|
ci = ci+1
|
|
until off + sz >= t.chrsize
|
|
chrrom = chrrom0
|
|
|
|
local prgswitchmodemap = { all=0, first=3<<2, last=2<<2 }
|
|
local chrswitchmodemap = { all=0, half=1<<4 }
|
|
mmc1ctrl = (mappers.header.mirror==1 and 2 or 3) | prgswitchmodemap[t.prgswitchmode] | chrswitchmodemap[t.chrswitchmode]
|
|
function mmc1write(reg)
|
|
sta reg lsr sta reg lsr sta reg lsr sta reg lsr sta reg
|
|
end
|
|
-- Can be turned into a function, A must contain the bank index.
|
|
-- eg:
|
|
-- @@switchprgrom_func switchprgrom_f()
|
|
-- switchprgrom_f = function() jsr switchprgrom_func rts end
|
|
function switchprgrom_f() mmc1write(0xe000) end
|
|
function switchprgrom(bankix)
|
|
assert(bankix < t.prgsize//0x4000)
|
|
lda #bankix
|
|
switchprgrom_f()
|
|
end
|
|
function switchchrrom_f(slot) assert(slot<2) mmc1write(slot==0 and 0xa000 or 0xc000) end
|
|
function switchchrrom(bankix, slot)
|
|
assert(bankix < t.chrsize//0x1000)
|
|
lda #bankix
|
|
switchchrrom_f(slot or 0)
|
|
end
|
|
function setmirror(mirror)
|
|
mirror = assert(({ h=3, v=2, hi=1, lo=0 })[mirror:lower()])
|
|
mmc1ctrl = (mmc1ctrl & ~3) | mirror
|
|
lda #mmc1ctrl mmc1write(0x8000)
|
|
end
|
|
function setprgswitchmode(mode)
|
|
mode = assert(prgswitchmodemap[mode])
|
|
mmc1ctrl = (mmc1ctrl & ~0xc) | mode
|
|
lda #mmc1ctrl mmc1write(0x8000)
|
|
end
|
|
function setchrswitchmode(mode)
|
|
mode = assert(chrswitchmodemap[mode])
|
|
mmc1ctrl = (mmc1ctrl & ~0x10) | mode
|
|
lda #mmc1ctrl mmc1write(0x8000)
|
|
end
|
|
mappers.init = function()
|
|
lda #0x80 sta 0x8000
|
|
lda #mmc1ctrl mmc1write(0x8000)
|
|
if t.prgswitchmode ~= 'all' then switchprgrom(1) end
|
|
switchchrrom(0) if t.chrswitchmode ~= 'all' then switchchrrom(1,1) end
|
|
end
|
|
end
|
|
mappers[1] = mappers.MMC1
|
|
|
|
--[[
|
|
https://wiki.nesdev.com/w/index.php/MMC3
|
|
|
|
prgroms are numbered from last (0) to first (#-1), so that adding more does not change
|
|
prgrom0, which must contain the reset vector (main).
|
|
Last 2 prg banks are merged into 1 16kB bank, to allow linker optimization - hence, 0 must
|
|
always be set on bit 6 of bank select, even; also, there is no prgrom1 as a consequence.
|
|
|
|
chrroms are all 1kB, so they can work with chr A12 inversion enabled or not.
|
|
|
|
With default submapper id of 0, this defaults to revision MM3C, which generates a scanline
|
|
interrupt at each scanline when counter is loaded with 0.
|
|
]]
|
|
mappers.MMC3 = function(t)
|
|
if not t then t = {} end
|
|
t.mapperid = 4
|
|
if not t.bramsize then t.bramsize = 8192 end
|
|
assert(t.bramsize == 8192, "bramsize must be 8kB")
|
|
if not t.prgsize then t.prgsize = 32768 end
|
|
assert(t.prgsize >= 0x8000 and t.prgsize <= 0x80000, "prgsize must be at least 32kB and at most 512kB")
|
|
if not t.chrsize then t.chrsize = 8192 end
|
|
assert(t.chrsize >= 0x2000 and t.chrsize <= 0x40000, "chrsize must be at least 8kB and at most 256kB")
|
|
hdrrom = location{0x7FF0, 0x7FFF, name='header'}
|
|
header(t)
|
|
local prgmap = t.prgmap or function(bi, bc) return 0x8000+(bi&1)*0x2000 end
|
|
local bc = t.prgsize//0x2000
|
|
for bi=0,bc-3 do
|
|
local o,ix = 0x8000 + bi*0x2000, bc-bi-1
|
|
_ENV['prgrom'..ix] = location{o, o+0x1fff, rorg=prgmap(ix,bc), name='prgrom'..ix}
|
|
end
|
|
do
|
|
local o = 0x8000 + (bc-2)*0x2000
|
|
prgrom0 = location{o, o+0x3fff, rorg=0xc000, name='prgrom0'}
|
|
section{"vectors", org=o+0x3ffa} dc.w nmi, main, irq
|
|
end
|
|
prgrom = prgrom0
|
|
local ci, chrstart = 0, 0x8000 + bc*0x2000
|
|
local chrmap = t.chrmap or function(ci) return ci*0x400, 0x400, (ci&7)*0x400 end
|
|
local cc = t.chrsize//0x400
|
|
repeat
|
|
local off, sz, rorg = chrmap(ci)
|
|
sz = sz or t.chrsize - off
|
|
local o = off + chrstart
|
|
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
|
|
ci = ci+1
|
|
until off + sz >= t.chrsize
|
|
chrrom = chrrom0
|
|
local a12inv = false
|
|
function seta12inv(enabled) a12inv = enabled end
|
|
function switchprgrom(bankix, slot)
|
|
assert(slot<2)
|
|
slot = slot + 6
|
|
if a12inv then slot = slot | 0x80 end
|
|
lda #slot sta 0x8000
|
|
assert(bankix < bc)
|
|
bankix = bc-1-bankix -- reverse index order, since we set 0 as last
|
|
lda #bankix sta 0x8001
|
|
end
|
|
-- slot [0, 7], each slot is 1kB counting in order, regardless of a12inv state
|
|
function switchchrrom(bankix, slot)
|
|
assert(slot<8)
|
|
if a12inv then
|
|
assert(slot ~= 5 and slot ~= 7)
|
|
if slot == 6 then slot = 1
|
|
elseif slot == 4 then slot = 0
|
|
else slot = slot + 2 end
|
|
slot = slot | 0x80
|
|
else
|
|
assert(slot ~= 1 and slot ~= 3)
|
|
if slot == 2 then slot = 1
|
|
elseif slot > 3 then slot = slot - 2 end
|
|
end
|
|
lda #slot sta 0x8000
|
|
assert(bankix < cc)
|
|
lda #bankix sta 0x8001
|
|
end
|
|
function setmirror(mirror)
|
|
mirror = assert(({ h=1, v=0 })[mirror:lower()])
|
|
lda #mirror sta 0xa000
|
|
end
|
|
function protectsram() lda 0x40 sta 0xa001 end
|
|
function scanlineirq(count) ldx #1 stx 0xe000 lda #count-1 sta 0xc000 sta 0xc001 stx 0xe001 end
|
|
mappers.init = function()
|
|
switchprgrom(2, 0) switchprgrom(3, 1)
|
|
switchchrrom(0, 0) switchchrrom(2, 2)
|
|
switchchrrom(4, 4) switchchrrom(5, 5) switchchrrom(6, 6) switchchrrom(7, 7)
|
|
local mirror = mappers.header.mirror
|
|
if mirror==0 or mirror==1 then lda #mirror~1 sta 0xa000 end
|
|
lda #0x80 sta 0xa001
|
|
end
|
|
end
|
|
mappers[4] = mappers.MMC3
|
|
|
|
--[[
|
|
https://wiki.nesdev.com/w/index.php/MMC5
|
|
https://forums.nesdev.com/viewtopic.php?f=9&t=16789
|
|
|
|
main MUST be in 0xe000-0xfff9 of last prgrom.
|
|
prgroms are numbered from last (0) to first (#-1), so that adding more does not change
|
|
prgrom0, which must contain the reset vector (main).
|
|
|
|
chrroms are all 1kB.
|
|
]]
|
|
mappers.MMC5 = function(t)
|
|
if not t then t = {} end
|
|
t.mapperid = 5
|
|
assert(val0(t.bramsize) + val0(t.wramsize) <= 0x10000, "bramsize + wramsize must be at most 64kB")
|
|
if not t.prgsize then t.prgsize = 8192 end
|
|
assert(t.prgsize <= 0x100000, "prgsize must be at most 1MB")
|
|
if not t.chrsize then t.chrsize = 8192 end
|
|
assert(t.chrsize <= 0x100000, "chrsize must be at most 1MB")
|
|
hdrrom = location{0x7FF0, 0x7FFF, name='header'}
|
|
header(t)
|
|
local prgmap = t.prgmap or function(bi, bc) return 0x8000+(bi&3)*0x2000 end
|
|
local bc = t.prgsize//0x2000
|
|
for bi=0,bc-2 do
|
|
local o,ix = 0x8000 + bi*0x2000, bc-bi-1
|
|
_ENV['prgrom'..ix] = location{o, o+0x1fff, rorg=prgmap(ix,bc), name='prgrom'..ix}
|
|
end
|
|
do
|
|
local o = 0x8000 + (bc-1)*0x2000
|
|
prgrom0 = location{o, o+0x1fff, rorg=0xe000, name='prgrom0'}
|
|
section{"vectors", org=o+0x1ffa} dc.w nmi, main, irq
|
|
section{"main", org=o} -- enforce entry point in last bank
|
|
end
|
|
prgrom = prgrom0
|
|
local ci, chrstart = 0, 0x8000 + bc*0x2000
|
|
local chrmap = t.chrmap or function(ci) return ci*0x400, 0x400, (ci&7)*0x400 end
|
|
local cc = t.chrsize//0x400
|
|
repeat
|
|
local off, sz, rorg = chrmap(ci)
|
|
sz = sz or t.chrsize - off
|
|
local o = off + chrstart
|
|
_ENV['chrrom'..ci] = location{o, o+sz-1, rorg=rorg or 0, name='chrrom'..ci}
|
|
ci = ci+1
|
|
until off + sz >= t.chrsize
|
|
chrrom = chrrom0
|
|
function prgbankmode(mode) if mode then lda #mode end sta 0x5100 end
|
|
function chrbankmode(mode) if mode then lda #mode end sta 0x5101 end
|
|
function wrammode(mode) if mode then lda #mode end sta 0x5104 end
|
|
function protectsram() lda #0 sta 0x5102 sta 0x5103 end
|
|
function screenmap(map) if map then lda #map end sta 0x5105 end
|
|
function filltile(tile) if tile then lda #tile end sta 0x5106 end
|
|
function fillcolor(col) if col then lda #col end sta 0x5107 end
|
|
function switchprgram(chip, bank)
|
|
assert(chip == 0 or chip == 1)
|
|
assert(bank < 4)
|
|
lda #chip<<2|bank sta 0x5113
|
|
end
|
|
function switchprgrom0(bank, isram) lda #bank|(isram and 0 or 0x80) sta 0x5114 end
|
|
function switchprgrom1(bank, isram) lda #bank|(isram and 0 or 0x80) sta 0x5115 end
|
|
function switchprgrom2(bank, isram) lda #bank|(isram and 0 or 0x80) sta 0x5116 end
|
|
function switchprgrom3(bank, isram) lda #bank|(isram and 0 or 0x80) sta 0x5117 end
|
|
function switchchrrom(bank, slot) -- 1kB mode
|
|
assert(slot < 12)
|
|
assert(bank < cc)
|
|
lda #bank>>7 sta 0x5130
|
|
lda #bank sta slot+0x5120
|
|
end
|
|
vsplitmode = 0
|
|
function vsplitstart(starttile)
|
|
assert(starttile < 32)
|
|
vsplitmode = (vsplitmode & ~31) | starttile
|
|
lda #vsplitmode sta 0x5200
|
|
end
|
|
function vsplitside(side)
|
|
assert(side == 'left' or side == 'right')
|
|
vsplitmode = (vsplitmode & ~0x40) | (side == 'right' and 1 or 0)
|
|
lda #vsplitmode sta 0x5200
|
|
end
|
|
function vsplitenable(enabled)
|
|
vsplitmode = (vsplitmode & ~0x80) | (enabled and 1 or 0)
|
|
lda #vsplitmode sta 0x5200
|
|
end
|
|
function vsplitscroll(vscroll) if vscroll then lda #vscroll end sta 0x5201 end
|
|
function vsplitbank(chrbank) if chrbank then lda #chrbank end sta 0x5202 end
|
|
function scanlineirq(atscanline) lda #0 sta 0x5204 lda #atscanline sta 0x5203 lda #0x80 sta 0x5204 end
|
|
function irqenable(enabled) lda #enabled and 0x80 or 0 sta 0x5204 end
|
|
-- 0x40 set: PPU is rendering visible scanlines
|
|
-- 0x80 set: a scanlineirq is pending (internal counter reach atscanline value set with scanlineirq())
|
|
function irqstatus() lda 0x5204 end
|
|
-- multiplies a*x = x:a (x hi, a lo)
|
|
function mul() sta 0x5205 stx 0x5206 lda 0x5205 ldx 0x5206 end
|
|
mappers.init = function()
|
|
ldx #2 stx 0x5102 dex stx 0x5103
|
|
lda #vsplitmode sta 0x5200
|
|
prgbankmode(2)
|
|
switchprgrom1(3) -- map prg rom 3, 2, x, 0
|
|
switchprgrom2(1) -- map prg rom 3, 2, 1, 0
|
|
chrbankmode(0) switchchrrom(0, 7) chrbankmode(3) -- map chr rom 0 to the 8kB, and set mode to 1kB slices
|
|
end
|
|
end
|
|
mappers[5] = mappers.MMC5
|