~groovestomp/gsnes

bb81caed82da920ee37533767af235d577f83b3b — GrooveStomp 1 year, 5 months ago 24c07d4
End of Part 4: PPU Backgrounds

Emulation is *not* working locally - see the TODO
24 files changed, 1085 insertions(+), 216 deletions(-)

M .gitignore
A TODO
M bus.c
M bus.h
M cart.c
M cart.h
M color.c
M color.h
M cpu.c
M cpu.h
M graphics.c
M graphics.h
M input.c
M input.h
M main.c
M mapper.h
M mapper000.c
M mapper000.h
M ppu.c
M ppu.h
M sprite.c
M sprite.h
A util.c
M util.h
M .gitignore => .gitignore +1 -0
@@ 2,3 2,4 @@ release
#*
debug
core
nestest.nes

A TODO => TODO +12 -0
@@ 0,0 1,12 @@
- Emulation isn't working.
  SP seems to be changing, but nothing else?

- Fuzzy font rendering.
  Font overdraw? Improper buffer clearing?

- Double-rendering for text.
  For invalid disassembly lines draw a red line and see red show through other spots where text is rendered.
  This may be the same bug as the fuzzy font rendering.

- Rendering CPU state includes garbage after valid string.
  I think the string capacity is specified incorrectly and/or there is a missing null character.
\ No newline at end of file

M bus.c => bus.c +24 -6
@@ 4,7 4,7 @@

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



@@ 56,17 56,30 @@ void BusDeinit(struct bus *bus) {
}

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

        } else if (addr >= 0x0000 && addr <= 0x1FFF) {
                // System RAM address range, mirrored every 2048.
                bus->cpuRam[addr & 0x07FF] = data;
        } else if (addr >= 0x2000 && addr <= 0x3FFF) {
                PpuWriteViaCpu(bus->ppu, addr & 0x0007, data);
        }
}

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

        if (CartCpuRead(bus->cart, addr, &data)) {
                // Cartridge address range
        } else if (addr >= 0x0000 && addr <= 0x1FFF) {
                // System RAM address range, mirrored every 2048.
                data = bus->cpuRam[addr & 0x07FF];
        } else if (addr >= 0x2000 && addr <= 0x3FFF) {
                // PPU address range, mirrored every 8.
                data = PpuReadViaCpu(bus->ppu, addr & 0x0007);
        }

        return 0x00;
        return data;
}

void BusAttachCart(struct bus *bus, struct cart *cart) {


@@ 88,5 101,10 @@ void BusTick(struct bus *bus) {
                CpuTick(bus->cpu);
        }

        if (PpuGetNmi(bus->ppu)) {
                PpuSetNmi(bus->ppu, false);
                CpuNmi(bus->cpu);
        }

        bus->tickCount++;
}

M bus.h => bus.h +7 -2
@@ 4,7 4,7 @@

  File: bus.h
  Created: 2019-10-16
  Updated: 2019-10-17
  Updated: 2019-11-19
  Author: Aaron Oman
  Notice: GNU AGPLv3 License



@@ 22,6 22,7 @@
struct cpu;
struct bus;
struct ppu;
struct cart;

struct bus *
BusInit(struct cpu *cpu, struct ppu *ppu);


@@ 46,6 47,10 @@ BusReset(struct bus *bus);
//! clocks.
//!
//! \param[in,out] bus
void BusTick(struct bus *bus);
void
BusTick(struct bus *bus);

void
BusAttachCart(struct bus *bus, struct cart *cart);

#endif // BUS_VERSION

M cart.c => cart.c +54 -46
@@ 4,7 4,7 @@

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



@@ 25,28 25,30 @@
#include "mapper000.h"

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;
        uint8_t mapperId;
        uint8_t prgBanks;
        uint8_t chrBanks;
        bool isImageValid;
        uint8_t *prgMem;
        uint8_t *chrMem;
        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;
        map_cpu_read_fn mapCpuRead;
        map_cpu_write_fn mapCpuWrite;
        map_ppu_read_fn mapPpuRead;
        map_ppu_write_fn mapPpuWrite;

        enum mirror mirror;
};

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



