~groovestomp/gsnes

da0fecdeab403ad4a9b92614caf5437e56403243 — GrooveStomp 1 year, 6 months ago 569e68e
Progress from the last few days

- Basic PPU implementation
- Basic memory mapper implementation
- Generic memory mapper interface
- New sprite datatype and methods
- Copied color datatype and methods from 3dsw
- util.h with some helper macros
16 files changed, 1083 insertions(+), 16 deletions(-)

M bus.c
A cart.c
A cart.h
A color.c
A color.h
M cpu.c
M cpu.h
M main.c
A mapper.h
A mapper000.c
A mapper000.h
A ppu.c
A ppu.h
A sprite.c
A sprite.h
A util.h
M bus.c => bus.c +41 -8
@@ 4,7 4,7 @@

  File: bus.c
  Created: 2019-10-16
  Updated: 2019-10-31
  Updated: 2019-11-05
  Author: Aaron Oman
  Notice: GNU AGPLv3 License



@@ 17,19 17,24 @@

#include "bus.h"
#include "cpu.h"
#include "ppu.h"
#include "cart.h"
//! \file bus.c

struct bus {
        struct cpu *cpu;
        uint8_t *ram; // Dummy RAM for prototyping
        struct ppu *ppu;
        struct cart *cart;
        uint8_t *cpu_ram; // Dummy RAM for prototyping
        uint32_t tick_count;
};

struct bus *BusInit(struct cpu *cpu) {
        struct bus *bus = (struct bus *)malloc(sizeof(struct bus));

        bus->ram = (uint8_t *)malloc(64 * 1024);
        bus->cpu_ram = (uint8_t *)malloc(64 * 1024);
        for (int i = 0; i < 64 * 1024; i++) {
                bus->ram[i] = 0x00;
                bus->cpu_ram[i] = 0x00;
        }

        bus->cpu = cpu;


@@ 41,8 46,8 @@ void BusDeinit(struct bus *bus) {
                return;
        }

