commit 7675355d1715bb65488ef0e08f11e08b743a8b8f Author: franchioping Date: Sat May 18 18:43:54 2024 +0100 first commit diff --git a/nes_emu.prg.tns b/nes_emu.prg.tns new file mode 100644 index 0000000..0ca2093 Binary files /dev/null and b/nes_emu.prg.tns differ diff --git a/nes_emu.tns b/nes_emu.tns new file mode 100644 index 0000000..babc03b Binary files /dev/null and b/nes_emu.tns differ diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..6edef81 --- /dev/null +++ b/readme.txt @@ -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. diff --git a/source/Makefile b/source/Makefile new file mode 100644 index 0000000..124b661 --- /dev/null +++ b/source/Makefile @@ -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 diff --git a/source/cpu.S b/source/cpu.S new file mode 100644 index 0000000..2276d87 --- /dev/null +++ b/source/cpu.S @@ -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 diff --git a/source/debug.S b/source/debug.S new file mode 100644 index 0000000..b65523f --- /dev/null +++ b/source/debug.S @@ -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 diff --git a/source/font.bin b/source/font.bin new file mode 100644 index 0000000..672d0e1 Binary files /dev/null and b/source/font.bin differ 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 0000000..0ca2093 Binary files /dev/null and b/source/nes_emu.prg.tns differ diff --git a/source/nes_emu.tns b/source/nes_emu.tns new file mode 100644 index 0000000..babc03b Binary files /dev/null and b/source/nes_emu.tns differ diff --git a/source/ppu.S b/source/ppu.S new file mode 100644 index 0000000..b797cad --- /dev/null +++ b/source/ppu.S @@ -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 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