@@ 56,10 58,11 @@ struct cart *CartInit(char *filename) {
                return NULL;
        }

        cart->is_image_valid = false;
        cart->mapper_id = 0;
        cart->prg_banks = 0;
        cart->chr_banks = 0;
        cart->isImageValid = false;
        cart->mapperId = 0;
        cart->prgBanks = 0;
        cart->chrBanks = 0;
        cart->mirror = MIRROR_HORIZONTAL;

        FILE *f = fopen(filename, "rb");
        if (NULL == f) {


@@ 80,7 83,8 @@ struct cart *CartInit(char *filename) {
        }

        // Determine mapper ID
        cart->mapper_id = ((header.mapper2 >> 4) << 4) | (header.mapper1 >> 4);
        cart->mapperId = ((header.mapper2 >> 4) << 4) | (header.mapper1 >> 4);
        cart->mirror = (header.mapper1 & 0x01) ? MIRROR_VERTICAL : MIRROR_HORIZONTAL;

        // "Discover" file format.
        uint8_t file_type = 1;


@@ 89,9 93,9 @@ struct cart *CartInit(char *filename) {
        }

        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);
                cart->prgBanks = header.prgRomChunks;
                cart->prgMem = (uint8_t *)malloc(sizeof(uint8_t) * KB_AS_B(16));
                objs_read = fread(cart->prgMem, 1, KB_AS_B(16), f);
                if (objs_read < KB_AS_B(16)) {
                        fclose(f);
                        free(cart);


@@ 99,9 103,9 @@ struct cart *CartInit(char *filename) {
                        // 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);
                cart->chrBanks = header.chrRomChunks;
                cart->chrMem = (uint8_t *)malloc(sizeof(uint8_t) * KB_AS_B(8));
                objs_read = fread(cart->chrMem, 1, KB_AS_B(8), f);
                if (objs_read < KB_AS_B(8)) {
                        fclose(f);
                        free(cart);


@@ 114,21 118,21 @@ struct cart *CartInit(char *filename) {
                // TODO unhandled file_type
        }

        switch(cart->mapper_id) {
        switch(cart->mapperId) {
                case 0: {
                        cart->mapper = Mapper000_Init(cart->prg_banks, cart->chr_banks);
                        cart->mapper = Mapper000_Init(cart->prgBanks, cart->chrBanks);
                        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;
                        cart->mapCpuRead = Mapper000_MapCpuRead;
                        cart->mapCpuWrite = Mapper000_MapCpuWrite;
                        cart->mapPpuRead = Mapper000_MapPpuRead;
                        cart->mapPpuWrite = Mapper000_MapPpuWrite;
                        break;
                }
        }

        cart->is_image_valid = true;
        cart->isImageValid = true;
        fclose(f);

        return cart;


@@ 138,19 142,19 @@ void CartDeinit(struct cart *cart) {
        if (NULL == cart)
                return;

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

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

        free(cart);
}

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];
        if (cart->mapCpuRead(cart->mapper, addr, &mapped_addr)) {
                *data = cart->prgMem[mapped_addr];
                return true;
        }



@@ 159,8 163,8 @@ bool CartCpuRead(struct cart *cart, uint16_t addr, uint8_t *data) {

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;
        if (cart->mapCpuWrite(cart->mapper, addr, &mapped_addr)) {
                cart->prgMem[mapped_addr] = data;
                return true;
        }



@@ 169,8 173,8 @@ bool CartCpuWrite(struct cart *cart, uint16_t addr, uint8_t data) {

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];
        if (cart->mapPpuRead(cart->mapper, addr, &mapped_addr)) {
                *data = cart->chrMem[mapped_addr];
                return true;
        }



@@ 179,10 183,14 @@ bool CartPpuRead(struct cart *cart, uint16_t addr, uint8_t *data) {

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;
        if (cart->mapPpuWrite(cart->mapper, addr, &mapped_addr)) {
                cart->chrMem[mapped_addr] = data;
                return true;
        }

        return false;
}

enum mirror CartMirroring(struct cart *cart) {
        return cart->mirror;
}

M cart.h => cart.h +11 -1
@@ 4,7 4,7 @@

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



@@ 22,6 22,13 @@

struct cart;

enum mirror {
        MIRROR_HORIZONTAL,
        MIRROR_VERTICAL,
        MIRROR_ONESCREEN_LO,
        MIRROR_ONESCREEN_HI,
};

struct cart *
CartInit();



@@ 40,4 47,7 @@ CartPpuRead(struct cart *cart, uint16_t addr, uint8_t *data);
bool
CartPpuWrite(struct cart *cart, uint16_t addr, uint8_t data);

enum mirror
CartMirroring(struct cart *cart);

#endif // CART_VERSION

M color.c => color.c +2 -1
@@ 4,7 4,7 @@

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



@@ 25,6 25,7 @@ struct color ColorPurple = { 0x7F00FFFF };
struct color ColorYellow = { 0xFFFF00FF };
struct color ColorCyan = { 0x00FFFFFF };
struct color ColorPink = { 0xFF00FFFF };
struct color ColorGray = { 0x888888FF };

void ColorSetInt(struct color *color, char component, unsigned int value) {
        unsigned int pos = 0;

M color.h => color.h +2 -1
@@ 4,7 4,7 @@

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



@@ 105,5 105,6 @@ extern struct color ColorPurple;
extern struct color ColorYellow;
extern struct color ColorCyan;
extern struct color ColorPink;
extern struct color ColorGray;

#endif // COLOR_VERSION

M cpu.c => cpu.c +24 -39
@@ 5,7 5,7 @@

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



@@ 22,6 22,7 @@

#include "cpu.h"
#include "bus.h"
#include "util.h"

// Addressing Modes
uint8_t ABS(struct cpu *cpu);


@@ 227,7 228,7 @@ void Irq(struct cpu *cpu) {
        }
}

void Nmi(struct cpu *cpu) {
void CpuNmi(struct cpu *cpu) {
        BusWrite(cpu->bus, 0x0100 + cpu->sp, (cpu->pc >> 8) & 0x00FF);
        cpu->sp--;
        BusWrite(cpu->bus, 0x0100 + cpu->sp, cpu->pc & 0x00FF);


@@ 1391,28 1392,6 @@ uint8_t XXX(struct cpu *cpu) {
}


//-- Helper Functions ----------------------------------------------------------


//! \brief convert number to hexadecimal string
//!
//! \param[in] n number to convert to hex
//! \param[in] d number of nibbles in data source
//! \param[in,out] buf pre-allocated char buffer
//! \param[in] size size of buf
void ToHexString(uint32_t n, uint8_t d, char *buf, uint8_t size) {
        if (d > size) {
                d = size;
        }

        for (int i = d - 1; i >= 0; i--, n >>= 4) {
                buf[i] = "0123456789ABCDEF"[n & 0xF];
        }

        buf[d] = '\0';
}


//-- Debug Structures ----------------------------------------------------------




@@ 1421,6 1400,10 @@ char **CpuDebugStateInit(struct cpu *cpu) {

        debug[0] = "        N V - B D I Z C";
        debug[1] = malloc(strlen("status: 1 1 - 1 1 1 1 1") + 1);

        #pragma GCC diagnostic push
        #pragma GCC diagnostic ignored "-Wformat-overflow"

        sprintf(debug[1], "Status: %d %d - %d %d %d %d %d",
                GetFlag(cpu, N),
                GetFlag(cpu, V),


@@ 1430,6 1413,8 @@ char **CpuDebugStateInit(struct cpu *cpu) {
                GetFlag(cpu, Z),
                GetFlag(cpu, C));

        #pragma GCC diagnostic pop

        debug[2] = malloc(strlen("pc: $0000") + 1);
        sprintf(debug[2], "PC: $%04X", cpu->pc);



@@ 1522,7 1507,7 @@ struct disassembly *DisassemblyInit(struct cpu *cpu, uint16_t start, uint16_t st
                struct instruction instruction;

                // Prefix instruction with address.
                ToHexString(addr, 4, hex_buf, hex_buf_len);
                HexToString(addr, 4, hex_buf, hex_buf_len);
                text_len += 3; // Adding $, <colon> and <space>
                snprintf(text, text_len, "$%s: ", hex_buf);
                strncpy(text_cpy, text, strnlen(text, text_len) + 1);


@@ 1540,73 1525,73 @@ struct disassembly *DisassemblyInit(struct cpu *cpu, uint16_t start, uint16_t st
                } else if (IMM == instruction.address) {
                        value = BusRead(cpu->bus, addr);
                        addr++;
                        ToHexString(value, 2, hex_buf, hex_buf_len);
                        HexToString(value, 2, hex_buf, hex_buf_len);
                        snprintf(text, 256, "%s#$%s {IMM}", text_cpy, hex_buf);
                } else if (ZP0 == instruction.address) {
                        lo = BusRead(cpu->bus, addr);
                        addr++;
                        hi = 0x00;
                        ToHexString(lo, 2, hex_buf, hex_buf_len);
                        HexToString(lo, 2, hex_buf, hex_buf_len);
                        snprintf(text, 256, "%s$%s {ZP0}", text_cpy, hex_buf);
                } else if (ZPX == instruction.address) {
                        lo = BusRead(cpu->bus, addr);
                        addr++;
                        hi = 0x00;
                        ToHexString(lo, 2, hex_buf, hex_buf_len);
                        HexToString(lo, 2, hex_buf, hex_buf_len);
                        snprintf(text, 256, "%s$%s, X {ZPX}", text_cpy, hex_buf);
                } else if (ZPY == instruction.address) {
                        lo = BusRead(cpu->bus, addr);
                        addr++;
                        hi = 0x00;
                        ToHexString(lo, 2, hex_buf, hex_buf_len);
                        HexToString(lo, 2, hex_buf, hex_buf_len);
                        snprintf(text, 256, "%s$%s, Y {ZPY}", text_cpy, hex_buf);
                } else if (IZX == instruction.address) {
                        lo = BusRead(cpu->bus, addr);
                        addr++;
                        hi = 0x00;
                        ToHexString(lo, 2, hex_buf, hex_buf_len);
                        HexToString(lo, 2, hex_buf, hex_buf_len);
                        snprintf(text, 256, "%s($%s, X) {IZX}", text_cpy, hex_buf);
                } else if (IZY == instruction.address) {
                        lo = BusRead(cpu->bus, addr);
                        addr++;
                        hi = 0x00;
                        ToHexString(lo, 2, hex_buf, hex_buf_len);
                        HexToString(lo, 2, hex_buf, hex_buf_len);
                        snprintf(text, 256, "%s($%s, Y) {IZY}", text_cpy, hex_buf);
                } else if (ABS == instruction.address) {
                        lo = BusRead(cpu->bus, addr);
                        addr++;
                        hi = BusRead(cpu->bus, addr);
                        addr++;
                        ToHexString((uint16_t)(hi << 8) | lo, 4, hex_buf, hex_buf_len);
                        HexToString((uint16_t)(hi << 8) | lo, 4, hex_buf, hex_buf_len);
                        snprintf(text, 256, "%s$%s {ABS}", text_cpy, hex_buf);
                } else if (ABX == instruction.address) {
                        lo = BusRead(cpu->bus, addr);
                        addr++;
                        hi = BusRead(cpu->bus, addr);
                        addr++;
                        ToHexString((uint16_t)(hi << 8) | lo, 4, hex_buf, hex_buf_len);
                        HexToString((uint16_t)(hi << 8) | lo, 4, hex_buf, hex_buf_len);
                        snprintf(text, 256, "%s$%s, X {ABX}", text_cpy, hex_buf);
                } else if (ABY == instruction.address) {
                        lo = BusRead(cpu->bus, addr);
                        addr++;
                        hi = BusRead(cpu->bus, addr);
                        addr++;
                        ToHexString((uint16_t)(hi << 8) | lo, 4, hex_buf, hex_buf_len);
                        HexToString((uint16_t)(hi << 8) | lo, 4, hex_buf, hex_buf_len);
                        snprintf(text, 256, "%s$%s, Y {ABY}", text_cpy, hex_buf);
                } else if (IND == instruction.address) {
                        lo = BusRead(cpu->bus, addr);
                        addr++;
                        hi = BusRead(cpu->bus, addr);
                        addr++;
                        ToHexString((uint16_t)(hi << 8) | lo, 4, hex_buf, hex_buf_len);
                        HexToString((uint16_t)(hi << 8) | lo, 4, hex_buf, hex_buf_len);
                        snprintf(text, 256, "%s($%s) {IND}", text_cpy, hex_buf);
                } else if (REL == instruction.address) {
                        value = BusRead(cpu->bus, addr);
                        addr++;
                        ToHexString(value, 2, hex_buf, hex_buf_len);
                        HexToString(value, 2, hex_buf, hex_buf_len);

                        char hex_buf2[5];
                        ToHexString(addr + (int8_t)value, 4, hex_buf2, hex_buf_len);
                        HexToString(addr + (int8_t)value, 4, hex_buf2, hex_buf_len);
                        snprintf(text, 256, "%s$%s [$%s] {REL}", text_cpy, hex_buf, hex_buf2);
                }



@@ 1636,10 1621,10 @@ int DisassemblyFindPc(struct disassembly *disassembly, struct cpu *cpu) {
        if (NULL == cpu) return -1;

        for (int i = 0; i < disassembly->count; i++) {
                if (NULL == disassembly || NULL == disassembly->map || NULL == disassembly->map[i]) continue;
                if (NULL == disassembly->map || NULL == disassembly->map[i]) continue;

                unsigned long parsed = strtoul(&disassembly->map[i]->text[1], NULL, 16);
                if ((uint16_t)parsed == cpu->pc) return i;
                if ((uint16_t)parsed == cpu->pc || (uint16_t)parsed == cpu->pc - 1) return i;
        }

        return -1;

M cpu.h => cpu.h +4 -1
@@ 4,7 4,7 @@

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



@@ 40,6 40,9 @@ CpuTick(struct cpu *cpu);
int
CpuIsComplete(struct cpu *cpu);

void
CpuNmi(struct cpu *cpu);

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

char **

M graphics.c => graphics.c +28 -18
@@ 4,7 4,7 @@

  File: graphics.c
  Created: 2019-06-25
  Updated: 2019-11-07
  Updated: 2019-11-19
  Author: Aaron Oman
  Notice: GNU GPLv3 License



@@ 24,22 24,6 @@
#include "graphics.h"
#include "sprite.h"

//! \brief A mostly generic implementation of swap
//!
//! Both v1 and v2 must point to data that is the same size, as specified in the
//! size parameter.
//!
//! \param[in,out] v1 first value
//! \param[in,out] v2 second value
//! \param[in] size v1 and v2 must each be this size
//!
void swap_generic(void *v1, void *v2, size_t size) {
        char temp[size];
        memmove(temp, v1, size);
        memmove(v1, v2, size);
        memmove(v2, temp, size);
}

//! \brief graphics state
struct graphics {
        SDL_Window *window;


@@ 125,7 109,7 @@ void GraphicsEnd(struct graphics *graphics) {

void GraphicsClearScreen(struct graphics *graphics, uint32_t color) {
        for (int i = 0; i < graphics->bytesPerRow * graphics->height; i+=4) {
                unsigned int *pixel = (unsigned int *)&graphics->pixels[i];
                unsigned int *pixel = (uint32_t *)&graphics->pixels[i];
                *pixel = color;
        }
}


@@ 352,3 336,29 @@ void GraphicsDrawSprite(struct graphics *graphics, int x, int y, struct sprite *
                        }
        }
}

void GraphicsDrawFilledRect(struct graphics *graphics, int x, int y, int w, int h, uint32_t color) {
        int x2 = x + w;
        int y2 = y + h;

        if (x < 0) x = 0;
        if (x >= graphics->width) x = graphics->width;
        if (y < 0) y = 0;
        if (y >= graphics->height) y = graphics->height;

        if (x2 < 0) x2 = 0;
        if (x2 >= graphics->width) x2 = graphics->width;
        if (y2 < 0) y2 = 0;
        if (y2 >= graphics->height) y2 = graphics->height;

        for (int i = x; i < x2; i++)
                for (int j = y; j < y2; j++)
                        GraphicsPutPixel(graphics, i, j, color);
}

void GraphicsDrawRect(struct graphics *graphics, int x, int y, int w, int h, uint32_t color) {
        GraphicsDrawLine(graphics, x, y, x + w, y, color);
        GraphicsDrawLine(graphics, x + w, y, x + w, y + h, color);
        GraphicsDrawLine(graphics, x + w, y + h, x, y + h, color);
        GraphicsDrawLine(graphics, x, y + h, x, y, color);
}

M graphics.h => graphics.h +25 -3
@@ 1,10 1,10 @@
/******************************************************************************
  GrooveStomp's Text Renderer
  GrooveStomp's NES Emulator
  Copyright (c) 2019 Aaron Oman (GrooveStomp)

  File: graphics.h
  Created: 2019-07-16
  Updated: 2019-11-07
  Updated: 2019-11-19
  Author: Aaron Oman
  Notice: GNU GPLv3 License



@@ 17,7 17,7 @@
#include <stdint.h>

#ifndef GRAPHICS_VERSION
#define GRAPHICS_VERSION "0.2-gstxt" //!< include guard and version info
#define GRAPHICS_VERSION "0.2-gsnes" //!< include guard and version info

struct sprite;



@@ 116,4 116,26 @@ GraphicsDrawLine(struct graphics *graphics, int x1, int y1, int x2, int y2, uint
void
GraphicsDrawSprite(struct graphics *graphics, int x, int y, struct sprite *sprite, int scale);

//! \brief Draws a filled rectangle at (x,y) of width w and height h
//!
//! \param[in,out] graphics
//! \param[in] x lower left corner x coordinate
//! \param[in] y lower left corner y coordinate
//! \param[in] w width of the rectangle in pixels
//! \param[in] h height of the rectangle in pixels
//! \param[in] color 32-bit color (R|G|B|A) to render line with
void
GraphicsDrawFilledRect(struct graphics *graphics, int x, int y, int w, int h, uint32_t color);

//! \brief Draws a rectangle outline at (x,y) of width w and height h
//!
//! \param[in,out] graphics
//! \param[in] x lower left corner x coordinate
//! \param[in] y lower left corner y coordinate
//! \param[in] w width of the rectangle in pixels
//! \param[in] h height of the rectangle in pixels
//! \param[in] color 32-bit color (R|G|B|A) to render line with
void
GraphicsDrawRect(struct graphics *graphics, int x, int y, int w, int h, uint32_t color);

#endif // GRAPHICS_VERSION

M input.c => input.c +59 -9
@@ 4,7 4,7 @@

  File: input.c
  Created: 2019-06-21
  Updated: 2019-11-07
  Updated: 2019-11-26
  Author: Aaron Oman
  Notice: GNU GPLv3 License



@@ 19,11 19,16 @@

#include "input.h"

int MapToSdlEnum(enum input_key_enum e);

//! \brief input state
struct input {
        const unsigned char *sdlKeyStates;
        SDL_Event event;
        int isQuitPressed;
        struct button_state *keyStates;
        bool *keyStatesNew;
        bool *keyStatesOld;
};

struct input *InputInit() {


@@ 34,15 39,27 @@ struct input *InputInit() {

        memset(input, 0, sizeof(struct input));

        /* input->keyStates = (struct input_key *)calloc(KEY_MAX + 1, sizeof(struct input_key)); */
        /* if (NULL == input) { */
        /*         free(input); */
        /*         return NULL; */
        /* } */
        input->keyStates = (struct button_state *)calloc(KEY_COUNT, sizeof(struct button_state));
        if (NULL == input->keyStates) {
                InputDeinit(input);
                return NULL;
        }

        /* for (int i = 0; i <= KEY_MAX; i++) { */
        /*         *input->keyStates[i] = { 0 }; */
        /* } */
        input->keyStatesNew = (bool *)calloc(KEY_COUNT, sizeof(bool));
        if (NULL == input->keyStatesNew) {
                InputDeinit(input);
                return NULL;
        }

        input->keyStatesOld = (bool *)calloc(KEY_COUNT, sizeof(bool));
        if (NULL == input->keyStatesOld) {
                InputDeinit(input);
                return NULL;
        }

        for (int i = 0; i <= KEY_COUNT; i++) {
                input->keyStates[i] = (struct button_state){ 0 };
        }

        input->sdlKeyStates = SDL_GetKeyboardState(NULL);
        input->isQuitPressed = 0;


@@ 54,6 71,15 @@ void InputDeinit(struct input *input) {
        if (NULL == input)
                return;

        if (NULL != input->keyStates)
                free(input->keyStates);

        if (NULL != input->keyStatesNew)
                free(input->keyStatesNew);

        if (NULL != input->keyStatesOld)
                free(input->keyStatesOld);

        free(input);
}



@@ 61,6 87,26 @@ void InputProcess(struct input *input) {
        input->isQuitPressed = 0;
        SDL_PumpEvents(); // Update sdlKeyState;

        for (enum input_key_enum i = KEY_A; i < KEY_COUNT; i++) {
                int sdlScancode = MapToSdlEnum(i);
                input->keyStatesNew[i] = input->sdlKeyStates[sdlScancode];

                if (input->keyStatesNew[i] == input->keyStatesOld[i]) {
                        input->keyStates[i].pressed = false;
                        input->keyStates[i].released = false;
                        continue;
                }

                if (input->keyStatesNew[i]) {
                        input->keyStates[i].pressed = !input->keyStates[i].held;
                        input->keyStates[i].held = true;
                } else {
                        input->keyStates[i].released = true;
                        input->keyStates[i].held = false;
                }
                input->keyStatesOld[i] = input->keyStatesNew[i];
        }

        while (SDL_PollEvent(&input->event)) {
                switch (input->event.type) {
                        case SDL_QUIT:


@@ 84,6 130,10 @@ int InputIsQuitRequested(struct input *input) {
        return input->isQuitPressed;
}

struct button_state InputGetKey(struct input *input, enum input_key_enum e) {
        return input->keyStates[e];
}

int MapToSdlEnum(enum input_key_enum e) {
        switch(e) {
                case KEY_A:

M input.h => input.h +14 -2
@@ 4,7 4,7 @@

  File: input.h
  Created: 2019-07-21
  Updated: 2019-11-09
  Updated: 2019-11-26
  Author: Aaron Oman
  Notice: GNU GPLv3 License



@@ 18,6 18,14 @@
#ifndef INPUT_VERSION
#define INPUT_VERSION "0.2-gsnes" //!< include guard and version info

#include <stdbool.h>

struct button_state {
        bool pressed;
        bool released;
        bool held;
};

struct input;

//! \brief Creates and initializes new input state


@@ 92,10 100,14 @@ enum input_key_enum {
        KEY_7,
        KEY_8,
        KEY_9,
        KEY_0
        KEY_0,
        KEY_COUNT
};

unsigned int
InputIsKeyPressed(struct input *input, enum input_key_enum e);

struct button_state
InputGetKey(struct input *input, enum input_key_enum e);

#endif // INPUT_VERSION

M main.c => main.c +69 -59
@@ 4,7 4,7 @@

  File: main.c
  Created: 2019-10-31
  Updated: 2019-10-31
  Updated: 2019-11-26
  Author: Aaron Oman
  Notice: GNU AGPLv3 License



@@ 19,47 19,30 @@
#include <string.h> // strlen
#include <stdio.h> // printf

#include "cpu.h"
#include "bus.h"
#include "ppu.h"
#include "cart.h"
#include "color.h"
#include "cpu.h"
#include "graphics.h"
#include "input.h"
#include "ppu.h"
#include "util.h"
#include "color.h"

// Load Program (assembled at https://www.masswerk.at/6502/assembler.html)
/*
 *=$8000
 LDX #10
 STX $0000
 LDX #3
 STX $0001
 LDY $0000
 LDA #0
 CLC
 loop
 ADC $0001
 DEY
 BNE loop
 STA $0002
 NOP
 NOP
 NOP
*/
static const char *program = "A2 0A 8E 00 00 A2 03 8E 01 00 AC 00 00 A9 00 18 6D 01 00 88 D0 FA 8D 02 00 EA EA EA";

static const int FONT_SCALE = 25;
static const int FONT_HEADER_SCALE = 20;
static const int FONT_SCALE = 15;

static const int NES_SCREEN_WIDTH = 256 * 3;
static const int NES_SCREEN_HEIGHT = 240 * 3;
static const int WIDTH = NES_SCREEN_WIDTH + 250;
static const int HEIGHT = NES_SCREEN_HEIGHT;
static const int SWATCH_SIZE = 5;

static struct cpu *cpu = NULL;
static struct ppu *ppu = NULL;
static struct bus *bus = NULL;
static struct input *input = NULL;
static struct graphics *graphics = NULL;
static struct cart *cart = NULL;
static char *font_buffer = NULL;

void Deinit(int code) {


@@ 75,6 58,8 @@ void Deinit(int code) {
                BusDeinit(bus);
        if (NULL != cpu)
                CpuDeinit(cpu);
        if (NULL != cart)
                CartDeinit(cart);

        exit(code);
}


@@ 82,6 67,12 @@ void Deinit(int code) {
void Init() {
        char *ttf_filename = "/usr/share/fonts/truetype/ubuntu/UbuntuMono-R.ttf";

        cart = CartInit("nestest.nes");
        if (NULL == cart) {
                fprintf(stderr, "Couldn't load cart");
                Deinit(1);
        }

        cpu = CpuInit();
        if (NULL == cpu) {
                fprintf(stderr, "Couldn't initialize cpu");


@@ 139,46 130,36 @@ void Init() {
        GraphicsInitText(graphics, (unsigned char *)font_buffer);
}

void LoadRom(struct bus *bus, char *data) {
        uint16_t ram_offset = 0;
        char *last_byte = NULL;

        unsigned long i = strtoul(data, &last_byte, 16);
        while (data != last_byte) {
                BusWrite(bus, ram_offset++, (uint8_t)i);
                data = last_byte;
                i = strtoul(data, &last_byte, 16);
        }
}

void DrawCpuState(int x, int y) {
        GraphicsDrawText(graphics, x, y, "CPU State", FONT_SCALE, 0x000000FF);
        GraphicsDrawText(graphics, x, y, "CPU State", FONT_HEADER_SCALE, 0x000000FF);
        char **cpu_state = CpuDebugStateInit(cpu);
        for (int i = 0; i < 7; i++) {
                GraphicsDrawText(graphics, x, (y - 20) - (18 * i), cpu_state[i], 15, 0x000000FF);
                GraphicsDrawText(graphics, x, (y - 15) - (18 * i), cpu_state[i], FONT_SCALE, 0x000000FF);
        }
        CpuDebugStateDeinit(cpu_state);
}

void DrawDisassembly(struct disassembly *disassembly, int x, int y, int numLines) {
        GraphicsDrawText(graphics, x, y, "Disassembly", FONT_SCALE, 0x000000FF);
        if (NULL == disassembly) return;

        GraphicsDrawText(graphics, x, y, "Disassembly", FONT_HEADER_SCALE, 0x000000FF);

        int pc = DisassemblyFindPc(disassembly, cpu);

        int halfLines = (int)(0.5f * (float)numLines);
        int min = pc - halfLines;
        if (min < 0 || min > 0xFFFF) min = 0;

        int max = pc + halfLines;
        if (max > 0xFFFF || max < 0) max = 0xFFFF;

        for (int i = min; i < max; i++) {
                if (NULL == disassembly || NULL == disassembly->map || NULL == disassembly->map[i]) {
                        GraphicsDrawLine(graphics, x, (y - 25) - (9 * i), x + 100, (y - 15) - (9 * i), ColorBlack.rgba);
        for (int i = min, si = 0; i < max; i++, si++) {
                int yOff = (y - 25) - (18 * si);
                if (i < 0 || i > 0xFFFF) {
                        GraphicsDrawLine(graphics, x, yOff + 5 , x + 230, yOff + 5, ColorRed.rgba);
                } else if (NULL == disassembly->map || NULL == disassembly->map[i]) {
                        GraphicsDrawLine(graphics, x, yOff + 5 , x + 230, yOff + 5, ColorGray.rgba);
                } else if (i == pc) {
                        GraphicsDrawText(graphics, x, (y - 25) - (18 * i), disassembly->map[i]->text, 15, ColorBlue.rgba);
                        GraphicsDrawText(graphics, x, yOff, disassembly->map[i]->text, FONT_SCALE, ColorBlue.rgba);
                } else {
                        GraphicsDrawText(graphics, x, (y - 25) - (18 * i), disassembly->map[i]->text, 15, ColorBlack.rgba);
                        GraphicsDrawText(graphics, x, yOff, disassembly->map[i]->text, FONT_SCALE, ColorBlack.rgba);
                }
        }
}


@@ 187,14 168,14 @@ int main(int argc, char **argv) {
        Init();

        CpuConnectBus(cpu, bus);
        LoadRom(bus, program);
        BusAttachCart(bus, cart);

        // Set reset vector.
        BusWrite(bus, 0xFFFC, 0x00);
        BusWrite(bus, 0xFFFD, 0x80);

        // Disassemble
        struct disassembly *disassembly = DisassemblyInit(cpu, 0x00000, 0x00FF);
        struct disassembly *disassembly = DisassemblyInit(cpu, 0x0000, 0xFFFF);

        struct timespec frameEnd;
        struct timespec frameStart;


@@ 203,6 184,7 @@ int main(int argc, char **argv) {
        double residualTime = 0.0;
        int isEmulating = 1;
        int isRunning = 1;
        int selectedPalette = 0;
        while (isRunning) {
                clock_gettime(CLOCK_REALTIME, &frameEnd);
                double elapsedTime = S_AS_MS(frameEnd.tv_sec - frameStart.tv_sec);


@@ 211,6 193,9 @@ int main(int argc, char **argv) {
                GraphicsBegin(graphics);
                GraphicsClearScreen(graphics, 0xFFFFFFFF);

                InputProcess(input);
                isRunning = !InputIsQuitRequested(input);

                if (isEmulating) {
                        if (0.0f < residualTime) {
                                residualTime -= elapsedTime;


@@ 245,20 230,45 @@ int main(int argc, char **argv) {
                        }
                }

                InputProcess(input);
                isRunning = !InputIsQuitRequested(input);
                if (InputGetKey(input, KEY_SPACE).pressed) isEmulating = !isEmulating;
                if (InputGetKey(input, KEY_R).pressed) BusReset(bus);
                if (InputGetKey(input, KEY_P).pressed) {
                        ++selectedPalette;
                        selectedPalette &= 0x07;
                }

                GraphicsDrawLine(graphics, NES_SCREEN_WIDTH, 0, NES_SCREEN_WIDTH, HEIGHT, ColorBlack.rgba);
                DrawCpuState(NES_SCREEN_WIDTH + 10, HEIGHT - (FONT_HEADER_SCALE + 5));

                if (InputIsKeyPressed(input, KEY_SPACE)) isEmulating = !isEmulating;
                if (InputIsKeyPressed(input, KEY_R)) BusReset(bus);
                GraphicsDrawLine(graphics, NES_SCREEN_WIDTH, HEIGHT - 160, WIDTH, HEIGHT - 160, ColorBlack.rgba);
                DrawDisassembly(disassembly, NES_SCREEN_WIDTH + 10, HEIGHT - 160 - (FONT_HEADER_SCALE + 5), 20);

                GraphicsDrawLine(graphics, NES_SCREEN_WIDTH, 0, NES_SCREEN_WIDTH, HEIGHT, 0x000000FF);
                DrawCpuState(NES_SCREEN_WIDTH + 10, HEIGHT - 30);
                // Iterate through each palette.
                for (int p = 0; p < 8; p++)
                        for (int s = 0; s < 4; s++) {
                                struct color *color = PpuGetColorFromPaletteRam(ppu, p, s);
                                int x = NES_SCREEN_WIDTH + 1 + (p * 5 * (SWATCH_SIZE + 1)) + (s * (SWATCH_SIZE + 1));
                                int y = NES_SCREEN_HEIGHT - 590;
                                GraphicsDrawFilledRect(graphics, x, y, SWATCH_SIZE, SWATCH_SIZE, color->rgba);
                        }

                // Draw selection reticule around selected palette.
                GraphicsDrawRect(graphics, NES_SCREEN_WIDTH + (selectedPalette * 5 * (SWATCH_SIZE + 1)), NES_SCREEN_HEIGHT - 591, SWATCH_SIZE * 4 + 4, SWATCH_SIZE + 1, ColorBlack.rgba);

                GraphicsDrawLine(graphics, NES_SCREEN_WIDTH, HEIGHT - 175, WIDTH, HEIGHT - 175, 0x000000FF);
                DrawDisassembly(disassembly, NES_SCREEN_WIDTH + 10, HEIGHT - 175 - 30, 25);
                // Draw the pattern tables.
                GraphicsDrawSprite(graphics, NES_SCREEN_WIDTH, HEIGHT - 719, PpuGetPatternTable(ppu, 0, selectedPalette), 1);
                GraphicsDrawSprite(graphics, NES_SCREEN_WIDTH + 129, HEIGHT - 719, PpuGetPatternTable(ppu, 1, selectedPalette), 1);

                GraphicsDrawSprite(graphics, 0, 0, PpuScreen(ppu), 3);

                /* for (int y = 0; y < 30; y++) { */
                /*         for (int x = 0; x < 32; x++) { */
                /*                 char buf[5]; */
                /*                 //HexToString(PpuNameTableEntry(ppu, 0)[y * 32 + x], 2, buf, 5); */
                /*                 //GraphicsDrawText(graphics, x * 16, y * 16, buf, 15, ColorBlack.rgba); */
                /*         } */
                /* } */

                GraphicsEnd(graphics);
        }


M mapper.h => mapper.h +1 -1
@@ 4,7 4,7 @@

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


M mapper000.c => mapper000.c +1 -1
@@ 4,7 4,7 @@

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


M mapper000.h => mapper000.h +1 -1
@@ 4,7 4,7 @@

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


M ppu.c => ppu.c +643 -21
@@ 4,7 4,7 @@

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



@@ 23,6 23,21 @@
#include "color.h"
#include "sprite.h"

//! CHR_ROM startx at 0x1000 / 4096 / 4K
static const int CHR_ROM = 0x1000;

union loopy_register {
        struct {
                uint16_t coarseX : 5;
                uint16_t coarseY : 5;
                uint16_t nametableX : 1;
                uint16_t nametableY : 1;
                uint16_t fineY : 3;
                uint16_t unused : 1;
        };
        uint16_t reg;
};

struct ppu {
        struct cart *cart;



@@ 53,6 68,50 @@ struct ppu {
                uint8_t reg;

        } mask;

        union {
                struct {
                        uint8_t unused : 5;
			uint8_t spriteOverflow : 1;
			uint8_t spriteZeroHit : 1;
			uint8_t verticalBlank : 1;
                };
                uint8_t reg;
        } status;

        union {
                struct {
			uint8_t nametableX : 1;
			uint8_t nametableY : 1;
			uint8_t incrementMode : 1;
			uint8_t patternSprite : 1;
			uint8_t patternBackground : 1;
			uint8_t spriteSize : 1;
			uint8_t slaveMode : 1; // unused
			uint8_t enableNmi : 1;
                };
                uint8_t reg;
        } control;

        union loopy_register vramAddr;
        union loopy_register tramAddr;

        uint8_t fineX;

        uint8_t addressLatch;
        uint8_t dataBuffer;
        uint16_t address;

        uint8_t bgNextTileId;
        uint8_t bgNextTileAttrib;
        uint8_t bgNextTileLsb;
        uint8_t bgNextTileMsb;
        uint16_t bgShifterPatternLo;
        uint16_t bgShifterPatternHi;
        uint16_t bgShifterAttribLo;
        uint16_t bgShifterAttribHi;

        bool nmi;
};

struct ppu *PpuInit() {


@@ 101,6 160,23 @@ struct ppu *PpuInit() {
        ppu->patternTableSprites[1] = SpriteInit(128, 128);
        // TODO: Error handling

        ppu->cycle = 0;
        ppu->addressLatch = 0;
        ppu->dataBuffer = 0;
        ppu->address = 0;
        ppu->nmi = false;
        ppu->vramAddr.reg = 0;
        ppu->tramAddr.reg = 0;
        ppu->fineX = 0x00;
        ppu->bgNextTileId = 0;
        ppu->bgNextTileAttrib = 0;
        ppu->bgNextTileLsb = 0;
        ppu->bgNextTileMsb = 0;
        ppu->bgShifterPatternLo = 0;
        ppu->bgShifterPatternHi = 0;
        ppu->bgShifterAttribLo = 0;
        ppu->bgShifterAttribHi = 0;

        ppu->palette[0x00] = ColorInitInts(84, 84, 84, 255);
	ppu->palette[0x01] = ColorInitInts(0, 30, 116, 255);
	ppu->palette[0x02] = ColorInitInts(8, 16, 144, 255);


@@ 169,7 245,6 @@ struct ppu *PpuInit() {
	ppu->palette[0x3E] = ColorInitInts(0, 0, 0, 255);
	ppu->palette[0x3F] = ColorInitInts(0, 0, 0, 255);

        ppu->cycle = 0;
        return ppu;
}



@@ 185,6 260,89 @@ void PpuAttachCart(struct ppu *ppu, struct cart *cart) {
        ppu->cart = cart;
}

void IncrementScrollX(struct ppu *ppu) {
        if (!ppu->mask.renderBackground && !ppu->mask.renderSprites) return;

        if (31 == ppu->vramAddr.coarseX) {
                ppu->vramAddr.coarseX = 0;
                ppu->vramAddr.nametableX = ~ppu->vramAddr.nametableX;
        } else {
                ppu->vramAddr.coarseX++;
        }
}

void IncrementScrollY(struct ppu *ppu) {
        if (!ppu->mask.renderBackground && !ppu->mask.renderSprites) return;

        if (ppu->vramAddr.fineY < 7) {
                ppu->vramAddr.fineY++;
        } else {
                ppu->vramAddr.fineY = 0;

                if (29 == ppu->vramAddr.coarseY) {
                        ppu->vramAddr.coarseY = 0;
                        ppu->vramAddr.nametableY = ~ppu->vramAddr.nametableY;
                } else if (31 == ppu->vramAddr.coarseY) {
                        ppu->vramAddr.coarseY = 0;
                } else {
                        ppu->vramAddr.coarseY++;
                }
        }
}

void TransferAddressX(struct ppu *ppu) {
        if (!ppu->mask.renderBackground && !ppu->mask.renderSprites) return;

        ppu->vramAddr.nametableX = ppu->tramAddr.nametableX;
        ppu->vramAddr.coarseX = ppu->tramAddr.coarseX;
}

void TransferAddressY(struct ppu *ppu) {
        if (!ppu->mask.renderBackground && !ppu->mask.renderSprites) return;

        ppu->vramAddr.fineY = ppu->tramAddr.fineY;
        ppu->vramAddr.nametableY = ppu->tramAddr.nametableY;
        ppu->vramAddr.coarseY = ppu->tramAddr.coarseY;
}

void LoadBackgroundShifters(struct ppu *ppu) {
        // Each PPU update we calculate one pixel. These shifters shift 1 bit
        // along feeding the pixel compositor with the binary information it
        // needs. Its 16 bits wide, because the top 8 bits are the current 8
        // pixels being drawn and the bottom 8 bits are the next 8 pixels to be
        // drawn. Naturally this means the required bit is always the MSB of the
        // shifter. However, "fine x" scrolling plays a part in this too, whcih
        // is seen later, so in fact we can choose any one of the top 8 bits.
        ppu->bgShifterPatternLo = (ppu->bgShifterPatternLo & 0xFF00) | ppu->bgNextTileLsb;
        ppu->bgShifterPatternHi = (ppu->bgShifterPatternHi & 0xFF00) | ppu->bgNextTileMsb;

        // Attribute bits do not change per pixel, rather they change every 8
        // pixels but are synchronised with the pattern shifters for
        // convenience, so here we take the bottom 2 bits of the attribute word
        // which represent which palette is being used for the current 8 pixels
        // and the next 8 pixels, and "inflate" them to 8 bit words.
        ppu->bgShifterAttribLo = (ppu->bgShifterAttribLo & 0xFF00) | (ppu->bgNextTileAttrib & 0x01) ? 0xFF : 0x00;
        ppu->bgShifterAttribHi = (ppu->bgShifterAttribHi & 0xFF00) | (ppu->bgNextTileAttrib & 0x02) ? 0xFF : 0x00;
}

void UpdateShifters(struct ppu *ppu) {
        if (!ppu->mask.renderBackground) return;

        // Every cycle the shifters storing pattern and attribute information
        // shift their contents by 1 bit. This is because every cycle, the
        // output progresses by 1 pixel. This means relatively, the state of the
        // shifter is in sync with the pixels being drawn for that 8 pixel
        // section of the scanline.

        // Shift background tile pattern row.
        ppu->bgShifterPatternLo <<= 1;
        ppu->bgShifterPatternHi <<= 1;

        // Shift palette attributes by 1.
        ppu->bgShifterAttribLo <<= 1;
        ppu->bgShifterAttribHi <<= 1;
}

//! \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


@@ 194,10 352,266 @@ void PpuAttachCart(struct ppu *ppu, struct cart *cart) {
//!
//! \param[in,out] ppu
void PpuTick(struct ppu *ppu) {
        // For now we are just drawing random noise.
        uint16_t idx = (rand() % 2) ? 0x3F : 0x30;
        struct color color = ppu->palette[idx];
        SpriteSetPixel(ppu->screen, ppu->cycle - 1, ppu->scanline, color.rgba);
        if (ppu->scanline >= -1 && ppu->scanline < 240) {
                // We've exited vblank/nmi and are ready to start rendering again.
                if (-1 == ppu->scanline && 1 == ppu->cycle) {
                        ppu->status.verticalBlank = 0;
                }

                if ((ppu->cycle >= 2 && ppu->cycle <= 258) || (ppu->cycle >= 321 && ppu->cycle < 338)) {
                        UpdateShifters(ppu);

                        switch ((ppu->cycle - 1) % 8) {
                                case 0:
                                        LoadBackgroundShifters(ppu);

                                        // Fetch the next background tile ID.
                                        // 0x2000: nametable address space.
                                        // 0x0FFF: Mask to 12 bits
                                        ppu->bgNextTileId = PpuRead(ppu, 0x2000 | (ppu->vramAddr.reg & 0x0FFF));
                                        // Explanation:
                                        // The bottom 12 bits of the loopy
                                        // register provide an index into the 4
                                        // nametables, regardless of nametable
                                        // mirroring configuration.
                                        // nametable_y(1) nametable_x(1)
                                        // coarse_y(5) coarse_x(5)
                                        //
                                        // Consider a single nametable is a
                                        // 32x32 array, and we have four of them:
                                        //   0                1
                                        // 0 +----------------+----------------+
                                        //   |                |                |
                                        //   |                |                |
                                        //   |    (32x32)     |    (32x32)     |
                                        //   |                |                |
                                        //   |                |                |
                                        // 1 +----------------+----------------+
                                        //   |                |                |
                                        //   |                |                |
                                        //   |    (32x32)     |    (32x32)     |
                                        //   |                |                |
                                        //   |                |                |
                                        //   +----------------+----------------+
                                        //
                                        // This means there are 4096 potential
                                        // locations in this array, which
                                        // just so happens to be 2^12!
                                        break;

                                case 2: {
                                        // Fetch the next background tile
                                        // attribute.

                                        // Recall that each nametable has two
                                        // rows of cells that are not tile
                                        // information, instead they represent
                                        // the attribute information that
                                        // indicates which palettes are applied
                                        // to which area on the screen.
                                        // Importantly (and frustratingly) there
                                        // is not a 1 to 1 correspondance
                                        // between background tile and
                                        // palette. Two rows of tile data holds
                                        // 64 attributes. Therfore we can assume
                                        // that the attributes affect 8x8 zones
                                        // on the screen for that
                                        // nametable. Given a working resolution
                                        // of 256x240, we can further assume
                                        // that each zone is 32x32 pixels in
                                        // screen space, or 4x4 tiles. Four
                                        // system palettes are allocated to
                                        // background rendering, so a palette
                                        // can be specified using just 2
                                        // bits. The attribute byte therefore
                                        // can specify 4 distinct palettes.
                                        // Therefore we can even further assume
                                        // that a single palette is applied to a
                                        // 2x2 tile combination of the 4x4 tile
                                        // zone. The very fact that background
                                        // tiles "share" a palette locally is
                                        // the reason why in some games you see
                                        // distortion in the colours at screen
                                        // edges.

                                        // As before when choosing the tile ID,
                                        // we can use the bottom 12 bits of the
                                        // loopy register, but we need to make
                                        // the implementation "coarser" because
                                        // instead of a specific tile, we want
                                        // the attribute byte for a group of 4x4
                                        // tiles, or in other words, we divide
                                        // our 32x32 address by 4 to give us an
                                        // equivalent 8x8 address, and we offset
                                        // this address into the attribute
                                        // section of the target nametable.

                                        // Reconstruct the 12 bit loopy address
                                        // into an offset into the attribute
                                        // memory

                                        // "(vram_addr.coarse_x >> 2)"        : integer divide coarse x by 4,
                                        //                                      from 5 bits to 3 bits
                                        // "((vram_addr.coarse_y >> 2) << 3)" : integer divide coarse y by 4,
                                        //                                      from 5 bits to 3 bits,
                                        //                                      shift to make room for coarse x

                                        // Result so far: YX00 00yy yxxx

                                        // All attribute memory begins at 0x03C0
                                        // within a nametable, so OR with result
                                        // to select target nametable, and
                                        // attribute byte offset.
                                        uint16_t addr =
                                                0x23C0 |
                                                (ppu->vramAddr.nametableY << 1) |
                                                (ppu->vramAddr.nametableX << 10) |
                                                ((ppu->vramAddr.coarseY >> 1) << 3) |
                                                (ppu->vramAddr.coarseX >> 2);

                                        ppu->bgNextTileAttrib = PpuRead(ppu, addr);

                                        // The attribute byte is assembled thus:
                                        // BR(76) BL(54) TR(32) TL(10)
                                        //
                                        // +----+----+			    +----+----+
                                        // | TL | TR |			    | ID | ID |
                                        // +----+----+ where TL =   +----+----+
                                        // | BL | BR |			    | ID | ID |
                                        // +----+----+			    +----+----+
                                        //
                                        // Since we know we can access a tile
                                        // directly from the 12 bit address, we
                                        // can analyse the bottom bits of the
                                        // coarse coordinates to provide us with
                                        // the correct offset into the 8-bit
                                        // word, to yield the 2 bits we are
                                        // actually interested in which
                                        // specifies the palette for the 2x2
                                        // group of tiles. We know if "coarse y
                                        // % 4" < 2 we are in the top half else
                                        // bottom half.  Likewise if "coarse x %
                                        // 4" < 2 we are in the left half else
                                        // right half.  Ultimately we want the
                                        // bottom two bits of our attribute word
                                        // to be the palette selected. So shift
                                        // as required.
                                        if (ppu->vramAddr.coarseY & 0x02)
                                                ppu->bgNextTileAttrib >>= 4;

                                        if (ppu->vramAddr.coarseX & 0x02)
                                                ppu->bgNextTileAttrib >>= 2;

                                        ppu->bgNextTileAttrib &= 0x03;
                                        break;
                                }

                                case 4:
                                        // Fetch the next background tile LSB
                                        // bit plane from the pattern memory The
                                        // Tile ID has been read from the
                                        // nametable. We will use this id to
                                        // index into the pattern memory to find
                                        // the correct sprite (assuming the
                                        // sprites lie on 8x8 pixel boundaries
                                        // in that memory, which they do even
                                        // though 8x16 sprites exist, as
                                        // background tiles are always 8x8).
                                        //
                                        // Since the sprites are effectively 1
                                        // bit deep, but 8 pixels wide, we can
                                        // represent a whole sprite row as a
                                        // single byte, so offsetting into the
                                        // pattern memory is easy. In total
                                        // there is 8KB so we need a 13 bit
                                        // address.

                                        // "(control.pattern_background << 12)"  : the pattern memory selector
                                        //                                         from control register, either 0K
                                        //                                         or 4K offset
                                        // "((uint16_t)bg_next_tile_id << 4)"    : the tile id multiplied by 16, as
                                        //                                         2 lots of 8 rows of 8 bit pixels
                                        // "(vram_addr.fine_y)"                  : Offset into which row based on
                                        //                                         vertical scroll offset
                                        // "+ 0"                                 : Mental clarity for plane offset

                                        // Note: No PPU address bus offset
                                        // required as it starts at 0x0000
                                        ppu->bgNextTileLsb =
                                                PpuRead(ppu,
                                                        (ppu->control.patternBackground << 12) +
                                                        ((uint16_t)ppu->bgNextTileId << 4) +
                                                        (ppu->vramAddr.fineY) + 0);
                                        break;

                                case 6:
                                        // Fetch the next background tile MSB
                                        // bit plane from the pattern memory
                                        // This is the same as above, but has a
                                        // +8 offset to select the next bit
                                        // plane
                                        ppu->bgNextTileMsb =
                                                PpuRead(ppu,
                                                        (ppu->control.patternBackground << 12) +
                                                        ((uint16_t)ppu->bgNextTileId << 4) +
                                                        (ppu->vramAddr.fineY) + 8);
                                        break;

                                case 7:
                                        IncrementScrollX(ppu);
                                        break;
                        }
                }

                if (256 == ppu->cycle) {
                        IncrementScrollY(ppu);
                }

                if (257 == ppu->cycle) {
                        LoadBackgroundShifters(ppu);
                        TransferAddressX(ppu);
                }

                // Superflous reads of tile ID at end of scanline.
                if (338 == ppu->cycle || 340 == ppu->cycle) {
                        ppu->bgNextTileId = PpuRead(ppu, 0x2000 | (ppu->vramAddr.reg & 0x0FFF));
                }

                // End of vblank period, so reset the Y address.
                if (-1 == ppu->scanline && ppu->cycle >= 280 && ppu->cycle < 305) {
                        TransferAddressY(ppu);
                }
        }

        if (240 == ppu->scanline) {
                // Post render scanline - Do nothing!
        }

        // We've entered vblank/nmi.
        if (241 == ppu->scanline && 1 == ppu->cycle) {
                ppu->status.verticalBlank = 1;
                if (ppu->control.enableNmi) ppu->nmi = true;
        }

        uint8_t bgPixel = 0x00;
        uint8_t bgPalette = 0x00;

        if (ppu->mask.renderBackground) {
                uint16_t bitMux = 0x8000 >> ppu->fineX;

                uint8_t p0Pixel = (ppu->bgShifterPatternLo & bitMux) > 0;
                uint8_t p1Pixel = (ppu->bgShifterPatternHi & bitMux) > 0;

                bgPixel = (p1Pixel << 1) | p0Pixel;

                uint8_t bgPal0 = (ppu->bgShifterAttribLo & bitMux) > 0;
                uint8_t bgPal1 = (ppu->bgShifterAttribHi & bitMux) > 0;

                bgPalette = (bgPal1 << 1) | bgPal0;
        }

        struct color *color = PpuGetColorFromPaletteRam(ppu, bgPalette, bgPixel);
        SpriteSetPixel(ppu->screen, ppu->cycle - 1, ppu->scanline, color->rgba);

        ppu->cycle++;



@@ 212,10 626,6 @@ void PpuTick(struct ppu *ppu) {
        }
}

struct sprite *PpuGetPatternTable(struct ppu *ppu, uint8_t i) {
        return ppu->patternTableSprites[i];
}

uint8_t PpuRead(struct ppu *ppu, uint16_t addr) {
        uint8_t data = 0x00;
        addr &= 0x3FFF; // 0x3FFFF is PPU base memory.


@@ 225,8 635,32 @@ uint8_t PpuRead(struct ppu *ppu, uint16_t addr) {
                // MSB determines which table.
                uint16_t pattern_index = (addr & 0x1000) >> 12;
                data = ppu->patternTables[pattern_index][addr & 0x0FFF];
        } else if (addr >= 0x2000 && addr <= 0x2FFF) {
        } else if (addr >= 0x3000 && addr <= 0x3FFF) { // Palette Memory.
        } else if (addr >= 0x2000 && addr <= 0x3EFF) {
                switch (CartMirroring(ppu->cart)) {
                        case MIRROR_VERTICAL: {
                                if (addr >= 0x0000 && addr <= 0x03FF)
                                        data = ppu->nameTables[0][addr & 0x03FF];
                                if (addr >= 0x0400 && addr <= 0x07FF)
                                        data = ppu->nameTables[1][addr & 0x03FF];
                                if (addr >= 0x0800 && addr <= 0x0BFF)
                                        data = ppu->nameTables[0][addr & 0x03FF];
                                if (addr >= 0x0C00 && addr <= 0x0FFF)
                                        data = ppu->nameTables[1][addr & 0x03FF];
                        } break;
                        case MIRROR_HORIZONTAL: {
                                if (addr >= 0x0000 && addr <= 0x03FF)
                                        data = ppu->nameTables[0][addr & 0x03FF];
                                if (addr >= 0x0400 && addr <= 0x07FF)
                                        data = ppu->nameTables[0][addr & 0x03FF];
                                if (addr >= 0x0800 && addr <= 0x0BFF)
                                        data = ppu->nameTables[1][addr & 0x03FF];
                                if (addr >= 0x0C00 && addr <= 0x0FFF)
                                        data = ppu->nameTables[1][addr & 0x03FF];
                        } break;
                        default:
                                break;
                }
        } else if (addr >= 0x3F00 && addr <= 0x3FFF) { // Palette Memory.
                addr &= 0x001F; // Mask the bottom 5 bits.

                // Mirroring;


@@ 251,8 685,33 @@ void PpuWrite(struct ppu *ppu, uint16_t addr, uint8_t data) {
                // MSB determines which table.
                uint16_t pattern_index = (addr & 0x1000) >> 12;
                ppu->patternTables[pattern_index][addr & 0x0FFF] = data;
        } else if (addr >= 0x2000 && addr <= 0x2FFF) {
        } else if (addr >= 0x3000 && addr <= 0x3FFF) { // Palette Memory.
        } else if (addr >= 0x2000 && addr <= 0x3EFF) {
                addr &= 0x0FFF;
                switch (CartMirroring(ppu->cart)) {
                        case MIRROR_VERTICAL: {
                                if (addr >= 0x0000 && addr <= 0x03FF)
                                        ppu->nameTables[0][addr & 0x03FF] = data;
                                if (addr >= 0x0400 && addr <= 0x07FF)
                                        ppu->nameTables[1][addr & 0x03FF] = data;
                                if (addr >= 0x0800 && addr <= 0x0BFF)
                                        ppu->nameTables[0][addr & 0x03FF] = data;
                                if (addr >= 0x0C00 && addr <= 0x0FFF)
                                        ppu->nameTables[1][addr & 0x03FF] = data;
                        } break;
                        case MIRROR_HORIZONTAL: {
                                if (addr >= 0x0000 && addr <= 0x03FF)
                                        ppu->nameTables[0][addr & 0x03FF] = data;
                                if (addr >= 0x0400 && addr <= 0x07FF)
                                        ppu->nameTables[0][addr & 0x03FF] = data;
                                if (addr >= 0x0800 && addr <= 0x0BFF)
                                        ppu->nameTables[1][addr & 0x03FF] = data;
                                if (addr >= 0x0C00 && addr <= 0x0FFF)
                                        ppu->nameTables[1][addr & 0x03FF] = data;
                        } break;
                        default:
                                break;
                }
        } else if (addr >= 0x3F00 && addr <= 0x3FFF) { // Palette Memory.
                addr &= 0x001F; // Mask the bottom 5 bits.

                // Mirroring;


@@ 264,13 723,117 @@ void PpuWrite(struct ppu *ppu, uint16_t addr, uint8_t data) {
        }
}

//! \brief Get the specified color from palette memory
//!
//! \param palette which palette to use for color
//! \param pixel 0, 1, 2 or 3
struct color *GetColorFromPaletteRam(struct ppu *ppu, uint8_t palette, uint8_t pixel) {
        uint16_t palette_id = palette << 2; // Multiply the palette by 4 to get the
                                   // physical offset.
void PpuWriteViaCpu(struct ppu *ppu, uint16_t addr, uint8_t data) {
        switch(addr) {
                case 0x0000: // Control
                        ppu->control.reg = data;
                        ppu->tramAddr.nametableX = ppu->control.nametableX;
                        ppu->tramAddr.nametableY = ppu->control.nametableY;
                break;

                case 0x0001: // Mask
                        ppu->mask.reg = data;
                break;

                case 0x0002: // Status
                break;

                case 0x0003: // OAM Address
                break;

                case 0x0004: // OAM Data
                break;

                case 0x0005: // Scroll
                        if (0 == ppu->addressLatch) {
                                ppu->fineX = data & 0x07;
                                ppu->tramAddr.coarseX = data >> 3;
                                ppu->addressLatch = 1;

                        } else {
                                ppu->tramAddr.fineY = data & 0x07;
                                ppu->tramAddr.coarseY = data >> 3;
                                ppu->addressLatch = 0;
                        }
                break;

                case 0x0006: // PPU Address
                        if (0 == ppu->addressLatch) {
                                ppu->tramAddr.reg = (ppu->tramAddr.reg & 0x00FF) | (data << 8);
                                ppu->addressLatch = 1;
                        } else {
                                ppu->tramAddr.reg = (ppu->tramAddr.reg & 0xFF00) | data;
                                ppu->vramAddr = ppu->tramAddr;
                                ppu->addressLatch = 0;
                        }
                break;

                case 0x0007: // PPU Data
                        PpuWrite(ppu, ppu->vramAddr.reg, data);
                        // Increment mode dictates whether we are writing data
                        // horizontally or vertically in memory.
                        ppu->vramAddr.reg += (ppu->control.incrementMode ? 32 : 1);
                break;
        }
}

uint8_t PpuReadViaCpu(struct ppu *ppu, uint16_t addr) {
        uint8_t data = 0x00;
        addr &= 0x3FFF;

        switch(addr) {
                case 0x0000: // Control
                break;

                case 0x0001: // Mask
                break;

                case 0x0002: // Status
                        // Only the top 3 bits are actual status data.
                        // The remaining 5 bits are garbage - but _most_likely_
                        // whatever was in the data buffer.
                        data = (ppu->status.reg & 0xE0) | (ppu->dataBuffer & 0x1F);

                        // Reading status also clears the vertical blank flag.
                        ppu->status.verticalBlank = 0;

                        // Reading status also resets the address latch.
                        ppu->addressLatch = 0;
                break;

                case 0x0003: // OAM Address
                break;

                case 0x0004: // OAM Data
                break;

                case 0x0005: // Scroll
                break;

                case 0x0006: // PPU Address
                break;

                case 0x0007: // PPU Data
                        // Normal reads are delayed by one cycle, so read the
                        // buffer, then refresh the buffer.
                        data = ppu->dataBuffer;
                        ppu->dataBuffer = PpuRead(ppu, ppu->vramAddr.reg);

                        // However, reading palette data is immediate.
                        if (ppu->vramAddr.reg > 0x3F00) {
                                data = ppu->dataBuffer;
                        }
                        ppu->vramAddr.reg += (ppu->control.incrementMode ? 32 : 1);
                break;
        }

        return data;
}


struct color *PpuGetColorFromPaletteRam(struct ppu *ppu, uint8_t palette, uint8_t pixel) {
        // Multiply the palette by 4 to get the physical offset.
        uint16_t palette_id = palette << 2;

        uint16_t addr = 0x3F00 + // Base address of palette memory.
                palette_id + // Which palette to read.


@@ 280,6 843,53 @@ struct color *GetColorFromPaletteRam(struct ppu *ppu, uint8_t palette, uint8_t p
        return &ppu->palette[PpuRead(ppu, addr) & 0x3F];
}

struct sprite *PpuGetPatternTable(struct ppu *ppu, uint8_t i, uint8_t palette) {
        // Loop through all 16x16 tiles
        for (int tileY = 0; tileY < 16; tileY++) {
                for (int tileX = 0; tileX < 16; tileX++) {
                        // Convert the 2D tile coordinate into a 1D offset into pattern table memory.
                        int byteOffset = tileY * 256 + tileX * 16;

                        // Loop through 8 rows of 8 pixels per tile.
                        for (int row = 0; row < 8; row++) {
                                // Each pixel is 2 bits, stored in two separate bit planes.
                                // Each bit plane is 64 bits, which means the LSb
                                // and MSb are always 64 bits (8 bytes) apart.
                                uint8_t tileLsb = PpuRead(ppu, i * CHR_ROM + byteOffset + row + 0);
                                uint8_t tileMsb = PpuRead(ppu, i * CHR_ROM + byteOffset + row + 8);

                                // We read 8 bits worth of data, now we iterate
                                // through each column of the current row.
                                for (int col = 0; col < 8; col++) {
                                        uint8_t pixel = (tileLsb & 0x01) + (tileMsb& 0x01);

                                        // Shift each byte right one bit so the
                                        // next iteration works on the next
                                        // column.
                                        tileLsb >>= 1;
                                        tileMsb >>= 1;

                                        // Because we are reading the LSb first,
                                        // we are effectively reading right to
                                        // left; so when we draw, we need to
                                        // make sure we are drawing in the right
                                        // order. Note that our index starts at
                                        // the left but our pixel is on the
                                        // right.
                                        int x = tileX * 8 + (7 - col);
                                        int y = tileY * 8 + row;

                                        struct color *color = PpuGetColorFromPaletteRam(ppu, palette, pixel);

                                        SpriteSetPixel(ppu->patternTableSprites[i], x, y, color->rgba);
                                }
                        }
                }
        }

        return ppu->patternTableSprites[i];
}

void PpuReset(struct ppu *ppu) {
        // TODO PpuReset()
}


@@ 295,3 905,15 @@ void PpuResetFrameCompletion(struct ppu *ppu) {
struct sprite *PpuScreen(struct ppu *ppu) {
        return ppu->screen;
}

uint8_t PpuGetNmi(struct ppu *ppu) {
        return ppu->nmi;
}

void PpuSetNmi(struct ppu *ppu, uint8_t trueOrFalse) {
        ppu->nmi = trueOrFalse;
}

uint8_t *PpuGetNameTable(struct ppu *ppu, uint8_t i) {
        return ppu->nameTables[i];
}

M ppu.h => ppu.h +42 -1
@@ 4,7 4,7 @@

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



@@ 17,9 17,12 @@
#ifndef PPU_VERSION
#define PPU_VERSION "0.1.0"

#include <stdint.h>

struct ppu;
struct cart;
struct sprite;
struct color;

struct ppu *
PpuInit();


@@ 45,4 48,42 @@ PpuResetFrameCompletion(struct ppu *ppu);
struct sprite *
PpuScreen(struct ppu *ppu);

//! \brief Draws CHR ROM for a given pattern table into a sprite
//!
//! \param[in,out] ppu
//! \param[in] i which pattern table to draw
//! \param[in] palette which palette to use
//! \return a sprite representing CHR ROM
struct sprite *
PpuGetPatternTable(struct ppu *ppu, uint8_t i, uint8_t palette);

//! \brief Get the specified color from palette memory
//!
//! \param[in,out] ppu
//! \param[in] palette which palette to use for color
//! \param[in] pixel 0, 1, 2 or 3
struct color *
PpuGetColorFromPaletteRam(struct ppu *ppu, uint8_t palette, uint8_t pixel);

uint8_t
PpuReadViaCpu(struct ppu *ppu, uint16_t addr);

void
PpuWriteViaCpu(struct ppu *ppu, uint16_t addr, uint8_t data);

uint8_t
PpuRead(struct ppu *ppu, uint16_t addr);

void
PpuWrite(struct ppu *ppu, uint16_t addr, uint8_t data);

uint8_t
PpuGetNmi(struct ppu *ppu);

void
PpuSetNmi(struct ppu *ppu, uint8_t trueOrFalse);

uint8_t *
PpuGetNameTable(struct ppu *ppu, uint8_t i);

#endif // PPU_VERSION

M sprite.c => sprite.c +1 -1
@@ 4,7 4,7 @@

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


M sprite.h => sprite.h +1 -1
@@ 4,7 4,7 @@

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


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

  File: util.c
  Created: 2019-11-15
  Updated: 2019-11-21
  Author: Aaron Oman
  Notice: GNU AGPLv3 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 util.c
#include <string.h> // memmove

#include "util.h"

void SwapGeneric(void *v1, void *v2, size_t size) {
        char temp[size];
        memmove(temp, v1, size);
        memmove(v1, v2, size);
        memmove(v2, temp, size);
}

void HexToString(uint32_t hex, uint8_t nibbles, char *buf, uint8_t size) {
        if (nibbles > size) {
                nibbles = size;
        }

        for (int i = nibbles - 1; i >= 0; i--, hex >>= 4) {
                buf[i] = "0123456789ABCDEF"[hex & 0xF];
        }

        buf[nibbles] = '\0';
}

M util.h => util.h +21 -1
@@ 4,7 4,7 @@

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



@@ 33,4 33,24 @@
#define MB_AS_B(x) KB((x)) * 1024
#define GB_AS_B(x) MB((x)) * 1024

//! \brief A mostly generic implementation of swap
//!
//! Both v1 and v2 must point to data that is the same size, as specified in the
//! size parameter.
//!
//! \param[in,out] v1 first value
//! \param[in,out] v2 second value
//! \param[in] size v1 and v2 must each be this size
void
SwapGeneric(void *v1, void *v2, size_t size);

//! \brief convert number to hexadecimal string
//!
//! \param[in] hex number to stringify
//! \param[in] nibbles number of nibbles in data source
//! \param[in,out] buf pre-allocated char buffer
//! \param[in] size size of buf
void
HexToString(uint32_t hex, uint8_t nibbles, char *buf, uint8_t size);

#endif // UTIL_VERSION