| @@ -1,20 +1,57 @@ | |||
| #CROSS_COMPILE = arm-none-eabi- | |||
| OS = linux | |||
| CC = $(CROSS_COMPILE)gcc | |||
| LD = $(CC) | |||
| #CFLAGS = -mcpu=cortex-m33 -mthumb | |||
| CFLAGS += -Wall -Werror -Wshadow | |||
| CFLAGS += -g -Ofast | |||
| CFLAGS += -DF6502_FLAT | |||
| CFLAGS += -DF6502_TEST | |||
| CFLAGS += -DF6502_HCF | |||
| CFLAGS += -I src | |||
| CFLAGS += -g #-Ofast | |||
| #CFLAGS += -DF6502_FLAT | |||
| #CFLAGS += -DF6502_TEST | |||
| #CFLAGS += -DF6502_HCF | |||
| #CFLAGS += -DF6502_TRACE | |||
| OBJS = src/f6502.o src/f6502_opcodes.o test.o | |||
| all: test | |||
| OBJDIR = obj | |||
| SRCDIR = src | |||
| BINDIR = bin | |||
| TEST_SRC_SRCS = f6502.c f6502_opcodes.c | |||
| TEST_SRCS = test.c | |||
| TEST_SRCS += $(TEST_SRC_SRCS:%=$(SRCDIR)/%) | |||
| TEST_OBJS = $(TEST_SRCS:%.c=$(OBJDIR)/%.o) | |||
| MAPDIR = $(SRCDIR)/map | |||
| NESE_SRC_SRCS = f6502.c f6502_opcodes.c | |||
| NESE_SRC_SRCS += nese.c nes.c cart.c mapper.c | |||
| NESE_SRC_SRCS += ppu.c | |||
| NESE_SRC_SRCS += $(OS)/port.c | |||
| NESE_MAP_SRCS = $(notdir $(wildcard $(MAPDIR)/*)) | |||
| NESE_DEBUG = CART | |||
| NESE_SRCS += $(NESE_SRC_SRCS:%=$(SRCDIR)/%) | |||
| NESE_SRCS += $(NESE_MAP_SRCS:%=$(MAPDIR)/%) | |||
| NESE_OBJS = $(NESE_SRCS:%.c=$(OBJDIR)/%.o) | |||
| all: $(BINDIR)/nese | |||
| $(BINDIR)/test: CFLAGS += -DF6502_FLAT -DF6502_TEST -DF6502_TRACE | |||
| $(BINDIR)/nese: CFLAGS += $(foreach debug,$(NESE_DEBUG), -DDEBUG_$(debug)) | |||
| $(BINDIR)/nese: $(NESE_OBJS) | |||
| @mkdir -p $(@D) | |||
| $(LD) $^ $(LDFLAGS) -o $@ | |||
| test: $(OBJS) | |||
| $(LD) $(LDFLAGS) -o $@ $^ | |||
| $(OBJDIR)/%.o : %.c | |||
| @mkdir -p $(@D) | |||
| $(CC) $(CFLAGS) -c $< -o $@ | |||
| clean: | |||
| rm -rf $(OBJS) | |||
| rm -rf $(OBJDIR) $(BINDIR) | |||
| @@ -0,0 +1,83 @@ | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include "cart.h" | |||
| #ifdef DEBUG_CART | |||
| #define DEBUG "Cart" | |||
| #endif | |||
| #include "log.h" | |||
| int nes_cart_load_mem(void* data, int size, nes* sys) { | |||
| int status = 0; | |||
| nes_Memory* mem = &sys->core.memory; | |||
| ines_Header* hdr = (ines_Header*)data; | |||
| void* ptr = &hdr[1]; | |||
| // Verify magic | |||
| if (0 != memcmp(hdr->magic, INES_MAGIC, 4)) { | |||
| LOGE("Bad magic: got %.4s, expected %.4s", hdr->magic, INES_MAGIC); | |||
| status = -1; | |||
| } | |||
| if (0 == status) { | |||
| // Skip the trainer | |||
| if (hdr->flags_6 & ines_Flag_Trainer) { | |||
| ptr += INES_TRAINER_SIZE; | |||
| } | |||
| // Mark the PRG ROM | |||
| int rom_size = hdr->prg_size_lsb * INES_PRG_ROM_SIZE; | |||
| LOGI("PRG ROM: %d kB", rom_size / 1024); | |||
| mem->rom = ptr; | |||
| mem->n_rom_banks = rom_size / NES_PRG_ROM_PAGE_SIZE; | |||
| ptr += rom_size; | |||
| // Mark the CHR ROM | |||
| if (hdr->chr_size_lsb > 0) { | |||
| int chr_size = hdr->chr_size_lsb * INES_CHR_ROM_SIZE; | |||
| LOGI("CHR ROM: %d kB", chr_size / 1024); | |||
| mem->ppu.chr = ptr; | |||
| mem->ppu.n_chr_banks = chr_size / | |||
| NES_CHR_ROM_PAGE_SIZE; | |||
| ptr += chr_size; | |||
| } | |||
| // Fail if we're beyond the bounds | |||
| if (ptr - data > size) { | |||
| LOGE("Past ROM bounds by %d bytes", (int)((ptr - data) - size)); | |||
| status = -1; | |||
| } | |||
| } | |||
| if (0 == status) { | |||
| // Unused & Palette | |||
| for (int page = 12; page < 16; ++page) { | |||
| mem->ppu.bank[page] = mem->ppu.pal_bank; | |||
| } | |||
| nes_PPU_set_mirroring( | |||
| &mem->ppu, hdr->flags_6 & ines_Flag_Vert_Mirror | |||
| ); | |||
| int i_mapper = (hdr->flags_7 & ines_Mapper_Nibble_Hi) | | |||
| ((hdr->flags_6 & ines_Mapper_Nibble_Lo) >> 4); | |||
| const nes_Mapper *mapper = nes_mappers[i_mapper]; | |||
| if (NULL == mapper || NULL == mapper->init) { | |||
| LOGE("Mapper %d not supported", i_mapper); | |||
| status = -1; | |||
| } else { | |||
| LOGI("Mapper %d", i_mapper); | |||
| mem->mapper = *mapper; | |||
| status = mem->mapper.init(&mem->mapper, hdr, mem); | |||
| } | |||
| } | |||
| if (0 == status) { | |||
| sys->cart_header = hdr; | |||
| } | |||
| return status; | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| #ifndef NESE_CART_H_ | |||
| #define NESE_CART_H_ | |||
| #include "ines.h" | |||
| #include "nes.h" | |||
| int nes_cart_load_mem(void* mem, int size, nes* sys); | |||
| #endif // NESE_CART_H_ | |||
| @@ -19,18 +19,18 @@ | |||
| // TOTO: Temp Hack | |||
| volatile uint8_t memval; | |||
| static inline uint8_t f6502_read_zp(f6502_Memory* mem, | |||
| static inline uint8_t f6502_read_zp(nes_Memory* mem, | |||
| uint8_t addr) { | |||
| return mem->ram[addr]; | |||
| } | |||
| static inline uint16_t f6502_read16_zp(f6502_Memory* mem, | |||
| static inline uint16_t f6502_read16_zp(nes_Memory* mem, | |||
| uint8_t addr) { | |||
| return ( f6502_read_zp(mem, addr) | | |||
| ((uint16_t)f6502_read_zp(mem, addr + 1) << 8)); | |||
| } | |||
| static inline uint8_t f6502_read(f6502_Memory* mem, | |||
| static inline uint8_t f6502_read(nes_Memory* mem, | |||
| uint16_t addr) { | |||
| #ifdef F6502_FLAT | |||
| #ifdef F6502_TRACE | |||
| @@ -55,7 +55,9 @@ static inline uint8_t f6502_read(f6502_Memory* mem, | |||
| return 0; | |||
| case 0x6000: | |||
| return mem->sram_bank[addr & 0x1FFFU]; | |||
| if (mem->sram_bank) { | |||
| return mem->sram_bank[addr & 0x1FFFU]; | |||
| } | |||
| } | |||
| // Upper byte stays on the bus | |||
| @@ -63,15 +65,15 @@ static inline uint8_t f6502_read(f6502_Memory* mem, | |||
| #endif | |||
| } | |||
| static inline uint16_t f6502_read16(f6502_Memory* mem, | |||
| static inline uint16_t f6502_read16(nes_Memory* mem, | |||
| uint16_t addr) { | |||
| return ( f6502_read(mem, addr) | | |||
| ((uint16_t)f6502_read(mem, addr + 1) << 8)); | |||
| } | |||
| static inline int f6502_write(f6502_Memory* mem, | |||
| uint16_t addr, uint8_t val) { | |||
| int ret = 0; | |||
| static inline bool f6502_write(nes_Memory* mem, | |||
| uint16_t addr, uint8_t val) { | |||
| bool ret = 0; | |||
| #ifdef F6502_TRACE | |||
| printf("W $%04X <- %02X\n", addr, val); | |||
| #endif | |||
| @@ -100,15 +102,36 @@ static inline int f6502_write(f6502_Memory* mem, | |||
| #endif | |||
| mem->ram[addr] = val; | |||
| #else | |||
| // TODO | |||
| memval = val; | |||
| switch (addr & 0xe000) { | |||
| case 0x0000: | |||
| mem->ram[addr & 0x7FFU] = val; | |||
| break; | |||
| case 0x2000: | |||
| // TODO: PPU | |||
| break; | |||
| case 0x4000: | |||
| // TODO: APU | |||
| break; | |||
| case 0x6000: | |||
| if (mem->sram_bank) { | |||
| mem->sram_bank[addr & 0x1FFFU] = val; | |||
| mem->flags |= nes_Mem_SRAM_Used; | |||
| } else if (mem->mapper.write_sram) { | |||
| ret = mem->mapper.write_sram(&mem->mapper, | |||
| addr, val); | |||
| } | |||
| } | |||
| #endif | |||
| return ret; | |||
| } | |||
| void f6502_init(f6502_Core* core) { | |||
| // TODO: ??? | |||
| // TODO: Nothing for now | |||
| } | |||
| void f6502_reset(f6502_Core* core) { | |||
| @@ -686,7 +709,6 @@ static int f6502_do_step(f6502_Core* core, int clocks) { | |||
| JMP(addr); | |||
| } | |||
| #else | |||
| // TODO: Can check for HCF loop here | |||
| JMP(A_ABS); | |||
| #endif | |||
| CLK(3); | |||
| @@ -1408,7 +1430,9 @@ static int f6502_do_step(f6502_Core* core, int clocks) { | |||
| if (int_trig) printf("Possible interrupt trigger\n"); | |||
| #endif | |||
| #ifdef F6502_HCF | |||
| step_done: | |||
| #endif | |||
| core->registers = (f6502_Registers){ | |||
| .PC = PC, | |||
| .S = S, | |||
| @@ -1424,6 +1448,7 @@ step_done: | |||
| int f6502_step(f6502_Core* core, int clocks) { | |||
| int clocks_elapsed = 0; | |||
| // TODO: Why are we processing seven clocks prior to NMI? | |||
| // This passes the interrupt test with 6 steps | |||
| /* | |||
| if (f6502_nmi_ready(core)) { | |||
| clocks_elapsed += f6502_do_step(core, 7); | |||
| @@ -4,8 +4,7 @@ | |||
| #include <stdbool.h> | |||
| #include <stdint.h> | |||
| #define F6502_RAM_SIZE (0x2000U) | |||
| #include "memory.h" | |||
| typedef enum { | |||
| @@ -41,22 +40,12 @@ typedef enum { | |||
| f6502_Int_NMI_Serviced = 0b10000000, | |||
| } f6502_Interrupt; | |||
| typedef struct __attribute__ ((__packed__)) { | |||
| #ifdef F6502_FLAT | |||
| uint8_t ram[65536]; | |||
| #else | |||
| uint8_t ram[F6502_RAM_SIZE / 4]; // Mirrored 3x | |||
| uint8_t *sram_bank; | |||
| uint8_t *rom_bank[4]; | |||
| // TODO | |||
| #endif | |||
| } f6502_Memory; | |||
| typedef struct __attribute__ ((__packed__)) { | |||
| struct f6502_Core { | |||
| f6502_Registers registers; | |||
| f6502_Interrupt interrupts; | |||
| f6502_Memory memory; | |||
| } f6502_Core; | |||
| nes_Memory memory; | |||
| }; | |||
| typedef struct f6502_Core f6502_Core; | |||
| void f6502_init(f6502_Core*); | |||
| void f6502_reset(f6502_Core*); | |||
| @@ -0,0 +1,45 @@ | |||
| #ifndef NESE_INES_H_ | |||
| #define NESE_INES_H_ | |||
| #include <stdint.h> | |||
| #define INES_TRAINER_SIZE (0x0200U) | |||
| #define INES_PRG_ROM_SIZE (0x4000U) | |||
| #define INES_CHR_ROM_SIZE (0x2000U) | |||
| #define INES_MAGIC "NES\x1a" | |||
| typedef enum __attribute__((packed)) { | |||
| ines_Flag_Vert_Mirror = 0b00000001, | |||
| ines_Flag_Battery = 0b00000010, | |||
| ines_Flag_Trainer = 0b00000100, | |||
| ines_Flag_Alt_Nametable = 0b00001000, | |||
| ines_Mapper_Nibble_Lo = 0b11110000, | |||
| } ines_6_Flag; | |||
| typedef enum __attribute__((packed)) { | |||
| ines_Console_Mask = 0b00000011, | |||
| ines_Console_NES = 0b00000000, | |||
| ines_Console_VS = 0b00000001, | |||
| ines_Console_PC10 = 0b00000010, | |||
| ines_Console_Ext = 0b00000011, | |||
| ines_NES_2_MASK = 0b00001100, | |||
| ines_NES_2_ID = 0b00001000, | |||
| ines_Mapper_Nibble_Hi = 0b11110000, | |||
| } ines_7_Flag; | |||
| typedef struct __attribute__((packed)) { | |||
| char magic[4]; | |||
| uint8_t prg_size_lsb; | |||
| uint8_t chr_size_lsb; | |||
| ines_6_Flag flags_6; | |||
| ines_7_Flag flags_7; | |||
| uint8_t prg_ram_size; | |||
| uint8_t tv_system; | |||
| uint8_t flags_10; | |||
| uint8_t padding[5]; | |||
| } ines_Header; | |||
| #endif // NESE_INES_H_ | |||
| @@ -0,0 +1,40 @@ | |||
| #include <errno.h> | |||
| #include <stdio.h> | |||
| #include <sys/mman.h> | |||
| #include "nese.h" | |||
| #include "port.h" | |||
| #define DEBUG "Port" | |||
| #include "log.h" | |||
| void* nese_map_file(FILE* file, int size) { | |||
| void* addr = mmap(NULL, size, PROT_READ, MAP_SHARED, | |||
| fileno(file), 0); | |||
| if (MAP_FAILED == addr || NULL == addr) { | |||
| fprintf(stderr, "mmap failed: %d\n", (int)errno); | |||
| addr = NULL; | |||
| } | |||
| return addr; | |||
| } | |||
| int nese_unmap_file(void* addr, int size) { | |||
| return munmap(addr, size); | |||
| } | |||
| static nes sys = {0}; | |||
| int main(int argc, char* argv[]) { | |||
| int status = 0; | |||
| if (argc <= 1) { | |||
| LOGE("No ROM file provided."); | |||
| status = -1; | |||
| } else { | |||
| status = nese_start(&sys, argv[1]); | |||
| } | |||
| return status; | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| #include <stdio.h> | |||
| #ifdef DEBUG | |||
| #define LOG(l, f, ...) fprintf(stderr, l ": " DEBUG ": " f "\n" __VA_OPT__(,) __VA_ARGS__) | |||
| #else // !DEBUG | |||
| #define LOG(...) | |||
| #endif // DEBUG | |||
| #define LOGE(f, ...) LOG("E", f, __VA_ARGS__) | |||
| #define LOGW(f, ...) LOG("W", f, __VA_ARGS__) | |||
| #define LOGI(f, ...) LOG("I", f, __VA_ARGS__) | |||
| #define LOGD(f, ...) LOG("D", f, __VA_ARGS__) | |||
| #define LOGV(f, ...) LOG("V", f, __VA_ARGS__) | |||
| @@ -0,0 +1,31 @@ | |||
| #include "memory.h" | |||
| static int map000_init(nes_Mapper* map, const ines_Header* hdr, | |||
| nes_Memory* mem) { | |||
| if (mem->n_rom_banks > 2) { | |||
| mem->rom_bank[0] = prg_rom_page(mem, 0); | |||
| mem->rom_bank[1] = prg_rom_page(mem, 1); | |||
| mem->rom_bank[2] = prg_rom_page(mem, 2); | |||
| mem->rom_bank[3] = prg_rom_page(mem, 3); | |||
| } else { | |||
| mem->rom_bank[0] = prg_rom_page(mem, 0); | |||
| mem->rom_bank[1] = prg_rom_page(mem, 1); | |||
| mem->rom_bank[2] = prg_rom_page(mem, 0); | |||
| mem->rom_bank[3] = prg_rom_page(mem, 1); | |||
| } | |||
| int page = 0; | |||
| // Pattern tables | |||
| for ( ; page < 8; ++page) { | |||
| mem->ppu.bank[page] = chr_page(&mem->ppu, page); | |||
| } | |||
| return 0; | |||
| } | |||
| const nes_Mapper map000 = { | |||
| .max_chr_banks = 8, | |||
| .init = map000_init, | |||
| }; | |||
| @@ -0,0 +1,9 @@ | |||
| #include "mapper.h" | |||
| extern const nes_Mapper map000; | |||
| const nes_Mapper* nes_mappers[256] = { | |||
| &map000, | |||
| }; | |||
| @@ -0,0 +1,30 @@ | |||
| #ifndef NESE_MAPPER_H_ | |||
| #define NESE_MAPPER_H_ | |||
| #include <stdbool.h> | |||
| #include "ines.h" | |||
| struct nes_Memory; | |||
| struct nes_Mapper { | |||
| int max_chr_banks; | |||
| int (*init)(struct nes_Mapper*, const ines_Header*, struct nes_Memory*); | |||
| bool (*write_rom)(struct nes_Mapper*, uint16_t addr, uint8_t val); | |||
| bool (*write_sram)(struct nes_Mapper*, uint16_t addr, uint8_t val); | |||
| bool (*write_apu)(struct nes_Mapper*, uint16_t addr, uint8_t val); | |||
| uint8_t (*read_apu)(struct nes_Mapper*, uint16_t addr); | |||
| void (*hsync)(struct nes_Mapper*); | |||
| void (*vsync)(struct nes_Mapper*); | |||
| void (*ppu_bus)(struct nes_Mapper*, uint16_t addr); | |||
| void (*ppu_mem_mode)(struct nes_Mapper*, uint8_t mode); | |||
| void* data; | |||
| }; | |||
| typedef struct nes_Mapper nes_Mapper; | |||
| extern const nes_Mapper* nes_mappers[256]; | |||
| #endif // NESE_MAPPER_H_ | |||
| @@ -0,0 +1,50 @@ | |||
| #ifndef NESE_MEMORY_H_ | |||
| #define NESE_MEMORY_H_ | |||
| #include <stdint.h> | |||
| #include "mapper.h" | |||
| #include "ppu.h" | |||
| #define NES_RAM_SIZE (0x2000U) | |||
| #define NES_SRAM_SIZE (0x2000U) | |||
| #define NES_PRG_ROM_PAGE_SIZE (0x2000U) | |||
| typedef enum { | |||
| nes_Mem_SRAM_Used = 0b00000001, | |||
| } nes_Memory_Flag; | |||
| struct nes_Memory { | |||
| #ifdef F6502_FLAT | |||
| uint8_t ram[65536]; | |||
| #else | |||
| uint8_t ram[NES_RAM_SIZE / 4]; // Mirrored 3x | |||
| nes_Memory_Flag flags; | |||
| uint8_t reserved[3]; | |||
| uint8_t* sram; // Actual SRAM, if used - could this be a flag? | |||
| uint8_t* sram_bank; // Mapped to 0x6000 - 0x7FFF | |||
| uint8_t* rom; | |||
| uint8_t* rom_bank[4]; | |||
| int n_rom_banks; | |||
| nes_PPU_Memory ppu; | |||
| nes_Mapper mapper; | |||
| #endif | |||
| }; | |||
| typedef struct nes_Memory nes_Memory; | |||
| static inline uint8_t* prg_rom_page(nes_Memory* mem, int page) { | |||
| return &mem->rom[page * NES_PRG_ROM_PAGE_SIZE]; | |||
| } | |||
| static inline uint8_t* prg_rom_last_page(nes_Memory* mem, int page) { | |||
| // TODO: Where's 0x2000U from? | |||
| return &mem->rom[ (mem->n_rom_banks * NES_PRG_ROM_PAGE_SIZE) - | |||
| ((page + 1) * 0x2000U)]; | |||
| } | |||
| #endif // NESE_MEMORY_H_ | |||
| @@ -0,0 +1,52 @@ | |||
| #include <stddef.h> | |||
| #include "nes.h" | |||
| void nes_init(nes* sys) { | |||
| f6502_init(&sys->core); | |||
| } | |||
| void nes_done(nes* sys) { | |||
| // TODO: deallocate RAM, etc. | |||
| } | |||
| static int nes_hsync(nes* sys) { | |||
| // TODO: APU sync | |||
| // TODO: PPU draw line if visible | |||
| // TODO: PPU update regs | |||
| // TODO: Increment scanline | |||
| // TODO: Scanline region updates | |||
| // TODO: nese_vsync called in nes_vsync | |||
| return 0; | |||
| } | |||
| int nes_loop(nes* sys) { | |||
| int status = 0; | |||
| int dot = 0; | |||
| while (0 == status) { | |||
| // TODO: Partial scanline if sprite 0 hit | |||
| int dots_remaining = nes_ppu_scanline_dots - dot; | |||
| int cpu_cycles = (dots_remaining + 2) / 3; | |||
| dot += 3 * f6502_step(&sys->core, cpu_cycles); | |||
| dot -= nes_ppu_scanline_dots; | |||
| // TODO: Validate dot >= 0? | |||
| // TODO: Frame IRQ | |||
| nes_Memory* mem = &sys->core.memory; | |||
| if (NULL != mem->mapper.hsync) { | |||
| mem->mapper.hsync(&mem->mapper); | |||
| } | |||
| status = nes_hsync(sys); | |||
| } | |||
| return status; | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| #ifndef NES_H_ | |||
| #define NES_H_ | |||
| #include "ines.h" | |||
| #include "f6502.h" | |||
| typedef struct { | |||
| const ines_Header* cart_header; | |||
| f6502_Core core; | |||
| // TODO: PPU | |||
| // TODO: APU | |||
| // TODO: Input | |||
| } nes; | |||
| void nes_init(nes*); | |||
| void nes_done(nes*); | |||
| int nes_loop(nes*); | |||
| #endif // NES_H_ | |||
| @@ -0,0 +1,49 @@ | |||
| #include <stdio.h> | |||
| #include "cart.h" | |||
| #include "nese.h" | |||
| #include "port.h" | |||
| static int nese_file_size(FILE* file) { | |||
| int size = -1; | |||
| if (0 == fseek(file, 0, SEEK_END)) { | |||
| size = ftell(file); | |||
| } | |||
| return size; | |||
| } | |||
| int nese_start(nes* sys, const char* filename) { | |||
| int status = 0; | |||
| void* cart_data = NULL; | |||
| int filesize = -1; | |||
| FILE* file = NULL; | |||
| file = fopen(filename, "rb"); | |||
| if (NULL == file) { | |||
| status = -1; | |||
| } | |||
| if (0 == status) { | |||
| filesize = nese_file_size(file); | |||
| cart_data = nese_map_file(file, filesize); | |||
| if (NULL == cart_data) { | |||
| fprintf(stderr, "Failed to map %s\n", filename); | |||
| status = -1; | |||
| } else { | |||
| status = nes_cart_load_mem(cart_data, filesize, sys); | |||
| } | |||
| } | |||
| nes_init(sys); | |||
| nes_loop(sys); | |||
| if (cart_data) nese_unmap_file(cart_data, filesize); | |||
| nes_done(sys); | |||
| return 0; | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| #ifndef NESE_H_ | |||
| #define NESE_H_ | |||
| #include "nes.h" | |||
| int nese_start(nes* sys, const char* filename); | |||
| #endif // NESE_H_ | |||
| @@ -0,0 +1,9 @@ | |||
| #ifndef NESE_PORT_H_ | |||
| #define NESE_PORT_H_ | |||
| void* nese_map_file(FILE* file, int size); | |||
| int nese_unmap_file(void* addr, int size); | |||
| #endif // NESE_PORT_H_ | |||
| @@ -0,0 +1,20 @@ | |||
| #include "ppu.h" | |||
| static const uint8_t mirror_schemes[][4] = { | |||
| [nes_Mirror_Horizontal] = {0, 0, 1, 1}, | |||
| [nes_Mirror_Vertical] = {0, 1, 0, 1}, | |||
| [nes_Mirror_Only_0] = {0, 0, 0, 0}, | |||
| [nes_Mirror_Only_1] = {1, 1, 1, 1}, | |||
| [nes_Mirror_Four] = {0, 1, 2, 3}, | |||
| }; | |||
| void nes_PPU_set_mirroring(nes_PPU_Memory* mem, | |||
| nes_Nametable_Mirroring mirror) { | |||
| for (int i = 0; i < 4; ++i) { | |||
| mem->bank[nes_PPU_Nametable_Bank_Index + i] = | |||
| vram_page(mem, (int)mirror_schemes[mirror][0]); | |||
| } | |||
| } | |||
| @@ -0,0 +1,56 @@ | |||
| #ifndef NESE_PPU_H_ | |||
| #define NESE_PPU_H_ | |||
| #include <stdint.h> | |||
| #define nes_ppu_scanline_dots (341U) | |||
| #define nes_ppu_visible_line (1U) | |||
| #define nes_ppu_postrender_line (241U) | |||
| #define nes_ppu_vblank_line (242U) | |||
| #define nes_ppu_frame_end_line (262U) | |||
| #define NES_CHR_ROM_PAGE_SIZE (0x0400U) | |||
| #define NES_VRAM_PAGE_SIZE (0x0400U) | |||
| #define NES_PPU_CHR_SIZE (0x2000U) | |||
| #define NES_PPU_VRAM_SIZE (0x1000U) | |||
| #define NES_PPU_RAM_SIZE (0x4000U) | |||
| #define NES_PPU_OAM_SIZE (64U * 4U) | |||
| typedef struct __attribute__ ((__packed__)) { | |||
| uint8_t* chr; | |||
| int n_chr_banks; | |||
| uint8_t* chr_ram; // Could this be a flag? | |||
| uint8_t* bank[16]; | |||
| uint8_t vram[NES_PPU_VRAM_SIZE]; | |||
| uint8_t pal_bank[NES_CHR_ROM_PAGE_SIZE]; // Mirrored in banks 12-15, also pal | |||
| uint8_t oam[NES_PPU_OAM_SIZE]; | |||
| } nes_PPU_Memory; | |||
| static inline uint8_t* chr_page(nes_PPU_Memory* mem, | |||
| int page) { | |||
| return &mem->chr[page * NES_CHR_ROM_PAGE_SIZE]; | |||
| } | |||
| static inline uint8_t* vram_page(nes_PPU_Memory* mem, | |||
| int page) { | |||
| return &mem->vram[page * NES_VRAM_PAGE_SIZE]; | |||
| } | |||
| #define nes_PPU_Nametable_Bank_Index (8U) | |||
| typedef enum { | |||
| nes_Mirror_Horizontal = 0, | |||
| nes_Mirror_Vertical, | |||
| nes_Mirror_Only_0, | |||
| nes_Mirror_Only_1, | |||
| nes_Mirror_Four, | |||
| } nes_Nametable_Mirroring; | |||
| void nes_PPU_set_mirroring(nes_PPU_Memory* mem, | |||
| nes_Nametable_Mirroring mirror); | |||
| #endif // NESE_PPU_H_ | |||