Browse Source

Added CommandWriter to handle the Protocol, created SerialConnection Wrapper

develop
PHENOM 9 months ago
parent
commit
e6bdc57d1f

+ 288
- 0
Game-Boy-Cartreader-Client/cartridge.go View File

@@ -0,0 +1,288 @@
package main

import (
"fmt"
"reflect"
)

// Cartridge Header Addresses
const (
addrCartridgeStart = 0x0000
addrEntryPointStart = 0x0100
addrEntryPointEnd = 0x0103
addrNintendoLogoStart = 0x0104
addrNintendoLogoEnd = 0x0133
addrTitleStart = 0x0134
addrTitleEnd = 0x0143
addrManufacturerCodeStart = 0x013F
addrManufacturerCodeEnd = 0x0142
addrCgbFlag = 0x0143
addrNewLicenseeCodeStart = 0x0144
addrNewLicenseeCodeEnd = 0x0145
addrSgbFlag = 0x0146
addrCartridgeType = 0x0147
addrROMsize = 0x0148
addrRAMsize = 0x0149
addrDestinationCode = 0x014A
addrOldLicenseeCode = 0x014B
addrMaskROMversionNumber = 0x014C
addrHeaderChecksum = 0x014D
addrGlobalChecksumStart = 0x014E
addrGlobalChecksumEnd = 0x014F
addrCartridgeEnd = 0x7FFF
)

var nintendoLogo = []byte{
0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D,
0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99,
0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E}

type CartridgeHeader struct {
EntryPoint []byte
NintendoLogo []byte
Title string
ManufacturerCode []byte
CgbFlag byte
NewLicenseeCode string
SgbFlag byte
CartridgeType byte
ROMsize byte
RAMsize byte
DestinationCode byte
OldLicenseeCode byte
MaskROMversionNumber byte
HeaderChecksum byte
GlobalChecksum uint16
}

// CGB Flags
const (
FlagCgbNone = 0x00
FlagCgbClassic = 0x80
FlagCgbColorOnly = 0xC0
)

// New Licensee Code List
var newLicenseeCodes = map[string]string{
"00": "none",
"01": "Nintendo R&D1",
"08": "Capcom",
"13": "Electronic Arts",
"18": "Hudson Soft",
"19": "b-ai",
"20": "kss",
"22": "pow",
"24": "PCM Complete",
"25": "san-x",
"28": "Kemco Japan",
"29": "seta",
"30": "Viacom",
"31": "Nintendo",
"32": "Bandai",
"33": "Ocean/Acclaim",
"34": "Konami",
"35": "Hector",
"37": "Taito",
"38": "Hudson",
"39": "Banpresto",
"41": "Ubi Soft",
"42": "Atlus",
"44": "Malibu",
"46": "angel",
"47": "Bullet-Proof",
"49": "irem",
"50": "Absolute",
"51": "Acclaim",
"52": "Activision",
"53": "American sammy",
"54": "Konami",
"55": "Hi tech entertainment",
"56": "LJN",
"57": "Matchbox",
"58": "Mattel",
"59": "Milton Bradley",
"60": "Titus",
"61": "Virgin",
"64": "LucasArts",
"67": "Ocean",
"69": "Electronic Arts",
"70": "Infogrames",
"71": "Interplay",
"72": "Broderbund",
"73": "sculptured",
"75": "sci",
"78": "THQ",
"79": "Accolade",
"80": "misawa",
"83": "lozc",
"86": "tokuma shoten i",
"87": "tsukuda ori",
"91": "Chunsoft",
"92": "Video system",
"93": "Ocean/Acclaim",
"95": "Varie",
"96": "Yonezawa/s'pal",
"97": "Kaneko",
"99": "Pack in soft",
"A4": "Konami (Yu-Gi-Oh!)",
}

const (
flagSgbNone = 0x00
flagSgbSupport = 0x03
)

type Cartridge struct {
serial *SerialConnection
commands *CommandWriter
}

