aboutsummaryrefslogtreecommitdiff
path: root/cart
diff options
context:
space:
mode:
Diffstat (limited to 'cart')
-rw-r--r--cart/cart.go83
-rw-r--r--cart/mapper.go30
-rw-r--r--cart/nrom.go47
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 }