-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathnes.l65
902 lines (860 loc) · 35.5 KB
/
nes.l65
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
-- 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
NTSC = {
CLOCK = 1789773
}
PAL = {
CLOCK = 1662607
}
-- 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:sub(1,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
-- 2a03 APU emulator
apuemu = {
--a=0,x=0,y=0,s=0,p=0,pc=0,xjam=false,
--reset = function(self)
-- self.a,self.x,self.y,self.s,self.p,self.pc,self.xjam = 0,0,0,0xff,
--end
}
-- plays a NSF tune and record its register writes for replay
-- stops at C00
-- FamiTracker must be in the path
-- Ported from Shiru's nsf2vgm
nsf = function(filename, song)
local f, nsf = assert(io.open(filename,'rb')) nsf=f:read('*all') f:close()
assert(nsf[0x7a] == 0, 'expansion chips are not supported')
nsf.songs = nsf[7]
nsf.loadadr=nsf[9]+(nsf[10]<<8) nsf.initadr=nsf[11]+(nsf[12]<<8) nsf.playadr=nsf[13]+(nsf[14]<<8)
local bs = false for i=1,8 do local p=nsf[0x70+i] nsf.page[i]=p if p~=0 then bs=true end end
if not bs then for i=1,8 do nsf.page[i]=i-1 end end
local ram,rom,page = {},{},{}
for i=1,#nsf-0x80 do rom[nsf.loadadr-0x7FFF] = nsf[0x81] end
local reg = { 0x13f, 0x108, 0x100, 0x100, 0x13f, 0x108, 0x100, 0x100, 0x180, 0x100, 0x100, 0x100, 0x13f, 0x100, 0x100, 0x100,
0x100, 0x100, 0x100, 0x100, 0x100, 0x11f, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100 }
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, "mappers.switchprgrom: bank out of range: " .. bankix .. ", expected 0-" .. (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://www.nesdev.org/wiki/UNROM_512
]]
mappers.UNROM512 = function(t)
if not t then t = {} end
t.mapperid = 30
if not t.prgsize then t.prgsize = 32 * 16384 end
assert(t.prgsize >= 0x8000 and t.prgsize <= 0x80000, "prgsize must be at least 32kB and at most 512kB")
assert(n0ne(t.chrsize), "chrsize must be 0")
if n0ne(t.chrramsize) and n0ne(t.chrbramsize) then t.chrramsize = 4 * 8192 end
local csize = val0(t.chrramsize) + val0(t.chrbramsize)
assert(csize == 0x2000 or csize == 0x4000 or csize == 0x8000, "combined chrram size must be 8, 16 or 32kB")
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
section{ "bankbytes", align=256 } -- for handling bus conflicts
for m=0,1 do
for c=0,3 do
for p=0x1f,0,-1 do byte(m<<7 | c<<5 | p) end
end
end
function clearchrram(page_count, offset)
ppu_addr(CHAR0 + (offset or 0)) tay ldx#(page_count or 32) @_clear sta PPUDATA iny bne _clear dex bne _clear
end
function loadchrram(var, page_count, offset)
ppu_addr(CHAR0 + (offset or 0)) ldx#(page_count or 32) ldy#0 @_load lda (var),y sta PPUDATA iny bne _load inc var+1 dex bne _load
end
function switch(prgbankix, chrbankix, mirror)
if prgbankix then assert(prgbankix < bc, "mappers.switch: PRG bank out of range: " .. prgbankix .. ", expected 0-" .. (bc-1)) end
if chrbankix then assert(chrbankix < 4, "mappers.switch: CHR bank out of range: " .. chrbankix .. ", expected 0-3") end
if mirror then assert(mirror == 0 or mirror == 1, "mappers.switch: mirror out of range: " .. mirror .. ", expected 0-1") end
if prgbankix or chrbankix or mirror then
local r = (mirror or 0)<<7 | (chrbankix or 0)<<5 | (prgbankix or 1)
lda #r
end
-- lda to reverse prg map [n..0] to [0..n]
tax lda bankbytes,x sta bankbytes,x
end
mappers.init = function()
switch(1)
end
end
mappers[30] = 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
local cc = t.chrsize//0x2000
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
-- if bankregister_shadow is negative, only immediate bankswitching is available
bankregister_shadow = -1
-- otherwise, just set the value directly: xxPPxxCC in A, xxxxPPCC in X with sta bankbytes0,x
function switchroms(prgbankix, chrbankix)
if prgbankix then
assert(prgbankix < bc) prgbankix = bc-1-prgbankix
assert(chrbankix, "GxROM must specify both PRG and CHR roms to switch simultaneously")
assert(chrbankix < cc)
local br = prgbankix<<4 | chrbankix
ldx #br&3|br>>2 lda #br sta bankbytes0,x
else -- A contains 00PP00CC
if bankregister_shadow >= 0 then
sta bankregister_shadow -- bankregister_shadow = 00PP00CC
lsr lsr -- A = 0000PP00, c
-- compute bc-1-prgbankix (reverse indexing)
eor #0xff adc #(bc-1<<2)+1 -- negate and +bc-1 combined, NN = bc-1-PP: A = 0000NN00
ora bankregister_shadow ;and #0xf tax -- X = 0000NNCC
else -- use stack instead
tsx sta 0x100,x lsr lsr -- A = 0000PP00, s[0] = 00PP00CC, c
-- compute bc-1-prgbankix (reverse indexing)
eor #0xff adc #(bc-1<<2)+1 -- negate and +bc-1 combined, NN = bc-1-PP: A = 0000NN00
ora 0x100,x ;and #0xf tax -- X = 0000NNCC
end
lda bankbytes0,x sta bankbytes0,x -- A = 00NN00CC, from table
end
end
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}
local start=section{"entry"..ix, org=o+0x8000-6-16-10} switchroms(0,0) if ix==0 then jmp main end
section{"vectors"..ix, org=o+0x8000-6} dc.w "nmi"..ix, start, "irq"..ix
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
function switchprgrom(bankix)
if bankix then
assert(bankix < bc)
lda #bc-1-bankix
else
clc eor #0xff adc #bc -- bc-1 - A
end
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
lda bankbytes0,x sta bankregister_shadow sta bankbytes0,x
end
function switchchrrom(bankix)
if bankix then
assert(bankix < cc)
lda #bankix
end
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
lda bankbytes0,x sta bankregister_shadow sta bankbytes0,x
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