func NewCartridge(serialConnection *SerialConnection) *Cartridge {
c := Cartridge{
serial: serialConnection,
commands: NewCommandWriter(serialConnection),
}
return &c
}

func (c *Cartridge) ResetDevice() error {
return c.commands.Reset()
}

func (c *Cartridge) ReadROMtoFile(filepath string) {

}

func (c *Cartridge) ReadRAMtoFile(filepath string) {

}

func (c *Cartridge) ReadHeader() (*CartridgeHeader, error) {
readBytes := func(adressStart, addressEnd int) ([]byte, error) {
return c.commands.ReadBytes(uint16(adressStart), uint16((addressEnd-adressStart)+1))
}
var err error
var tmp []byte
ch := CartridgeHeader{}

ch.EntryPoint, err = readBytes(addrEntryPointStart, addrEntryPointEnd)
ch.NintendoLogo, err = readBytes(addrNintendoLogoStart, addrNintendoLogoEnd)

tmp, err = readBytes(addrTitleStart, addrTitleEnd)
ch.Title = string(tmp)

ch.ManufacturerCode, err = readBytes(addrManufacturerCodeStart, addrManufacturerCodeEnd)
ch.CgbFlag, err = c.commands.ReadByte(addrCgbFlag)

tmp, err = readBytes(addrNewLicenseeCodeStart, addrNewLicenseeCodeEnd)
ch.NewLicenseeCode = string(tmp)

ch.SgbFlag, err = c.commands.ReadByte(addrSgbFlag)
ch.CartridgeType, err = c.commands.ReadByte(addrCartridgeType)
ch.ROMsize, err = c.commands.ReadByte(addrROMsize)
ch.RAMsize, err = c.commands.ReadByte(addrRAMsize)
ch.DestinationCode, err = c.commands.ReadByte(addrDestinationCode)
ch.OldLicenseeCode, err = c.commands.ReadByte(addrOldLicenseeCode)
ch.MaskROMversionNumber, err = c.commands.ReadByte(addrMaskROMversionNumber)
ch.HeaderChecksum, err = c.commands.ReadByte(addrHeaderChecksum)

tmp, err = readBytes(addrGlobalChecksumStart, addrGlobalChecksumEnd)
ch.GlobalChecksum = uint16((uint16(tmp[0]) << 8) | uint16(tmp[1]))

return &ch, err
}

func (c *Cartridge) VerifyNintendoLogo() error {
logo, err := c.commands.ReadBytes(uint16(addrNintendoLogoStart), uint16((addrNintendoLogoEnd-addrNintendoLogoStart)+1))
if err != nil {
return err
}

if reflect.DeepEqual(logo, nintendoLogo) == false {
return fmt.Errorf("cartridge Nintendo Logo Pattern %02X doesn't match with original Nintendo Logo Pattern (%02X)", logo, nintendoLogo)
}

return nil
}

func (c *Cartridge) CalculateHeaderChecksum() (byte, error) {
var sum byte
buf, err := c.commands.ReadBytes(addrTitleStart, (addrHeaderChecksum - addrTitleStart))
if err != nil {
return sum, err
}
for _, value := range buf {
sum += -value - 1
}
return sum, err
}

// TODO: Doesn't work correctly, Possibly ROM Bank switching required
func (c *Cartridge) CalculateGlobalChecksum() (uint16, error) {
var sum uint64
var tmp byte
var err error
buf, err := c.commands.ReadBytes(addrCartridgeStart, addrCartridgeEnd+1)
if err != nil {
return uint16(sum), err
}
for _, value := range buf {
sum += uint64(value)
}

tmp, err = c.commands.ReadByte(addrGlobalChecksumStart)
if err != nil {
return uint16(sum), err
}
sum -= uint64(tmp)

tmp, err = c.commands.ReadByte(addrGlobalChecksumEnd)
if err != nil {
return uint16(sum), err
}
sum -= uint64(tmp)

return uint16(sum), err
}

