aboutsummaryrefslogtreecommitdiff
path: root/cart/cart.go
blob: 2438c1baff4d295bbbf9cb6fa2da467212686883 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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
}