func (d *Device) CmdUpgradeStart() error {
	return d.SendCmd(CmdUpgradeStart, nil, nil, nil)
}
CmdUpgradeStart triggers a firmware upgrade. The firmware must have been already loaded
in BankCARTROM at offset 0. The upgrade happens in background; use CmdUpgradeReport to
get a report on the status of the upgrade.
NOTE: removing power during an upgrade might brick the 64drive unit.
package drive64
import (
	"bytes"
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"strings"
	"time"
	"github.com/ziutek/ftdi"
)
var (
	ErrNoDevices       = errors.New("no 64drive devices found")
	ErrMultipleDevices = errors.New("multiple 64drive devices found")
	ErrFrozen          = errors.New("64drive seems frozen, please reset it")
	ErrUnsupported     = errors.New("operation is not supported on this 64drive revision")
	ErrInvalidFifoHead = errors.New("invalid FIFO header")
)
const vid = 0x0403
var pids = []int{0x6010, 0x6011, 0x6012, 0x6013, 0x6014}
type DeviceDesc struct {
	Manufacturer string  
	Description  string  
	Serial       string  
	VendorID     int     
	ProductID    int     
}
func (d *DeviceDesc) guessVariant() Variant {
	switch {
	case d.ProductID == 0x6010:
		return VarRevA
	default:
		return VarRevB
	}
}
func (d *DeviceDesc) Open() (*Device, error) {
	usb, err := ftdi.Open(vid, d.ProductID, d.Description, d.Serial, 0, ftdi.ChannelAny)
	if d.guessVariant() != VarRevA {
		if err == nil {
			err = usb.SetBitmode(0xFF, ftdi.ModeReset)
		}
		if err == nil {
			err = usb.SetBitmode(0xFF, ftdi.ModeSyncFF)
		}
	}
	if err == nil {
		err = usb.PurgeBuffers()
	}
	return &Device{usb: drive64Device{usb}, desc: *d}, err
}
func Enumerate() []DeviceDesc {
	var devices []DeviceDesc
	for _, pid := range pids {
		devs, err := ftdi.FindAll(vid, pid)
		if err != nil {
			panic(err)
		}
		for _, d := range devs {
			if d.Manufacturer == "Retroactive" && strings.HasPrefix(d.Description, "64drive") {
				devices = append(devices, DeviceDesc{
					Manufacturer: d.Manufacturer,
					Description:  d.Description,
					Serial:       d.Serial,
					VendorID:     vid,
					ProductID:    pid,
				})
			}
		}
	}
	return devices
}
type drive64Device struct {
	*ftdi.Device
}
func (d *drive64Device) Read(buf []byte) (int, error) {
	for retry := 0; retry < 5; retry++ {
		n, err := d.Device.Read(buf)
		if n == 0 && err == nil {
			time.Sleep(5 * time.Millisecond)
			continue
		}
		return n, err
	}
	return 0, ErrFrozen
}
type Device struct {
	usb  drive64Device
	desc DeviceDesc
	vers [8]byte
}
func NewDeviceSingle() (*Device, error) {
	devs := Enumerate()
	if len(devs) == 0 {
		return nil, ErrNoDevices
	}
	if len(devs) > 1 {
		return nil, ErrMultipleDevices
	}
	return devs[0].Open()
}
func NewDeviceBySerial(serial string) (*Device, error) {
	for _, d := range Enumerate() {
		if d.Serial == serial {
			return d.Open()
		}
	}
	return nil, ErrNoDevices
}
func (d *Device) Description() DeviceDesc {
	return d.desc
}
func (d *Device) Close() error {
	return d.usb.Close()
}
func (d *Device) SendCmd(cmd Cmd, args []uint32, in []byte, out []byte) error {
	var buf bytes.Buffer
	var abuf [4]byte
	buf.Write([]byte{byte(cmd), 0x43, 0x4D, 0x44})
	for _, a := range args {
		binary.BigEndian.PutUint32(abuf[:], a)
		buf.Write(abuf[:])
	}
	if len(in) != 0 {
		buf.Write(in)
	}
	if n, err := d.usb.Write(buf.Bytes()); err != nil {
		return err
	} else if n != buf.Len() {
		panic("partial USB write")
	}
	if len(out) > 0 {
		if _, err := io.ReadFull(&d.usb, out); err != nil {
			return err
		}
	}
	if _, err := io.ReadFull(&d.usb, abuf[:]); err != nil {
		return err
	}
	if abuf[0] != 0x43 || abuf[1] != 0x4D || abuf[2] != 0x50 || abuf[3] != byte(cmd) {
		return fmt.Errorf("SendCmd: invalid completion packet (%x)", abuf)
	}
	return nil
}
func (d *Device) CmdVersionRequest() (hwver Variant, fwver Version, magic [4]byte, err error) {
	if binary.LittleEndian.Uint64(d.vers[:]) == 0 {
		if err = d.SendCmd(CmdVersionRequest, nil, nil, d.vers[:]); err != nil {
			return
		}
	}
	hwver = Variant(binary.BigEndian.Uint16(d.vers[0:2]))
	fwver = Version(binary.BigEndian.Uint16(d.vers[2:4]))
	copy(magic[:], d.vers[4:8])
	return
}
func (d *Device) CmdSetCicType(cic CIC) error {
	hwver, _, _, err := d.CmdVersionRequest()
	if err != nil {
		return err
	}
	if hwver < VarRevB {
		return ErrUnsupported
	}
	var args [1]uint32
	args[0] = 0x80000000 | uint32(cic)
	return d.SendCmd(CmdSetCicType, args[:], nil, nil)
}
func (d *Device) CmdSetSaveType(st SaveType) error {
	var args [1]uint32
	args[0] = uint32(st)
	return d.SendCmd(CmdSetSaveType, args[:], nil, nil)
}
func idealChunkSize(size int64) int {
	switch {
	case size >= 16*1024*1024:
		return 32 * 128 * 1024
	case size >= 2*1024*1024:
		return 16 * 128 * 1024
	default:
		return 4 * 128 * 1024
	}
}
func (d *Device) CmdUpload(ctx context.Context, r io.Reader, n int64, bank Bank, offset uint32) error {
	var cmdargs [2]uint32
	cmdargs[0] = offset
	chunkSize := idealChunkSize(n)
	d.usb.SetWriteChunkSize(chunkSize + 12)
	for n > 0 && ctx.Err() == nil {
		sz := chunkSize
		if n > 0 && int64(sz) > n {
			sz = int(n)
		}
		buf := make([]byte, sz)
		read, err := io.ReadFull(r, buf)
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
		for len(buf)%512 != 0 {
			buf = append(buf, 0xFF)
			read += 1
		}
		cmdargs[1] = uint32(bank)<<24 | uint32(read)
		if err := d.SendCmd(CmdLoadFromPc, cmdargs[:], buf, nil); err != nil {
			return err
		}
		cmdargs[0] += uint32(read)
		n -= int64(read)
	}
	return ctx.Err()
}
func (d *Device) CmdDownload(ctx context.Context, w io.Writer, n int64, bank Bank, offset uint32) error {
	var cmdargs [2]uint32
	cmdargs[0] = offset
	chunkSize := idealChunkSize(n)
	d.usb.SetReadChunkSize(chunkSize)
	for n > 0 && ctx.Err() == nil {
		sz := chunkSize
		if int64(sz) > n {
			sz = int(n)
		}
		buf := make([]byte, sz)
		cmdargs[1] = uint32(bank)<<24 | uint32(sz)
		if err := d.SendCmd(CmdDumpToPc, cmdargs[:], nil, buf); err != nil {
			return err
		}
		read, err := w.Write(buf)
		if err != nil {
			return err
		} else if read != len(buf) {
			panic("provided writer does not respect io.Writer interface")
		}
		cmdargs[0] += uint32(read)
		n -= int64(read)
	}
	return ctx.Err()
}