func (c *Cartridge) VerifyHeaderChecksum() error {
var err error
var readChecksum byte
var calcChecksum byte
readChecksum, err = c.commands.ReadByte(addrHeaderChecksum)
if err != nil {
return err
}
calcChecksum, err = c.CalculateHeaderChecksum()
if err != nil {
return err
}
if readChecksum != calcChecksum {
return fmt.Errorf("calculated cartridge header checksum 0x%02X mismatches with read out header checksum 0x%02X", calcChecksum, readChecksum)
}
return nil
}

// TODO: Doesn't work correctly, Possibly ROM Bank switching required
func (c *Cartridge) VerifyGlobalChecksum() error {
var err error
var tmp []byte
var calcChecksum uint16
var readChecksum uint16

tmp, err = c.commands.ReadBytes(uint16(addrGlobalChecksumStart), uint16((addrGlobalChecksumEnd-addrGlobalChecksumStart)+1))
readChecksum = uint16((uint16(tmp[0]) << 8) | uint16(tmp[1]))
if err != nil {
return err
}

calcChecksum, err = c.CalculateGlobalChecksum()
if err != nil {
return err
}

if readChecksum != calcChecksum {
return fmt.Errorf("calculated cartridge global checksum 0x%02X mismatches with read out global checksum 0x%02X", calcChecksum, readChecksum)
}
return nil
}

+ 107
- 0
Game-Boy-Cartreader-Client/commandWriter.go View File

@@ -0,0 +1,107 @@
package main

import (
"fmt"
)

const (
strReady = "READY"
strOk = "OK"
)

type CommandWriter struct {
serial *SerialConnection
}

func NewCommandWriter(serialConnection *SerialConnection) *CommandWriter {
cw := CommandWriter{
serial: serialConnection,
}
return &cw
}

func makeWordNibbles(word uint16) (byte, byte) {
h := byte((word & 0xFF00) >> 8)
l := byte(word & 0x00FF)
return h, l
}

func makeLongNibbles(long uint32) (byte, byte, byte, byte) {
a := byte((long & uint32(0xFF000000)) >> 24)
b := byte((long & uint32(0x00FF0000)) >> 16)
c := byte((long & uint32(0x0000FF00)) >> 8)
d := byte(long & uint32(0x000000FF))
return a, b, c, d
}

// Trigger CPU Reset and print READY(0x52 0x45 0x41 0x44 0x59) after back Online
func (cw *CommandWriter) Reset() error {
if _, err := cw.serial.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); err != nil { // Write a few zeroes just in case previously a command got executed and interrupted
return err
}

if response, _, err := cw.serial.Read(len(strReady)); err != nil {
return err
} else if string(response) != strReady {
return fmt.Errorf("expected %s when doing Cartridge reset. Got '%s' instead", strReady, string(response))
}

return nil
}

// Set the Baudrate (Initially set to 9600)
func (cw *CommandWriter) SetBaudrate(baudRate int, changeSerialBaud bool) error {
a1, b1, a2, b2 := makeLongNibbles(uint32(baudRate))
if _, err := cw.serial.Write([]byte{0x01, a1, b1, a2, b2}); err != nil {
return err
}

if changeSerialBaud {
if err := cw.serial.SetBaudRate(baudRate); err != nil {
return err
}
}

return nil
}

// Read Byte from Address
func (cw *CommandWriter) ReadByte(address uint16) (byte, error) {
h, l := makeWordNibbles(address)
if _, err := cw.serial.Write([]byte{0x03, h, l}); err != nil {
return 0, err
}

if response, _, err := cw.serial.Read(1); err != nil {
return 0, err
} else if err == nil {
return response[0], err
}

return 0, fmt.Errorf("error while trying to read byte at address 0x%02X", address)
}

