From 7675355d1715bb65488ef0e08f11e08b743a8b8f Mon Sep 17 00:00:00 2001 From: franchioping Date: Sat, 18 May 2024 18:43:54 +0100 Subject: [PATCH] first commit --- nes_emu.prg.tns | Bin 0 -> 35892 bytes nes_emu.tns | Bin 0 -> 35276 bytes readme.txt | 45 ++ source/Makefile | 22 + source/cpu.S | 958 +++++++++++++++++++++++++++++++++++++++++ source/debug.S | 158 +++++++ source/font.bin | Bin 0 -> 4096 bytes source/main.S | 416 ++++++++++++++++++ source/memory.S | 372 ++++++++++++++++ source/menu.S | 264 ++++++++++++ source/nes.inc | 132 ++++++ source/nes_emu.prg.tns | Bin 0 -> 35892 bytes source/nes_emu.tns | Bin 0 -> 35276 bytes source/ppu.S | 589 +++++++++++++++++++++++++ source/rom.S | 609 ++++++++++++++++++++++++++ 15 files changed, 3565 insertions(+) create mode 100644 nes_emu.prg.tns create mode 100644 nes_emu.tns create mode 100644 readme.txt create mode 100644 source/Makefile create mode 100644 source/cpu.S create mode 100644 source/debug.S create mode 100644 source/font.bin create mode 100644 source/main.S create mode 100644 source/memory.S create mode 100644 source/menu.S create mode 100644 source/nes.inc create mode 100644 source/nes_emu.prg.tns create mode 100644 source/nes_emu.tns create mode 100644 source/ppu.S create mode 100644 source/rom.S diff --git a/nes_emu.prg.tns b/nes_emu.prg.tns new file mode 100644 index 0000000000000000000000000000000000000000..0ca2093efc80cf3732263b59d508eb5e3a9388b6 GIT binary patch literal 35892 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 literal 0 HcmV?d00001 diff --git a/nes_emu.tns b/nes_emu.tns new file mode 100644 index 0000000000000000000000000000000000000000..babc03b8f54e65e6faef4e0ff1a2889faa68a26b GIT binary patch literal 35276 zcmeHweSB2K)&JbPo6WLW!VL)+Lkbs21rhY3ltxTpc@^K6H>F5*L7>n^NCB0$6gMv* z!C<0-f{FD(?o`;u!QP)U_4tu*`lp1XH8lQn|$>F@XX z{qd|G=FH5QGiT16IWu==?#+z3^A}2tu|2hnO-2~>Ahrew{Ys;ZX~-Ekm$6nr!92z` zEnsZocNx1DumP|MP*M&WKv@N25kTQ$#)<*+0p);oOF#?ImNGUB5C_x%_5$7kJbeda zjezi-j46PbfEb|QF7N^z2ebl|?;{QHIN(XZNx&()0`5Ci-M@Dd=n0x|)n17-rA1UwB;?uHz9Gig~hW3K^% zHHZTW0mU^;uBm47Ccu+`m#SU8hM;IaAPW!xWCOaQ<}hI4+$Fcpz5Nc>dh3v*1NYWD zJ~rg&eFf~;iF|g<$F?5n!YCYa^rRR61R>kI#cAEL^$3cy-pNZ$3Zs*+WUV(0IodU< zn-ku<&Pwim!|vLU!dXPOCzR{9KX#)Ph}1TpP9}?jGisf@YicdOs-t|P$&~whNJ?W~ zfH8=>uSsG%n+7n=zJzJ{pn=4V<|&kmUsUT1leRdFH8@Fs!d6(+Mwz0o`*b_V;?_G# zy~R|+W}0sMP%rGNMG8kO33P=XodUWIE}d>mpp!s1EL7{17S%dsgKC|fBWj(2SJyhZ zXgf51?3EmL%<9UHB@nWhZnFwN9Oa%y*r?mrNUY^#Xrlu;PFx83;GZ`-t@T@vtZS%u z-fry+-WwgJHaIGH`_;H34?sDD7v}4Bsd~Rtg7nfs8=X?M(#Z!s>C_h)ax}`eIFh2< zVI}T_&<;wYb`DdvI3-4dGY}vdx~&?Oj@%6W8gVCx_+W&Epb59^b9}~qR=8!GYZFB#6Fh-P}ZN{|(uj(%>lSHn-n~_Sfx-z7HTR?nKlv*7)aD zUjh2^CE(M67Xk;5k|fjZB>J&Lt+%d)jrbAIM*OjhYprV#=lNMYAN4B!^LpeD_-g+- z11~7)H}t$Y`2%k3KfYgIwdX|YIo(Xc#!DdAekoy+-hD87pVHTm{fZq_tMcP4&z2C% z;XGSLCzWeqdJJuyS&656F7rhkzqXhIhtpiEFVsFEcvB-%?O3R zyVvv~ta|xg*KS;y*f>Ki(9Mw2&NGugY}h`DF~*{XeaZCQjx=+nH7&Z*YE9Hzg9mPO zNLE>GG%-(IEx}g&u$h4fCFPwaw8xMlihg-3hh5QSXf{@a*B~%mI(rLCa9) zmtRok1(caDl-Z9mTfKUI@&#p{Lz$a}GJimsHuO&p=8;>nS~@ zMgso^;3?q00=x{k9P!r?z6tmv;A@Ba-OlB;6mVo zC=bxj%PT=xiuiayU*J;^-U{dq{40Q-fCY#z2AmK42M8+xwSXRo*CSj9_%R?4@t-4n z29OK9DH3%CJ|1qEv_)`d< z2Yes+j{q+Ne+{tDOFM|r0xU+Jf$#|6W57QF-$PmukPBD{xE(MT&Jf^|dtV7tDeTF622&w>1$2inpiF;B(}c>-S9d%d#6e^o5SpXb$Z8N7C$*Gap= z9-r@L(1t;qDf2Bz<9fIq^s>~;-lx7}T-26y=N4Y?JmST5t3t0>6C^dC<YCAyW2*rX*(MWf1+}b-a57>o5-5>*M{m+bieRF?W?)N9K4)aG5+k zO@Us%sJ*=I=QGMo=tP&C_Q?#|D?oducCSG`x5v+<;xUPpU`;E|l7gC+Pk22}WQ#&0WPk9-1G=_9<42g3|Hs~(ESff4=pBp%z0NT>2Y?4;e?#tvoBR)AJ)(+*m#R* z;X7!qX*k_+uK5GyK@O_#Ok-nuC$jLd@mnqncyt^a?YiM||FRQWQEqW8JPuk~i;!%7 zmT;eyxDGdSKA!C2m>ANDti1mEjB?_bz#7z>Pd_hrWhdpZNz_5-^AP7_;0~e}?Y$HG z#~z9GDa!DEnoniC_GHdMr+lfpu%Fm3Hs{v{*%L1}gUd$x#~2`eiget*qMgvi((_2X z!+IAO0w>@GSlR+L>FLvMq=eGH}c z8@QhLa9+?e)h&*pnVrbXWq3KGoH&NAKSR03PRfn(=tcS0A&rltpHdl5KJv?% z-YJ%^=vaO$m%-CnlX(FP z?OS+xuYIE5{!sd2-9|lLGzLQ$Uo4|fCuZ;!{rY~+x0C)IafWiQ^KzZ&VQ@w{u^lh* za-Ha;HavR>7K_5-icdVb&Kbt{6&g)+(qwVXF&imkTD&y^u&Hj7G`E4`h z@xDF7_#X5Ha`7>KFPDq@*;{*c)_=SmcXvYHSs#jPu*E3D*I>bn=a}zueqLL|G4L7A z^Uoy9%#5<)cs+iWve$J|_I;G;tSuUJ$k$_|s9XnoyQ#zY;r8}5qT~IOG4@_XJ>L5E z(%76o8sf9e04^8xM|#|Ktb>oG;tbkMo}8|G23@L+@h=>60is9!F5S|HKQ~s|PPOYG zvrgXrzYz`j1WyO6)BVx0E!?h-X3*ny&gXQ*X%A=6elOi`9ow`W>3l5hqcYxDsz23n zu6&*Hcwe1iEZx+JOne-@$Yr8_^Ttu8Zn*rv=!CYjJ`u;zV<^);hB`jSJi_^TZ4k%M zpiX4wa@1v%6UR^S8Op8bq?`i!&e|{w>3r-|pd8M|Edn}oAsgfNvH*GEPmy0zW&C6~ zo^w8`H*ijEPPk$_j@Aw|r|vrGX0|3Fz{(ydWc9m=Q0|h#$uJFzRv7<#BfkkE+QQ74`fb;duygkEZ1lz;T>s z`!3vW_xl=TA6jd~OCNMvg9jX0d%L-{bhk4qJlXD}%KiF(qZUi9w+9%ghsyX_KHU|O zaUZ0+jPl2EUM>&99Thz9Q=6Mg4>(=IwU&?VZT8^|L`GX}#$?K~z@P3~wSa$IOV}1d zN*@Q_67yL*aIa!qX|0>qxn6gt^~CkMT+r+7XdmgAAf?wGX?nVwQ;Pc($8leW@+m$r zBVL#h4+?S8E8SP{8=ITxJ^@q58Mq19aur|#fZ8zz;pKn~8)q8~W7ev35_P35ef4JXJM0}41 zQ~EuE@Hn8qUpN24(@-V}8eR_a73E&K!|tQ)u#KVB;F;%k=Vc6T483H?bjYmk}bYVztbg*px3zAJWMj4+FRt({i^>;y7|Xl$TtUIe-x zhr1TU$FB^*eIMP_FTg%P-&%tW#CQD=i+DZ*Pt+I6 z%;&QDmCa3onW=G3^^xrbLX+vP;E10DU{4b24B`$QiRfgaxoG=kf5Y9d zE}tY@TCW>&)Od2ELw8GXSH_Z9h3$Ldbsya`LH$Jq%&_Gr*4S}1WL+|FH|T1eR^%Om zZ3J?PaCe5`|EPVuaHDesJd%TTUlL5}n{MzKC6|T`eHPd~pOF$n)7*zEBw04yuA30s>aF;Z)p*b1zV-GW8{0zD&xkop{@t~!}SKCrn zfxU8yp4UgyeYo?PAB^dC$xCYuIZ$BJ-5|+0*c7kGvVOi~wJCSO{lkiTy8GC9w&cpz z+m$+}|-`T+M9KSsMzrsa#upigmiq_?eB+?S7X4lnl+-6K;bS-5vtME5CAuBN_j zG!>@jcBqN5cd=LeZnLbu3!nXNGm~+*E)X|;{)yyTD8&0)|<~N>&@rX_2%>1dQ%F@PM&`FI=8=Zr?^9ZQy;UdXnaL1-hW{>%^b?uBYX_e_>qb#ExF4iI}19J z&);UNN~JaO;yg?0HCpc0b+UcQSNh5{hKwg`-SOH3WA%o=C39NWZapH&{@{oB(fDC; z=R@=q0B@e2y`%Tl$tVx(Jr>}QL)v%9i+Kh%aV#s^Xi874$=^+J z*kd?4S0TG!Q=?1%Dm_nH6GZ!UUt3JiFPRfCo|v^t$&O}Of!V8#3s&l8uo5z^-rxjc ztBtJaC(XgwADzI|m8R^QO8&L41o;r=m6Xx^UvDVdgAUofFZ!y~fBLI=)P^K}ZEbho zefspMQKwGz>C@WUt(#$FXD1RqUpy{JFZQm*JgD2P7{^`IZLaU7``R=HyI?&)>i|Fc zDT49i*IJzuktG%!+#_K&)s!N2Rn>X04cG{a?sFvE=L@oF=&x-LcZq^mWUA%frNkII z0iF_TpI7#?daM6eYORR6(!v5DMY z47rMCA}@|Sznj;9ac7va5_j_wgHkdDWZ}m!0J4N1;@v{R4+h??tMJQV>=#S+sXbVJ zBzLv^B-10yVxv_V(|~jgixT>Mx^=VfP~YD1^L~?KL=Mb)J$|q5k7V8B#~m;Chi?DI zD>H8$_x{Jf{_N?`et)|8Z&j;~UjO#3H)sDhUP@prnH=@YS9aa{$5Cxpw0-i=ciV;~ zPc}dN%fJ6u+m_N@l>eGLFB#@*p<3s)BCInd_$b`L-#I9CPxGO{xa-?j&+{wsCej1h z4B1Gu{EZjBgpl(h9#lRH{HQ#Y%?3_osazM}R4%iO6uQc-m&#CC zv7C%NDof?4d}djyud}+udg$GfX?^f+2ky(WNTaztYWTj3Vf2gKT1$ccB^rNt*1&vy zVgc?H(|kD#`3CH&ulD%g@c9^qCy38CQP=h0brR`Wu$j{cvoUAl8Og_Dx(|!`ZtnXZ%1T3&gUVisvRJp{ z{xWCJPy2b-U-q=u zBYZ5G3|%1W=5e%*=I-XA16i$#Bx8LlHR8RNchq{UQzx5WL*4yQ_jA$3=5vw7n46>S zJ2Y9W(m0MbQ2q3t%Jt)?6fK_3Zd-Im`J9CVma2;?<}Os{EhwMM7A{(<&RVE~pglf! z;i5afJzu?j?(K^ff4{xZ;<>ZtsFQEFj@_|v!R;00b8nx!aOvDR>g}^CD(2!fEZ%

V; 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 xUd7m)d+@#m;Q!a30, no name table change + cmp r2, #0x03C0 + eoreq r1, r1, #0x0BC0 @ If Y wraps 29->0, name table change +1: str r1, [r9, #s_ppu_address] + +rendering_off: + str r10, [r9, #s_ppu_flags] + ldr r0, [r9, #s_ppu_scanline] + cmp r0, #-21 + bleq newframe + pop {r4-r8, r10-r11, pc} + + .pool + +swap_name_table: + ldr r2, [r9, #s_ppu_address] + mov r8, #0 + eor r2, r2, #0x0400 +get_name_table_pointers: + and r2, r2, #0x0FE0 + orr r2, r2, #0x2000 + mov r5, r2, lsr #10 + add r5, r9, r5, lsl #2 + ldr r5, [r5, #s_ppu_mem_map] + + and r0, r2, #0xFC00 + orr r0, r0, r2, lsr #4 + orr r0, r0, #0x03C0 + bic r0, r0, #7 + + add r2, r5, r2 + add r0, r5, r0 + + bx lr + +background_disabled: + @ Weird NES behavior: if rendering is completely disabled (both BG and sprite), + @ and PPUADDR points inside palette, draw that color. + @ Otherwise, just draw color 0 + tst r10, #0x1800 + bne 1f + ldr r0, [r9, #s_ppu_address] + ands lr, r0, #0x3F00 + and r0, r0, #0x1F + orr r0, r0, #0x20 @ use alternate palette (doesn't map $04,$08,$0C -> $00) + orr r0, r0, r0, lsl #8 + orr r0, r0, r0, lsl #16 + cmp lr, #0x3F00 +1: movne r0, #0 + + mov r11, #264 +1: subs r11, r11, #4 + str r0, [sp, r11] + bne 1b + b background_done + +sprite_0_check: + bl fetch_sprite_bits + movs r5, r5 + orrne r10, r10, #0x400000 + b sprite_0_done + +fetch_sprite_bits: + @ Vertical flip + tst r4, #0x800000 + rsbne r5, r5, r12 + + @ Get CHR address + tst r10, #0x0020 + moveq r8, r10, lsl #9 + movne r8, r4, lsl #4 + and r8, r8, #0x1000 + moveq r6, #0xFF + movne r6, #0xFE + and r6, r6, r4, lsr #8 + addne r6, r6, r5, lsr #3 + and r5, r5, #7 + + add r6, r8, r6, lsl #4 + mov r8, r6, lsr #10 + add r8, r9, r8, lsl #2 + ldr r8, [r8, #s_ppu_mem_map] + add r6, r8, r6 + + ldrb r5, [r6, r5]! @ low plane + ldrb r6, [r6, #8] @ high plane + + orr r5, r5, r6, lsl #8 + + @ Check if sprite needs to be clipped against left edge of screen + cmp r4, #0x08000000 + bxcs lr +clip_sprite: + tst r10, #0x0400 + bxne lr + mov r3, r4, lsr #24 + add r3, pc, r3, lsl #2 + ldr r3, [r3, #sprite_clip_table - (.+4)] + tst r4, #0x400000 + biceq r5, r5, r3 + bicne r5, r5, r3, lsr #16 + bx lr +sprite_clip_table: + .word 0xFFFFFFFF + .word 0x7F7FFEFE + .word 0x3F3FFCFC + .word 0x1F1FF8F8 + .word 0x0F0FF0F0 + .word 0x0707E0E0 + .word 0x0303C0C0 + .word 0x01018080 + +refresh_spr_loc_table: + @ Start by clearing the table (0 sprites for every scanline) + add r1, r9, #s_spr_loc_table + mov r2, #0 + mov r3, #240 +1: strb r2, [r1], #9 + subs r3, r3, #1 + bne 1b + + add r2, r9, #s_spr_loc_table + add r2, r2, #9 + + @ Loop over each sprite + add r0, r9, #s_ppu_oam_ram + sub r0, r0, #4 + mov r6, #64 +spr_loc_loop1: + @ Get first scanline (minus one) of sprite + ldrb r3, [r0, #4]! + + rsbs r4, r3, #239 @ Number of visible scanlines + bls spr_loc_done + cmp r4, r12 + addhi r4, r12, #1 + + add r1, r2, r3, lsl #3 + add r1, r1, r3 + + @ Loop over each scanline this sprite is in, + @ appending the sprite index to each one's list +spr_loc_loop2: + ldrb r5, [r1] + add r5, r5, #1 + cmp r5, #8 + strlsb r0, [r1, r5] @ Assuming OAM is 256-byte aligned + strb r5, [r1], #9 + subs r4, r4, #1 + bne spr_loc_loop2 +spr_loc_done: + subs r6, r6, #1 + bne spr_loc_loop1 + + mov r0, #1 + strb r0, [r9, #s_spr_loc_table_valid] + bx lr + +refresh_palette_cache_bw: + adr r0, nes_color_to_gray_table + add r1, r9, #s_ppu_palette + add r2, r5, #0x40 + mov r3, #0x1F +1: ldrb r6, [r1, r3] + ldrb r6, [r0, r6] + strb r6, [r2, #-1]! + subs r3, r3, #1 + bpl 1b + mov r3, #0x1F +1: tst r3, #0x03 + ldreqb r6, [r1] + ldrneb r6, [r1, r3] + ldrb r6, [r0, r6] + strb r6, [r2, #-1]! + subs r3, r3, #1 + bpl 1b + strb r3, [r9, #s_palette_cache_valid] + bx lr + +refresh_palette_cache_color: + adr r0, nes_color_to_rgb_table + add r1, r9, #s_ppu_palette + add r2, r5, #0x80 + mov r3, #0x1F +1: ldrb r6, [r1, r3] + add r6, r6 + ldrh r6, [r0, r6] + strh r6, [r2, #-2]! + subs r3, r3, #1 + bpl 1b + mov r3, #0x1F +1: tst r3, #0x03 + ldreqb r6, [r1] + ldrneb r6, [r1, r3] + add r6, r6 + ldrh r6, [r0, r6] + strh r6, [r2, #-2]! + subs r3, r3, #1 + bpl 1b + strb r3, [r9, #s_palette_cache_valid] + bx lr + +.globl invert_colors +invert_colors: + adr r0, nes_color_to_gray_table + mov r2, #64 +1: subs r2, r2, #1 + ldrb r1, [r0, r2] + eor r1, r1, #0x0F + strb r1, [r0, r2] + bne 1b + adr r0, nes_color_to_rgb_table + mov r2, #128 +1: subs r2, r2, #4 + ldr r1, [r0, r2] + mvn r1, r1 + str r1, [r0, r2] + bne 1b + strb r2, [r9, #s_palette_cache_valid] + bx lr + +nes_color_to_gray_table: + .byte 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0 + .byte 10, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0 + .byte 15,10,10,10,10,10,10,10,10,10,10,10,10, 5, 0, 0 + .byte 15,13,13,13,13,13,13,13,13,13,13,13,13,11, 0, 0 + +@ .byte 7, 3, 4, 4, 5, 6, 6, 4, 3, 3, 4, 3, 3, 0, 0, 0 +@ .byte 11, 7, 6, 7, 7, 8, 8, 8, 7, 7, 8, 7, 7, 0, 0, 0 +@ .byte 15,10, 9, 8,11,11,10,11,12,10,11,12,12, 7, 0, 0 +@ .byte 15,13,13,13,13,13,12,13,14,14,13,14,14,12, 0, 0 + +nes_color_to_rgb_table: + .hword 0x73ae,0x20d1,0x0015,0x4013,0x880e,0xa802,0xa000,0x7840 + .hword 0x4160,0x0220,0x0280,0x01e2,0x19eb,0x0000,0x0000,0x0000 + .hword 0xbdf7,0x039d,0x21dd,0x801e,0xb817,0xe00b,0xd940,0xca61 + .hword 0x8b80,0x04a0,0x0540,0x0487,0x0411,0x0000,0x0000,0x0000 + .hword 0xffff,0x3dff,0x5cbf,0x445f,0xf3df,0xfbb6,0xfbac,0xfcc7 + .hword 0xf5e7,0x8682,0x4ee9,0x5fd3,0x075b,0x7bcf,0x0000,0x0000 + .hword 0xffff,0xaf3f,0xc6bf,0xd65f,0xfe3f,0xfe3b,0xfdf6,0xfed5 + .hword 0xff34,0xe7f4,0xaf97,0xb7f9,0x9ffe,0xc638,0x0000,0x0000 + +.globl toggle_border +toggle_border: + ldr r0, [r9, #s_border_color] + mvn r0, r0 + str r0, [r9, #s_border_color] +.globl clear_screen +clear_screen: + ldr r1, [r9, #s_border_color] + mov r0, #0xC0000000 + ldr r0, [r0, #0x10] + ldr r2, [r9, #s_hw_color] + movs r2, r2 + mov r2, #0x9600 + lslne r2, #2 +1: str r1, [r0], #4 + subs r2, #4 + bne 1b + bx lr + +.globl display_ingame_message +display_ingame_message: + mov r1, #60 + strb r1, [r9, #s_message_timer] + mov r1, #4 + mov r2, #36 + ldr r3, [r9, #s_border_color] + mvn r3, r3 + b display_string diff --git a/source/rom.S b/source/rom.S new file mode 100644 index 0000000..b2435a0 --- /dev/null +++ b/source/rom.S @@ -0,0 +1,609 @@ +#include "nes.inc" + +map_prg_32kB: + ldr r1, [r9, #s_prg_size] + ldr r2, [r9, #s_prg_ptr] + and r0, r1, r0, lsl #15 + add r0, r0, r2 +map_prg_32kB_from_pointer: + sub r0, r0, #0x8000 + str r0, [r9, #s_mem_map + 16] @ 8000 + str r0, [r9, #s_mem_map + 20] @ A000 + str r0, [r9, #s_mem_map + 24] @ C000 + str r0, [r9, #s_mem_map + 28] @ E000 + bx lr +map_prg_16kB_to_8000: + ldr r1, [r9, #s_prg_size] + ldr r2, [r9, #s_prg_ptr] + and r0, r1, r0, lsl #14 + add r0, r0, r2 + sub r0, r0, #0x8000 + str r0, [r9, #s_mem_map + 16] @ 8000 + str r0, [r9, #s_mem_map + 20] @ A000 + bx lr +map_prg_16kB_to_C000: + ldr r1, [r9, #s_prg_size] + ldr r2, [r9, #s_prg_ptr] + and r0, r1, r0, lsl #14 + add r0, r0, r2 + sub r0, r0, #0xC000 + str r0, [r9, #s_mem_map + 24] @ C000 + str r0, [r9, #s_mem_map + 28] @ E000 + bx lr + +map_prg_8kB: + ldr r2, [r9, #s_prg_size] + ldr r3, [r9, #s_prg_ptr] + and r0, r2, r0, lsl #13 + add r0, r0, r3 + sub r0, r0, r1 + add r1, r9, r1, lsr #11 + str r0, [r1, #s_mem_map] + bx lr + + +map_chr_8kB: + ldr r2, [r9, #s_chr_size] + ldr r3, [r9, #s_chr_ptr] + and r0, r2, r0, lsl #13 + add r0, r0, r3 + str r0, [r9, #s_ppu_mem_map + 0x00] @ 0000 + str r0, [r9, #s_ppu_mem_map + 0x04] @ 0400 + str r0, [r9, #s_ppu_mem_map + 0x08] @ 0800 + str r0, [r9, #s_ppu_mem_map + 0x0C] @ 0C00 + str r0, [r9, #s_ppu_mem_map + 0x10] @ 1000 + str r0, [r9, #s_ppu_mem_map + 0x14] @ 1400 + str r0, [r9, #s_ppu_mem_map + 0x18] @ 1800 + str r0, [r9, #s_ppu_mem_map + 0x1C] @ 1C00 + bx lr +map_chr_4kB_to_0000: + mov r1, #0x0000 + b map_chr_4kB +map_chr_4kB_to_1000: + mov r1, #0x1000 +map_chr_4kB: + ldr r2, [r9, #s_chr_size] + ldr r3, [r9, #s_chr_ptr] + and r0, r2, r0, lsl #12 + add r0, r0, r3 + sub r0, r0, r1 + add r1, r9, r1, lsr #8 + str r0, [r1, #s_ppu_mem_map + 0x00] @ +0000 + str r0, [r1, #s_ppu_mem_map + 0x04] @ +0400 + str r0, [r1, #s_ppu_mem_map + 0x08] @ +0800 + str r0, [r1, #s_ppu_mem_map + 0x0C] @ +0C00 + bx lr +map_chr_2kB: + ldr r2, [r9, #s_chr_size] + ldr r3, [r9, #s_chr_ptr] + and r0, r2, r0, lsl #10 + add r0, r0, r3 + sub r0, r0, r1 + add r1, r9, r1, lsr #8 + str r0, [r1, #s_ppu_mem_map + 0x00] @ +0000 + str r0, [r1, #s_ppu_mem_map + 0x04] @ +0400 + bx lr +map_chr_1kB: + ldr r2, [r9, #s_chr_size] + ldr r3, [r9, #s_chr_ptr] + and r0, r2, r0, lsl #10 + add r0, r0, r3 + sub r0, r0, r1 + add r1, r9, r1, lsr #8 + str r0, [r1, #s_ppu_mem_map] + bx lr + +mirror_1screen_lo: + add r0, r9, #s_name_table_ram - 0x2000 + b mirror_1screen +mirror_1screen_hi: + add r0, r9, #s_name_table_ram + 0x0400 - 0x2000 +mirror_1screen: + str r0, [r9, #s_ppu_mem_map + 0x20] @ 2000 + sub r0, r0, #0x0400 + str r0, [r9, #s_ppu_mem_map + 0x24] @ 2400 + sub r0, r0, #0x0400 + str r0, [r9, #s_ppu_mem_map + 0x28] @ 2800 + sub r0, r0, #0x0400 + str r0, [r9, #s_ppu_mem_map + 0x2C] @ 2C00 + sub r0, r0, #0x0400 + str r0, [r9, #s_ppu_mem_map + 0x30] @ 3000 + sub r0, r0, #0x0400 + str r0, [r9, #s_ppu_mem_map + 0x34] @ 3400 + sub r0, r0, #0x0400 + str r0, [r9, #s_ppu_mem_map + 0x38] @ 3800 + sub r0, r0, #0x0400 + str r0, [r9, #s_ppu_mem_map + 0x3C] @ 3C00 + bx lr + +mirror_vert: + add r0, r9, #s_name_table_ram - 0x2000 + str r0, [r9, #s_ppu_mem_map + 0x20] @ 2000 + str r0, [r9, #s_ppu_mem_map + 0x24] @ 2400 + add r0, r9, #s_name_table_ram - 0x2800 + str r0, [r9, #s_ppu_mem_map + 0x28] @ 2800 + str r0, [r9, #s_ppu_mem_map + 0x2C] @ 2C00 + add r0, r9, #s_name_table_ram - 0x3000 + str r0, [r9, #s_ppu_mem_map + 0x30] @ 3000 + str r0, [r9, #s_ppu_mem_map + 0x34] @ 3400 + add r0, r9, #s_name_table_ram - 0x3800 + str r0, [r9, #s_ppu_mem_map + 0x38] @ 3800 + str r0, [r9, #s_ppu_mem_map + 0x3C] @ 3C00 + bx lr + +mirror_horiz: + add r0, r9, #s_name_table_ram - 0x2000 + str r0, [r9, #s_ppu_mem_map + 0x20] @ 2000 + add r0, r9, #s_name_table_ram - 0x2400 + str r0, [r9, #s_ppu_mem_map + 0x24] @ 2400 + str r0, [r9, #s_ppu_mem_map + 0x28] @ 2800 + add r0, r9, #s_name_table_ram - 0x2800 + str r0, [r9, #s_ppu_mem_map + 0x2C] @ 2C00 + add r0, r9, #s_name_table_ram - 0x3000 + str r0, [r9, #s_ppu_mem_map + 0x30] @ 2000 + add r0, r9, #s_name_table_ram - 0x3400 + str r0, [r9, #s_ppu_mem_map + 0x34] @ 2400 + str r0, [r9, #s_ppu_mem_map + 0x38] @ 2800 + add r0, r9, #s_name_table_ram - 0x3800 + str r0, [r9, #s_ppu_mem_map + 0x3C] @ 2C00 + bx lr + +mirror_4screen: + add r0, r9, #s_name_table_ram - 0x2000 + str r0, [r9, #s_ppu_mem_map + 0x20] @ 2000 + str r0, [r9, #s_ppu_mem_map + 0x24] @ 2400 + str r0, [r9, #s_ppu_mem_map + 0x28] @ 2800 + str r0, [r9, #s_ppu_mem_map + 0x2C] @ 2C00 + add r0, r9, #s_name_table_ram - 0x3000 + str r0, [r9, #s_ppu_mem_map + 0x30] @ 3000 + str r0, [r9, #s_ppu_mem_map + 0x34] @ 3400 + str r0, [r9, #s_ppu_mem_map + 0x38] @ 3800 + str r0, [r9, #s_ppu_mem_map + 0x3C] @ 3C00 + bx lr + +.globl load_rom +load_rom: + push {r4-r11, lr} + + adr r1, file_mode + swi e_fopen + movs r4, r0 + moveq r5, #error_open - error_messages + beq error + + @ Read ROM header + add r0, r9, #s_rom_header + mov r1, #16 + mov r2, #1 + mov r3, r4 + swi e_fread + mov r5, #error_bad_header - error_messages + movs r0, r0 + beq error_fclose + ldr r1, [r9, #s_rom_header] + ldr r2, =0x1A53454E + cmp r1, r2 + bne error_fclose + + ldrb r5, [r9, #s_rom_header + 4] @ Number of PRG-ROM banks + movs r5, r5, lsl #14 + moveq r5, #0x400000 + sub r0, r5, #1 + str r0, [r9, #s_prg_size] + + ldrb r7, [r9, #s_rom_header + 5] @ Number of CHR-ROM banks + movs r6, r7, lsl #13 + moveq r6, #0x2000 + sub r0, r6, #1 + str r0, [r9, #s_chr_size] + + add r0, r5, r6 + swi e_malloc + movs r0, r0 + moveq r5, #error_no_memory - error_messages + beq error_fclose + str r0, [r9, #s_prg_ptr] + add r0, r5 + str r0, [r9, #s_chr_ptr] + + @ Clear CHR-RAM if present + movs r1, r7 + moveq r2, #0x2000 + moveq r6, #0 + swieq e_memset + + @ Read PRG-ROM and CHR-ROM from file + ldr r0, [r9, #s_prg_ptr] + add r1, r5, r6 + mov r2, #1 + mov r3, r4 + swi e_fread + movs r0, r0 + moveq r5, #error_rom_read - error_messages + beq error_fclose + + mov r0, r4 + swi e_fclose + + @ Initialize CPU memory map + @ RAM + str r9, [r9, #s_mem_map + 0] @ 0000 + @ SRAM + add r1, r9, #s_sram - 0x6000 + str r1, [r9, #s_mem_map + 12] @ 6000 + @ ROM low + mov r0, #0 + bl map_prg_16kB_to_8000 + @ ROM high + mov r0, #-1 + bl map_prg_16kB_to_C000 + @ RAM wraparound + sub r1, r9, #0x10000 + str r1, [r9, #s_mem_map + 32] @ 10000 + + @ Initialize PPU memory map + mov r0, #0 + bl map_chr_8kB + + @ Name table + ldrb r6, [r9, #s_rom_header + 6] + adr lr, 1f + tst r6, #8 + bne mirror_4screen + tst r6, #1 + bne mirror_vert + beq mirror_horiz +1: + + @ Get low 4 bits of mapper number + mov r0, r6, lsr #4 + @ Get high 4 bits of mapper number (unless it looks like + @ there's junk in the header, in which case ignore them) + ldr r1, [r9, #s_rom_header + 12] + movs r1, r1 + ldreqb r1, [r9, #s_rom_header + 7] + andeq r1, r1, #0xF0 + orreq r0, r0, r1 + + adr r1, mapper_table + adr r2, mapper_table_end +1: cmp r2, r1 + moveq r5, #error_bad_mapper - error_messages + beq error + ldrh r5, [r2, #-2]! + ldrh r4, [r2, #-2]! + cmp r0, r4 + bne 1b + add r0, r1, r5 + str r0, [r9, #s_mapper] + + mov r0, #0 + pop {r4-r11, pc} + .pool + +file_mode: + .string "rb" +error_open: + .string "couldn't open file" +error_bad_header: + .string "not an NES file" +error_no_memory: + .string "not enough memory" +error_rom_read: + .string "couldn't read ROM" +error_bad_mapper: + .string "unimplemented mapper" + .align 4 + +error_fclose: + mov r0, r4 + swi e_fclose +error: + ldr r0, [r9, #s_prg_ptr] + swi e_free + mov r0, #0 + str r0, [r9, #s_prg_ptr] +error_messages = .+8 + add r0, pc, r5 + pop {r4-r11, pc} + +mapper_table: +.macro MAPPER n, addr; .hword \n, \addr - mapper_table; .endm + MAPPER 0, mapper_NROM + MAPPER 1, mapper_MMC1 + MAPPER 2, mapper_UxROM + MAPPER 3, mapper_CNROM + MAPPER 4, mapper_MMC3 + MAPPER 7, mapper_AxROM + MAPPER 11, mapper_Color_Dreams + MAPPER 34, mapper_BxROM + MAPPER 66, mapper_GxROM + MAPPER 228, mapper_Action_Enterprises +mapper_table_end: + +mapper_NROM: + bx lr + +#define s_mmc1_shift_reg (s_mapper_state) +#define s_mmc1_control (s_mapper_state+4) +#define s_mmc1_chr0 (s_mapper_state+5) +#define s_mmc1_chr1 (s_mapper_state+6) +#define s_mmc1_prg (s_mapper_state+7) +mapper_MMC1: + push {lr} + ldrb r3, [r9, #s_mmc1_shift_reg] + tst r0, #0x80 + bne mmc1_reset + and r0, r0, #1 + movs r3, r3, lsr #1 + orr r0, r3, r0, lsl #4 + strb r0, [r9, #s_mmc1_shift_reg] + popcc {pc} + and r2, r2, #0x6000 + add r2, r9, r2, lsr #13 + strb r0, [r2, #s_mmc1_control] + b mmc1_update +mmc1_reset: + ldrb r0, [r9, #s_mmc1_control] + orr r0, r0, #0x0C + strb r0, [r9, #s_mmc1_control] +mmc1_update: + mov r0, #0x10 + strb r0, [r9, #s_mmc1_shift_reg] + + ldrb r3, [r9, #s_mmc1_control] + adr lr, 1f + and r3, r3, #3 + add pc, pc, r3, lsl #2 + nop + b mirror_1screen_lo + b mirror_1screen_hi + b mirror_vert + b mirror_horiz +1: + + @ Update CHR + ldrb r3, [r9, #s_mmc1_control] + ldrb r0, [r9, #s_mmc1_chr0] + tst r3, #0x10 + bne 1f + mov r0, r0, lsr #1 + bl map_chr_8kB + b 2f +1: bl map_chr_4kB_to_0000 + ldrb r0, [r9, #s_mmc1_chr1] + bl map_chr_4kB_to_1000 +2: + + @ Update PRG + ldrb r3, [r9, #s_mmc1_control] + ldrb r0, [r9, #s_mmc1_prg] + @ Mode 0-1 + tst r3, #0x08 + bne 1f + mov r0, r0, lsr #1 + bl map_prg_32kB + pop {pc} +1: + + @ Mode 2 + tst r3, #0x04 + bne 1f + bl map_prg_16kB_to_C000 + mov r0, #0 + bl map_prg_16kB_to_8000 + pop {pc} + @ Mode 3 +1: bl map_prg_16kB_to_8000 + mov r0, #-1 + bl map_prg_16kB_to_C000 + pop {pc} + +mapper_UxROM: + b map_prg_16kB_to_8000 + +mapper_CNROM: + b map_chr_8kB + +#define s_mmc3_bank (s_mapper_state) +#define s_mmc3_bank_select (s_mapper_state+8) +#define s_mmc3_counter_reload (s_mapper_state+9) +#define s_mmc3_counter (s_mapper_state+10) +#define s_mmc3_counter_reset (s_mapper_state+11) +#define s_mmc3_irq_enabled (s_mapper_state+12) +mapper_MMC3: + and r1, r2, #0x6000 + and r2, r2, #0x0001 + orr r2, r2, r1, lsr #12 + add pc, pc, r2, lsl #2 + nop + b mmc3_bank_select + b mmc3_bank_data + b mmc3_mirroring + bx lr + b mmc3_irq_latch + b mmc3_irq_reload + b mmc3_irq_disable + b mmc3_irq_enable +mmc3_bank_select: + strb r0, [r9, #s_mmc3_bank_select] + b mmc3_update +mmc3_bank_data: + ldrb r1, [r9, #s_mmc3_bank_select] + and r3, r1, #7 + add r3, r3, r9 + strb r0, [r3, #s_mmc3_bank] + b mmc3_update +mmc3_mirroring: + @ Don't do anything if game uses 4-screen mirroring + ldrb r3, [r9, #s_rom_header+6] + tst r3, #8 + bxne lr + tst r0, #1 + beq mirror_vert + b mirror_horiz +mmc3_irq_latch: + strb r0, [r9, #s_mmc3_counter_reload] + bx lr +mmc3_irq_reload: + mov r0, #0xFF + strb r0, [r9, #s_mmc3_counter_reset] + bx lr +mmc3_irq_disable: + mov r0, #0 + strb r0, [r9, #s_irq_from_mapper] +mmc3_irq_enable: + and r0, r2, #1 + strb r0, [r9, #s_mmc3_irq_enabled] + bx lr +.globl mmc3_scanline +mmc3_scanline: + ldrb r0, [r9, #s_mmc3_counter] + + ldrb r1, [r9, #s_mmc3_counter_reset] + bic r1, r0, r1 + subs r1, r1, #1 + ldrmib r1, [r9, #s_mmc3_counter_reload] + strb r1, [r9, #s_mmc3_counter] + mov r2, #0 + strb r2, [r9, #s_mmc3_counter_reset] + + cmp r1, #0 + bxne lr + cmp r0, #0 + bxeq lr + + ldrb r0, [r9, #s_mmc3_irq_enabled] + strb r0, [r9, #s_irq_from_mapper] + bx lr +mmc3_update: + push {r4, r5, lr} + ldrb r5, [r9, #s_mmc3_bank_select] + + mov r4, #0x1000 + and r4, r4, r5, lsl #5 + ldrb r0, [r9, #s_mmc3_bank+0]; eor r1, r4, #0x0000; bl map_chr_2kB + ldrb r0, [r9, #s_mmc3_bank+1]; eor r1, r4, #0x0800; bl map_chr_2kB + ldrb r0, [r9, #s_mmc3_bank+2]; eor r1, r4, #0x1000; bl map_chr_1kB + ldrb r0, [r9, #s_mmc3_bank+3]; eor r1, r4, #0x1400; bl map_chr_1kB + ldrb r0, [r9, #s_mmc3_bank+4]; eor r1, r4, #0x1800; bl map_chr_1kB + ldrb r0, [r9, #s_mmc3_bank+5]; eor r1, r4, #0x1C00; bl map_chr_1kB + + mov r4, #0x4000 + and r4, r4, r5, lsl #8 + ldrb r0, [r9, #s_mmc3_bank+6]; eor r1, r4, #0x8000; bl map_prg_8kB + ldrb r0, [r9, #s_mmc3_bank+7]; mov r1, #0xA000; bl map_prg_8kB + mov r0, #-2; eor r1, r4, #0xC000; bl map_prg_8kB + + pop {r4, r5, pc} + +mapper_AxROM: + push {r4, lr} + mov r4, r0 + tst r4, #0x10 + bleq mirror_1screen_lo + blne mirror_1screen_hi + mov r0, r4 + pop {r4, lr} + b map_prg_32kB + +mapper_Color_Dreams: + push {r4, lr} + mov r4, r0 + bl map_prg_32kB + mov r0, r4, lsr #4 + pop {r4, lr} + b map_chr_8kB + +mapper_BxROM: + b map_prg_32kB + +mapper_GxROM: + push {r4, lr} + mov r4, r0 + bl map_chr_8kB + mov r0, r4, lsr #4 + pop {r4, lr} + b map_prg_32kB + +mapper_Action_Enterprises: + push {r4, lr} + mov r4, r2 + and r0, r0, #3 + orr r0, r0, r2, lsl #2 + bl map_chr_8kB + mov r0, r4, lsr #7 + and r0, r0, #0x3F + mov r0, r0, lsl #15 + ldr r1, [r9, #s_prg_size] + ldr r2, [r9, #s_prg_ptr] + cmp r0, r1 + andhi r0, r0, r1 + add r0, r0, r2 + bl map_prg_32kB_from_pointer + bl mirror_vert + pop {r4, pc} + +.globl sram_load +sram_load: + ldrb r0, [r9, #s_rom_header+6] + tst r0, #0x02 + bxeq lr + push {r4, lr} + ldr r0, [r9, #s_path_extension] + adr r1, save_ext + swi e_strcpy + add r0, r9, #s_path + adr r1, save_read_mode + swi e_fopen + movs r4, r0 + popeq {r4, pc} + add r0, r9, #s_sram + mov r1, #0x2000 + mov r2, #1 + mov r3, r4 + swi e_fread + mov r0, r4 + swi e_fclose + pop {r4, pc} +.globl sram_save +sram_save: + push {r4-r5, lr} + ldrb r0, [r9, #s_rom_header+6] + tst r0, #0x02 + adreq r5, no_saves + beq 1f + ldr r0, [r9, #s_path_extension] + adr r1, save_ext + swi e_strcpy + add r0, r9, #s_path + adr r1, save_write_mode + swi e_fopen + adr r5, save_error + movs r4, r0 + beq 1f + add r0, r9, #s_sram + mov r1, #0x2000 + mov r2, #1 + mov r3, r4 + swi e_fwrite + movs r0, r0 + adrne r5, save_success + mov r0, r4 + swi e_fclose +1: mov r0, r5 + pop {r4-r5, lr} + b display_ingame_message +save_ext: + .string "sav.tns" +save_read_mode: + .string "rb" +save_write_mode: + .string "wb" +no_saves: + .string "Game has no save memory" +save_error: + .string "File error while saving" +save_success: + .string "Saved" + .align 4

V; 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 XOG=F`1wa?eF!{6JKDm(9(7XQ&G-=%) literal 0 HcmV?d00001 diff --git a/source/main.S b/source/main.S new file mode 100644 index 0000000..4203e5b --- /dev/null +++ b/source/main.S @@ -0,0 +1,416 @@ +#include "nes.inc" + + .string "PRG" + +.globl main +main: + push {r4-r11, lr} + @ Allocate the state data structure from the stack and zero it out + mov r5, sp + sub sp, sp, #s_SIZE + bic sp, sp, #s_ALIGN - 1 + mov r9, sp + sub sp, sp, #s_SIZE // Reserve space for state ADDED + + mov r4, #0 + mov r6, #s_SIZE +1: subs r6, r6, #4 + str r4, [r9, r6] + bne 1b + str r5, [r9, #s_saved_sp] + + @ Get our folder path + add r4, r9, #s_path + mov r2, r4 + movs r0, r0 @ argc + ldrne r1, [r1] @ argv[0] + movnes r1, r1 + beq 2f +1: ldrb r0, [r1], #1 + strb r0, [r4], #1 + teq r0, #'\' + teqne r0, #'/' + moveq r2, r4 + movs r0, r0 + bne 1b +2: str r2, [r9, #s_path_filename] + + @ Check hardware type + ldr r0, =0x900A0000 + ldr r0, [r0] + bic r0, #0xFF000000 + cmp r0, #0x10 + bne 1f + @ Non-CX + mov r0, #0xDC000000 + add r0, #0x08 + adr r1, interrupt_handler_noncx + mvn r2, #0 + mov r3, #0 + mov r4, #3 + b 2f +1: + sub r0, #0x100 + cmp r0, #0x001 + bne unknown_hardware + @ CX + mov r0, #0xDC000000 + add r0, #0x10 + adr r1, interrupt_handler_cx + mov r2, #0 + mov r3, #1 + mov r4, #1 +2: + str r0, [r9, #s_hw_irq_masks] + str r1, [r9, #s_hw_irq_handler] + str r2, [r9, #s_hw_keypad_invert] + str r3, [r9, #s_hw_color] + str r4, [r9, #s_frameskip] + + bl init_interrupts + bl init_keypad + bl toggle_border + bl rom_menu + bl clear_screen + + @ Set CPU to power-on state + mov cpu_a, #0 + mov cpu_x, #0 + mov cpu_y, #0 + mov cpu_sp, #0x100 @ RESET will bring this to 0x1FD + mov cpu_flags, #0 + + @ Start CPU emulation + b reset + +.globl exit_emulator +exit_emulator: + ldr r0, [r9, #s_prg_ptr] + swi e_free + bl restore_interrupts +unknown_hardware: + ldr sp, [r9, #s_saved_sp] + pop {r4-r11, pc} + +init_interrupts: + str r9, [pc, #state_ptr - (.+8)] + + msr cpsr_c, #0xD3 @ Interrupts off + + @ Disable everything except the timer interrupt (IRQ 19) + ldr r0, [r9, #s_hw_irq_masks] + ldr r2, [r0] + str r2, [r9, #s_saved_irq_mask] + str r2, [r0, #4] + mov r2, #1 << 19 + str r2, [r0] + + @ Set the IRQ vector + mov r1, #0xA4000000 + ldr r2, [r1, #0x38] + str r2, [r9, #s_saved_irq_handler] + ldr r2, [r9, #s_hw_irq_handler] + str r2, [r1, #0x38] + + msr cpsr_c, #0x13 @ Interrupts on + bx lr + +interrupt_handler_cx: + push {r0-r1, lr} + ldr r0, =0x900D0000 + mov r1, #1 + str r1, [r0, #0x0C] + b interrupt_handler_common +interrupt_handler_noncx: + push {r0-r1, lr} + mov r0, #0xDC000000 + ldr r1, =0x900A0000 + ldr lr, [r0, #0x24] + ldr lr, [r0, #0x28] + mov lr, #1 + str lr, [r1, #0x20] + mov lr, #1 << 19 + str lr, [r0, #0x04] + mov lr, #8 + str lr, [r0, #0x2C] +interrupt_handler_common: + @ Advance the frame timer by 3/300 of a second + ldr r1, [pc, #state_ptr - (.+8)] + ldrb lr, [r1, #s_frame_timer] + add lr, lr, #3 + strb lr, [r1, #s_frame_timer] + pop {r0-r1, lr} + subs pc, lr, #4 + .pool +state_ptr: + .word 0 + +restore_interrupts: + msr cpsr_c, #0xD3 @ Interrupts off + + ldr r0, [r9, #s_hw_irq_masks] + mvn r2, #0 + str r2, [r0, #4] + ldr r2, [r9, #s_saved_irq_mask] + str r2, [r0] + + mov r1, #0xA4000000 + ldr r2, [r9, #s_saved_irq_handler] + str r2, [r1, #0x38] + bx lr + +.globl newframe +newframe: + str lr, [sp, #-4]! + + ldrb r0, [r9, #s_message_timer] + subs r0, #1 + strplb r0, [r9, #s_message_timer] + + ldr r0, [r9, #s_frameskip_cur] + ldr r10, [r9, #s_frameskip] + subs r0, r0, #1 + addmi r0, r0, r10 + str r0, [r9, #s_frameskip_cur] + + mov r8, #0 +pause_loop: + +#define num_command_keys 12 + @ Scan keypad + ldr r3, [r9, #s_hw_keypad_invert] + mov r4, #0 + ldr r5, =0x900E0010 + mov r6, #num_command_keys - 1 + ldr r7, [r9, #s_keypad_command_map] +1: ldrb r0, [r7, r6] + and r1, r0, #0x60 + ldr r1, [r5, r1, lsr #3] + eor r1, r3, r1, ror r0 + and r1, #1 + orr r4, r1, lsl r6 + subs r6, #1 + bpl 1b + + ldr r5, [r9, #s_command_keys_pressed] + str r4, [r9, #s_command_keys_pressed] + bic r5, r4, r5 + + tst r5, #1 << 0; movne r10, #1 + tst r5, #1 << 1; movne r10, #2 + //tst r5, #1 << 2; movne r10, #3 + tst r5, #1 << 2; blne save_state + //tst r5, #1 << 3; movne r10, #4 + tst r5, #1 << 3; blne load_state + + tst r5, #1 << 4; movne r10, #5 + tst r5, #1 << 5; movne r10, #6 + tst r5, #1 << 6; blne toggle_border @ B (Border) + tst r5, #1 << 7; mvnne r8, r8 @ P (Pause) + tst r5, #1 << 8; bne exit_emulator @ Q (Quit) + tst r5, #1 << 9; blne invert_colors @ R (Reverse) + tst r5, #1 << 10; blne sram_save @ S (Save SRAM) + tst r4, #1 << 11; bne fast_forward @ * + + @ Keep looping until the frame timer reaches 5/300 (1/60) of a second + ldrb r0, [r9, #s_frame_timer] + subs r0, r0, #5 + movcc r0, #0 + mcrcc p15, 0, r0, c7, c0, 4 + bcc pause_loop + strb r0, [r9, #s_frame_timer] +#ifdef DEBUG + bl fps_counter +#endif + movs r8, r8 + bne pause_loop +fast_forward: + str r10, [r9, #s_frameskip] + + mov lr, pc + ldr pc, [r9, #s_keypad_read_input] + str r0, [r9, #s_input_status] + + ldr pc, [sp], #4 + +init_keypad: + str lr, [sp, #-4]! + + @ Temporarily enable access to the ADC (if it wasn't enabled already) + @ and get the last read value from channel 3 (keypad type) + @ Would use the system call, but it wasn't present yet in Ndless 1.7 + ldr r0, =0x900B0018 + ldr r1, [r0] + bic r2, r1, #0x10 + str r2, [r0] + mov r2, #0xC4000000 + ldr r2, [r2, #0x170] + str r1, [r0] + + sub r2, #0x40 + cmp r2, #0x59 - 0x40 + adrcc r2, touchpad_command_map + adrcs r2, clickpad_command_map + str r2, [r9, #s_keypad_command_map] + adrcc r2, touchpad_read_input + adrcs r2, clickpad_read_input + str r2, [r9, #s_keypad_read_input] + + ldrcs pc, [sp], #4 + + mov r0, #0xFF + mov r1, #0xFF + adr r2, touchpad_info_page + swi e_touchpad_write + mov r0, #0x04 + mov r1, #0x07 + add r2, r9, #s_touchpad_size + swi e_touchpad_read + mov r0, #0xFF + mov r1, #0xFF + adr r2, touchpad_main_page + swi e_touchpad_write + + ldr pc, [sp], #4 + +touchpad_info_page: + .byte 0x10 +touchpad_main_page: + .byte 0x04 + + @ 1 2 3 4 5 6 B P Q R S * +clickpad_command_map: + .byte 0x17,0x15,0x13,0x27,0x25,0x23,0x64,0x28,0x26,0x24,0x22,0x31 +touchpad_command_map: + .byte 0x17,0x64,0x13,0x27,0x56,0x23,0x45,0x22,0x21,0x20,0x16,0x48 + + .align 4 + +clickpad_read_input: + mvn r2, #0xFF + ldr r0, =0x900E0000 + ldrd r0, [r0, #0x18] + ldr r3, [r9, #s_hw_keypad_invert] + eor r0, r3 + eor r1, r3 + tst r0, #1 << 25; orrne r2, r2, #0x08 @ Caps (Start) + tst r1, #1 << 7; orrne r2, r2, #0x01 @ Esc (A) + tst r1, #1 << 9; orrne r2, r2, #0x02 @ Tab (B) + tst r1, #1 << 16; orrne r2, r2, #0x10 @ Up + tst r1, #1 << 18; orrne r2, r2, #0x80 @ Right + tst r1, #1 << 20; orrne r2, r2, #0x20 @ Down + tst r1, #1 << 22; orrne r2, r2, #0x40 @ Left + tst r1, #1 << 24; orrne r2, r2, #0x04 @ Clear (Select) + mov r0, r2 + bx lr + +touchpad_read_input: + push {r4, lr} + mvn r4, #0xFF + + ldr r0, =0x900E0000 + ldrd r0, [r0, #0x18] + ldr r2, [r9, #s_hw_keypad_invert] + eor r0, r2 + eor r1, r2 + tst r1, #1 << 7; orrne r4, r4, #0x01 @ Esc (A) + tst r1, #1 << 9; orrne r4, r4, #0x02 @ Tab (B) + tst r0, #1 << 25; orrne r4, r4, #0x04 @ Clear (Select) + tst r1, #1 << 24; orrne r4, r4, #0x08 @ Caps (Start) + + sub sp, #0x0C + mov r0, #0x02 + mov r1, #0x0A + add r2, sp, #0x02 + swi e_touchpad_read + movs r0, r0 + beq 1f + ldrb r0, [sp, #0x0A] + tst r0, #0x01 + beq 1f + + ldrb r0, [sp, #0x02] + ldrb r1, [sp, #0x03] + ldrb r2, [r9, #s_touchpad_size] + ldrb r3, [r9, #s_touchpad_size+1] + orr r0, r1, r0, lsl #8 + orr r2, r3, r2, lsl #8 + add r0, r0, lsl #1 + cmp r0, r2; orrcc r4, r4, #0x40 @ Left + cmp r0, r2, lsl #1; orrcs r4, r4, #0x80 @ Right + + ldrb r0, [sp, #0x04] + ldrb r1, [sp, #0x05] + ldrb r2, [r9, #s_touchpad_size+2] + ldrb r3, [r9, #s_touchpad_size+3] + orr r0, r1, r0, lsl #8 + orr r2, r3, r2, lsl #8 + add r0, r0, lsl #1 + cmp r0, r2; orrcc r4, r4, #0x20 @ Down + cmp r0, r2, lsl #1; orrcs r4, r4, #0x10 @ Up +1: + add sp, #0x0C + + mov r0, r4 + pop {r4, pc} + + .pool + +save_state: + str lr, saved_state_cpu_status+56 + adr lr, saved_state_cpu_status + stm lr, {r0-r13} + mrs r0, cpsr + str r0, saved_state_cpu_cpsr + + mov r1, #s_SIZE + sub r2, r9, #s_SIZE + mov r3, r9 + sstate_loop: + ldr r0, [r3] + str r0, [r2] + add r3, r3, #4 + add r2, r2, #4 + sub r1, r1, #4 + cmp r1, #0 + bne sstate_loop + + mov r0, #1 + str r0, save_state_exists + + ldr lr, saved_state_cpu_status+56 // subroutine return + +load_state: + ldr r0, save_state_exists + cmp r0, #0 + moveq pc,lr + + mov r1, #s_SIZE + sub r3, r9, #s_SIZE + mov r2, r9 + lstate_loop: + ldr r0, [r3] + str r0, [r2] + add r3, r3, #4 + add r2, r2, #4 + sub r1, r1, #4 + cmp r1, #0 + bne lstate_loop + + ldr r0, saved_state_cpu_cpsr + msr cpsr_all, r0 + adr lr, saved_state_cpu_status + ldm lr, {r0-r13,pc} // returns to previous instruction after save_state + + +save_state_exists: .word 0 + +saved_state_cpu_status: + .rept 15 + .word 0 + .endr +saved_state_cpu_cpsr: + .word 0 + + .pool + diff --git a/source/memory.S b/source/memory.S new file mode 100644 index 0000000..bf17454 --- /dev/null +++ b/source/memory.S @@ -0,0 +1,372 @@ +#include "nes.inc" + +.globl mem_read_split +.globl mem_read +.globl mem_write_split +.globl mem_write +.globl mem_jump_split +.globl mem_jump + +@ Input: +@ r2 = address low byte (split), full address (unsplit) +@ r3 = address high byte (split) +@ Output: +@ r0 = byte read +@ r2 = full address +@ r3 invalidated +@ All other registers preserved + +mem_read_split: + add r2, r2, r3, lsl #8 +mem_read: + mov r3, r2, lsr #13 + add pc, pc, r3, lsl #4 + nop +mem_read_ram: +@ 0000-1FFF: RAM + bic r3, r2, #0x11800 + ldrb r0, [r9, r3] + bx lr + nop +@ 2000-3FFF: PPU registers + and r3, r2, #7 + add r3, pc, r3, lsl #2 + add pc, r3, #ppu_read_table - (. + 4) + nop +@ 4000-5FFF: 2A03 registers + sub r3, r2, #0x4000 + cmp r3, #0x16 + beq mem_read_4016 + b mem_read_bad +@ 6000-7FFF: SRAM + add r3, r9, #s_sram - 0x6000 + ldrb r0, [r3, r2] + bx lr + nop +@ 8000-9FFF: ROM + ldr r3, [r9, #s_mem_map + 0x10] + ldrb r0, [r3, r2] + bx lr + nop +@ A000-BFFF: ROM + ldr r3, [r9, #s_mem_map + 0x14] + ldrb r0, [r3, r2] + bx lr + nop +@ C000-DFFF: ROM + ldr r3, [r9, #s_mem_map + 0x18] + ldrb r0, [r3, r2] + bx lr + nop +@ E000-FFFF: ROM + ldr r3, [r9, #s_mem_map + 0x1C] + ldrb r0, [r3, r2] + bx lr + nop +@ 10000-100FE: RAM (overflow) + b mem_read_ram +mem_read_bad: + @ TODO: print debug message + mov r0, #0 + bx lr +ppu_read_table: + b mem_read_bad + b mem_read_bad + b mem_read_2002 + b mem_read_bad + b mem_read_2004 + b mem_read_bad + b mem_read_bad +mem_read_2007: + ldr r2, [r9, #s_ppu_address] + bic r2, r2, #0xC000 + mov r3, r2, lsr #10 + add r3, r9, r3, lsl #2 + + @ Buffered VRAM read + ldr r3, [r3, #s_ppu_mem_map] + ldrb r0, [r9, #s_ppu_data] + ldrb r3, [r3, r2] + strb r3, [r9, #s_ppu_data] + + @ Palette is read directly + cmp r2, #0x3F00 + andcs r2, r2, #0x1F + addcs r3, r9, #s_ppu_palette + ldrcsb r0, [r3, r2] + + @ Advance ppu_address by 1 or 32 + ldr r3, [r9, #s_ppu_control] + ldr r2, [r9, #s_ppu_address] + tst r3, #0x04 + addeq r2, r2, #0x0001 + addne r2, r2, #0x0020 + bic r2, r2, #0x8000 + str r2, [r9, #s_ppu_address] + @ Fix our flagrant mangling of r2 + mov r2, #0x2000 + add r2, r2, #0x7 + bx lr +mem_read_2004: + ldrb r3, [r9, #s_ppu_oam_addr] + add r0, r9, #s_ppu_oam_ram + ldrb r0, [r0, r3] + and r3, r3, #0x03 + cmp r3, #0x02 + andeq r0, r0, #0xE3 + bx lr +mem_read_2002: + ldrb r0, [r9, #s_ppu_status] + mov r3, #0x00 + strb r3, [r9, #s_ppu_scroll+2] + bic r3, r0, #0x80 + strb r3, [r9, #s_ppu_status] + bx lr + +mem_read_4016: + ldr r3, [r9, #s_input_queue] + and r0, r3, #1 + mov r3, r3, asr #1 + str r3, [r9, #s_input_queue] + bx lr + +@ Input: +@ r0 = byte to write (high bits ignored) +@ r2 = address low byte (split), full address (unsplit) +@ r3 = address high byte (split) +@ Output: +@ r0, r2, r3 invalidated +@ All other registers preserved + +mem_write_split: + add r2, r2, r3, lsl #8 +mem_write: + @push {r0} + @mov r3, r2, lsr #13 + @add r3, r9, r3, lsl #2 + @ldr r0, [r3, #0xC00] + @add r0, r0, #1 + @str r0, [r3, #0xC00] + @pop {r0} + mov r3, r2, lsr #13 + add pc, pc, r3, lsl #4 + nop +@ 0000-1FFF: RAM +mem_write_ram: + bic r3, r2, #0x11800 + strb r0, [r9, r3] + bx lr + nop +@ 2000-3FFF: PPU registers + and r3, r2, #7 + add r3, pc, r3, lsl #2 + add pc, r3, #ppu_write_table - (. + 4) + nop +@ 4000-5FFF: APU registers + sub r3, r2, #0x4000 + b mem_write_4000_to_4017 + nop + nop +@ 6000-7FFF: SRAM + add r3, r9, #s_sram - 0x6000 + strb r0, [r3, r2] + bx lr + nop +@ 8000-FFFF: Mapper registers + .rept 4 + str lr, [sp, #-4]! + adr lr, return_from_mapper + ldr pc, [r9, #s_mapper] + nop + .endr +@ 10000-100FE: RAM (overflow) + b mem_write_ram +return_from_mapper: + ldr r0, [r9, #s_pc_base] + ldr lr, [sp], #4 + sub r4, r4, #1 + sub r2, r4, r0 + b mem_jump + +ppu_write_table: + b mem_write_2000 + b mem_write_2001 + bx lr @ No-op + b mem_write_2003 + b mem_write_2004 + b mem_write_2005 + b mem_write_2006 +@ Reg 2007: +mem_write_2007: + ldr r2, [r9, #s_ppu_address] + bic r2, r2, #0xC000 + + cmp r2, #0x3F00 + bcs mem_write_2007_palette + mov r3, r2, lsr #10 + add r3, r9, r3, lsl #2 + ldr r3, [r3, #s_ppu_mem_map] + strb r0, [r3, r2] @ TODO: don't allow write to CHR-ROM + + b mem_write_2007_common +mem_write_2007_palette: + and r0, r0, #0x3F + and r2, r2, #0x1F + + @ +00/+10, +04/+14, +08/+18, +0C/+1C are mirrored pairs + add r3, r9, #s_ppu_palette + tst r2, #0x03 + strb r0, [r3, r2] + eoreq r2, r2, #0x10 + streqb r0, [r3, r2] + + mov r0, #0 + strb r0, [r9, #s_palette_cache_valid] +mem_write_2007_common: + @ Advance ppu_address by 1 or 32 + ldr r3, [r9, #s_ppu_control] + ldr r2, [r9, #s_ppu_address] + tst r3, #0x04 + addeq r2, r2, #0x0001 + addne r2, r2, #0x0020 + bic r2, r2, #0x8000 + str r2, [r9, #s_ppu_address] + bx lr + +@ Reg 2006: Set PPU address, first hi byte, then lo +mem_write_2006: + ldr r3, [r9, #s_ppu_scroll] + tst r3, #0x10000 + eor r3, r3, #0x10000 + str r3, [r9, #s_ppu_scroll] + bne mem_write_2006_second +mem_write_2006_first: + and r0, r0, #0x3F + strb r0, [r9, #s_ppu_scroll + 1] + bx lr +mem_write_2006_second: + strb r0, [r9, #s_ppu_scroll] + strb r0, [r9, #s_ppu_address] + mov r0, r3, lsr #8 + strb r0, [r9, #s_ppu_address + 1] + bx lr +@ Reg 2005: Set scroll position, first x, then y +mem_write_2005: + ldr r3, [r9, #s_ppu_scroll] + and r0, r0, #0xFF + tst r3, #0x10000 + eor r3, r3, #0x10000 + bne mem_write_2005_second +mem_write_2005_first: + bic r3, r3, #0xE0000000 + bic r3, r3, #0x0000001F + orr r3, r3, r0, ror #3 + str r3, [r9, #s_ppu_scroll] + bx lr +mem_write_2005_second: + mov r0, r0, ror #3 + bic r3, r3, #0x03E0 + orr r3, r3, r0, lsl #5 + bic r3, r3, #0x7000 + orr r3, r3, r0, lsr #17 + str r3, [r9, #s_ppu_scroll] + bx lr +@ Reg 2004: Sprite data +mem_write_2004: + ldrb r3, [r9, #s_ppu_oam_addr] + add r2, r9, #s_ppu_oam_ram + strb r0, [r2, r3] + add r3, r3, #1 + strb r3, [r9, #s_ppu_oam_addr] + mov r0, #0 + strb r0, [r9, #s_spr_loc_table_valid] + bx lr +@ Reg 2003: Sprite address +mem_write_2003: + strb r0, [r9, #s_ppu_oam_addr] + bx lr +@ Reg 2001: PPU Mask +mem_write_2001: + strb r0, [r9, #s_ppu_mask] + bx lr +@ Reg 2000: PPU Control +mem_write_2000: + @ TODO: if sprites changed 8x8 <-> 8x16, invalidate table + ldr r3, [r9, #s_ppu_scroll] + strb r0, [r9, #s_ppu_control] + bic r3, #0x0C00 + and r0, r0, #0x03 + orr r3, r3, r0, lsl #10 + str r3, [r9, #s_ppu_scroll] + @ TODO: Generate NMI if 2002.b7 set and 2000.b7 changed from 0 to 1 + bx lr + +mem_write_4000_to_4017: + cmp r3, #0x14 + beq mem_write_4014 + cmp r3, #0x16 + bxne lr +mem_write_4016: + ldr r0, [r9, #s_input_status] + str r0, [r9, #s_input_queue] + bx lr + +mem_write_4014: + push {r1} + + @ Store 256 bytes to OAM RAM + mov r0, r0, lsl #8 + mov r2, r0, lsr #13 + add r2, r9, r2, lsl #2 + ldr r2, [r2, #s_mem_map] + add r0, r2, r0 + + ldrb r3, [r9, #s_ppu_oam_addr] + add r2, r9, #s_ppu_oam_ram + add r2, r2, r3 + rsb r3, r3, #0x100 +mem_write_4014_loop: + ldrb r1, [r0], #1 + subs r3, r3, #1 + strb r1, [r2], #1 + bne mem_write_4014_loop + + ldrb r3, [r9, #s_ppu_oam_addr] + add r2, r9, #s_ppu_oam_ram + cmp r3, #0 + beq mem_write_4014_done +mem_write_4014_loop2: + ldrb r1, [r0], #1 + subs r3, r3, #1 + strb r1, [r2], #1 + bne mem_write_4014_loop2 +mem_write_4014_done: + mov r0, #0 + strb r0, [r9, #s_spr_loc_table_valid] + pop {r1} + @ CPU is paused for 513 cycles while the transfer completes + sub cpu_cycles, cpu_cycles, #512 * CPU_CYCLE_LENGTH + sub cpu_cycles, cpu_cycles, #1 * CPU_CYCLE_LENGTH + bx lr + +mem_jump_split: + add r2, r2, r3, lsl #8 +mem_jump: + @ Most jumps will probably be to ROM. Optimize for higher addresses + cmp r2, #0x6000 + bcc mem_jump_low +mem_jump_ok: + mov r0, r2, lsr #13 + add r0, r9, r0, lsl #2 + ldr r0, [r0, #s_mem_map] + str r0, [r9, #s_pc_base] + + add cpu_pc, r0, r2 + ldrb r1, [cpu_pc], #1 + bx lr +mem_jump_low: + cmp r2, #0x2000 + biccc r2, r2, #0x1800 @ RAM mirroring + bcc mem_jump_ok + @ Jump to 2000-5FFF range - should never happen. + ERROR 0x0001 diff --git a/source/menu.S b/source/menu.S new file mode 100644 index 0000000..aad534e --- /dev/null +++ b/source/menu.S @@ -0,0 +1,264 @@ +#include "nes.inc" + +nes_rom_file_mask: + .string "*.nes.tns" +title: + .string " NESpire v0.30 -- Shift=start, Esc=quit" + .align 4 + +.globl rom_menu +rom_menu: + push {r4-r11, lr} + + ldr r0, [r9, #s_path_filename] + adr r1, nes_rom_file_mask + swi e_strcpy + +#define liststart r4 +#define listend r5 +#define listmax r8 + @ Using save ram as a buffer to hold list of filenames + add liststart, r9, #s_sram + mov listend, liststart + add listmax, liststart, #0x2000 + + sub sp, #308 + mov r0, sp + add r1, r9, #s_path + swi e_NU_Get_First + movs r0, r0 + bne no_files +next_file: + @ Append filename to list + mov r0, listend + add r1, sp, #13 +1: teq r0, listmax + beq filename_buf_full + ldrb r2, [r1], #1 + movs r2, r2 + strb r2, [r0], #1 + bne 1b + mov listend, r0 + + mov r0, sp + swi e_NU_Get_Next + movs r0, r0 + beq next_file +filename_buf_full: + mov r0, sp + swi e_NU_Done +no_files: + add sp, #308 + +#define cursor r6 +#define pagetop r7 +#define pagebottom r8 +#define keypressed r10 +#define keyrepeat r11 + mov cursor, liststart + mov pagetop, liststart + mov keypressed, #0 + mov keyrepeat, #0 + +menu_redraw: + adr r0, title + mov r1, #0 + mov r2, #40 + mov r3, #-1 + bl display_string + + mov pagebottom, pagetop + str r10, [sp, #-4]! + mov r10, #640 +menu_next_row: + teq pagebottom, cursor + moveq r0, #0x10 + movne r0, #' ' + mov r1, r10 + bl display_char + cmp pagebottom, listend + bcs menu_draw_done + mov r0, pagebottom + add r1, r10, #1 + add r2, r10, #40 + mov r3, #0 + bl display_string +1: ldrb r0, [pagebottom], #1 + movs r0, r0 + bne 1b + add r10, #640 + teq r10, #640 * 15 + bne menu_next_row +menu_draw_done: + ldr r10, [sp], #4 + +menu_waitkey: + mov r0, #0 + mcr p15, 0, r0, c7, c0, 4 + mov lr, pc + ldr pc, [r9, #s_keypad_read_input] + + mov r1, keypressed + mov keypressed, r0 + + teq r0, r1 + movne keyrepeat, #25 + bne 1f + movs keyrepeat, keyrepeat + subnes keyrepeat, #1 + bne menu_waitkey + mov keyrepeat, #5 +1: + + tst r0, #0x01 + bne exit_emulator + tst r0, #0x10 + bne menu_up + tst r0, #0x20 + bne menu_down + tst r0, #0x08 + bne menu_start + b menu_waitkey + +menu_up: + teq cursor, liststart + beq menu_waitkey +1: sub cursor, #1 + teq cursor, liststart + ldrneb r0, [cursor, #-1] + movnes r0, r0 + bne 1b + cmp cursor, pagetop + movcc pagetop, cursor + b menu_redraw + +menu_down: + mov r0, cursor +1: ldrb r1, [r0], #1 + movs r1, r1 + bne 1b + cmp r0, listend + bcs menu_waitkey + mov cursor, r0 + cmp cursor, pagebottom + bcc menu_redraw +1: ldrb r0, [pagetop], #1 + movs r0, r0 + bne 1b + b menu_redraw + +menu_start: + @ Append filename to directory + ldr r0, [r9, #s_path_filename] + mov r1, cursor +1: ldrb r2, [r1], #1 + movs r2, r2 + strb r2, [r0], #1 + bne 1b + sub r0, #8 + str r0, [r9, #s_path_extension] + + @ Try to load ROM (full path) + add r0, r9, #s_path + bl load_rom + movs r0, r0 + beq load_success + + @ Display error message + mov r1, #7 + mov r2, #40 + mov r3, #-1 + bl display_string + adr r0, error_hdr + mov r1, #0 + mov r2, #7 + mov r3, #-1 + bl display_string + b menu_waitkey +error_hdr: + .string "ERROR:" + .align 4 + +load_success: + @ Clear save ram (since we used it to hold filenames) + add r0, r9, #s_sram + mov r1, #0 + mov r2, #0x2000 + swi e_memset + + @ If game has battery-backed save ram, try to load from save file + bl sram_load + + pop {r4-r11, pc} + +@ r0 = character +@ r1 = position (row * 640 + column) +display_char: + mov r2, #0 +@ r2 = color (0 = normal, -1 = reverse) +display_char_withcolor: + push {r4, lr} + mov r12, #0xC0000000 + ldr r12, [r12, #0x10] + adr r4, font + add r4, r0, lsl #4 + mov r3, #0x10 + ldr r0, [r9, #s_hw_color] + movs r0, r0 + bne display_char_16bpp +display_char_4bpp: + add r12, r1, lsl #2 +2: ldrb r1, [r4], #1 + mvn r0, r2 +1: ror r0, #24 + lsrs r1, #1 + eorcs r0, #0x0F + lsrs r1, #1 + eorcs r0, #0xF0 + adds r3, #0x40000000 + bcc 1b + str r0, [r12], #160 + subs r3, #1 + bne 2b + pop {r4, pc} +display_char_16bpp: + add r12, r1, lsl #4 +2: ldrb r1, [r4], #1 + lsl r1, #24 +1: mvn r0, #0 + lsls r1, #1 + andcs r0, r0, lsl #16 + lsls r1, #1 + andcs r0, r0, lsr #16 + eor r0, r2 + str r0, [r12], #4 + adds r3, #0x40000000 + bcc 1b + add r12, #640 - 16 + subs r3, #1 + bne 2b + pop {r4, pc} +@ r0 = string +@ r1 = start position +@ r2 = end position +@ r3 = color (0 = normal, -1 = reverse) +.globl display_string +display_string: + push {r4-r7, lr} + mov r4, r0 + mov r5, r1 + mov r6, r2 + mov r7, r3 + +1: ldrb r0, [r4] + movs r0, r0 + addne r4, #1 + mov r1, r5 + add r5, #1 + mov r2, r7 + bl display_char_withcolor + cmp r5, r6 + bcc 1b + pop {r4-r7, pc} +font: + .incbin "font.bin" diff --git a/source/nes.inc b/source/nes.inc new file mode 100644 index 0000000..66fee92 --- /dev/null +++ b/source/nes.inc @@ -0,0 +1,132 @@ +//#define DEBUG + +//#define TRACE bl trace +#define TRACE + +#ifdef DEBUG +.macro ERROR code + bkpt #\code +.endm +.macro CERROR cond, code + b\cond .+8 + b .+8 + bkpt #\code +.endm +#else +.macro ERROR code + b exit_emulator +.endm +.macro CERROR cond, code + b\cond exit_emulator +.endm +#endif + +// System calls +#define e_fopen 0 +#define e_fread 1 +#define e_fwrite 2 +#define e_fclose 3 +#define e_malloc 5 +#define e_free 6 +#define e_memset 7 +#define e_NU_Get_First 23 +#define e_NU_Get_Next 24 +#define e_NU_Done 25 +#define e_strcpy 27 +#define e_touchpad_read 75 +#define e_touchpad_write 76 + +// Global data structure: r9 points to this at all times +// Note some requirements: +// * to do "ldr reg, [r9, #s_xxx]", offset must be below 0x1000 +// * to do "add reg, r9, #s_xxx", offset must be representable as a shifted byte +// When changing an existing offset, don't forget to "del *.o"! + +#define s_wram 0x0000 // 0800 bytes + +// CPU data +#define s_mem_map 0x0800 // 0024 bytes (9 entries, 4 bytes each) +#define s_flags_di 0x0824 // 0004 bytes +#define s_pc_base 0x0828 // 0004 bytes +#define s_interrupts 0x082C +#define s_irq_from_apu 0x082C +#define s_irq_from_mapper 0x082D +#define s_nmi_reset 0x082F // set to FF for reset, 80 for nmi + +#define s_input_status 0x0840 +#define s_input_queue 0x0844 + +// PPU ("Picture Processing Unit") data +#define s_ppu_mem_map 0x0880 // 0040 bytes +#define s_ppu_palette 0x08C0 // 0020 bytes +#define s_ppu_flags 0x08E0 +#define s_ppu_control s_ppu_flags+0 // $2000 +#define s_ppu_mask s_ppu_flags+1 // $2001 +#define s_ppu_status s_ppu_flags+2 // $2002 +#define s_ppu_oam_addr 0x08E4 // $2003 +#define s_ppu_scroll 0x08E8 // $2005 (x/y toggle in bit 16, x fine in bits 29-31) +#define s_ppu_address 0x08EC // $2006 +#define s_ppu_data 0x08F0 // $2007 buffer +#define s_ppu_scanline 0x08F4 +#define s_ppu_oam_ram 0x0900 // 0100 bytes - must be 256-byte aligned + +// Emulation data (no relation to anything in an actual NES) +#define s_frame_count 0x0A00 +#define s_frame_count_rtc 0x0A04 +#define s_saved_sp 0x0A08 +#define s_frame_timer 0x0A0C +#define s_touchpad_size 0x0A10 +#define s_keypad_command_map 0x0A14 +#define s_keypad_read_input 0x0A18 +#define s_command_keys_pressed 0x0A1C +#define s_saved_irq_mask 0x0A20 +#define s_saved_irq_handler 0x0A24 +#define s_frameskip 0x0A28 +#define s_frameskip_cur 0x0A2C +#define s_spr_loc_table_valid 0x0A30 +#define s_hw_irq_masks 0x0A34 +#define s_hw_irq_handler 0x0A38 +#define s_hw_keypad_invert 0x0A3C +#define s_palette_cache 0x0A40 // 0080 bytes +#define s_palette_cache_valid 0x0AC0 +#define s_hw_color 0x0AC4 +#define s_border_color 0x0AC8 +#define s_message_timer 0x0AD0 + +// ROM data +#define s_rom_header 0x0AE0 +#define s_mapper 0x0AEC +#define s_prg_size 0x0AF0 +#define s_prg_ptr 0x0AF4 +#define s_chr_size 0x0AF8 +#define s_chr_ptr 0x0AFC + +#define s_mapper_state 0x0B00 // 0010 bytes + +#define s_path_filename 0x0B80 +#define s_path_extension 0x0B84 + +// The big stuff +#define s_spr_loc_table 0x0B90 // 0870 bytes (9 * 240) +#define s_name_table_ram 0x1400 // 1000 bytes - must be 128-byte aligned +#define s_sram 0x2400 // 2000 bytes +#define s_path 0x4400 // 0200 bytes + +#define s_SIZE 0x4600 +#define s_ALIGN 0x0100 + +// CPU register usage: +// r0 = general purpose temporary +// r1 = next instruction byte +// r2 = address low byte or full address +// r3 = address high byte +#define cpu_pc r4 +#define cpu_cycles r5 +#define cpu_a r6 +#define cpu_x r7 +#define cpu_y r8 +#define cpu_sp r10 +#define cpu_flags r11 +#define cpu_itable r12 + +#define CPU_CYCLE_LENGTH 3 diff --git a/source/nes_emu.prg.tns b/source/nes_emu.prg.tns new file mode 100644 index 0000000000000000000000000000000000000000..0ca2093efc80cf3732263b59d508eb5e3a9388b6 GIT binary patch literal 35892 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 literal 0 HcmV?d00001 diff --git a/source/nes_emu.tns b/source/nes_emu.tns new file mode 100644 index 0000000000000000000000000000000000000000..babc03b8f54e65e6faef4e0ff1a2889faa68a26b GIT binary patch literal 35276 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