diff options
| author | crecher <crecher@crecher.net> | 2026-10-12 04:07:09 +0200 |
|---|---|---|
| committer | crecher <crecher@crecher.net> | 2026-10-12 04:07:09 +0200 |
| commit | 8b0292cccf768efdd5295fe0b662488cbc671079 (patch) | |
| tree | f2896047a2328d8aea9a7f7815a6cf8d9c0636ea /cart | |
| parent | 7775f9ad5407f2be4b03c300d1db4c745fda2f69 (diff) | |
| download | gones-8b0292cccf768efdd5295fe0b662488cbc671079.tar.gz gones-8b0292cccf768efdd5295fe0b662488cbc671079.zip | |
added cart package, more tests, tests are now at ./tests and added .gitignore
Diffstat (limited to 'cart')
| -rw-r--r-- | cart/cart.go | 83 | ||||
| -rw-r--r-- | cart/mapper.go | 30 | ||||
| -rw-r--r-- | cart/nrom.go | 47 |
3 files changed, 160 insertions, 0 deletions
diff --git a/cart/cart.go b/cart/cart.go new file mode 100644 index 0000000..2438c1b --- /dev/null +++ b/cart/cart.go @@ -0,0 +1,83 @@ +package cart + +import ( + "bytes" + "errors" + "fmt" + "os" +) + +type Cartridge struct { + PRG []uint8 + CHR []uint8 + CHRIsRAM bool + MapperID uint8 + Mirroring Mirroring + Battery bool + + Mapper Mapper +} + +func Load(path string) (*Cartridge, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + return Parse(data) +} + +func Parse(data []byte) (*Cartridge, error) { + if len(data) < 16 || !bytes.Equal(data[:4], []byte("NES\x1A")) { + return nil, errors.New("cart: not an iNES file") + } + + prgSize := int(data[4]) * 16 * 1024 + chrSize := int(data[5]) * 8 * 1024 + flags6, flags7 := data[6], data[7] + + isNES2 := flags7&0x0C == 0x08 + mapperID := flags6 >> 4 + if isNES2 || bytes.Equal(data[12:16], []byte{0, 0, 0, 0}) { + mapperID |= flags7 & 0xF0 + } + + c := &Cartridge{ + MapperID: mapperID, + Battery: flags6&0x02 != 0, + } + + switch { + case flags6&0x08 != 0: + c.Mirroring = FourScreen + case flags6&0x01 != 0: + c.Mirroring = Vertical + default: + c.Mirroring = Horizontal + } + + offset := 16 + if flags6&0x04 != 0 { + offset += 512 + } + if len(data) < offset+prgSize+chrSize { + return nil, fmt.Errorf("cart: file truncated: need %d bytes, have %d", + offset+prgSize+chrSize, len(data)) + } + + c.PRG = data[offset : offset+prgSize] + offset += prgSize + + if chrSize == 0 { + c.CHR = make([]uint8, 8*1024) + c.CHRIsRAM = true + } else { + c.CHR = data[offset : offset+chrSize] + } + + m, err := newMapper(c) + if err != nil { + return nil, err + } + c.Mapper = m + return c, nil +} diff --git a/cart/mapper.go b/cart/mapper.go new file mode 100644 index 0000000..7ddd8a9 --- /dev/null +++ b/cart/mapper.go @@ -0,0 +1,30 @@ +package cart + +import "fmt" + +type Mirroring uint8 + +const ( + Horizontal Mirroring = iota + Vertical + FourScreen + SingleScreenLower + SingleScreenUpper +) + +type Mapper interface { + CPURead(addr uint16) uint8 + CPUWrite(addr uint16, v uint8) + PPURead(addr uint16) uint8 + PPUWrite(addr uint16, v uint8) + Mirroring() Mirroring +} + +func newMapper(c *Cartridge) (Mapper, error) { + switch c.MapperID { + case 0: + return newNROM(c), nil + default: + return nil, fmt.Errorf("cart: unsupported mapper %d", c.MapperID) + } +} diff --git a/cart/nrom.go b/cart/nrom.go new file mode 100644 index 0000000..3828c16 --- /dev/null +++ b/cart/nrom.go @@ -0,0 +1,47 @@ +package cart + +type nrom struct { + prg []uint8 + chr []uint8 + chrIsRAM bool + prgRAM [8 * 1024]uint8 + mirroring Mirroring +} + +func newNROM(c *Cartridge) *nrom { + return &nrom{ + prg: c.PRG, + chr: c.CHR, + chrIsRAM: c.CHRIsRAM, + mirroring: c.Mirroring, + } +} + +func (m *nrom) CPURead(addr uint16) uint8 { + switch { + case addr >= 0x8000: + return m.prg[int(addr-0x8000)%len(m.prg)] + case addr >= 0x6000: + return m.prgRAM[addr-0x6000] + default: + return 0 + } +} + +func (m *nrom) CPUWrite(addr uint16, v uint8) { + if addr >= 0x6000 && addr < 0x8000 { + m.prgRAM[addr-0x6000] = v + } +} + +func (m *nrom) PPURead(addr uint16) uint8 { + return m.chr[int(addr)%len(m.chr)] +} + +func (m *nrom) PPUWrite(addr uint16, v uint8) { + if m.chrIsRAM { + m.chr[int(addr)%len(m.chr)] = v + } +} + +func (m *nrom) Mirroring() Mirroring { return m.mirroring } |