// Write Byte on Address
func (cw *CommandWriter) WriteByte(address uint16, value byte) error {
h, l := makeWordNibbles(address)
_, err := cw.serial.Write([]byte{0x04, h, l, value})
return err
}

// Reads n Bytes from Address to (Address + Length)
func (cw *CommandWriter) ReadBytes(address, length uint16) ([]byte, error) {
hAddr, lAddr := makeWordNibbles(address)
hLen, lLen := makeWordNibbles(length)

if _, err := cw.serial.Write([]byte{0x05, hAddr, lAddr, hLen, lLen}); err != nil {
return []byte{}, err
}

if response, _, err := cw.serial.Read(int(length)); err != nil {
return []byte{}, err
} else if err == nil {
return response, err
}

return []byte{}, fmt.Errorf("error while trying to read %d bytes starting at address 0x%02X", length, address)
}

+ 25
- 106
Game-Boy-Cartreader-Client/gameboyCartreader.go View File

@@ -1,117 +1,36 @@
package main

import (
"bytes"
"fmt"
"github.com/tarm/serial"
"log"
"time"
)

func main() {
if port, err := openComPort(2, 9600); err == nil {
if resetCart(port) {
log.Println("OK!")
} else {
log.Println("FAIL!")
}
log.Printf("0x%02X", readBytesCart(port, 0x0100, 0x4F))
} else {
log.Panicln(err)
}
}

func openComPort(comNr, baud int) (*serial.Port, error) {
c := &serial.Config{Name: fmt.Sprintf("COM%d", comNr), Baud: baud}
s, err := serial.OpenPort(c)
return s, err
}

func changeBaud(comNr, baud int) {
// Check if Port is already opened
s, err := serial.OpenPort(&serial.Config{Name: fmt.Sprintf("COM%d", comNr)})

if err != nil {
// Close Port first
if err := s.Close(); err != nil {
openComPort(comNr, baud)
} else {
panic(err)
}
}
// Do nothing in case the port isn't opened
}

func enumerateComPorts() []string {
var ports []string
for i := 0; i < 256+1; i++ {
s, err := openComPort(i, 9600)
if err == nil {
if err := s.Close(); err == nil {
ports = append(ports, fmt.Sprintf("COM%d", i))
} else {
panic(err)
}
}
}
return ports
}

func writeSerial(port *serial.Port, buf []byte) {
if _, err := port.Write(buf); err != nil {
panic(err)
}
}

