Skip to content

Instantly share code, notes, and snippets.

@scottferg
Created September 26, 2012 01:48
Show Gist options
  • Save scottferg/3785545 to your computer and use it in GitHub Desktop.
Save scottferg/3785545 to your computer and use it in GitHub Desktop.
NES PPU and MMC1 mapper
package main
import (
"fmt"
)
const (
BankUpper = iota
BankLower
)
type Mmc1 struct {
RomBanks [][]Word
VromBanks [][]Word
PrgBankCount int
ChrRomCount int
BatteryBacked bool
Data []byte
Buffer int
BufferCounter uint
PrgSwapBank int
PrgBankSize int
ChrBankSize int
}
func (m *Mmc1) Write(v Word, a int) {
// If reset bit is set
if v&0x80 != 0 {
fmt.Println("Resetting MMC")
m.BufferCounter = 0
m.Buffer = 0x0
m.PrgSwapBank = BankLower
m.PrgBankSize = Size16k
} else {
// Buffer the write
m.Buffer = (m.Buffer & (0xFF - (0x1 << m.BufferCounter))) | ((int(v) & 0x1) << m.BufferCounter)
m.BufferCounter++
// If the buffer is filled
if m.BufferCounter == 0x5 {
m.SetRegister(m.RegisterNumber(a), m.Buffer)
// Reset buffer
m.BufferCounter = 0
m.Buffer = 0
}
}
}
func (m *Mmc1) SetRegister(reg int, v int) {
switch reg {
// Control register
case 0:
// Set mirroring
switch v & 0x3 {
case 0x0:
ppu.Nametables.SetMirroring(MirroringSingleUpper)
case 0x1:
ppu.Nametables.SetMirroring(MirroringSingleLower)
case 0x2:
ppu.Nametables.SetMirroring(MirroringVertical)
case 0x3:
ppu.Nametables.SetMirroring(MirroringHorizontal)
}
switch (v >> 0x2) & 0x3 {
case 0x0:
fallthrough
case 0x1:
m.PrgBankSize = Size32k
m.PrgSwapBank = BankLower
case 0x2:
m.PrgBankSize = Size16k
m.PrgSwapBank = BankUpper
case 0x3:
m.PrgBankSize = Size16k
m.PrgSwapBank = BankLower
}
// Set CHR bank size
switch (v >> 0x4) & 0x1 {
case 0x0:
m.ChrBankSize = Size8k
case 0x1:
m.ChrBankSize = Size4k
}
// CHR Bank 0
case 1:
if m.ChrRomCount == 0 {
return
}
// Select VROM at 0x0000
switch m.ChrBankSize {
case Size8k:
// Swap 8k VROM (in 8k mode, ignore first bit D0)
var bank int
if v&0x10 == 0x10 {
bank = (len(m.VromBanks) / 2) + (v & 0xF)
} else {
bank = v & 0xF
}
WriteVramBank(m.VromBanks, bank, 0x0, Size8k)
case Size4k:
// Swap 4k VROM
var bank int
if v&0x10 == 0x10 {
bank = (len(m.VromBanks) / 2) + (v & 0xF)
} else {
bank = v & 0xF
}
WriteVramBank(m.VromBanks, bank, 0x0, Size4k)
}
// CHR Bank 1
case 2:
if m.ChrRomCount == 0 {
return
}
// Select VROM bank at 0x1000, ignored in
// 8k switching mode
if m.ChrBankSize == Size4k {
var bank int
if v&0x10 == 0x10 {
bank = (len(m.VromBanks) / 2) + (v & 0xF)
} else {
bank = v & 0xF
}
WriteVramBank(m.VromBanks, bank, 0x1000, Size4k)
}
// PRG Bank
case 3:
switch m.PrgBankSize {
case Size32k:
// Swap 32k ROM (in 32k mode, ignore first bit D0)
bank := ((v >> 0x1) & 0x7) * 2
fmt.Printf("32k write to: %d\n", bank/2)
WriteRamBank(m.RomBanks, bank, 0x8000, Size16k)
WriteRamBank(m.RomBanks, bank+1, 0xC000, Size16k)
case Size16k:
// Swap 16k ROM
bank := v & 0xF
if m.PrgSwapBank == BankUpper {
WriteRamBank(m.RomBanks, bank, 0xC000, Size16k)
} else {
WriteRamBank(m.RomBanks, bank, 0x8000, Size16k)
}
}
}
}
func (m *Mmc1) RegisterNumber(a int) int {
switch {
case a >= 0x8000 && a <= 0x9FFF:
return 0
case a >= 0xA000 && a <= 0xBFFF:
return 1
case a >= 0xC000 && a <= 0xDFFF:
return 2
}
return 3
}
package main
import (
"fmt"
"math"
)
const (
StatusSpriteOverflow = iota
StatusSprite0Hit
StatusVblankStarted
Pal = 70
Ntsc = 20
)
type SpriteData struct {
Tiles [256]Word
YCoordinates [256]Word
Attributes [256]Word
XCoordinates [256]Word
}
type Flags struct {
BaseNametableAddress Word
VramAddressInc Word
SpritePatternAddress Word
BackgroundPatternAddress Word
SpriteSize Word
MasterSlaveSel Word
NmiOnVblank Word
}
type Pixel struct {
Color int
Value int
}
type Masks struct {
Grayscale bool
ShowBackgroundOnLeft bool
ShowSpritesOnLeft bool
ShowBackground bool
ShowSprites bool
IntensifyReds bool
IntensifyGreens bool
IntensifyBlues bool
}
type Registers struct {
Control Word
Mask Word
Status Word
VramDataBuffer Word
VramAddress int
VramLatch int
SpriteRamAddress int
FineX Word
Data Word
WriteLatch bool
HighBitShift uint16
LowBitShift uint16
}
type Ppu struct {
Registers
Flags
Masks
SpriteData
Vram [0xFFFF]Word
SpriteRam [0x100]Word
Nametables Nametable
PaletteRam [0x20]Word
AttributeLocation [0x400]uint
AttributeShift [0x400]uint
Palettebuffer []Pixel
Framebuffer []int
Output chan []int
Cycle int
Scanline int
Timestamp int
VblankTime int
FrameCount int
FrameCycles int
}
func (p *Ppu) Init() (chan []int, chan []int) {
p.WriteLatch = true
p.Output = make(chan []int)
p.Cycle = 0
p.Scanline = -1
p.FrameCount = 0
p.VblankTime = 20 * 341 * 5 // NTSC
for i, _ := range p.Vram {
p.Vram[i] = 0x00
}
for i, _ := range p.SpriteRam {
p.SpriteRam[i] = 0x00
}
for i, _ := range p.AttributeShift {
x := uint(i)
p.AttributeShift[i] = ((x >> 4) & 0x04) | (x & 0x02)
p.AttributeLocation[i] = ((x >> 2) & 0x07) | (((x >> 4) & 0x38) | 0x3C0)
}
p.Palettebuffer = make([]Pixel, 0xF000)
p.Framebuffer = make([]int, 0xF000)
return p.Output, nil
}
func (p *Ppu) PpuRegRead(a int) (Word, error) {
switch a & 0x7 {
case 0x2:
return p.ReadStatus()
case 0x4:
return p.ReadOamData()
case 0x7:
return p.ReadData()
}
return 0, nil
}
func (p *Ppu) PpuRegWrite(v Word, a int) {
switch a & 0x7 {
case 0x0:
p.WriteControl(v)
case 0x1:
p.WriteMask(v)
case 0x3:
p.WriteOamAddress(v)
case 0x4:
p.WriteOamData(v)
case 0x5:
p.WriteScroll(v)
case 0x6:
p.WriteAddress(v)
case 0x7:
p.WriteData(v)
}
if a == 0x4014 {
p.WriteDma(v)
}
}
// Writes to mirrored regions of VRAM
func (p *Ppu) writeMirroredVram(a int, v Word) {
if a >= 0x3F00 {
base := a & 0x1F // 0b11111
if base == 0x0 || base == 0x10 {
p.PaletteRam[0x10] = v
p.PaletteRam[0x00] = v
} else {
p.PaletteRam[base] = v
}
p.PaletteRam[0x10] = p.PaletteRam[0x0]
p.PaletteRam[0x14] = p.PaletteRam[0x4]
p.PaletteRam[0x18] = p.PaletteRam[0x8]
p.PaletteRam[0x1C] = p.PaletteRam[0xC]
} else {
p.Vram[a-0x1000] = v
}
}
func (p *Ppu) raster() {
length := len(p.Palettebuffer)
for i := length - 1; i >= 0; i-- {
y := int(math.Floor(float64(i / 256)))
x := i - (y * 256)
var color int
color = p.Palettebuffer[i].Color
p.Framebuffer[(y*256)+x] = color
p.Palettebuffer[i].Value = 0
}
p.Output <- p.Framebuffer
}
func (p *Ppu) Step() {
switch {
case p.Scanline == 241:
if p.Cycle == 1 {
// We're in VBlank
p.setStatus(StatusVblankStarted)
// $2000.7 enables/disables NMIs
if p.NmiOnVblank == 0x1 {
// Request NMI
cpu.RequestInterrupt(InterruptNmi)
}
// TODO: This should happen per scanline
if p.ShowSprites && (p.SpriteSize&0x1 == 0x1) {
for i := 0; i < 240; i++ {
p.evaluateScanlineSprites(i)
}
}
p.raster()
p.Cycle++
}
case p.Scanline == 261: // End of vblank
if p.Cycle == 341 {
// Clear VBlank flag
p.clearStatus(StatusVblankStarted)
p.Scanline = -1
p.Cycle = 0
p.FrameCount++
return
}
case p.Scanline < 240 && p.Scanline > -1:
if p.Cycle == 254 {
if p.ShowBackground {
p.renderTileRow()
}
// TODO: Shouldn't have to do this
if p.ShowSprites && (p.SpriteSize&0x1 == 0) {
p.evaluateScanlineSprites(p.Scanline)
}
} else if p.Cycle == 256 {
if p.ShowBackground {
p.updateEndScanlineRegisters()
}
} else if p.Cycle == 341 {
p.Cycle = 0
p.Scanline++
return
}
case p.Scanline == -1:
if p.Cycle == 1 {
p.clearStatus(StatusSprite0Hit)
p.clearStatus(StatusSpriteOverflow)
} else if p.Cycle == 304 {
// Copy scroll latch into VRAMADDR register
if p.ShowBackground || p.ShowSprites {
// p.VramAddress = (p.VramAddress) | (p.VramLatch & 0x41F)
p.VramAddress = p.VramLatch
}
}
}
if p.Cycle == 341 {
p.Cycle = 0
p.Scanline++
}
p.Cycle++
}
func (p *Ppu) updateEndScanlineRegisters() {
/*******************************************************
TODO: Some documentatino implies that the X increment
should occur 34 times per scanline. These may not be
necessary.
***********************************************************/
// Flip bit 10 on wraparound
if p.VramAddress&0x1F == 0x1F {
// If rendering is enabled, at the end of a scanline
// copy bits 10 and 4-0 from VRAM latch into VRAMADDR
p.VramAddress ^= 0x41F
} else {
p.VramAddress++
}
// Flip bit 10 on wraparound
if p.VramAddress&0x1F == 0x1F {
// If rendering is enabled, at the end of a scanline
// copy bits 10 and 4-0 from VRAM latch into VRAMADDR
p.VramAddress ^= 0x41F
} else {
p.VramAddress++
}
// Scanline has ended
if p.VramAddress&0x7000 == 0x7000 {
tmp := p.VramAddress & 0x3E0
p.VramAddress &= 0xFFF
switch tmp {
case 0x3A0:
p.VramAddress ^= 0xBA0
case 0x3E0:
p.VramAddress ^= 0x3E0
default:
p.VramAddress += 0x20
}
} else {
// Increment the fine-Y
p.VramAddress += 0x1000
}
if p.ShowBackground && p.ShowSprites {
// fmt.Printf("Nametable before: %d ", (p.VramAddress&0xC00)>>10)
p.VramAddress = (p.VramAddress & 0x7BE0) | (p.VramLatch & 0x41F)
// fmt.Printf("Nametable after: %d\n", (p.VramAddress&0xC00)>>10)
}
}
// $2000
func (p *Ppu) WriteControl(v Word) {
p.Control = v
// Control flag
// 7654 3210
// |||| ||||
// |||| ||++- Base nametable address
// |||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
// |||| |+--- VRAM address increment per CPU read/write of PPUDATA
// |||| | (0: increment by 1, going across; 1: increment by 32, going down)
// |||| +---- Sprite pattern table address for 8x8 sprites
// |||| (0: $0000; 1: $1000; ignored in 8x16 mode)
// |||+------ Background pattern table address (0: $0000; 1: $1000)
// ||+------- Sprite size (0: 8x8; 1: 8x16)
// |+-------- PPU master/slave select (has no effect on the NES)
// +--------- Generate an NMI at the start of the
// vertical blanking interval (0: off; 1: on)
p.BaseNametableAddress = v & 0x03
p.VramAddressInc = (v >> 2) & 0x01
p.SpritePatternAddress = (v >> 3) & 0x01
p.BackgroundPatternAddress = (v >> 4) & 0x01
p.SpriteSize = (v >> 5) & 0x01
p.NmiOnVblank = (v >> 7) & 0x01
p.VramLatch = (p.VramLatch & 0x73FF) | ((int(v) & 0x03) << 10)
}
// $2001
func (p *Ppu) WriteMask(v Word) {
p.Mask = v
// 76543210
// ||||||||
// |||||||+- Grayscale (0: normal color; 1: produce a monochrome display)
// ||||||+-- 1: Show background in leftmost 8 pixels of screen; 0: Hide
// |||||+--- 1: Show sprites in leftmost 8 pixels of screen; 0: Hide
// ||||+---- 1: Show background
// |||+----- 1: Show sprites
// ||+------ Intensify reds (and darken other colors)
// |+------- Intensify greens (and darken other colors)
// +-------- Intensify blues (and darken other colors)
p.Grayscale = (v&0x01 == 0x01)
p.ShowBackgroundOnLeft = (((v >> 1) & 0x01) == 0x01)
p.ShowSpritesOnLeft = (((v >> 2) & 0x01) == 0x01)
p.ShowBackground = (((v >> 3) & 0x01) == 0x01)
p.ShowSprites = (((v >> 4) & 0x01) == 0x01)
p.IntensifyReds = (((v >> 5) & 0x01) == 0x01)
p.IntensifyGreens = (((v >> 6) & 0x01) == 0x01)
p.IntensifyBlues = (((v >> 7) & 0x01) == 0x01)
}
func (p *Ppu) clearStatus(s Word) {
current := Ram[0x2002]
switch s {
case StatusSpriteOverflow:
current = current & 0xDF
case StatusSprite0Hit:
current = current & 0xBF
case StatusVblankStarted:
current = current & 0x7F
}
Ram.WriteMirroredRam(current, 0x2002)
}
func (p *Ppu) setStatus(s Word) {
current := Ram[0x2002]
switch s {
case StatusSpriteOverflow:
current = current | 0x20
case StatusSprite0Hit:
current = current | 0x40
case StatusVblankStarted:
current = current | 0x80
}
Ram.WriteMirroredRam(current, 0x2002)
}
// $2002
func (p *Ppu) ReadStatus() (s Word, e error) {
p.WriteLatch = true
s = Ram[0x2002]
// Clear VBlank flag
p.clearStatus(StatusVblankStarted)
p.VramLatch = 0
return
}
// $2003
func (p *Ppu) WriteOamAddress(v Word) {
p.SpriteRamAddress = int(v)
}
// $2004
func (p *Ppu) WriteOamData(v Word) {
p.SpriteRam[p.SpriteRamAddress] = v
p.updateBufferedSpriteMem(p.SpriteRamAddress, v)
p.SpriteRamAddress++
p.SpriteRamAddress %= 0x100
}
// $4014
func (p *Ppu) WriteDma(v Word) {
// Halt the CPU for 512 cycles
cpu.CyclesToWait = 512
// Fill sprite RAM
addr := int(v) * 0x100
for i := 0; i < 256; i++ {
d, _ := Ram.Read(addr + i)
p.SpriteRam[i] = d
p.updateBufferedSpriteMem(i, d)
}
}
func (p *Ppu) updateBufferedSpriteMem(a int, v Word) {
i := int(math.Floor(float64(a / 4)))
switch a % 4 {
case 0x0:
p.YCoordinates[i] = v
case 0x1:
p.Tiles[i] = v
case 0x2:
// Attribute
p.Attributes[i] = v
case 0x3:
p.XCoordinates[i] = v
}
}
// $2004
func (p *Ppu) ReadOamData() (Word, error) {
fmt.Println("Reading OAM")
return p.SpriteRam[p.SpriteRamAddress], nil
}
// $2005
func (p *Ppu) WriteScroll(v Word) {
if p.WriteLatch {
p.VramLatch = p.VramLatch & 0x7FE0
p.VramLatch = p.VramLatch | ((int(v) & 0xF8) >> 3)
p.FineX = v & 0x07
} else {
p.VramLatch = p.VramLatch & 0xC1F
p.VramLatch = p.VramLatch | (((int(v) & 0xF8) << 2) | ((int(v) & 0x07) << 12))
}
p.WriteLatch = !p.WriteLatch
}
// $2006
func (p *Ppu) WriteAddress(v Word) {
if p.WriteLatch {
p.VramLatch = p.VramLatch & 0xFF
p.VramLatch = p.VramLatch | ((int(v) & 0x3F) << 8)
} else {
p.VramLatch = p.VramLatch & 0x7F00
p.VramLatch = p.VramLatch | int(v)
p.VramAddress = p.VramLatch
}
p.WriteLatch = !p.WriteLatch
}
// $2007
func (p *Ppu) WriteData(v Word) {
if p.VramAddress > 0x3000 {
p.writeMirroredVram(p.VramAddress, v)
} else if p.VramAddress >= 0x2000 && p.VramAddress < 0x3000 {
// Nametable mirroring
p.Nametables.writeNametableData(p.VramAddress, v)
} else {
p.Vram[p.VramAddress&0x3FFF] = v
}
p.incrementVramAddress()
}
// $2007
func (p *Ppu) ReadData() (Word, error) {
// Reads from $2007 are buffered with a
// 1-byte delay
tmp := p.VramDataBuffer
p.VramDataBuffer = p.Vram[p.VramAddress]
p.incrementVramAddress()
return tmp, nil
}
func (p *Ppu) incrementVramAddress() {
switch p.VramAddressInc {
case 0x01:
p.VramAddress = p.VramAddress + 0x20
default:
p.VramAddress = p.VramAddress + 0x01
}
}
func (p *Ppu) sprPatternTableAddress(i int) int {
if p.SpriteSize&0x01 != 0x0 {
// 8x16 Sprites
if i&0x01 != 0 {
return 0x1000 | ((int(i) >> 1) * 0x20)
} else {
return ((int(i) >> 1) * 0x20)
}
}
// 8x8 Sprites
var a int
if p.SpritePatternAddress == 0x01 {
a = 0x1000
} else {
a = 0x0
}
return int(i)*0x10 + a
}
func (p *Ppu) bgPatternTableAddress(i Word) int {
var a int
if p.BackgroundPatternAddress == 0x01 {
a = 0x1000
} else {
a = 0x0
}
return (int(i) << 4) | (p.VramAddress >> 12) | a
}
func (p *Ppu) renderTileRow() {
// Generates each tile, one scanline at a time
// and applies the palette
// Load first two tiles into shift registers at start, then load
// one per loop and shift the other back out
fetchTileAttributes := func() (uint16, uint16, Word) {
attrAddr := 0x23C0 | (p.VramAddress & 0xC00) | int(p.AttributeLocation[p.VramAddress&0x3FF])
shift := p.AttributeShift[p.VramAddress&0x3FF]
attr := ((p.Nametables.readNametableData(attrAddr) >> shift) & 0x03) << 2
index := p.Nametables.readNametableData(p.VramAddress)
t := p.bgPatternTableAddress(index)
// Flip bit 10 on wraparound
if p.VramAddress&0x1F == 0x1F {
// If rendering is enabled, at the end of a scanline
// copy bits 10 and 4-0 from VRAM latch into VRAMADDR
p.VramAddress ^= 0x41F
} else {
p.VramAddress++
}
return uint16(p.Vram[t]), uint16(p.Vram[t+8]), attr
}
// Move first tile into shift registers
low, high, attr := fetchTileAttributes()
p.LowBitShift, p.HighBitShift = low, high
low, high, attrBuf := fetchTileAttributes()
// Get second tile, move the pixels into the right side of
// shift registers
// Current tile to render is attrBuf
p.LowBitShift = (p.LowBitShift << 8) | low
p.HighBitShift = (p.HighBitShift << 8) | high
for x := 0; x < 32; x++ {
var palette int
var b uint
for b = 0; b < 8; b++ {
fbRow := p.Scanline*256 + ((x * 8) + int(b))
pixel := (p.LowBitShift >> (15 - b - uint(p.FineX))) & 0x01
pixel += ((p.HighBitShift >> (15 - b - uint(p.FineX)) & 0x01) << 1)
// If we're grabbing the pixel from the high
// part of the shift register, use the buffered
// palette, not the current one
if (15 - b - uint(p.FineX)) < 8 {
palette = p.bgPaletteEntry(attrBuf, pixel)
} else {
palette = p.bgPaletteEntry(attr, pixel)
}
p.Palettebuffer[fbRow] = Pixel{
PaletteRgb[palette%64],
int(pixel),
}
}
// xcoord = p.VramAddress & 0x1F
attr = attrBuf
// Shift the first tile out, bring the new tile in
low, high, attrBuf = fetchTileAttributes()
p.LowBitShift = (p.LowBitShift << 8) | low
p.HighBitShift = (p.HighBitShift << 8) | high
}
}
func (p *Ppu) evaluateScanlineSprites(line int) {
spriteCount := 0
for i, y := range p.SpriteData.YCoordinates {
// if p.Scanline - int(y)+1 >= 0 && p.Scanline - int(y)+1 < 8 {
if int(y) > (line-1)-8 && int(y)+7 < (line-1)+8 {
attrValue := p.Attributes[i] & 0x3
t := p.SpriteData.Tiles[i]
c := (line - 1) - int(y)
// If vertical flip is set
ycoord := int(p.YCoordinates[i]) + c + 1
if (p.Attributes[i]>>7)&0x1 == 0x1 {
ycoord = int(p.YCoordinates[i]) + (8 - c)
}
if p.SpriteSize&0x01 != 0x0 {
// 8x16 Sprite
s := p.sprPatternTableAddress(int(t))
tile := p.Vram[s : s+16]
p.decodePatternTile([]Word{tile[c], tile[c+8]},
int(p.XCoordinates[i]),
ycoord,
p.sprPaletteEntry(uint(attrValue)),
&p.Attributes[i], i == 0)
// Next tile
tile = p.Vram[s+16 : s+32]
p.decodePatternTile([]Word{tile[c], tile[c+8]},
int(p.XCoordinates[i]),
ycoord+8,
p.sprPaletteEntry(uint(attrValue)),
&p.Attributes[i], i == 0)
} else {
// 8x8 Sprite
s := p.sprPatternTableAddress(int(t))
tile := p.Vram[s : s+16]
p.decodePatternTile([]Word{tile[c], tile[c+8]},
int(p.XCoordinates[i]),
ycoord,
p.sprPaletteEntry(uint(attrValue)),
&p.Attributes[i], i == 0)
}
spriteCount++
if spriteCount == 8 {
p.setStatus(StatusSpriteOverflow)
break
}
}
}
}
func (p *Ppu) decodePatternTile(t []Word, x, y int, pal []Word, attr *Word, spZero bool) {
var b uint
for b = 0; b < 8; b++ {
var xcoord int
if (*attr>>6)&0x1 != 0 {
xcoord = x + int(b)
} else {
xcoord = x + int(7-b)
}
if (*attr>>7)&0x1 == 0x1 {
// fmt.Printf("Y: %d\n", y)
}
fbRow := y*256 + xcoord
// Store the bit 0/1
pixel := (t[0] >> b) & 0x01
pixel += ((t[1] >> b & 0x01) << 1)
trans := false
if attr != nil && pixel == 0 {
trans = true
}
// Set the color of the pixel in the buffer
//
if fbRow < 0xF000 && !trans {
priority := (*attr >> 5) & 0x1
if p.Palettebuffer[fbRow].Value != 0 && spZero {
// Since we render background first, if we're placing an opaque
// pixel here and the existing pixel is opaque, we've hit
// Sprite 0
p.setStatus(StatusSprite0Hit)
}
if p.Palettebuffer[fbRow].Value != 0 && priority == 1 {
// Pixel is already rendered and priority
// 1 means show behind background
continue
}
p.Palettebuffer[fbRow] = Pixel{
PaletteRgb[int(pal[pixel])%64],
int(pixel),
}
}
}
}
func (p *Ppu) bgPaletteEntry(a Word, pix uint16) (pal int) {
if pix == 0x0 {
return int(p.PaletteRam[0x00])
}
switch a {
case 0x0:
return int(p.PaletteRam[0x00+pix])
case 0x4:
return int(p.PaletteRam[0x04+pix])
case 0x8:
return int(p.PaletteRam[0x08+pix])
case 0xC:
return int(p.PaletteRam[0x0C+pix])
}
return
}
func (p *Ppu) sprPaletteEntry(a uint) (pal []Word) {
switch a {
case 0x0:
pal = []Word{
p.PaletteRam[0x10],
p.PaletteRam[0x11],
p.PaletteRam[0x12],
p.PaletteRam[0x13],
}
case 0x1:
pal = []Word{
p.PaletteRam[0x10],
p.PaletteRam[0x15],
p.PaletteRam[0x16],
p.PaletteRam[0x17],
}
case 0x2:
pal = []Word{
p.PaletteRam[0x10],
p.PaletteRam[0x19],
p.PaletteRam[0x1A],
p.PaletteRam[0x1B],
}
case 0x3:
pal = []Word{
p.PaletteRam[0x10],
p.PaletteRam[0x1D],
p.PaletteRam[0x1E],
p.PaletteRam[0x1F],
}
}
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment