first commit

This commit is contained in:
franchioping 2024-05-18 18:43:54 +01:00
commit 7675355d17
15 changed files with 3565 additions and 0 deletions

BIN
nes_emu.prg.tns Normal file

Binary file not shown.

BIN
nes_emu.tns Normal file

Binary file not shown.

45
readme.txt Normal file
View File

@ -0,0 +1,45 @@
NESpire v0.30 - Franchioping's mod (just tangrs' save states on 0.30v)
[ About ]
NESpire is an emulator that allows you to play Nintendo Entertainment
System (NES) games on the TI-Nspire family of calculators.
[ Using NESpire ]
First, you must have Ndless installed to use NESpire. Ndless can be found
at http://www.ticalc.org/archives/files/fileinfo/426/42626.html.
Any NES ROM you wish to play must be named with a .nes.tns extension, and
sent to the calculator in the same folder NESpire was sent to. Open NESpire
from the documents menu, and you will be presented with the ROM selection
menu. Choose a ROM using the arrow keys and press Shift to select one,
or press Esc to exit.
[ Controls ]
Game controls:
Tab = B
Esc = A
Clear = Select
Shift = Start
Emulator controls:
3 - Save State (doesnt write to file)
4 - Load State (doesnt write to file)
1,2,5,6 = Set frameskip
* = Fast-forward
B = Reverse border color
P = Pause (Many NES games have their own pause feature, but it will
still consume just as much power as when playing the game.
Using P to pause is preferred, so as not to waste battery life.)
Q = Quit
R = Reverse colors
S = Write save file (Only applicable to games which actually have
battery-backed save memory; e.g. Zelda, Final Fantasy; Doesn't
write the State)
[ Known Issues ]
* Marble Madness: Display of text window at beginning of level is glitchy.
* Super Mario Bros. 3: Ground in title screen shakes up a down by 1 pixel.

22
source/Makefile Normal file
View File

@ -0,0 +1,22 @@
PREFIX=nspire-
all: nes_emu.tns clean_p
@echo DONE
nes_emu.tns : nespire.elf
genzehn --input $< --output $@ --name "nes_emu"
make-prg $@ ./nes_emu.prg.tns
nespire.elf : main.o cpu.o debug.o memory.o ppu.o rom.o menu.o
$(PREFIX)ld main.o cpu.o debug.o memory.o ppu.o rom.o menu.o -o $@
%.o : %.S
$(PREFIX)gcc -Wall -W -marm -Os -c $< -o $@
clean_p:
@rm -f *.o nespire.elf
clean: clean_p
@rm -f nes_emu.tns nes_emu.prg.tns

958
source/cpu.S Normal file
View File