func readSerial(port *serial.Port, bufferSize int) []byte {
buf := make([]byte, bufferSize)
_, err := port.Read(buf)
func checkError(err error) {
if err != nil {
panic(err)
}
buf = bytes.TrimRight(buf, "\x00")

return buf
}

func readResponse(port *serial.Port, bufferSize int, timeToWait time.Duration) []byte {
waitTimer := time.NewTimer(timeToWait)
<-waitTimer.C
waitTimer.Stop()
return readSerial(port, 10)
}

func resetCart(port *serial.Port) bool {
writeSerial(port, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) // Write a few zeroes just in case previously a command got executed and interrupted
time.Sleep(time.Second)
if string(readSerial(port, 5)) == "READY" {
return true
log.Panicln(err)
}
return false
}

func makeWordNibbles(word uint16) (byte, byte) {
h := byte((word & 0xFF00) >> 8)
l := byte(word & 0x00FF)
return h, l
}

func readByteCart(port *serial.Port, address uint16) byte {
h, l := makeWordNibbles(address)
writeSerial(port, []byte{0x03, h, l})
return readSerial(port, 1)[0]
}

func writeByteCart(port *serial.Port, address uint16, value byte) {
h, l := makeWordNibbles(address)
writeSerial(port, []byte{0x04, h, l, value})
}

func readBytesCart(port *serial.Port, address, length uint16) []byte {
hAddr, lAddr := makeWordNibbles(address)
hLen, lLen := makeWordNibbles(length)
writeSerial(port, []byte{0x05, hAddr, lAddr, hLen, lLen})
time.Sleep(time.Second)
return readSerial(port, int(length))
//return readResponse(port, int(length), 250*time.Millisecond)
func main() {
sc := NewSerialConnection("COM2", 9600)
checkError(sc.Open())
log.Println("COM Port Opened!")

cart := NewCartridge(sc)
checkError(cart.ResetDevice())
log.Println("Game Boy Cart ready for operation!")

log.Println("Checking Header Checksum...")
log.Println(cart.CalculateHeaderChecksum())
log.Println(cart.VerifyHeaderChecksum())
cart.commands.SetBaudrate(1000000, true)
log.Println(cart.VerifyGlobalChecksum())

/*
err = cw.SetBaudrate(1000000, true)
checkError(err)
log.Println("Beginning to read ROM and SRAM starting from 0x0000 to 0xFFFF")
response, err := cw.ReadBytes(0x0000, 0xFFFF)
checkError(err)
log.Printf("%s", string(response))
*/
}

+ 1
- 0
Game-Boy-Cartreader-Client/mbcs.go View File

@@ -0,0 +1 @@
package main

+ 107
- 0
Game-Boy-Cartreader-Client/serialConnection.go View File

@@ -0,0 +1,107 @@
package main

import (
"fmt"
"github.com/tarm/serial"
)

type SerialConnection struct {
port *serial.Port
config *serial.Config
buffer []byte
isOpen bool
}

func NewSerialConnection(portName string, baudRate int) *SerialConnection {
sc := SerialConnection{
config: &serial.Config{
Name: portName,
Baud: baudRate,
},
buffer: make([]byte, 128),
}
return &sc
}

func GetAvailablePorts(format string) ([]string, error) {
var ports []string
for i := 0; i < 256+1; i++ {
c := serial.Config{Name: fmt.Sprintf(format, i), Baud: 9600}
s, err := serial.OpenPort(&c)
if err == nil {
if err := s.Close(); err == nil {
ports = append(ports, fmt.Sprintf(format, i))
} else {
return ports, err
}
}
}
return ports, nil
}

func (sc *SerialConnection) Open() error {
if !sc.isOpen {
port, err := serial.OpenPort(sc.config)
if err != nil {
return err
}
sc.port = port
sc.isOpen = true
return err
}

return fmt.Errorf("couldn't open Port %s, since it's already opened", sc.config.Name)
}

func (sc *SerialConnection) Close() error {
if sc.isOpen {
sc.isOpen = false
return sc.port.Close()
}

return fmt.Errorf("couldn't close Port %s, since it isn't opened", sc.config.Name)
}

func (sc *SerialConnection) SetBufferSize(size int) {
sc.buffer = make([]byte, size)
}

func (sc *SerialConnection) SetBaudRate(baudRate int) error {
if sc.isOpen {
if err := sc.Close(); err != nil {
return err
}
sc.config.Baud = baudRate
return sc.Open()
}

return fmt.Errorf("couldn't change Baud of port %s to %d, since it isn't opened", sc.config.Name, baudRate)
}

func (sc *SerialConnection) Write(buf []byte) (int, error) {
if sc.isOpen {
return sc.port.Write(buf)
}

return 0, fmt.Errorf("couldn't write %d bytes, since the port %s isn't opened", len(buf), sc.config.Name)
}

func (sc *SerialConnection) Read(length int) ([]byte, int, error) {
if sc.isOpen {
retBuf := []byte{}
n := 0
bytesReceived := 0
var err error
for bytesReceived < length {

if n, err = sc.port.Read(sc.buffer); err != nil {
return retBuf, bytesReceived, err
}

retBuf = append(retBuf, sc.buffer[0:n]...)
bytesReceived += n
}
return retBuf, bytesReceived, err
}
return nil, 0, fmt.Errorf("couldn't read %d bytes from port %s, since it isn't opened", length, sc.config.Name)
}

Loading…
Cancel
Save