        if (NULL != bus->ram) {
                free(bus->ram);
        if (NULL != bus->cpu_ram) {
                free(bus->cpu_ram);
        }

        free(bus);


@@ 50,14 55,42 @@ void BusDeinit(struct bus *bus) {

void BusWrite(struct bus *bus, uint16_t addr, uint8_t data) {
        if (addr >= 0x0000 && addr <= 0xFFFF) {
                bus->ram[addr] = data;
                bus->cpu_ram[addr] = data;
        }
}

uint8_t BusRead(struct bus *bus, uint16_t addr) {
        if (addr >= 0x0000 && addr <= 0xFFFF) {
                return bus->ram[addr];
                return bus->cpu_ram[addr];
        }

        return 0x00;
}

void BusAttachCart(struct bus *bus, struct cart *cart) {
        bus->cart = cart;
        PpuAttachCart(bus->ppu, cart);
}

void BusReset(struct bus *bus) {
        CpuReset(bus->cpu);
        bus->tick_count = 0;
}

//! \brief Increment the system by the fastest clock tick
//!
//! The running frequency is determined by the fastest clock in the system - in
//! this case, the PPU.  Every other clock is some fraction slower than the PPU,
//! so we compute a modulus every cycle to determine when to increment other
//! clocks.
//!
//! \param[in,out] bus
void BusTick(struct bus *bus) {
        PpuTick(bus->ppu);

        if (0 == (bus->tick_count % 3)) {
                CpuTick(bus->cpu);
        }

        bus->tick_count++;
}

A cart.c => cart.c +188 -0
@@ 0,0 1,188 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: cart.c
  Created: 2019-11-03
  Updated: 2019-11-04
  Author: Aaron Oman
  Notice: GNU AGPLv3 License

  Based off of: One Lone Coder NES Emulator Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
 ******************************************************************************/
#include <stdint.h>
#include <stdio.h> // fopen
#include <stdbool.h> // bool
#include <stdlib.h> // malloc

#include "cart.h"
#include "util.h"
#include "mapper.h"
#include "mapper000.h"
//! \file cart.c

struct cart {
        uint8_t mapper_id;
        uint8_t prg_banks;
        uint8_t chr_banks;
        bool is_image_valid;
        uint8_t *prg_mem;
        uint8_t *chr_mem;
        void *mapper;
        map_cpu_read_fn map_cpu_read;
        map_cpu_write_fn map_cpu_write;
        map_ppu_read_fn map_ppu_read;
        map_ppu_write_fn map_ppu_write;
};

struct header {
        char name[4];
        uint8_t prg_rom_chunks;
        uint8_t chr_rom_chunks;
        uint8_t mapper1;
        uint8_t mapper2;
        uint8_t prg_ram_size;
        uint8_t tv_system1;
        uint8_t tv_system2;
        char unused[5];
};

struct cart *CartInit(char *filename) {
        struct cart *cart = (struct cart *)malloc(sizeof(struct cart));
        if (NULL == cart) {
                return NULL;
        }

        cart->is_image_valid = false;
        cart->mapper_id = 0;
        cart->prg_banks = 0;
        cart->chr_banks = 0;

        FILE *f = fopen(filename, "rb");
        if (NULL == f) {
                free(cart);
                return NULL;
        }
        struct header header;
        size_t objs_read = fread(&header, sizeof(struct header), 1, f);
        if (objs_read != 1) {
                fclose(f);
                free(cart);
                return NULL;
        }

        // For this mapper, the next 512 bytes are trainer info; which we ignore.
        if (header.mapper1 & 0x04) {
                fseek(f, 512, SEEK_CUR);
        }

        // Determine mapper ID
        cart->mapper_id = ((header.mapper2 >> 4) << 4) | (header.mapper1 >> 4);

        // "Discover" file format.
        uint8_t file_type = 1;
        if (0 == file_type) {
                // TODO unhandled file_type
        }

        if (1 == file_type) {
                cart->prg_banks = header.prg_rom_chunks;
                cart->prg_mem = (uint8_t *)malloc(sizeof(uint8_t) * KB_AS_B(16));
                objs_read = fread(cart->prg_mem, 1, KB_AS_B(16), f);
                if (objs_read < KB_AS_B(16)) {
                        fclose(f);
                        free(cart);
                        return NULL;
                        // TODO Couldn't read data from file - set appropriate error
                }

                cart->chr_banks = header.chr_rom_chunks;
                cart->chr_mem = (uint8_t *)malloc(sizeof(uint8_t) * KB_AS_B(8));
                objs_read = fread(cart->chr_mem, 1, KB_AS_B(8), f);
                if (objs_read < KB_AS_B(8)) {
                        fclose(f);
                        free(cart);
                        return NULL;
                        // TODO Couldn't read data from file - set appropriate error
                }
        }

        if (2 == file_type) {
                // TODO unhandled file_type
        }

        switch(cart->mapper_id) {
                case 0: {
                        cart->mapper = Mapper000_Init(cart->prg_banks, cart->chr_banks);
                        if (NULL == cart->mapper) {
                                // TODO handle cart->mapper not being initialized
                        }
                        cart->map_cpu_read = Mapper000_MapCpuRead;
                        cart->map_cpu_write = Mapper000_MapCpuWrite;
                        cart->map_ppu_read = Mapper000_MapPpuRead;
                        cart->map_ppu_write = Mapper000_MapPpuWrite;
                        break;
                }
        }

        cart->is_image_valid = true;
        fclose(f);
        return cart;
}

void CartDeinit(struct cart *cart) {
        if (NULL == cart) {
                return;
        }

        if (NULL != cart->chr_mem) {
                free(cart->chr_mem);
        }

        if (NULL != cart->prg_mem) {
                free(cart->prg_mem);
        }
}

bool CartCpuRead(struct cart *cart, uint16_t addr, uint8_t *data) {
        uint32_t mapped_addr = 0;
        if (cart->map_cpu_read(cart->mapper, addr, &mapped_addr)) {
                *data = cart->prg_mem[mapped_addr];
                return true;
        }

        return false;
}

bool CartCpuWrite(struct cart *cart, uint16_t addr, uint8_t data) {
        uint32_t mapped_addr = 0;
        if (cart->map_cpu_write(cart->mapper, addr, &mapped_addr)) {
                cart->prg_mem[mapped_addr] = data;
                return true;
        }

        return false;
}

bool CartPpuRead(struct cart *cart, uint16_t addr, uint8_t *data) {
        uint32_t mapped_addr = 0;
        if (cart->map_ppu_read(cart->mapper, addr, &mapped_addr)) {
                *data = cart->chr_mem[mapped_addr];
                return true;
        }

        return false;
}

bool CartPpuWrite(struct cart *cart, uint16_t addr, uint8_t data) {
        uint32_t mapped_addr = 0;
        if (cart->map_ppu_write(cart->mapper, addr, &mapped_addr)) {
                cart->chr_mem[mapped_addr] = data;
                return true;
        }

        return false;
}

A cart.h => cart.h +43 -0
@@ 0,0 1,43 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: cart.h
  Created: 2019-11-03
  Updated: 2019-11-03
  Author: Aaron Oman
  Notice: GNU AGPLv3 License

  Based off of: One Lone Coder NES Emulator Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
 ******************************************************************************/
#include <stdint.h>
#include <stdbool.h>
//! \file cart.h

#ifndef CART_VERSION
#define CART_VERSION "0.1.0"

struct cart;

struct cart *
CartInit();

void
CartDeinit(struct cart *cart);

bool
CartCpuRead(struct cart *cart, uint16_t addr, uint8_t *data);

bool
CartCpuWrite(struct cart *cart, uint16_t addr, uint8_t data);

bool
CartPpuRead(struct cart *cart, uint16_t addr, uint8_t *data);

bool
CartPpuWrite(struct cart *cart, uint16_t addr, uint8_t data);

#endif // CART_VERSION

A color.c => color.c +115 -0
@@ 0,0 1,115 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: color.c
  Created: 2019-08-15
  Updated: 2019-11-05
  Author: Aaron Oman
  Notice: GNU GPLv3 License

  Based off of: One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
 ******************************************************************************/

//! \file color.c

#include "color.h"

struct color ColorWhite = { 0xFFFFFFFF };
struct color ColorBlack = { 0x000000FF };
struct color ColorRed = { 0xFF0000FF };
struct color ColorGreen = { 0x00FF00FF };
struct color ColorBlue = { 0x0000FFFF };
struct color ColorPurple = { 0x7F00FFFF };
struct color ColorYellow = { 0xFFFF00FF };
struct color ColorCyan = { 0x00FFFFFF };
struct color ColorPink = { 0xFF00FFFF };

void ColorSetInt(struct color *color, char component, unsigned int value) {
        unsigned int pos = 0;
        switch (component) {
                case 'r':
                        pos = 3;
                        break;
                case 'g':
                        pos = 2;
                        break;
                case 'b':
                        pos = 1;
                        break;
                case 'a':
                        pos = 0;
                        break;
                default:
                        pos = 0;
        }

        unsigned int shift = pos * 8;

        unsigned int rgba = color->rgba & ~(0xFF << shift);
        color->rgba = rgba | (value << shift);
}

void ColorSetFloat(struct color *color, char component, float value) {
        unsigned int intVal = (unsigned int)(value * 255.0f);
        ColorSetInt(color, component, intVal);
}

struct color ColorInitFloats(float r, float g, float b, float a) {
        struct color color = { 0 };

        ColorSetFloat(&color, 'r', r);
        ColorSetFloat(&color, 'g', g);
        ColorSetFloat(&color, 'b', b);
        ColorSetFloat(&color, 'a', a);

        return color;
}

unsigned int ColorGetInt(struct color color, char component) {
        unsigned int pos = 0;
        switch (component) {
                case 'r':
                        pos = 3;
                        break;
                case 'g':
                        pos = 2;
                        break;
                case 'b':
                        pos = 1;
                        break;
                case 'a':
                        pos = 0;
                        break;
                default:
                        pos = 0;
        }

        unsigned int shift = pos * 8;
        return (color.rgba >> shift) & 0xFF;
}

float ColorGetFloat(struct color color, char component) {
        int value = ColorGetInt(color, component);
        return (float)value / 255.0f;
}

struct color ColorInitInts(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
        struct color color = { 0 };

        ColorSetInt(&color, 'r', r);
        ColorSetInt(&color, 'g', g);
        ColorSetInt(&color, 'b', b);
        ColorSetInt(&color, 'a', a);

        return color;
}

struct color ColorInitInt(uint32_t rgba) {
        struct color color = { 0 };
        color.rgba = rgba;
        return color;
}

A color.h => color.h +112 -0
@@ 0,0 1,112 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: color.h
  Created: 2019-08-15
  Updated: 2019-11-05
  Author: Aaron Oman
  Notice: GNU GPLv3 License

  Based off of: One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
  Notice: Creative Commons Attribution 4.0 International License (CC-BY 4.0)
 ******************************************************************************/
#include <stdint.h>

//! \file color.h
//! This interface attempts to provide an intuitive wrapper around "raw"
//! unsigned integer colors.
//!
//! An unsigned integer color is packed 32-bit value consisting of 4 pixel
//! elements: RGBA.  These elements are stored as written: RGBA, or, visually
//! mapped as hex symbols: RRGGBBAA.

#ifndef COLOR_VERSION
#define COLOR_VERSION "0.2-gsnes" //!< include guard

//! RGBA color quad
struct color {
        uint32_t rgba;
};

//! \brief Initialize a new color with individual R, G, B, A components as floats.
//!
//! \param r Red component from 0 to 1
//! \param g Green componenet from 0 to 1
//! \param b Blue component from 0 to 1
//! \param a Alpha component, from 0 to 1
//! \return resulting color object
struct color
ColorInitFloats(float r, float g, float b, float a);

//! \brief Initialize a new color with individual R, G, B, A components as ints.
//!
//! \param r Red component from 0 to 255
//! \param g Green componenet from 0 to 255
//! \param b Blue component from 0 to 255
//! \param a Alpha component, from 0 to 255
//! \return resulting color object
struct color
ColorInitInts(uint8_t r, uint8_t g, uint8_t b, uint8_t a);

//! \brief Initialize a new color with an rgba component from a color
//!
//! \param rgba 32-bit r,g,b,a packed int.
//! \return resulting color object
struct color
ColorInitInt(uint32_t rgba);

//! \brief Get the color component
//!
//! The component is returned as the raw integer value, in the range [0,255]
//!
//! \param color color object to read
//! \param component 'r', 'g', 'b' or 'a' exclusively.
//! \return value of the color component
unsigned int
ColorGetInt(struct color color, char component);

//! \brief Get the color component
//!
//! The component is returned as a float in the range [0.0,1.0]
//!
//! \param color color object to read
//! \param component 'r', 'g', 'b' or 'a' exclusively.
//! \return value of the color component
float
ColorGetFloat(struct color color, char component);

//! \brief Set the color component
//!
//! The value should be an integer in the range [0,255]
//!
//! \param color pointer to the color object to write
//! \param component 'r', 'g', 'b' or 'a' exclusively.
//! \param value value of the color component to set
void
ColorSetInt(struct color *color, char component, unsigned int value);

//! \brief Set the color component
//!
//! The value should be a float in the range [0.0,1.0]
//!
//! \param color pointer to the color object to write
//! \param component 'r', 'g', 'b' or 'a' exclusively.
//! \param value value of the color component to set
void
ColorSetFloat(struct color *color, char component, float value);

extern struct color ColorWhite;
extern struct color ColorBlack;
extern struct color ColorRed;
extern struct color ColorGreen;
extern struct color ColorBlue;
extern struct color ColorPurple;
extern struct color ColorYellow;
extern struct color ColorCyan;
extern struct color ColorPink;

#endif // COLOR_VERSION

M cpu.c => cpu.c +9 -5
@@ 1,10 1,11 @@

/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: cpu.c
  Created: 2019-10-16
  Updated: 2019-10-31
  Updated: 2019-11-05
  Author: Aaron Oman
  Notice: GNU AGPLv3 License



@@ 95,6 96,7 @@ struct cpu {
        uint16_t addr_rel;
        uint8_t opcode; //!< Opcode for the currently executing instruction
        uint8_t cycles; //!< How many cycles the current instruction takes
        uint32_t tick_count;
};

enum status_flags {


@@ 124,6 126,7 @@ struct cpu *CpuInit() {
        cpu->addr_rel = 0x00;
        cpu->opcode = 0x00;
        cpu->cycles = 0;
        cpu->tick_count = 0;
}

void CpuDeinit(struct cpu *cpu) {


@@ 136,18 139,18 @@ void CpuConnectBus(struct cpu *cpu, struct bus *bus) {
        cpu->bus = bus;
}

uint8_t GetFlag(struct cpu *cpu, enum status_flags f) {
static uint8_t GetFlag(struct cpu *cpu, enum status_flags f) {
        return ((cpu->status & f) > 0) ? 1 : 0;
}

void SetFlag(struct cpu *cpu, enum status_flags f, bool v) {
static void SetFlag(struct cpu *cpu, enum status_flags f, bool v) {
        if (v)
                cpu->status |= f;
        else
                cpu->status &= ~f;
}

uint8_t Fetch(struct cpu* cpu) {
static uint8_t Fetch(struct cpu* cpu) {
        if (!(instruction_map[cpu->opcode].address == IMP)) {
                cpu->fetched = BusRead(cpu->bus, cpu->addr_abs);
        }


@@ 156,7 159,7 @@ uint8_t Fetch(struct cpu* cpu) {
}

//! \brief Simulate clock ticks
void Clock(struct cpu *cpu) {
void CpuTick(struct cpu *cpu) {
        if (0 == cpu->cycles) {
                // Read the next byte to determine which opcode we are using.
                cpu->opcode = BusRead(cpu->bus, cpu->pc);


@@ 175,6 178,7 @@ void Clock(struct cpu *cpu) {
                cpu->cycles += (need_more_cycles_1 & need_more_cycles_2);
        }

        cpu->tick_count++;
        cpu->cycles--;
}


M cpu.h => cpu.h +3 -0
@@ 35,6 35,9 @@ CpuConnectBus(struct cpu *cpu, struct bus *bus);
void
CpuReset(struct cpu *cpu);

void
CpuTick(struct cpu *cpu);

//-- Debug ---------------------------------------------------------------------

struct debug_instruction {

M main.c => main.c +3 -3
@@ 70,12 70,12 @@ int main(int argc, char **argv) {
        struct debug_instruction_map *map = CpuDisassemble(cpu, 0x0000, 0x000F);

        for (int i = 0; i < map->count; i++) {
                printf("map{%p}", map);
                printf("map{%p}", (void *)map);
                if (NULL != map) {
                        printf("->map{%p}", map->map);
                        printf("->map{%p}", (void *)map->map);

                        if (NULL != map->map) {
                                printf("[i]{%p}", map->map[i]);
                                printf("[i]{%p}", (void *)map->map[i]);

                                if (NULL != map->map[i]) {
                                        printf("->text{%s}", map->map[i]->text);

A mapper.h => mapper.h +29 -0
@@ 0,0 1,29 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: mapper.h
  Created: 2019-11-04
  Updated: 2019-11-04
  Author: Aaron Oman
  Notice: GNU AGPLv3 License

  Based off of: One Lone Coder NES Emulator Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
 ******************************************************************************/
#include <stdint.h>
//! \file mapper.h

typedef bool (*map_cpu_read_fn)(void *interface, uint16_t addr, uint32_t *mapped_addr);

typedef bool (*map_cpu_write_fn)(void *interface, uint16_t addr, uint32_t *mapped_addr);

typedef bool (*map_ppu_read_fn)(void *interface, uint16_t addr, uint32_t *mapped_addr);

typedef bool (*map_ppu_write_fn)(void *interface, uint16_t addr, uint32_t *mapped_addr);

typedef void *(*mapper_init_fn)(uint8_t prg_banks, uint8_t chr_banks);

typedef void (*mapper_deinit_fn)(void *interface);

A mapper000.c => mapper000.c +145 -0
@@ 0,0 1,145 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: mapper000.c
  Created: 2019-11-04
  Updated: 2019-11-04
  Author: Aaron Oman
  Notice: GNU AGPLv3 License

  Based off of: One Lone Coder NES Emulator Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
 ******************************************************************************/
#include <stdlib.h> // malloc, free

#include "mapper000.h"
//! \file mapper000.c

struct mapper000 {
        uint8_t prg_banks;
        uint8_t chr_banks;
};

void *Mapper000_Init(uint8_t prg_banks, uint8_t chr_banks) {
        struct mapper000 *mapper = (struct mapper000 *)malloc(sizeof(struct mapper000));
        if (NULL == mapper) {
                return NULL;
        }

        mapper->prg_banks = prg_banks;
        mapper->chr_banks = chr_banks;

        return (void *)mapper;
}

void Mapper000_Deinit(void *interface) {
        if (NULL == interface) {
                return;
        }

        free(interface);
        interface = NULL;
}

//! \brief Map cpu read address to expanded address
//!
//! If PRGRPM is 16KB:
//!   Cpu Address Bus           PRG ROM
//!   ---------------           ---------
//!   0x8000 -> 0xBFFF: Map     0x0000 -> 0x3FFF
//!   0xC000 -> 0xFFFF: Mirror  0x0000 -> 0x3FFF
//!
//! If PRGRPM is 32KB:
//!   Cpu Address Bus           PRG ROM
//!   ---------------           ---------
//!   0x8000 -> 0xBFFF: Map     0x0000 -> 0x7FFF
//!
//! \see Mapper000_MapCpuWrite
//!
//! \param[in,out] mapper
//! \param[in] addr Address to be mapped
//! \param[out] mapped_addr Mapped address
//! \return true if address has been mapped
bool Mapper000_MapCpuRead(void *interface, uint16_t addr, uint32_t *mapped_addr) {
        struct mapper000 *mapper = (struct mapper000 *)interface;
        uint8_t mask = (mapper->prg_banks > 1) ? 0x7FFF : 0x3FFF;

        if (addr >= 0x8000 && addr <= 0xFFFF) {
                *mapped_addr = addr & mask;
                return true;
        }

        return false;
}

//! \brief Map cpu write address to expanded address
//!
//! If PRGRPM is 16KB:
//!   Cpu Address Bus           PRG ROM
//!   ---------------           ---------
//!   0x8000 -> 0xBFFF: Map     0x0000 -> 0x3FFF
//!   0xC000 -> 0xFFFF: Mirror  0x0000 -> 0x3FFF
//!
//! If PRGRPM is 32KB:
//!   Cpu Address Bus           PRG ROM
//!   ---------------           ---------
//!   0x8000 -> 0xBFFF: Map     0x0000 -> 0x7FFF
//!
//! \see Mapper000_MapCpuRead
//!
//! \param[in,out] mapper
//! \param[in] addr Address to be mapped
//! \param[out] mapped_addr Mapped address
//! \return true if address has been mapped
bool Mapper000_MapCpuWrite(void *interface, uint16_t addr, uint32_t *mapped_addr) {
        struct mapper000 *mapper = (struct mapper000 *)interface;
        uint8_t mask = (mapper->prg_banks > 1) ? 0x7FFF : 0x3FFF;

        if (addr >= 0x8000 && addr <= 0xFFFF) {
                *mapped_addr = addr & mask;
                return true;
        }

        return false;
}

//! \brief Map ppu read address to expanded address
//!
//! \see Mapper000_MapPpuWrite
//!
//! \param[in,out] mapper
//! \param[in] addr Address to be mapped
//! \param[out] mapped_addr Mapped address
//! \return true if address has been mapped
bool Mapper000_MapPpuRead(void *interface, uint16_t addr, uint32_t *mapped_addr) {
        if (addr >= 0x0000 && addr <= 0x1FFF) {
                *mapped_addr = addr;
                return true;
        }

        return false;
}

//! \brief Map ppu write address to expanded address
//!
//! \see Mapper000_MapPpuRead
//!
//! \param[in,out] mapper
//! \param[in] addr Address to be mapped
//! \param[out] mapped_addr Mapped address
//! \return true if address has been mapped
bool Mapper000_MapPpuWrite(void *interface, uint16_t addr, uint32_t *mapped_addr) {
        struct mapper000 *mapper = (struct mapper000 *)interface;
        if (addr >= 0x0000 && addr <= 0x1FFF) {
                if (0 == mapper->chr_banks) {
                        // Treat as RAM.
                        *mapped_addr = addr;
                        return true;
                }
        }

        return false;
}

A mapper000.h => mapper000.h +38 -0
@@ 0,0 1,38 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: mapper000.h
  Created: 2019-11-04
  Updated: 2019-11-04
  Author: Aaron Oman
  Notice: GNU AGPLv3 License

  Based off of: One Lone Coder NES Emulator Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
 ******************************************************************************/
#include <stdint.h>
#include <stdbool.h>
//! \file mapper000.h

struct mapper000;

void *
Mapper000_Init(uint8_t prg_banks, uint8_t chr_banks);

void
Mapper000_Deinit(void *mapper);

bool
Mapper000_MapCpuRead(void *mapper, uint16_t addr, uint32_t *mapped_addr);

bool
Mapper000_MapCpuWrite(void *mapper, uint16_t addr, uint32_t *mapped_addr);

bool
Mapper000_MapPpuRead(void *mapper, uint16_t addr, uint32_t *mapped_addr);

bool
Mapper000_MapPpuWrite(void *mapper, uint16_t addr, uint32_t *mapped_addr);

A ppu.c => ppu.c +173 -0
@@ 0,0 1,173 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: ppu.c
  Created: 2019-11-03
  Updated: 2019-11-05
  Author: Aaron Oman
  Notice: GNU AGPLv3 License

  Based off of: One Lone Coder NES Emulator Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
 ******************************************************************************/
#include <stdlib.h> // malloc, free
#include <stdbool.h> // bool

#include "ppu.h"
#include "cart.h"
#include "color.h"
#include "sprite.h"
//! \file ppu.c

struct ppu {
        struct cart *cart;

        bool is_frame_complete;
        int16_t scanline; //!< Which row on the screen we are computing.
        int16_t cycle; //!< Which column on the screen we are computing.

        struct color *palette;
        struct sprite *screen;
        struct sprite **name_tables;
        struct sprite **pattern_tables;
};

struct ppu *PpuInit() {
        struct ppu *ppu = (struct ppu *)malloc(sizeof(struct ppu));
        if (NULL == ppu) {
                return NULL;
        }

        ppu->palette = (struct color *)malloc(sizeof(struct color) * 0x40);
        if (NULL == ppu->palette) {
                free(ppu);
                return NULL;
        }

        ppu->screen = SpriteInit(256, 240);
        if (NULL == ppu->screen) {
                return NULL;
        }

        ppu->name_tables = (struct sprite **)malloc(sizeof(struct sprite *) * 2);
        ppu->name_tables[0] = SpriteInit(256, 240);
        ppu->name_tables[1] = SpriteInit(256, 240);
        // TODO: Error handling

        ppu->pattern_tables = (struct sprite **)malloc(sizeof(struct sprite *) * 2);
        ppu->pattern_tables[0] = SpriteInit(128, 128);
        ppu->pattern_tables[1] = SpriteInit(128, 128);
        // TODO: Error handling

        ppu->palette[0x00] = ColorInitInts(84, 84, 84, 255);
	ppu->palette[0x01] = ColorInitInts(0, 30, 116, 255);
	ppu->palette[0x02] = ColorInitInts(8, 16, 144, 255);
	ppu->palette[0x03] = ColorInitInts(48, 0, 136, 255);
	ppu->palette[0x04] = ColorInitInts(68, 0, 100, 255);
	ppu->palette[0x05] = ColorInitInts(92, 0, 48, 255);
	ppu->palette[0x06] = ColorInitInts(84, 4, 0, 255);
	ppu->palette[0x07] = ColorInitInts(60, 24, 0, 255);
	ppu->palette[0x08] = ColorInitInts(32, 42, 0, 255);
	ppu->palette[0x09] = ColorInitInts(8, 58, 0, 255);
	ppu->palette[0x0A] = ColorInitInts(0, 64, 0, 255);
	ppu->palette[0x0B] = ColorInitInts(0, 60, 0, 255);
	ppu->palette[0x0C] = ColorInitInts(0, 50, 60, 255);
	ppu->palette[0x0D] = ColorInitInts(0, 0, 0, 255);
	ppu->palette[0x0E] = ColorInitInts(0, 0, 0, 255);
	ppu->palette[0x0F] = ColorInitInts(0, 0, 0, 255);

	ppu->palette[0x10] = ColorInitInts(152, 150, 152, 255);
	ppu->palette[0x11] = ColorInitInts(8, 76, 196, 255);
	ppu->palette[0x12] = ColorInitInts(48, 50, 236, 255);
	ppu->palette[0x13] = ColorInitInts(92, 30, 228, 255);
	ppu->palette[0x14] = ColorInitInts(136, 20, 176, 255);
	ppu->palette[0x15] = ColorInitInts(160, 20, 100, 255);
	ppu->palette[0x16] = ColorInitInts(152, 34, 32, 255);
	ppu->palette[0x17] = ColorInitInts(120, 60, 0, 255);
	ppu->palette[0x18] = ColorInitInts(84, 90, 0, 255);
	ppu->palette[0x19] = ColorInitInts(40, 114, 0, 255);
	ppu->palette[0x1A] = ColorInitInts(8, 124, 0, 255);
	ppu->palette[0x1B] = ColorInitInts(0, 118, 40, 255);
	ppu->palette[0x1C] = ColorInitInts(0, 102, 120, 255);
	ppu->palette[0x1D] = ColorInitInts(0, 0, 0, 255);
	ppu->palette[0x1E] = ColorInitInts(0, 0, 0, 255);
	ppu->palette[0x1F] = ColorInitInts(0, 0, 0, 255);

	ppu->palette[0x20] = ColorInitInts(236, 238, 236, 255);
	ppu->palette[0x21] = ColorInitInts(76, 154, 236, 255);
	ppu->palette[0x22] = ColorInitInts(120, 124, 236, 255);
	ppu->palette[0x23] = ColorInitInts(176, 98, 236, 255);
	ppu->palette[0x24] = ColorInitInts(228, 84, 236, 255);
	ppu->palette[0x25] = ColorInitInts(236, 88, 180, 255);
	ppu->palette[0x26] = ColorInitInts(236, 106, 100, 255);
	ppu->palette[0x27] = ColorInitInts(212, 136, 32, 255);
	ppu->palette[0x28] = ColorInitInts(160, 170, 0, 255);
	ppu->palette[0x29] = ColorInitInts(116, 196, 0, 255);
	ppu->palette[0x2A] = ColorInitInts(76, 208, 32, 255);
	ppu->palette[0x2B] = ColorInitInts(56, 204, 108, 255);
	ppu->palette[0x2C] = ColorInitInts(56, 180, 204, 255);
	ppu->palette[0x2D] = ColorInitInts(60, 60, 60, 255);
	ppu->palette[0x2E] = ColorInitInts(0, 0, 0, 255);
	ppu->palette[0x2F] = ColorInitInts(0, 0, 0, 255);

	ppu->palette[0x30] = ColorInitInts(236, 238, 236, 255);
	ppu->palette[0x31] = ColorInitInts(168, 204, 236, 255);
	ppu->palette[0x32] = ColorInitInts(188, 188, 236, 255);
	ppu->palette[0x33] = ColorInitInts(212, 178, 236, 255);
	ppu->palette[0x34] = ColorInitInts(236, 174, 236, 255);
	ppu->palette[0x35] = ColorInitInts(236, 174, 212, 255);
	ppu->palette[0x36] = ColorInitInts(236, 180, 176, 255);
	ppu->palette[0x37] = ColorInitInts(228, 196, 144, 255);
	ppu->palette[0x38] = ColorInitInts(204, 210, 120, 255);
	ppu->palette[0x39] = ColorInitInts(180, 222, 120, 255);
	ppu->palette[0x3A] = ColorInitInts(168, 226, 144, 255);
	ppu->palette[0x3B] = ColorInitInts(152, 226, 180, 255);
	ppu->palette[0x3C] = ColorInitInts(160, 214, 228, 255);
	ppu->palette[0x3D] = ColorInitInts(160, 162, 160, 255);
	ppu->palette[0x3E] = ColorInitInts(0, 0, 0, 255);
	ppu->palette[0x3F] = ColorInitInts(0, 0, 0, 255);

        ppu->cycle = 0;
        return ppu;
}

void PpuDeinit(struct ppu *ppu) {
        if (NULL == ppu) {
                return;
        }

        free(ppu);
}

void PpuAttachCart(struct ppu *ppu, struct cart *cart) {
        ppu->cart = cart;
}

//! \brief advance the renderer one pixel across the screen
//!
//! Advance the pixel right one pixel on the current row, or to the start of the
//! next row if we've reached the screen edge.
//! When the ppu has reached the end of the screen entirely, set
//! is_frame_complete to true.
//!
//! \param[in,out] ppu
void PpuTick(struct ppu *ppu) {
        uint16_t idx = (rand() % 2) ? 0x3F : 0x30;
        struct color color = ppu->palette[idx];
        SpriteSetPixel(ppu->screen, ppu->cycle - 1, ppu->scanline, color.rgba);

        ppu->cycle++;

        if (341 < ppu->cycle) {
                ppu->cycle = 0;
                ppu->scanline++;

                if (261 < ppu->scanline) {
                        ppu->scanline = -1;
                        ppu->is_frame_complete = true;
                }
        }
}

A ppu.h => ppu.h +35 -0
@@ 0,0 1,35 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: ppu.h
  Created: 2019-11-03
  Updated: 2019-11-03
  Author: Aaron Oman
  Notice: GNU AGPLv3 License

  Based off of: One Lone Coder NES Emulator Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
 ******************************************************************************/
#ifndef PPU_VERSION
#define PPU_VERSION "0.1.0"
//! \file ppu.h

struct ppu;
struct cart;

struct ppu *
PpuInit();

void
PpuDeinit(struct ppu *ppu);

void
PpuAttachCart(struct ppu *ppu, struct cart *cart);

void
PpuTick(struct ppu *ppu);

#endif // PPU_VERSION

A sprite.c => sprite.c +79 -0
@@ 0,0 1,79 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: sprite.c
  Created: 2019-11-05
  Updated: 2019-11-05
  Author: Aaron Oman
  Notice: GNU AGPLv3 License

  Based off of: One Lone Coder NES Emulator Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
******************************************************************************/
#include <stdlib.h> // malloc, free
#include <math.h> // fmin

#include "sprite.h"
#include "color.h"
//! \file sprite.c

struct sprite {
        struct color *pixels;
        uint32_t width;
        uint32_t height;
};

struct sprite *SpriteInit(unsigned int width, unsigned int height) {
        struct sprite *sprite = (struct sprite *)malloc(sizeof(struct sprite));
        if (NULL == sprite) {
                return NULL;
        }

        sprite->pixels = (struct color *)malloc(sizeof(struct color) * width * height);
        if (NULL == sprite->pixels) {
                free(sprite);
                return NULL;
        }

        sprite->width = width;
        sprite->height = height;

        return sprite;
}

void SpriteDeinit(struct sprite *sprite) {
        if (NULL == sprite) {
                return;
        }

        if (NULL != sprite->pixels) {
                free(sprite->pixels);
                sprite->pixels = NULL;
        }

        free(sprite);
}

void SpriteSetPixel(struct sprite *sprite, unsigned int x, unsigned int y, uint32_t rgba) {
        if (x >= 0 && x < sprite->width && y >= 0 && y < sprite->height) {
                sprite->pixels[y * sprite->width + x] = ColorInitInt(rgba);
        }
}

uint32_t SpriteSample(struct sprite *sprite, float x, float y) {
        int32_t sx = fminf(x * (float)sprite->width, sprite->width - 1);
        int32_t sy = fminf(y * (float)sprite->height, sprite->height - 1);

        if (sx >= 0 && sx < sprite->width && sy >= 0 && sy < sprite->height) {
                return sprite->pixels[sy * sprite->width + sx].rgba;
        }

        return ColorInitFloats(0, 0, 0, 0).rgba;
}

struct color *SpriteData(struct sprite *sprite) {
        return sprite->pixels;
}

A sprite.h => sprite.h +35 -0
@@ 0,0 1,35 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: sprite.h
  Created: 2019-11-05
  Updated: 2019-11-05
  Author: Aaron Oman
  Notice: GNU AGPLv3 License

  Based off of: One Lone Coder NES Emulator Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
 ******************************************************************************/
#include <stdint.h>
//! \file sprite.h

struct sprite;
struct color;

struct sprite *
SpriteInit(unsigned int width, unsigned int height);

void
SpriteDeinit(struct sprite *sprite);

void
SpriteSetPixel(struct sprite *sprite, unsigned int x, unsigned int y, uint32_t rgba);

uint32_t
SpriteSample(struct sprite *sprite, float x, float y);

struct color *
SpriteData(struct sprite *sprite);

A util.h => util.h +35 -0
@@ 0,0 1,35 @@
/******************************************************************************
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: util.h
  Created: 2019-11-03
  Updated: 2019-11-03
  Author: Aaron Oman
  Notice: GNU AGPLv3 License

  Based off of: One Lone Coder NES Emulator Copyright (C) 2018 Javidx9
  This program comes with ABSOLUTELY NO WARRANTY.
  This is free software, and you are welcome to redistribute it under certain
  conditions; See LICENSE for details.
 ******************************************************************************/
#include <stdint.h>
//! \file util.h

#ifndef UTIL_VERSION
#define UTIL_VERSION "0.1.0"

#define S_AS_MS(x) (x) * 1000.0 //!< Convert seconds to milliseconds
#define NS_AS_MS(x) (x) / 1000000.0 //!< Convert nanoseconds to milliseconds
#define MS_AS_NS(x) (x) * 1000000.0 //!< Convert milliseconds to nanoseconds
#define HZ_AS_MS(x) (1.0 / (x)) * 1000.0 //!< Convert hertz to milliseconds per frame

#define B_AS_KB(x) (x) * (1.0 / 1024)
#define B_AS_MB(x) B_TO_KB((x)) * (1.0 / 1024)
#define B_AS_GB(x) B_TO_MB((x)) * (1.0 / 1024)

#define KB_AS_B(x) (x) * 1024
#define MB_AS_B(x) KB((x)) * 1024
#define GB_AS_B(x) MB((x)) * 1024

#endif // UTIL_VERSION