@ -0,0 +1,958 @@
#include "nes.inc"
@ NES 2A03 (like 6502, just without decimal mode) CPU emulation core
@ Some caveats:
@ - Code is assumed to be contiguous in memory (apparently "The Magic of
@ Scheherazade" violates this by having code cross from bank 3 to bank 7,
@ but it's not worth it to kill performance just for one obscure game)
@ - Extra memory accesses (in read-modify-write instructions,
@ or indexing carry) are not implemented
@ - Most of the "undocumented" instructions are not implemented
.equ NES_FLAG_C, 0x01
.equ NES_FLAG_Z, 0x02
.equ NES_FLAG_I, 0x04
.equ NES_FLAG_D, 0x08
.equ NES_FLAG_B, 0x10
.equ NES_FLAG_V, 0x40
.equ NES_FLAG_N, 0x80
.equ ARM_FLAG_V, 0x10000000
.equ ARM_FLAG_C, 0x20000000
.equ ARM_FLAG_Z, 0x40000000
.equ ARM_FLAG_N, 0x80000000
@ Advance to next instruction. Its first byte must be pre-loaded into r1.
.macro NEXT cycles
subs cpu_cycles, cpu_cycles, #\cycles * CPU_CYCLE_LENGTH
TRACE
addpl pc, cpu_itable, r1, lsl #2
b cpu_leave
.endm
@ To avoid having to deal with the whole Cartesian product of operations and
@ addressing modes, we make the assembler generate them with macros. Each R_*,
@ W_*, or RMW_* macro is a template for instructions that do read, write, or
@ read-modify-write operations, respectively, in a particular addressing mode.
.macro R_Imm op
ldrb r0, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
\op
NEXT 2
.endm
.macro RMW_Reg reg, op
ldrb r1, [cpu_pc], #1
\op \reg
NEXT 2
.endm
.macro RMW_A op; RMW_Reg cpu_a, \op; .endm
.macro RMW_X op; RMW_Reg cpu_x, \op; .endm
.macro RMW_Y op; RMW_Reg cpu_y, \op; .endm
.macro R_Zp op
ldrb r2, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
ldrb r0, [r9, r2]
\op
NEXT 3
.endm
.macro W_Zp op
ldrb r2, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
\op
strb r0, [r9, r2]
NEXT 3
.endm
.macro RMW_Zp op
ldrb r2, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
ldrb r0, [r9, r2]
\op
strb r0, [r9, r2]
NEXT 5
.endm
.macro R_Zp_XY index, op
ldrb r2, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
add r2, \index, r2, lsl #24
ldrb r0, [r9, r2, lsr #24]
\op
NEXT 4
.endm
.macro W_Zp_XY index, op
ldrb r2, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
\op
add r2, \index, r2, lsl #24
strb r0, [r9, r2, lsr #24]
NEXT 4
.endm
.macro RMW_Zp_XY index, op
ldrb r2, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
add r2, \index, r2, lsl #24
ldrb r0, [r9, r2, lsr #24]
\op
strb r0, [r9, r2, lsr #24]
NEXT 6
.endm
.macro R_Zp_X op; R_Zp_XY cpu_x, \op; .endm
.macro R_Zp_Y op; R_Zp_XY cpu_y, \op; .endm
.macro W_Zp_X op; W_Zp_XY cpu_x, \op; .endm
.macro W_Zp_Y op; W_Zp_XY cpu_y, \op; .endm
.macro RMW_Zp_X op; RMW_Zp_XY cpu_x, \op; .endm
.macro R_Abs op
ldrb r2, [cpu_pc], #1
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
bl mem_read_split
\op
NEXT 4
.endm
.macro W_Abs op
ldrb r2, [cpu_pc], #1
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
\op
bl mem_write_split
NEXT 4
.endm
.macro RMW_Abs op
ldrb r2, [cpu_pc], #1
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
bl mem_read_split
\op
bl mem_write
NEXT 6
.endm
.macro R_Abs_XY index, op
ldrb r2, [cpu_pc], #1
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
add r2, r2, \index, lsr #24
tst r2, #0x100
subne cpu_cycles, cpu_cycles, #CPU_CYCLE_LENGTH
bl mem_read_split
\op
NEXT 4
.endm
.macro W_Abs_XY index, op
ldrb r2, [cpu_pc], #1
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
add r2, r2, \index, lsr #24
\op
bl mem_write_split
NEXT 5
.endm
.macro RMW_Abs_XY index, op
ldrb r2, [cpu_pc], #1
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
add r2, r2, \index, lsr #24
bl mem_read_split
\op
bl mem_write
NEXT 7
.endm
.macro R_Abs_X op; R_Abs_XY cpu_x, \op; .endm
.macro R_Abs_Y op; R_Abs_XY cpu_y, \op; .endm
.macro W_Abs_X op; W_Abs_XY cpu_x, \op; .endm
.macro W_Abs_Y op; W_Abs_XY cpu_y, \op; .endm
.macro RMW_Abs_X op; RMW_Abs_XY cpu_x, \op; .endm
.macro RMW_Abs_Y op; RMW_Abs_XY cpu_y, \op; .endm
.macro R_Ind_X op
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
add r3, cpu_x, r3, lsl #24
ldrb r2, [r9, r3, lsr #24]
add r3, r3, #0x01000000
ldrb r3, [r9, r3, lsr #24]
bl mem_read_split
\op
NEXT 6
.endm
.macro W_Ind_X op
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
\op
add r3, cpu_x, r3, lsl #24
ldrb r2, [r9, r3, lsr #24]
add r3, r3, #0x01000000
ldrb r3, [r9, r3, lsr #24]
bl mem_write_split
NEXT 6
.endm
.macro RMW_Ind_X op
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
add r3, cpu_x, r3, lsl #24
ldrb r2, [r9, r3, lsr #24]
add r3, r3, #0x01000000
ldrb r3, [r9, r3, lsr #24]
bl mem_read_split
\op
bl mem_write
NEXT 8
.endm
.macro R_Ind_Y op
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
ldrb r2, [r9, r3]
add r3, r3, #1
and r3, r3, #0xFF
ldrb r3, [r9, r3]
add r2, r2, cpu_y, lsr #24
tst r2, #0x100
subne cpu_cycles, cpu_cycles, #CPU_CYCLE_LENGTH
bl mem_read_split
\op
NEXT 5
.endm
.macro W_Ind_Y op
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
\op
ldrb r2, [r9, r3]
add r3, r3, #1
and r3, r3, #0xFF
ldrb r3, [r9, r3]
add r2, r2, cpu_y, lsr #24
bl mem_write_split
NEXT 6
.endm
.macro RMW_Ind_Y op
ldrb r3, [cpu_pc], #1
ldrb r1, [cpu_pc], #1
ldrb r2, [r9, r3]
add r3, r3, #1
and r3, r3, #0xFF
ldrb r3, [r9, r3]
add r2, r2, cpu_y, lsr #24
bl mem_read_split
\op
bl mem_write
NEXT 8
.endm
@ Read operations
.macro OP_LDA
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z
movs cpu_a, r0, lsl #24
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
.endm
.macro OP_LDX
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z
movs cpu_x, r0, lsl #24
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
.endm
.macro OP_LDY
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z
movs cpu_y, r0, lsl #24
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
.endm
.macro OP_ORA
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z
orrs cpu_a, cpu_a, r0, lsl #24
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
.endm
.macro OP_AND
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z
ands cpu_a, cpu_a, r0, lsl #24
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
.endm
.macro OP_EOR
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z
eors cpu_a, cpu_a, r0, lsl #24
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
.endm
.macro OP_CMP
@ ARM sets V, 6502 does not, so must deal with flags manually
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z | ARM_FLAG_C
cmp cpu_a, r0, lsl #24
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
orrcs cpu_flags, cpu_flags, #ARM_FLAG_C
.endm
.macro OP_CPX
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z | ARM_FLAG_C
cmp cpu_x, r0, lsl #24
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
orrcs cpu_flags, cpu_flags, #ARM_FLAG_C
.endm
.macro OP_CPY
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z | ARM_FLAG_C
cmp cpu_y, r0, lsl #24
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
orrcs cpu_flags, cpu_flags, #ARM_FLAG_C
.endm
.macro OP_ADC
msr cpsr_f, cpu_flags
subcs r0, r0, #0x100
adcs cpu_a, cpu_a, r0, ror #8
mrs cpu_flags, cpsr
.endm
.macro OP_SBC
msr cpsr_f, cpu_flags
subcc r0, r0, #0x100
sbcs cpu_a, cpu_a, r0, ror #8
mrs cpu_flags, cpsr
.endm
.macro OP_BIT
bic cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z | ARM_FLAG_V
tst cpu_a, r0, lsl #24
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
movs r0, r0, lsl #25
orrcs cpu_flags, cpu_flags, #ARM_FLAG_N
orrmi cpu_flags, cpu_flags, #ARM_FLAG_V
.endm
@ Write operations
.macro OP_STA
mov r0, cpu_a, lsr #24
.endm
.macro OP_STX
mov r0, cpu_x, lsr #24
.endm
.macro OP_STY
mov r0, cpu_y, lsr #24
.endm
@ Read-modify-write operations
.macro OP_ASL
msr cpsr_f, cpu_flags
movs r0, r0, lsl #25
mrs cpu_flags, cpsr
mov r0, r0, lsr #24
.endm
.macro OP_ASLR reg
msr cpsr_f, cpu_flags
movs \reg, \reg, lsl #1
mrs cpu_flags, cpsr
.endm
.macro OP_ROL
mov r0, r0, lsl #24
tst cpu_flags, #ARM_FLAG_C
addne r0, r0, #0x800000
msr cpsr_f, cpu_flags
movs r0, r0, lsl #1
mrs cpu_flags, cpsr
mov r0, r0, lsr #24
.endm
.macro OP_ROLR reg
tst cpu_flags, #ARM_FLAG_C
addne \reg, \reg, #0x800000
msr cpsr_f, cpu_flags
movs \reg, \reg, lsl #1
mrs cpu_flags, cpsr
.endm
.macro OP_LSR
msr cpsr_f, cpu_flags
movs r0, r0, lsr #1
mrs cpu_flags, cpsr
.endm
.macro OP_LSRR reg
msr cpsr_f, cpu_flags
movs \reg, \reg, lsr #25
mov \reg, \reg, lsl #24
mrs cpu_flags, cpsr
.endm
.macro OP_ROR
msr cpsr_f, cpu_flags
movs r0, r0, rrx
mrs cpu_flags, cpsr
orr r0, r0, lsr #24
.endm
.macro OP_RORR reg
mov \reg, \reg, lsr #24
msr cpsr_f, cpu_flags
movs \reg, \reg, rrx
mrs cpu_flags, cpsr
orr \reg, \reg, lsl #24
and \reg, \reg, #0xFF000000
.endm
.macro OP_DEC
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z
sub r0, r0, #1
movs r3, r0, lsl #24
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
.endm
.macro OP_DECR reg
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z
subs \reg, \reg, #0x01000000
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
.endm
.macro OP_INC
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z
add r0, r0, #1
movs r3, r0, lsl #24
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
.endm
.macro OP_INCR reg
bic cpu_flags, cpu_flags, #ARM_FLAG_N | ARM_FLAG_Z
adds \reg, \reg, #0x01000000
orrmi cpu_flags, cpu_flags, #ARM_FLAG_N
orreq cpu_flags, cpu_flags, #ARM_FLAG_Z
.endm
#define R(op, arg) R_##arg OP_##op
#define W(op, arg) W_##arg OP_##op
#define RMW(op, arg) RMW_##arg OP_##op
@ Other stuff...
.macro SPUSH reg
strb \reg, [r9, cpu_sp]
sub cpu_sp, cpu_sp, #1
orr cpu_sp, cpu_sp, #0x100
.endm
.macro SPULL reg
add cpu_sp, cpu_sp, #1
bic cpu_sp, cpu_sp, #0x200
orr cpu_sp, cpu_sp, #0x100
ldrb \reg, [r9, cpu_sp]
.endm
.macro INTERRUPT num, flags
.if num == 0xFFFC
subs cpu_sp, cpu_sp, #3
orr cpu_sp, cpu_sp, #0x100
.else
bl push_pc
mov r2, #\flags
bl push_flags
.endif
ldr r0, [r9, #s_flags_di]
ldr r2, [r9, #s_mem_map + 28]
orr r0, r0, #NES_FLAG_I
str r0, [r9, #s_flags_di]
add r2, r2, #0x10000
ldrh r2, [r2, #\num - 0x10000]
bl mem_jump
NEXT 7
.endm
.macro BRANCH flag, value
tst cpu_flags, #ARM_FLAG_\flag
ldrsb r2, [cpu_pc], #1
.if \value
bne take_branch
.else
beq take_branch
.endif
ldrb r1, [cpu_pc], #1
NEXT 2
.endm
take_branch:
@ Get PC
ldr r0, [r9, #s_pc_base]
sub cpu_pc, cpu_pc, r0
@ Add the branch offset already stored in r2
add r2, cpu_pc, r2
bic r2, r2, #0xFF000000
bic r2, r2, #0x00FF0000
@ Extra cycle if page changed
eor r0, cpu_pc, r2
tst r0, #0xFF00
subne cpu_cycles, cpu_cycles, #CPU_CYCLE_LENGTH
@ Set new PC
bl mem_jump
NEXT 3
.macro CHANGE_FLAG op, flag
.if NES_FLAG_\flag & 0xC3
ldrb r1, [cpu_pc], #1
\op cpu_flags, cpu_flags, #ARM_FLAG_\flag
.else
ldr r0, [r9, #s_flags_di]
ldrb r1, [cpu_pc], #1
\op r0, r0, #NES_FLAG_\flag
str r0, [r9, #s_flags_di]
.endif
NEXT 2
.endm
push_flags:
ldr r0, [r9, #s_flags_di]
msr cpsr_f, cpu_flags
orrmi r0, r0, #NES_FLAG_N
orrvs r0, r0, #NES_FLAG_V
orreq r0, r0, #NES_FLAG_Z
orrcs r0, r0, #NES_FLAG_C
orr r0, r0, r2
SPUSH r0
bx lr
pull_flags:
SPULL r0
and r2, r0, #0x0C
str r2, [r9, #s_flags_di]
bic cpu_flags, cpu_flags, #ARM_FLAG_N|ARM_FLAG_Z|ARM_FLAG_C|ARM_FLAG_V
and r2, r0, #NES_FLAG_N
orr cpu_flags, cpu_flags, r2, lsl #24
and r2, r0, #NES_FLAG_V
orr cpu_flags, cpu_flags, r2, lsl #22
and r2, r0, #NES_FLAG_Z | NES_FLAG_C
orr cpu_flags, cpu_flags, r2, lsl #29
bx lr
push_pc:
ldr r0, [r9, #s_pc_base]
sub r0, cpu_pc, r0
mov r1, r0, lsr #8
SPUSH r1
SPUSH r0
bx lr
.global reset
reset:
mov r0, #0
str r0, [r9, #s_ppu_scanline]
mov cpu_cycles, #-1
strb cpu_cycles, [r9, #s_nmi_reset] @ 0xFF = reset
main_loop:
add cpu_cycles, cpu_cycles, #0x100
add cpu_cycles, cpu_cycles, #0x055
adr cpu_itable, insn_table
ldr r2, [r9, #s_interrupts]
ldr r0, [r9, #s_flags_di]
movs r2, r2
bmi nmi_or_reset
beq cpu_enter
tst r0, #NES_FLAG_I
beq irq
cpu_enter:
ldrb r1, [cpu_pc], #1
TRACE
add pc, cpu_itable, r1, lsl #2
cpu_leave:
sub cpu_pc, cpu_pc, #1
bl ppu_next_scanline
@ TODO: this should be for MMC3 only
ldr r0, [r9, #s_ppu_control]
tst r0, #0x1800
beq 1f
ldr r0, [r9, #s_ppu_scanline]
cmp r0, #241
blcc mmc3_scanline
1:
b main_loop
nmi_or_reset:
tst r2, #0x7F000000
bic r2, r2, #0xFF000000
str r2, [r9, #s_interrupts]
bne intr_reset
INTERRUPT 0xFFFA, 0x20
intr_reset:
INTERRUPT 0xFFFC, 0x00
irq:
INTERRUPT 0xFFFE, 0x20
insn_table:
b insn_00;b insn_01;b insn_02;b insn_03;b insn_04;b insn_05;b insn_06;b insn_07
b insn_08;b insn_09;b insn_0a;b insn_0b;b insn_0c;b insn_0d;b insn_0e;b insn_0f
b insn_10;b insn_11;b insn_12;b insn_13;b insn_14;b insn_15;b insn_16;b insn_17
b insn_18;b insn_19;b insn_1a;b insn_1b;b insn_1c;b insn_1d;b insn_1e;b insn_1f
b insn_20;b insn_21;b insn_22;b insn_23;b insn_24;b insn_25;b insn_26;b insn_27
b insn_28;b insn_29;b insn_2a;b insn_2b;b insn_2c;b insn_2d;b insn_2e;b insn_2f
b insn_30;b insn_31;b insn_32;b insn_33;b insn_34;b insn_35;b insn_36;b insn_37
b insn_38;b insn_39;b insn_3a;b insn_3b;b insn_3c;b insn_3d;b insn_3e;b insn_3f
b insn_40;b insn_41;b insn_42;b insn_43;b insn_44;b insn_45;b insn_46;b insn_47
b insn_48;b insn_49;b insn_4a;b insn_4b;b insn_4c;b insn_4d;b insn_4e;b insn_4f
b insn_50;b insn_51;b insn_52;b insn_53;b insn_54;b insn_55;b insn_56;b insn_57
b insn_58;b insn_59;b insn_5a;b insn_5b;b insn_5c;b insn_5d;b insn_5e;b insn_5f
b insn_60;b insn_61;b insn_62;b insn_63;b insn_64;b insn_65;b insn_66;b insn_67
b insn_68;b insn_69;b insn_6a;b insn_6b;b insn_6c;b insn_6d;b insn_6e;b insn_6f
b insn_70;b insn_71;b insn_72;b insn_73;b insn_74;b insn_75;b insn_76;b insn_77
b insn_78;b insn_79;b insn_7a;b insn_7b;b insn_7c;b insn_7d;b insn_7e;b insn_7f
b insn_80;b insn_81;b insn_82;b insn_83;b insn_84;b insn_85;b insn_86;b insn_87
b insn_88;b insn_89;b insn_8a;b insn_8b;b insn_8c;b insn_8d;b insn_8e;b insn_8f
b insn_90;b insn_91;b insn_92;b insn_93;b insn_94;b insn_95;b insn_96;b insn_97
b insn_98;b insn_99;b insn_9a;b insn_9b;b insn_9c;b insn_9d;b insn_9e;b insn_9f
b insn_a0;b insn_a1;b insn_a2;b insn_a3;b insn_a4;b insn_a5;b insn_a6;b insn_a7
b insn_a8;b insn_a9;b insn_aa;b insn_ab;b insn_ac;b insn_ad;b insn_ae;b insn_af
b insn_b0;b insn_b1;b insn_b2;b insn_b3;b insn_b4;b insn_b5;b insn_b6;b insn_b7
b insn_b8;b insn_b9;b insn_ba;b insn_bb;b insn_bc;b insn_bd;b insn_be;b insn_bf
b insn_c0;b insn_c1;b insn_c2;b insn_c3;b insn_c4;b insn_c5;b insn_c6;b insn_c7
b insn_c8;b insn_c9;b insn_ca;b insn_cb;b insn_cc;b insn_cd;b insn_ce;b insn_cf
b insn_d0;b insn_d1;b insn_d2;b insn_d3;b insn_d4;b insn_d5;b insn_d6;b insn_d7
b insn_d8;b insn_d9;b insn_da;b insn_db;b insn_dc;b insn_dd;b insn_de;b insn_df
b insn_e0;b insn_e1;b insn_e2;b insn_e3;b insn_e4;b insn_e5;b insn_e6;b insn_e7
b insn_e8;b insn_e9;b insn_ea;b insn_eb;b insn_ec;b insn_ed;b insn_ee;b insn_ef
b insn_f0;b insn_f1;b insn_f2;b insn_f3;b insn_f4;b insn_f5;b insn_f6;b insn_f7
b insn_f8;b insn_f9;b insn_fa;b insn_fb;b insn_fc;b insn_fd;b insn_fe;b insn_ff
insn_00: @ BRK
add cpu_pc, cpu_pc, #1
INTERRUPT 0xFFFE, 0x30
insn_01: R(ORA, Ind_X)
insn_05: R(ORA, Zp)
insn_06: RMW(ASL, Zp)
insn_08: @ PHP
ldrb r1, [cpu_pc], #1
mov r2, #0x30
bl push_flags
NEXT 3
insn_09: R(ORA, Imm)
insn_0a: RMW(ASLR, A)
insn_0d: R(ORA, Abs)
insn_0e: RMW(ASL, Abs)
insn_10: BRANCH N, 0 @ BPL
insn_11: R(ORA, Ind_Y)
insn_15: R(ORA, Zp_X)
insn_16: RMW(ASL, Zp_X)
insn_18: CHANGE_FLAG bic, C
insn_19: R(ORA, Abs_Y)
insn_1d: R(ORA, Abs_X)
insn_1e: RMW(ASL, Abs_X)
insn_20: @ JSR Absolute
ldrb r2, [cpu_pc], #1
ldrb r3, [cpu_pc]
bl push_pc
bl mem_jump_split
NEXT 6
insn_21: R(AND, Ind_X)
insn_24: R(BIT, Zp)
insn_25: R(AND, Zp)
insn_26: RMW(ROL, Zp)
insn_28: @ PLP
ldrb r1, [cpu_pc], #1
bl pull_flags
NEXT 4
insn_29: R(AND, Imm)
insn_2a: RMW(ROLR, A)
insn_2c: R(BIT, Abs)
insn_2d: R(AND, Abs)
insn_2e: RMW(ROL, Abs)
insn_30: BRANCH N, 1
insn_31: R(AND, Ind_Y)
insn_35: R(AND, Zp_X)
insn_36: RMW(ROL, Zp_X)
insn_38: CHANGE_FLAG orr, C
insn_39: R(AND, Abs_Y)
insn_3d: R(AND, Abs_X)
insn_3e: RMW(ROL, Abs_X)
insn_40: @ RTI
bl pull_flags
SPULL r2
SPULL r3
bl mem_jump_split
NEXT 6
insn_41: R(EOR, Ind_X)
insn_45: R(EOR, Zp)
insn_46: RMW(LSR, Zp)
insn_48: @ PHA
mov r0, cpu_a, lsr #24
ldrb r1, [cpu_pc], #1
SPUSH r0
NEXT 3
insn_49: R(EOR, Imm)
insn_4a: RMW(LSRR, A)
insn_4c: @ JMP Absolute
ldrb r2, [cpu_pc], #1
ldrb r3, [cpu_pc], #1
bl mem_jump_split
NEXT 3
insn_4d: R(EOR, Abs)
insn_4e: RMW(LSR, Abs)
insn_50: BRANCH V, 0
insn_51: R(EOR, Ind_Y)
insn_55: R(EOR, Zp_X)
insn_56: RMW(LSR, Zp_X)
insn_58: CHANGE_FLAG bic, I
insn_59: R(EOR, Abs_Y)
insn_5d: R(EOR, Abs_X)
insn_5e: RMW(LSR, Abs_X)
insn_60: @ RTS
SPULL r2
SPULL r3
add r2, r2, #1
bl mem_jump_split
NEXT 6
insn_61: R(ADC, Ind_X)
insn_65: R(ADC, Zp)
insn_66: RMW(ROR, Zp)
insn_68: @ PLA
SPULL r0
ldrb r1, [cpu_pc], #1
msr cpsr_f, cpu_flags
mov r0, r0, lsl #24
movs cpu_a, r0
mrs cpu_flags, cpsr
NEXT 4
insn_69: R(ADC, Imm)
insn_6a: RMW(RORR, A)
insn_6c: @ JMP Indirect
ldrb r2, [cpu_pc], #1
ldrb r3, [cpu_pc], #1
bl mem_read_split
mov r1, r0
add r2, r2, #1
tst r2, #0xFF
subeq r2, r2, #0x100 @ Correct for "bug"
bl mem_read
add r2, r1, r0, lsl #8
bl mem_jump
NEXT 5
insn_6d: R(ADC, Abs)
insn_6e: RMW(ROR, Abs)
insn_70: BRANCH V, 1
insn_71: R(ADC, Ind_Y)
insn_75: R(ADC, Zp_X)
insn_76: RMW(ROR, Zp_X)
insn_78: CHANGE_FLAG orr, I
insn_79: R(ADC, Abs_Y)
insn_7d: R(ADC, Abs_X)
insn_7e: RMW(ROR, Abs_X)
insn_81: W(STA, Ind_X)
insn_84: W(STY, Zp)
insn_85: W(STA, Zp)
insn_86: W(STX, Zp)
insn_88: RMW(DECR, Y)
insn_8a: @ TXA
ldrb r1, [cpu_pc], #1
msr cpsr_f, cpu_flags
movs cpu_a, cpu_x
mrs cpu_flags, cpsr
NEXT 2
insn_8c: W(STY, Abs)
insn_8d: W(STA, Abs)
insn_8e: W(STX, Abs)
insn_90: BRANCH C, 0
insn_91: W(STA, Ind_Y)
insn_94: W(STY, Zp_X)
insn_95: W(STA, Zp_X)
insn_96: W(STX, Zp_Y)
insn_98: @ TYA
ldrb r1, [cpu_pc], #1
msr cpsr_f, cpu_flags
movs cpu_a, cpu_y
mrs cpu_flags, cpsr
NEXT 2
insn_99: W(STA, Abs_Y)
insn_9a: @ TXS
ldrb r1, [cpu_pc], #1
mov cpu_sp, cpu_x, lsr #24
orr cpu_sp, cpu_sp, #0x100
NEXT 2
insn_9d: W(STA, Abs_X)
insn_a0: R(LDY, Imm)
insn_a1: R(LDA, Ind_X)
insn_a2: R(LDX, Imm)
insn_a4: R(LDY, Zp)
insn_a5: R(LDA, Zp)
insn_a6: R(LDX, Zp)
insn_a8: @ TAY
ldrb r1, [cpu_pc], #1
msr cpsr_f, cpu_flags
movs cpu_y, cpu_a
mrs cpu_flags, cpsr
NEXT 2
insn_a9: R(LDA, Imm)
insn_aa: @ TAX
ldrb r1, [cpu_pc], #1
msr cpsr_f, cpu_flags
movs cpu_x, cpu_a
mrs cpu_flags, cpsr
NEXT 2
insn_ac: R(LDY, Abs)
insn_ad: R(LDA, Abs)
insn_ae: R(LDX, Abs)
insn_b0: BRANCH C, 1
insn_b1: R(LDA, Ind_Y)
insn_b4: R(LDY, Zp_X)
insn_b5: R(LDA, Zp_X)
insn_b6: R(LDX, Zp_Y)
insn_b8: CHANGE_FLAG bic, V
insn_b9: R(LDA, Abs_Y)
insn_ba: @ TSX
ldrb r1, [cpu_pc], #1
msr cpsr_f, cpu_flags
mov r0, cpu_sp, lsl #24
movs cpu_x, r0
mrs cpu_flags, cpsr
NEXT 2
insn_bc: R(LDY, Abs_X)
insn_bd: R(LDA, Abs_X)
insn_be: R(LDX, Abs_Y)
insn_c0: R(CPY, Imm)
insn_c1: R(CMP, Ind_X)
insn_c4: R(CPY, Zp)
insn_c5: R(CMP, Zp)
insn_c6: RMW(DEC, Zp)
insn_c8: RMW(INCR, Y)
insn_c9: R(CMP, Imm)
insn_ca: RMW(DECR, X)
insn_cc: R(CPY, Abs)
insn_cd: R(CMP, Abs)
insn_ce: RMW(DEC, Abs)
insn_d0: BRANCH Z, 0
insn_d1: R(CMP, Ind_Y)
insn_d5: R(CMP, Zp_X)
insn_d6: RMW(DEC, Zp_X)
insn_d8: CHANGE_FLAG bic, D
insn_d9: R(CMP, Abs_Y)
insn_dd: R(CMP, Abs_X)
insn_de: RMW(DEC, Abs_X)
insn_e0: R(CPX, Imm)
insn_e1: R(SBC, Ind_X)
insn_e4: R(CPX, Zp)
insn_e5: R(SBC, Zp)
insn_e6: RMW(INC, Zp)
insn_e8: RMW(INCR, X)
insn_eb: @ undocumented SBC #Imm
insn_e9: R(SBC, Imm)
insn_ec: R(CPX, Abs)
insn_ed: R(SBC, Abs)
insn_ee: RMW(INC, Abs)
insn_f0: BRANCH Z, 1
insn_f1: R(SBC, Ind_Y)
insn_f5: R(SBC, Zp_X)
insn_f6: RMW(INC, Zp_X)
insn_f8: CHANGE_FLAG orr, D
insn_f9: R(SBC, Abs_Y)
insn_fd: R(SBC, Abs_X)
insn_fe: RMW(INC, Abs_X)
@ No-ops
insn_14: @ NOP Zp,X
insn_34: @ NOP Zp,X
insn_54: @ NOP Zp,X
insn_74: @ NOP Zp,X
insn_d4: @ NOP Zp,X
insn_f4: @ NOP Zp,X
sub cpu_cycles, cpu_cycles, #CPU_CYCLE_LENGTH
insn_04: @ NOP Zp
insn_44: @ NOP Zp
insn_64: @ NOP Zp
sub cpu_cycles, cpu_cycles, #CPU_CYCLE_LENGTH
insn_80: @ NOP Imm
insn_82: @ NOP Imm
insn_89: @ NOP Imm
insn_c2: @ NOP Imm
insn_e2: @ NOP Imm
add cpu_pc, cpu_pc, #1
insn_1a: @ NOP
insn_3a: @ NOP
insn_5a: @ NOP
insn_7a: @ NOP
insn_da: @ NOP
insn_ea: @ NOP (official)
insn_fa: @ NOP
ldrb r1, [cpu_pc], #1
NEXT 2
@ Opcodes with undocumented functionality (not implemented)
insn_03:
insn_07:
insn_0b:
insn_0c:
insn_0f:
insn_13:
insn_17:
insn_1b:
insn_1c:
insn_1f:
insn_23:
insn_27:
insn_2b:
insn_2f:
insn_33:
insn_37:
insn_3b:
insn_3c:
insn_3f:
insn_43:
insn_47:
insn_4b:
insn_4f:
insn_53:
insn_57:
insn_5b:
insn_5c:
insn_5f:
insn_63:
insn_67:
insn_6b:
insn_6f:
insn_73:
insn_77:
insn_7b:
insn_7c:
insn_7f:
insn_83:
insn_87:
insn_8b:
insn_8f:
insn_93:
insn_97:
insn_9b:
insn_9c:
insn_9e:
insn_9f:
insn_a3:
insn_a7:
insn_ab:
insn_af:
insn_b3:
insn_b7:
insn_bb:
insn_bf:
insn_c3:
insn_c7:
insn_cb:
insn_cf:
insn_d3:
insn_d7:
insn_db:
insn_dc:
insn_df:
insn_e3:
insn_e7:
insn_ef:
insn_f3:
insn_f7:
insn_fb:
insn_fc:
insn_ff:
@ Opcodes that hang the CPU
insn_02:
insn_12:
insn_22:
insn_32:
insn_42:
insn_52:
insn_62:
insn_72:
insn_92:
insn_b2:
insn_d2:
insn_f2:
ERROR 0x0000

158
source/debug.S Normal file
View File

@ -0,0 +1,158 @@
#include "nes.inc"
#ifdef DEBUG
.globl chrout
chrout:
mov r1, #0x90000000
add r1, r1, #0x00020000
strb r0, [r1]
bx lr
.globl hexout
hexout:
mov r2, #0x90000000
add r2, r2, #0x00020000
hexloop:
sub r1, r1, #4
mov r3, r0, lsr r1
and r3, #15
cmp r3, #10
addcc r3, r3, #'0'
addcs r3, r3, #'A'-10
strb r3, [r2]
cmp r1, #0
bne hexloop
bx lr
.globl fps_counter
fps_counter:
push {r0-r3,lr}
ldr r2, [r9, #s_frame_count]
add r2, r2, #1
str r2, [r9, #s_frame_count]
ldr r0, [r9, #s_frame_count_rtc]
mov r1, #0x90000000
add r1, r1, #0x00090000
ldr r1, [r1]
str r1, [r9, #s_frame_count_rtc]
cmp r0, r1
popeq {r0-r3,pc}
mov r0, r2
mov r1, #32
bl hexout
mov r0, #10
bl chrout
mov r2, #0
str r2, [r9, #s_frame_count]
pop {r0-r3,pc}
.globl trace_read
trace_read:
push {r0-r12,lr}
mov r0, #'R'
bl chrout
mov r0, r2
mov r1, #16
bl hexout
mov r0, #13
bl chrout
mov r0, #10
bl chrout
pop {r0-r12,lr}
b mem_read+4
.globl trace_write
trace_write:
push {r0-r12,lr}
mov r4, r0
mov r0, #'W'
bl chrout
mov r0, r2
mov r1, #16
bl hexout
mov r0, #'='
bl chrout
mov r0, r4
mov r1, #8
bl hexout
mov r0, #13
bl chrout
mov r0, #10
bl chrout
pop {r0-r12,lr}
b mem_write+4
.globl trace
trace:
push {r0-r12, lr}
mrs r12, cpsr
mov r0, #'P'; bl chrout
mov r0, #'C'; bl chrout
mov r0, #'='; bl chrout
ldr r0, [r9, #s_pc_base]
sub r0, cpu_pc, r0
sub r0, r0, #1
mov r1, #16
bl hexout
mov r0, #'['; bl chrout
ldrb r0, [cpu_pc, #-1]
mov r1, #8
bl hexout
mov r0, #']'; bl chrout
mov r0, #' '; bl chrout
mov r0, #'A'; bl chrout
mov r0, #'='; bl chrout
mov r0, cpu_a, lsr #24; mov r1, #8; bl hexout
mov r0, #' '; bl chrout
mov r0, #'X'; bl chrout
mov r0, #'='; bl chrout
mov r0, cpu_x, lsr #24; mov r1, #8; bl hexout
mov r0, #' '; bl chrout
mov r0, #'Y'; bl chrout
mov r0, #'='; bl chrout
mov r0, cpu_y, lsr #24; mov r1, #8; bl hexout
mov r0, #' '; bl chrout
mov r0, #'F'; bl chrout
mov r0, #'='; bl chrout
ldr r4, [r9, #s_flags_di]
mov r0, #'N'; tst cpu_flags, #0x80000000; moveq r0, #'n'; bl chrout
mov r0, #'V'; tst cpu_flags, #0x10000000; moveq r0, #'v'; bl chrout
mov r0, #'-'; bl chrout
mov r0, #'-'; bl chrout
mov r0, #'D'; tst r4, #0x08; moveq r0, #'d'; bl chrout
mov r0, #'I'; tst r4, #0x04; moveq r0, #'i'; bl chrout
mov r0, #'Z'; tst cpu_flags, #0x40000000; moveq r0, #'z'; bl chrout
mov r0, #'C'; tst cpu_flags, #0x20000000; moveq r0, #'c'; bl chrout
mov r0, #' '; bl chrout
mov r0, #'S'; bl chrout
mov r0, #'='; bl chrout
mov r0, cpu_sp; mov r1, #8; bl hexout
mov r0, #' '; bl chrout
ldr r0, [r9, #s_ppu_scanline]
mov r1, #16; bl hexout
mov r0, #':'; bl chrout
mov r0, #0x0100
add r0, #0x0054
sub r0, cpu_cycles
mov r1, #16; bl hexout
mov r0, #13; bl chrout
mov r0, #10; bl chrout
msr cpsr_f, r12
pop {r0-r12, pc}
#endif

BIN
source/font.bin Normal file

Binary file not shown.

416
source/main.S Normal file
View File

@ -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

372
source/memory.S Normal file
View File

@ -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

264
source/menu.S Normal file
View File

@ -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"

132
source/nes.inc Normal file
View File

@ -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

BIN
source/nes_emu.prg.tns Normal file

Binary file not shown.

BIN
source/nes_emu.tns Normal file

Binary file not shown.

589
source/ppu.S Normal file
View File

@ -0,0 +1,589 @@
#include "nes.inc"
.globl ppu_next_scanline
ppu_next_scanline:
push {r4-r8, r10-r11, lr}
ldr r10, [r9, #s_ppu_flags]
ldr r0, [r9, #s_ppu_scanline]
add r0, r0, #1
cmp r0, #-1 @ Vblank is over
biceq r10, r10, #0xC00000
cmp r0, #241 @ Vblank is starting
bne 1f
mov r0, #-21
orr r10, r10, #0x800000
and r2, r10, #0x80 @ NMI enabled?
strb r2, [r9, #s_nmi_reset]
1:
str r0, [r9, #s_ppu_scanline]
cmp r0, #240
bcs rendering_off
tst r10, #0x1800
beq 1f
cmp r0, #0
@ Line 0: Move to (X,Y) position specified in s_ppu_scroll
@ Lines 1-239: Move back to X position specified in s_ppu_scroll
ldr r1, [r9, #s_ppu_address]
ldr r0, [r9, #s_ppu_scroll]
moveq r2, #0xFF00
addeq r2, r2, #0x00FF
movne r2, #0x0400
addne r2, r2, #0x001F
and r0, r0, r2
bic r1, r1, r2
orr r1, r1, r0
str r1, [r9, #s_ppu_address]
1:
ldr r0, [r9, #s_frameskip_cur]
cmp r0, #0
bne frameskipped
@ Allocate scanline pixel buffer
sub sp, sp, #272
@ Step I: Render background
tst r10, #0x0800
beq background_disabled
mov r1, r10, lsl #8
and r1, r1, #0x1000
ldr r2, [r9, #s_ppu_address]
and r8, r2, #0x1F
add r1, r1, r2, lsr #12
bl get_name_table_pointers
mov r11, #33
ldr r6, =0x08040201
ldr r7, =0xEEEEEEEE
draw_chr:
@ Get attribute for this block
ldrb r12, [r0, r8, lsr #2]
@ Get character
ldrb r3, [r2, r8]
tst r2, #0x40 @ Assuming nametable is 128-byte aligned
movne r12, r12, lsr #4
tst r8, #0x02
moveq r12, r12, lsl #2
and r12, r12, #0x0C
orr r12, r12, #0x03
orr r12, r12, lsl #8
orr r12, r12, lsl #16
@ Get pixels for appropriate row of character
add r4, r1, r3, lsl #4
mov r3, r4, lsr #10
add r3, r9, r3, lsl #2
ldr r3, [r3, #s_ppu_mem_map]
ldrb r3, [r4, r3]! @ Low plane
ldrb r4, [r4, #8] @ High plane
@ Move to next character over
add r8, r8, #1
cmp r8, #0x20
bleq swap_name_table
@ Unpack each pixel into a nybble
mul r3, r6, r3
mul r4, r6, r4
orr r3, r7, r3, lsr #3
orr r4, r7, r4, lsr #3
and r4, r3, r4, ror #31
@ Unpack nybbles into bytes
and r3, r12, r4, lsr #4
and r4, r12, r4
stmia sp!, {r3-r4}
subs r11, r11, #1
bne draw_chr
sub sp, sp, #264
@ Blank out left 8 pixels
tst r10, #0x0200
bne background_done
ldr r0, [r9, #s_ppu_scroll]
mov r3, #0
add r0, sp, r0, lsr #29
strb r3, [r0]
strb r3, [r0, #1]
strb r3, [r0, #2]
strb r3, [r0, #3]
strb r3, [r0, #4]
strb r3, [r0, #5]
strb r3, [r0, #6]
strb r3, [r0, #7]
background_done:
ldr r0, [r9, #s_ppu_scroll]
add sp, sp, r0, lsr #29
@ Step II: Sprites
tst r10, #0x1000
beq no_sprites
@ Get sprite height (minus 1)
tst r10, #0x0020
moveq r12, #7
movne r12, #15
ldrb r1, [r9, #s_spr_loc_table_valid]
movs r1, r1
bleq refresh_spr_loc_table
ldr r0, [r9, #s_ppu_scanline]
add r2, r9, #s_spr_loc_table
add r2, r2, r0, lsl #3
ldrb r1, [r2, r0]!
cmp r1, #0
beq no_sprites
@ Only up to 8 sprites are actually stored in the table
cmp r1, #8
movcs r1, #8
sub r0, r0, #1
sprite_loop:
ldrb r4, [r2, #1]!
add r8, r9, #s_ppu_oam_ram
ldr r4, [r8, r4]
@ Get offset from the top
and r6, r4, #0xFF
sub r5, r0, r6
bl fetch_sprite_bits
@ Get palette
mov r8, r4, lsr #14
and r8, r8, #0x0C
orr r8, r8, #0x30
tst r4, #0x400000
movne r5, r5, lsl #7
moveq r3, #31
movne r3, #1
mov r6, #8
add r11, sp, r4, lsr #24
tst r4, #0x200000
bne sprite_draw_low_pri
sprite_draw_high_pri:
ldrb lr, [r11], #1
and r7, r5, #0x80
orr r7, r8, r7, lsr #7
tst r5, #0x8000
addne r7, r7, #2
tst r7, #3
beq 1f
tst lr, #0x20
streqb r7, [r11, #-1]
1: mov r5, r5, ror r3
subs r6, r6, #1
bne sprite_draw_high_pri
b sprite_next
sprite_draw_low_pri:
ldrb lr, [r11], #1
and r7, r5, #0x80
orr r7, r8, r7, lsr #7
tst r5, #0x8000
addne r7, r7, #2
tst r7, #3
beq 1f
tst lr, #3
orrne r7, lr, #0x20
strb r7, [r11, #-1]
1: mov r5, r5, ror r3
subs r6, r6, #1
bne sprite_draw_low_pri
sprite_next:
subs r1, r1, #1
bne sprite_loop
no_sprites:
@ Step III: Draw to screen
ldrb r0, [r9, #s_message_timer]
ldr r8, [r9, #s_ppu_scanline]
movs r0, r0
movne r0, #16
cmp r8, r0
addcc sp, #256
bcc draw_done
mov r4, #0xC0000000
ldr r4, [r4, #0x10]
add r5, r9, #s_palette_cache
ldr r0, [r9, #s_hw_color]
ldrb r1, [r9, #s_palette_cache_valid]
movs r0, r0
bne draw_color
movs r1, r1
bleq refresh_palette_cache_bw
@ Draw in black and white
add r8, r8, r8, lsl #2
add r4, r4, r8, lsl #5
add r4, r4, #0x10
mov r8, #256
1: ldrb r0, [sp], #1
ldrb r1, [sp], #1
ldrb r2, [sp], #1
ldrb r3, [sp], #1
ldrb r0, [r5, r0]
ldrb r1, [r5, r1]
ldrb r2, [r5, r2]
ldrb r3, [r5, r3]
orr r0, r1, r0, lsl #4
orr r0, r0, r2, lsl #12
orr r0, r0, r3, lsl #8
strh r0, [r4], #2
subs r8, r8, #4
bne 1b
b draw_done
draw_color:
movs r1, r1
bleq refresh_palette_cache_color
add r8, r8, r8, lsl #2
add r4, r4, r8, lsl #7
add r4, r4, #0x40
mov r8, #256
1: ldrb r0, [sp], #1
ldrb r1, [sp], #1
ldrb r2, [sp], #1
ldrb r3, [sp], #1
add r0, r0
add r1, r1
add r2, r2
add r3, r3
ldrh r0, [r5, r0]
ldrh r1, [r5, r1]
ldrh r2, [r5, r2]
ldrh r3, [r5, r3]
orr r0, r1, lsl #16
str r0, [r4], #4
orr r2, r3, lsl #16
str r2, [r4], #4
subs r8, r8, #4
bne 1b
draw_done:
ldr r0, [r9, #s_ppu_scroll]
add sp, sp, #16
sub sp, sp, r0, lsr #29
frameskipped:
@ Check for sprite 0 hit
@ (TODO: should only occur when two opaque pixels collide)
tst r10, #0x1000
beq sprite_0_done
tst r10, #0x0020
moveq r12, #7
movne r12, #15
ldr r0, [r9, #s_ppu_scanline]
ldr r4, [r9, #s_ppu_oam_ram]
sub r0, r0, #1
and r6, r4, #0xFF
sub r5, r0, r6
cmp r5, r12
bls sprite_0_check
sprite_0_done:
tst r10, #0x1800
beq rendering_off
@ Move down by one pixel
ldr r1, [r9, #s_ppu_address]
add r1, r1, #0x1000
cmp r1, #0x8000
bcc 1f
bic r1, r1, #0x8000
add r1, r1, #0x0020
ands r2, r1, #0x03E0
subeq r1, r1, #0x0400 @ If Y wraps 31->0, 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

609
source/rom.S Normal file
View File

@ -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