Lots of work on disks. Also added a2 command-line utility.
This commit is contained in:
parent
5e28969748
commit
b8fb9c6ab8
|
@ -0,0 +1,40 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/gonuts/commander"
|
||||||
|
"github.com/zellyn/goapple2/disk"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdDiskConvert = &commander.Command{
|
||||||
|
Run: runDiskConvert,
|
||||||
|
UsageLine: "diskconvert infile outfile",
|
||||||
|
Short: "convert apple II disk images",
|
||||||
|
Long: `
|
||||||
|
DiskConvert is a simple disk conversion utility.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
var diskVolume uint
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmdDiskConvert.Flag.UintVar(&diskVolume, "v", 0, "The volume of the disk, or 0 for default.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDiskConvert(cmd *commander.Command, args []string) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
cmd.Usage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if diskVolume > 254 {
|
||||||
|
log.Fatalf("disk volume must be 0-254, got %d", diskVolume)
|
||||||
|
}
|
||||||
|
nyb, err := disk.DiskFromFile(args[0], byte(diskVolume))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = disk.DiskToFile(args[1], nyb); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ func init() {
|
||||||
Name: os.Args[0],
|
Name: os.Args[0],
|
||||||
Commands: []*commander.Command{
|
Commands: []*commander.Command{
|
||||||
cmdDisasm,
|
cmdDisasm,
|
||||||
|
cmdDiskConvert,
|
||||||
},
|
},
|
||||||
Flag: flag.NewFlagSet("a2", flag.ExitOnError),
|
Flag: flag.NewFlagSet("a2", flag.ExitOnError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@ type Disk interface {
|
||||||
Write(byte)
|
Write(byte)
|
||||||
SetHalfTrack(byte)
|
SetHalfTrack(byte)
|
||||||
HalfTrack() byte
|
HalfTrack() byte
|
||||||
SetVolume(byte)
|
|
||||||
Volume() byte
|
|
||||||
Writeable() bool
|
Writeable() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +51,11 @@ func NewDiskCard(rom []byte, slot byte, cm CardManager) (*DiskCard, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *DiskCard) String() string {
|
func (dc *DiskCard) String() string {
|
||||||
return fmt.Sprintf("Disk Card (slot %d)", dc.slot)
|
return fmt.Sprintf(
|
||||||
|
"Disk Card (slot %d): halfTrack=%d, active=%d, phases=$%02X, mode=$%02X, onoff=%v, dr=$%02X, la=%d",
|
||||||
|
dc.slot, dc.disks[0].HalfTrack(), dc.active, dc.phases, dc.mode, dc.onOff, dc.dataRegister,
|
||||||
|
dc.lastAccess)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *DiskCard) Slot() byte {
|
func (dc *DiskCard) Slot() byte {
|
||||||
|
@ -125,7 +127,7 @@ func (dc *DiskCard) handleAccess(address byte) {
|
||||||
dc.handlePhase(0, dc.phases&1 == 1) // No change: force update
|
dc.handlePhase(0, dc.phases&1 == 1) // No change: force update
|
||||||
}
|
}
|
||||||
case 0xC, 0xD:
|
case 0xC, 0xD:
|
||||||
dc.mode = dc.mode&^2 | address&1<<2
|
dc.mode = dc.mode&^2 | address&1<<1
|
||||||
case 0xE, 0xF:
|
case 0xE, 0xF:
|
||||||
dc.mode = dc.mode&^1 | address&1
|
dc.mode = dc.mode&^1 | address&1
|
||||||
}
|
}
|
||||||
|
@ -154,6 +156,8 @@ func (dc *DiskCard) Read16(address byte) byte {
|
||||||
case MODE_WRITE | MODE_LOAD:
|
case MODE_WRITE | MODE_LOAD:
|
||||||
// Nonsense for reading: just return last data
|
// Nonsense for reading: just return last data
|
||||||
return dc.dataRegister
|
return dc.dataRegister
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Unexpected disk card mode: %d", dc.mode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0xFF
|
return 0xFF
|
||||||
|
@ -165,17 +169,19 @@ func (dc *DiskCard) Write16(address byte, value byte) {
|
||||||
switch dc.mode {
|
switch dc.mode {
|
||||||
case MODE_READ | MODE_SHIFT:
|
case MODE_READ | MODE_SHIFT:
|
||||||
// Normal read
|
// Normal read
|
||||||
panic("Write while in read mode")
|
// panic("Write while in read mode")
|
||||||
case MODE_READ | MODE_LOAD:
|
case MODE_READ | MODE_LOAD:
|
||||||
// Check write-protect
|
// Check write-protect
|
||||||
panic("Write while in check-write-protect mode")
|
// panic("Write while in check-write-protect mode")
|
||||||
case MODE_WRITE | MODE_SHIFT:
|
case MODE_WRITE | MODE_SHIFT:
|
||||||
// Shifting data to disk
|
// Shifting data to disk
|
||||||
panic("Write while in shift mode")
|
// panic("Write while in shift mode")
|
||||||
case MODE_WRITE | MODE_LOAD:
|
case MODE_WRITE | MODE_LOAD:
|
||||||
if dc.disks[dc.active].Writeable() {
|
if dc.disks[dc.active].Writeable() {
|
||||||
dc.writeOne(value)
|
dc.writeOne(value)
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Unexpected disk card mode: %d", dc.mode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,9 +191,6 @@ func (dc *DiskCard) readOne() byte {
|
||||||
return dc.dataRegister
|
return dc.dataRegister
|
||||||
}
|
}
|
||||||
disk := dc.disks[dc.active]
|
disk := dc.disks[dc.active]
|
||||||
if dc.lastAccess > 300 {
|
|
||||||
disk.Skip(dc.lastAccess / 36)
|
|
||||||
}
|
|
||||||
dc.lastAccess = 0
|
dc.lastAccess = 0
|
||||||
dc.dataRegister = disk.Read()
|
dc.dataRegister = disk.Read()
|
||||||
return dc.dataRegister
|
return dc.dataRegister
|
||||||
|
|
189
disk/convert.go
189
disk/convert.go
|
@ -2,6 +2,9 @@ package disk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dos33writeTable is the list of valid DOS 3.3 bytes.
|
// Dos33writeTable is the list of valid DOS 3.3 bytes.
|
||||||
|
@ -17,6 +20,17 @@ var Dos33writeTable = [64]byte{
|
||||||
0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
|
0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var Dos33readTable [256]int
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
Dos33readTable[i] = -1
|
||||||
|
}
|
||||||
|
for i, j := range Dos33writeTable {
|
||||||
|
Dos33readTable[j] = int(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dos33LogicalToPhysicalSectorMap maps logical sector numbers to physical ones.
|
// Dos33LogicalToPhysicalSectorMap maps logical sector numbers to physical ones.
|
||||||
// See [UtA2 9-42 - Read Routines].
|
// See [UtA2 9-42 - Read Routines].
|
||||||
var Dos33LogicalToPhysicalSectorMap = []byte{
|
var Dos33LogicalToPhysicalSectorMap = []byte{
|
||||||
|
@ -102,7 +116,6 @@ func append44(target []byte, b byte) []byte {
|
||||||
// appendAddress appends the encoded sector address to the slice, and returns the resulting slice.
|
// appendAddress appends the encoded sector address to the slice, and returns the resulting slice.
|
||||||
func appendAddress(target []byte, t, s, v byte) []byte {
|
func appendAddress(target []byte, t, s, v byte) []byte {
|
||||||
target = append(target, 0xD5, 0xAA, 0x96)
|
target = append(target, 0xD5, 0xAA, 0x96)
|
||||||
fmt.Println("s=", s)
|
|
||||||
target = append44(target, v)
|
target = append44(target, v)
|
||||||
target = append44(target, t)
|
target = append44(target, t)
|
||||||
target = append44(target, s)
|
target = append44(target, s)
|
||||||
|
@ -170,3 +183,177 @@ func dos16ToNybbleTracks(source []byte, v byte, preSync, intraSync int, sectorOr
|
||||||
}
|
}
|
||||||
return tracks, nil
|
return tracks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NybbleToDos16(nyb *Nybble, sectorOrder []byte) (bytes []byte, err error) {
|
||||||
|
// Save current disk position
|
||||||
|
pos := nyb.GetPos()
|
||||||
|
defer nyb.SetPos(pos)
|
||||||
|
var trackBytes [DOS_TRACK_BYTES]byte
|
||||||
|
for track := byte(0); track < NUM_TRACKS; track++ {
|
||||||
|
nyb.SetHalfTrack(track * 2)
|
||||||
|
seen := uint16(0)
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
sector, err := readOneSector(nyb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if sector.Sector > 15 {
|
||||||
|
return nil, fmt.Errorf("Found unexpected sector number on track %d: %d", track, sector.Sector)
|
||||||
|
}
|
||||||
|
if seen&(1<<sector.Sector) > 0 {
|
||||||
|
return nil, fmt.Errorf("Found sector %d twice on track %d", sector.Sector, track)
|
||||||
|
}
|
||||||
|
seen |= 1 << sector.Sector
|
||||||
|
start := 256 * int(sectorOrder[sector.Sector])
|
||||||
|
copy(trackBytes[start:start+256], sector.Data[:])
|
||||||
|
}
|
||||||
|
bytes = append(bytes, trackBytes[:]...)
|
||||||
|
}
|
||||||
|
return bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiskToFile saves a Nybble disk to a given filename.
|
||||||
|
func DiskToFile(filename string, disk *Nybble) error {
|
||||||
|
ext := path.Ext(filename)
|
||||||
|
switch ext {
|
||||||
|
case ".nib":
|
||||||
|
var bytes []byte
|
||||||
|
for _, track := range disk.Tracks {
|
||||||
|
bytes = append(bytes, track...)
|
||||||
|
}
|
||||||
|
err := ioutil.WriteFile(filename, bytes, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case ".dsk", ".do", ".po":
|
||||||
|
sectorOrder := Dos33PhysicalToLogicalSectorMap
|
||||||
|
if ext == ".po" {
|
||||||
|
sectorOrder = ProDosPhysicalToLogicalSectorMap
|
||||||
|
}
|
||||||
|
bytes, err := NybbleToDos16(disk, sectorOrder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = ioutil.WriteFile(filename, bytes, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Cannot save to file %s: unexpected extension %s", filename, ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiskFromFile loads a Nybble disk, given a filename.
|
||||||
|
// defaultVolume is the disk volume used if the file doesn't encode it, 0 for default (254).
|
||||||
|
func DiskFromFile(filename string, defaultVolume byte) (disk *Nybble, err error) {
|
||||||
|
volume := defaultVolume
|
||||||
|
if volume == 0 {
|
||||||
|
volume = DEFAULT_VOLUME
|
||||||
|
}
|
||||||
|
ext := path.Ext(filename)
|
||||||
|
bytes, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch ext {
|
||||||
|
case ".dsk", ".do", ".po":
|
||||||
|
sectorOrder := Dos33PhysicalToLogicalSectorMap
|
||||||
|
if ext == ".po" {
|
||||||
|
sectorOrder = ProDosPhysicalToLogicalSectorMap
|
||||||
|
}
|
||||||
|
if len(bytes) != DOS_DISK_BYTES {
|
||||||
|
return nil, fmt.Errorf("Expected %d bytes in file %s, got %d", DOS_DISK_BYTES, filename, len(bytes))
|
||||||
|
}
|
||||||
|
tracks, err := dos16ToNybbleTracks(bytes, volume, DEFAULT_PRESYNC, DEFAULT_INTRASYNC, sectorOrder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nyb := NewNybble(tracks)
|
||||||
|
return nyb, nil
|
||||||
|
case ".nib":
|
||||||
|
if len(bytes) != NYBBLE_DISK_BYTES {
|
||||||
|
return nil, fmt.Errorf("Expected %d bytes in file %s, got %d", NYBBLE_DISK_BYTES, filename, len(bytes))
|
||||||
|
}
|
||||||
|
var tracks [][]byte
|
||||||
|
for i := 0; i < NUM_TRACKS; i++ {
|
||||||
|
start := NYBBLE_TRACK_BYTES * i
|
||||||
|
tracks = append(tracks, bytes[start:start+NYBBLE_TRACK_BYTES])
|
||||||
|
}
|
||||||
|
nyb := NewNybble(tracks)
|
||||||
|
return nyb, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Unknown suffix (not .dsk, .do, or .po): %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sector struct {
|
||||||
|
Volume byte
|
||||||
|
Track byte
|
||||||
|
Sector byte
|
||||||
|
Data [256]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func read44(nyb *Nybble) byte {
|
||||||
|
return (nyb.Read()<<1 | 1) & nyb.Read()
|
||||||
|
}
|
||||||
|
|
||||||
|
func readOneSector(nyb *Nybble) (result Sector, err error) {
|
||||||
|
count := int(NYBBLE_TRACK_BYTES)
|
||||||
|
OUTER:
|
||||||
|
for ; count > 0; count-- {
|
||||||
|
if nyb.Read() != 0xD5 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nyb.Read() != 0xAA {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nyb.Read() != 0x96 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result.Volume = read44(nyb)
|
||||||
|
result.Track = read44(nyb)
|
||||||
|
result.Sector = read44(nyb)
|
||||||
|
checksum := read44(nyb)
|
||||||
|
count -= 10
|
||||||
|
if checksum != result.Volume^result.Track^result.Sector {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for nyb.Read() != 0xD5 {
|
||||||
|
count--
|
||||||
|
}
|
||||||
|
if nyb.Read() != 0xAA {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nyb.Read() != 0xAD {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var raw [342]byte
|
||||||
|
xor := byte(0)
|
||||||
|
for i := 341; i >= 256; i-- {
|
||||||
|
count--
|
||||||
|
bi := Dos33readTable[nyb.Read()]
|
||||||
|
if bi < 0 {
|
||||||
|
continue OUTER
|
||||||
|
}
|
||||||
|
b := byte(bi) ^ xor
|
||||||
|
raw[i] = b
|
||||||
|
xor = b
|
||||||
|
}
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
count--
|
||||||
|
bi := Dos33readTable[nyb.Read()]
|
||||||
|
if bi < 0 {
|
||||||
|
continue OUTER
|
||||||
|
}
|
||||||
|
b := byte(bi) ^ xor
|
||||||
|
raw[i] = b
|
||||||
|
xor = b
|
||||||
|
}
|
||||||
|
if int(xor) != Dos33readTable[nyb.Read()] {
|
||||||
|
log.Print("Checksum error")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result.Data = PostNybble(raw[:])
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
return result, fmt.Errorf("Unable to read sector")
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package disk
|
package disk
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
NUM_TRACKS = 35
|
||||||
DOS_DISK_BYTES = 143360 // 35 tracks * 16 sectors * 256 bytes
|
DOS_DISK_BYTES = 143360 // 35 tracks * 16 sectors * 256 bytes
|
||||||
DOS_TRACK_BYTES = DOS_DISK_BYTES / 35
|
DOS_TRACK_BYTES = DOS_DISK_BYTES / 35
|
||||||
NYBBLE_DISK_BYTES = 232960
|
NYBBLE_DISK_BYTES = 232960
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package disk
|
package disk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,3 +31,28 @@ func TestPrePostNybble(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNybbleAndBack(t *testing.T) {
|
||||||
|
var randomDosDisk [DOS_DISK_BYTES]byte
|
||||||
|
for i := range randomDosDisk {
|
||||||
|
randomDosDisk[i] = byte(rand.Intn(256))
|
||||||
|
}
|
||||||
|
sectorOrder := Dos33PhysicalToLogicalSectorMap
|
||||||
|
tracks, err := dos16ToNybbleTracks(randomDosDisk[:], 0, DEFAULT_PRESYNC, DEFAULT_INTRASYNC, sectorOrder)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
nyb := NewNybble(tracks)
|
||||||
|
bytesOut, err := NybbleToDos16(nyb, sectorOrder)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(bytesOut) != DOS_DISK_BYTES {
|
||||||
|
t.Fatalf("Expected %d bytes out as dos disk, got %d", DOS_DISK_BYTES, len(bytesOut))
|
||||||
|
}
|
||||||
|
for i := range bytesOut {
|
||||||
|
if bytesOut[i] != randomDosDisk[i] {
|
||||||
|
t.Fatalf("Difference at %d: %d should be %d", i, bytesOut[i], randomDosDisk[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,14 +22,6 @@ func (v Dummy) HalfTrack() byte {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v Dummy) SetVolume(byte) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Dummy) Volume() byte {
|
|
||||||
return byte(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Dummy) Writeable() bool {
|
func (v Dummy) Writeable() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,24 @@
|
||||||
package disk
|
package disk
|
||||||
|
|
||||||
import (
|
type SavedPos struct {
|
||||||
"fmt"
|
halfTrack byte
|
||||||
"io/ioutil"
|
position int
|
||||||
"strings"
|
}
|
||||||
)
|
|
||||||
|
|
||||||
type Nybble struct {
|
type Nybble struct {
|
||||||
Tracks [][]byte
|
Tracks [][]byte
|
||||||
volume byte
|
|
||||||
halfTrack byte
|
halfTrack byte
|
||||||
position int
|
position int
|
||||||
writeable bool
|
writeable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNybble() *Nybble {
|
func NewNybble(tracks [][]byte) *Nybble {
|
||||||
nd := Nybble{
|
nd := Nybble{
|
||||||
volume: DEFAULT_VOLUME,
|
Tracks: tracks,
|
||||||
}
|
}
|
||||||
return &nd
|
return &nd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (disk *Nybble) LoadDosDisk(filename string) error {
|
|
||||||
var sectorOrder []byte
|
|
||||||
switch {
|
|
||||||
case strings.HasSuffix(filename, ".dsk"):
|
|
||||||
sectorOrder = Dos33PhysicalToLogicalSectorMap
|
|
||||||
case strings.HasSuffix(filename, ".do"):
|
|
||||||
sectorOrder = Dos33PhysicalToLogicalSectorMap
|
|
||||||
case strings.HasSuffix(filename, ".po"):
|
|
||||||
sectorOrder = ProDosPhysicalToLogicalSectorMap
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unknown suffix (not .dsk, .do, or .po): %s", filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(bytes) != DOS_DISK_BYTES {
|
|
||||||
return fmt.Errorf("Disk images should be %d bytes, got %d: %s", DOS_DISK_BYTES, len(bytes), filename)
|
|
||||||
}
|
|
||||||
tracks, err := dos16ToNybbleTracks(bytes, disk.Volume(), DEFAULT_PRESYNC, DEFAULT_INTRASYNC, sectorOrder)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
disk.Tracks = tracks
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (disk *Nybble) Read() byte {
|
func (disk *Nybble) Read() byte {
|
||||||
track := disk.Tracks[disk.halfTrack/2]
|
track := disk.Tracks[disk.halfTrack/2]
|
||||||
disk.position = (disk.position + 1) % len(track)
|
disk.position = (disk.position + 1) % len(track)
|
||||||
|
@ -76,14 +46,15 @@ func (disk *Nybble) HalfTrack() byte {
|
||||||
return disk.halfTrack
|
return disk.halfTrack
|
||||||
}
|
}
|
||||||
|
|
||||||
func (disk *Nybble) SetVolume(volume byte) {
|
|
||||||
disk.volume = volume
|
|
||||||
}
|
|
||||||
|
|
||||||
func (disk *Nybble) Volume() byte {
|
|
||||||
return disk.volume
|
|
||||||
}
|
|
||||||
|
|
||||||
func (disk *Nybble) Writeable() bool {
|
func (disk *Nybble) Writeable() bool {
|
||||||
return disk.writeable
|
return disk.writeable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (disk *Nybble) GetPos() SavedPos {
|
||||||
|
return SavedPos{disk.halfTrack, disk.position}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disk *Nybble) SetPos(pos SavedPos) {
|
||||||
|
disk.halfTrack = pos.halfTrack
|
||||||
|
disk.position = pos.position
|
||||||
|
}
|
||||||
|
|
|
@ -712,36 +712,3 @@ If track 0 has >15 sync count, the rest of the tracks will have (sync count - 2)
|
||||||
| | (* 416 16) 6656 | (* 413 16) 6608 | (* 16 391) 6256 | (* 16 12708) 203328 |
|
| | (* 416 16) 6656 | (* 413 16) 6608 | (* 16 391) 6256 | (* 16 12708) 203328 |
|
||||||
| | 6656 | (+ 6608 48) 6656 | (+ 6256 108) 6364 | (+ 203328 4320) 207648 |
|
| | 6656 | (+ 6608 48) 6656 | (+ 6256 108) 6364 | (+ 203328 4320) 207648 |
|
||||||
| | | | | (/ 207648 4) 51912 |
|
| | | | | (/ 207648 4) 51912 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
tracksize 51024
|
|
||||||
(/ 51024 8)
|
|
||||||
6378
|
|
||||||
|
|
||||||
* System disk masters
|
|
||||||
https://github.com/cmosher01/Apple-II-System-Masters.git
|
|
||||||
http://beagle.applearchives.com/the_software/vintage_beagle_bros_softwar/prontodos.html
|
|
||||||
|
|
||||||
|
|
||||||
| 452f496e7c9c447052331f72f643117f | dos331/clean331sysmas.do |
|
|
||||||
| 452f496e7c9c447052331f72f643117f | dos331/original331sysmas.do |
|
|
||||||
| 452f496e7c9c447052331f72f643117f | dos331/stock331mastercreated.do |
|
|
||||||
| 5a72964c04e4a37bb8b13a471aa046de | dos331/stock331init.do |
|
|
||||||
| 5ed656da29221d891d056b6b576ae2ce | dos330/stock330init.do |
|
|
||||||
| 6987207d16a72c60de03d8d1c5e267f3 | dos332/clean332sysmas.do |
|
|
||||||
| 6987207d16a72c60de03d8d1c5e267f3 | dos332/original332sysmas.do |
|
|
||||||
| 6987207d16a72c60de03d8d1c5e267f3 | dos332/stock332mastercreated.do |
|
|
||||||
| 7df440bb6b3ddd8cbc44009f90a8d18f | dos330/clean330sysmas.do |
|
|
||||||
| 7df440bb6b3ddd8cbc44009f90a8d18f | dos330/original330sysmas.do |
|
|
||||||
| 7df440bb6b3ddd8cbc44009f90a8d18f | dos330/stock330mastercreated.do |
|
|
||||||
| 9f79e87fcbcb9885a47f2f462868833f | dos332/stock332init.do |
|
|
||||||
| 7df440bb6b3ddd8cbc44009f90a8d18f | Apple DOS 3.3 August 1980.dsk |
|
|
||||||
| 452f496e7c9c447052331f72f643117f | Apple DOS 3.3 January 1983.dsk |
|
|
||||||
| 92e8fc0c4e79e50f2d81a14cfa55b151 | DavidDOS.dsk |
|
|
||||||
| c17928ee915b9bf5c4041621fa8a0cb4 | DiversiDOS.dsk |
|
|
||||||
| 6390fc84edf64dd88682b920ff88bbfa | Prontodos/Prontodos.dsk |
|
|
||||||
| 5b9894778b6b97d56d1199ab13b0db66 | EsDos.dsk |
|
|
||||||
|
|
||||||
| db809f937c0de0a12cb70897ed9a72da | chivalry.dsk |
|
|
||||||
|
|
||||||
|
|
|
@ -89,3 +89,165 @@ $1DCA: 85 77 STA $77
|
||||||
$1DCC: BD 5E 1E LDA $1E5E,X
|
$1DCC: BD 5E 1E LDA $1E5E,X
|
||||||
$1DCF: 85 64 STA $64
|
$1DCF: 85 64 STA $64
|
||||||
$1DD1: 60 RTS
|
$1DD1: 60 RTS
|
||||||
|
|
||||||
|
** Boot sector addresses
|
||||||
|
$26 buffer pointer
|
||||||
|
$2B boot slot
|
||||||
|
$3D Last bsectr read
|
||||||
|
$3E address btemp
|
||||||
|
$5C offset to reader
|
||||||
|
|
||||||
|
* apple][js shim
|
||||||
|
disk_categories['Local Saves'].push({category: 'Game', filename: 'http://localhost:8000/chivalry.json', name: 'Chivalry'})
|
||||||
|
|
||||||
|
* Nybble files
|
||||||
|
|
||||||
|
** apple2js
|
||||||
|
d5 aa 96 ff fe aa aa af af fa fb de aa eb
|
||||||
|
|
||||||
|
d5 aa ad ff 96 96 a7 9d ae 96 d6 b4 ed 96 96 96 a7 9e b2 96 d6 b6 f2
|
||||||
|
9b 96 96 a7 96 b2 97 d7 b6 ef 9a 96 96 9d 97 97 96 d6 b5 ee 97 96 96
|
||||||
|
96 96 96 96 d6 b9 f9 9a 9b a7 ae ae 96 96 d6 96 fa 9e a6 a7 a7 a7 96
|
||||||
|
96 b4 f3 9f 97 9b 9d 9d 9d 96 96 d6 ed ef d7 f2 ed b9 bd ae ea 96 96
|
||||||
|
96 ac ad 96 96 ad ad 97 96 96 96 a7 ac ac ac ac 9b af 96 96 96 b2 ac
|
||||||
|
ad ad ac ac 97 96 96 96 ac ac 96 96 ac ac da 9f 96 96 96 96 96 96 96
|
||||||
|
96 9a a6 96 96 96 96 96 96 96 a6 96 a6 96 96 96 96 96 96 96 a6 96 a6
|
||||||
|
96 96 96 96 96 96 96 a6 96 a6 96 96 96 96 96 96 96 a6 96 a6 96 96 96
|
||||||
|
96 96 96 96 a6 96 a6 96 96 96 96 96 96 96 a6 97 97 96 96 96 96 96 96
|
||||||
|
96 96 db 96 96 96 ac ad 96 96 ad ad 97 96 96 96 a7 ac ac ac ac 9b af
|
||||||
|
96 96 96 b2 ac ad ad ac ac 97 96 96 96 ac ac 96 96 ac ac da 9f 96 96
|
||||||
|
96 96 96 96 96 96 9a a6 96 96 96 96 96 96 96 a6 96 a6 96 96 96 96 96
|
||||||
|
96 96 a6 96 a6 96 96 96 96 96 96 96 a6 96 a6 96 96 96 96 96 96 96 a6
|
||||||
|
96 a6 96 96 96 96 96 96 96 a6 96 a6 96 96 96 96 96 96 96 a6 97 97 96
|
||||||
|
96 96 96 96 96 96 96 9f 96 96 96 96 96 96 96 b2 f3 fb ef be ad af eb
|
||||||
|
9a de aa eb
|
||||||
|
|
||||||
|
** Mine
|
||||||
|
|
||||||
|
d5 aa 96 ff fe aa aa af af fa fb de aa eb
|
||||||
|
|
||||||
|
d5 aa ad ff 96 96 a7 9d ae 96 d6 b4 ed 96 96 96 a7 9e b2 96 d6 b6 f2
|
||||||
|
9b 96 96 a7 96 b2 97 d7 b6 ef 9a 96 96 9d 97 97 96 d6 b5 ee 97 96 96
|
||||||
|
96 96 96 96 d6 b9 f9 9a 9b a7 ae ae 96 96 d6 96 fa 9e a6 a7 a7 a7 96
|
||||||
|
96 b4 f3 9f 97 9b 9d 9d 9d 96 96 d6 ed ef d7 f2 ed b9 df ae ce 96 96
|
||||||
|
96 ac ad 96 96 ad ad 97 96 96 96 a7 ac ac ac ac 9b af 96 96 96 b2 ac
|
||||||
|
ad ad ac ac 97 96 96 96 ac ac 96 96 ac ac da 9f 96 96 96 96 96 96 96
|
||||||
|
96 9a a6 96 96 96 96 96 96 96 a6 96 a6 96 96 96 96 96 96 96 a6 96 a6
|
||||||
|
96 96 96 96 96 96 96 a6 96 a6 96 96 96 96 96 96 96 a6 96 a6 96 96 96
|
||||||
|
96 96 96 96 a6 96 a6 96 96 96 96 96 96 96 a6 97 97 96 96 96 96 96 96
|
||||||
|
96 96 db 96 96 96 ac ad 96 96 ad ad 97 96 96 96 a7 ac ac ac ac 9b af
|
||||||
|
96 96 96 b2 ac ad ad ac ac 97 96 96 96 ac ac 96 96 ac ac da 9f 96 96
|
||||||
|
96 96 96 96 96 96 9a a6 96 96 96 96 96 96 96 a6 96 a6 96 96 96 96 96
|
||||||
|
96 96 a6 96 a6 96 96 96 96 96 96 96 a6 96 a6 96 96 96 96 96 96 96 a6
|
||||||
|
96 a6 96 96 96 96 96 96 96 a6 96 a6 96 96 96 96 96 96 96 a6 97 97 96
|
||||||
|
96 96 96 96 96 96 96 9f 96 96 96 96 96 96 96 b2 f3 fb ef be ad af eb
|
||||||
|
9a de aa eb
|
||||||
|
* Divergence?
|
||||||
|
$B700: (797128) No
|
||||||
|
$8004: (14016092) Yes
|
||||||
|
|
||||||
|
* Slot 6 accesses from:
|
||||||
|
|
||||||
|
1 Slot6 access from $C632
|
||||||
|
1 Slot6 access from $C635
|
||||||
|
1 Slot6 access from $C638
|
||||||
|
1 Slot6 access from $C63B
|
||||||
|
1 Slot6 access from $C640
|
||||||
|
1 Slot6 access from $C64A
|
||||||
|
|
||||||
|
$C661: 14, 367, 767, etc.
|
||||||
|
$C6AD: 86
|
||||||
|
$C6BF: 256
|
||||||
|
|
||||||
|
$B952: 10, 367, etc.
|
||||||
|
$B907: 86
|
||||||
|
$B918: 256
|
||||||
|
|
||||||
|
0xB942: error return
|
||||||
|
0xB942: good return
|
||||||
|
|
||||||
|
|
||||||
|
* Possibly innocuous differences:
|
||||||
|
38,39c38,40
|
||||||
|
< $0026: FD FD
|
||||||
|
---
|
||||||
|
> $0026: F7 F2
|
||||||
|
|
||||||
|
|
||||||
|
< $002C: FD 02
|
||||||
|
---
|
||||||
|
> $002C: F2 0D
|
||||||
|
|
||||||
|
|
||||||
|
< $01FC: 30
|
||||||
|
---
|
||||||
|
> $01FC: 34
|
||||||
|
|
||||||
|
|
||||||
|
26,27
|
||||||
|
2C:
|
||||||
|
2D:
|
||||||
|
|
||||||
|
|
||||||
|
2D: 0D - sector number from header
|
||||||
|
2E: 01 - track number from sector header
|
||||||
|
2F: FE - volume number from sector header
|
||||||
|
2C: F2 - sector header checksum
|
||||||
|
|
||||||
|
|
||||||
|
CURRENT.TRACK .EQ $478
|
||||||
|
DRIVE.1.TRACK .EQ $478 THRU 47F (INDEX BY SLOT)
|
||||||
|
DRIVE.2.TRACK .EQ $4F8 THRU 4FF (INDEX BY SLOT)
|
||||||
|
SEARCH.COUNT .EQ $4F8
|
||||||
|
RETRY.COUNT .EQ $578
|
||||||
|
SLOT .EQ $5F8
|
||||||
|
SEEK.COUNT .EQ $6F8
|
||||||
|
SECTOR .EQ $2D
|
||||||
|
TRACK .EQ $2A
|
||||||
|
VOLUME .EQ $2F
|
||||||
|
DRIVE.NO .EQ $35
|
||||||
|
DCT.PNTR .EQ $3C,3D
|
||||||
|
BUF.PNTR .EQ $3E,3F
|
||||||
|
MOTOR.TIME .EQ $46,47
|
||||||
|
IOB.PNTR .EQ $48,49
|
||||||
|
|
||||||
|
* Append A,X,Y,SP,P,PC
|
||||||
|
NOP: EA
|
||||||
|
LDA #$00: A9 00
|
||||||
|
LDX #$00: A2 00
|
||||||
|
LDY #$00: A0 00
|
||||||
|
CMP #$AA: C9 AA
|
||||||
|
PHP #$08
|
||||||
|
PLA #$68
|
||||||
|
|
||||||
|
|
||||||
|
* Notes - status
|
||||||
|
|
||||||
|
0xB99E - is not a good address, because it's used to return from READ.ADDRESS as well as READ.SECTOR.
|
||||||
|
|
||||||
|
** After 69th sector access
|
||||||
|
100: a2js: 0xB8C7, goa2: 0xB8C7 - match
|
||||||
|
125000: a2js: 0x1DC0, goa2: 0x1DC0 - match
|
||||||
|
187500: a2js: 0x1DA5, goa2: 0x1DA5 - match
|
||||||
|
195312: a2js: 0x1F69, goa2: 0x1F69 - match
|
||||||
|
197265: a2js: 0x1DB7, goa2: 0x1DB7 - match
|
||||||
|
197509: a2js: 0x02F8, goa2: 0x02F8 - match
|
||||||
|
197631: a2js: 0x030C, goa2: 0x030C - match
|
||||||
|
197646: a2js: 0xBD13, goa2: 0xBD13 - match
|
||||||
|
197653: a2js: 0xBD3F, goa2: 0xBD3F - match
|
||||||
|
197657: a2js: 0xBD43, goa2: 0xBD43 - match
|
||||||
|
197659: a2js: 0xBD49, goa2: 0xBD49 - DIFF in P
|
||||||
|
197661: a2js: 0xBD4F, goa2: 0xBD4C - LOTS!
|
||||||
|
197692: a2js: 0xBD70, goa2: 0xBD3C - LOTS!
|
||||||
|
197753: a2js: 0xBA03, goa2: 0xBD5A - DIFF!
|
||||||
|
198241: a2js: 0xBA03, goa2: 0xBA03 - DIFF in A,X
|
||||||
|
199218: a2js: 0xBA02, goa2: 0xBA02 - DIFF in X
|
||||||
|
203125: a2js: 0xBA03, goa2: 0xBA03 - DIFF in A,X
|
||||||
|
218750: a2js: 0xBA05, goa2: 0xBA02 - DIFF!
|
||||||
|
250000: a2js: 0xBA03, goa2: 0xBA0C - DIFF!
|
||||||
|
500000: a2js: 0xBDA0, goa2: 0xBDA1 - DIFF!
|
||||||
|
|
||||||
|
(/ (+ 197657 197661) 2)
|
||||||
|
197659
|
||||||
|
|
||||||
|
|
||||||
|
|
6802
docs/disk-md5s.org
6802
docs/disk-md5s.org
File diff suppressed because it is too large
Load Diff
90
goapple2.go
90
goapple2.go
|
@ -6,7 +6,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zellyn/go6502/cpu"
|
"github.com/zellyn/go6502/cpu"
|
||||||
"github.com/zellyn/goapple2/cards"
|
"github.com/zellyn/goapple2/cards"
|
||||||
|
@ -18,11 +17,18 @@ type PCActionType int
|
||||||
const (
|
const (
|
||||||
ActionDumpMem PCActionType = iota + 1
|
ActionDumpMem PCActionType = iota + 1
|
||||||
ActionLogRegisters
|
ActionLogRegisters
|
||||||
|
ActionTrace
|
||||||
|
ActionSetLimit
|
||||||
|
ActionHere
|
||||||
|
ActionDiskStatus
|
||||||
)
|
)
|
||||||
|
|
||||||
type PCAction struct {
|
type PCAction struct {
|
||||||
Type PCActionType
|
Type PCActionType
|
||||||
String string
|
String string
|
||||||
|
Mask byte
|
||||||
|
Masked byte
|
||||||
|
Delay uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apple II struct
|
// Apple II struct
|
||||||
|
@ -44,14 +50,16 @@ type Apple2 struct {
|
||||||
card12kConflict bool "True if more than one card is handling the 12k ROM area"
|
card12kConflict bool "True if more than one card is handling the 12k ROM area"
|
||||||
card12kHandler byte
|
card12kHandler byte
|
||||||
cardTickerMask byte
|
cardTickerMask byte
|
||||||
pcActions map[uint16][]PCAction
|
pcActions map[uint16][]*PCAction
|
||||||
|
limit int
|
||||||
|
cycle uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApple2(p videoscan.Plotter, rom []byte, charRom [2048]byte) *Apple2 {
|
func NewApple2(p videoscan.Plotter, rom []byte, charRom [2048]byte) *Apple2 {
|
||||||
a2 := Apple2{
|
a2 := Apple2{
|
||||||
// BUG(zellyn): this is not how the apple2 keyboard actually works
|
// BUG(zellyn): this is not how the apple2 keyboard actually works
|
||||||
keys: make(chan byte, 16),
|
keys: make(chan byte, 16),
|
||||||
pcActions: make(map[uint16][]PCAction),
|
pcActions: make(map[uint16][]*PCAction),
|
||||||
}
|
}
|
||||||
copy(a2.mem[len(a2.mem)-len(rom):len(a2.mem)], rom)
|
copy(a2.mem[len(a2.mem)-len(rom):len(a2.mem)], rom)
|
||||||
a2.scanner = videoscan.NewScanner(&a2, p, charRom)
|
a2.scanner = videoscan.NewScanner(&a2, p, charRom)
|
||||||
|
@ -207,6 +215,9 @@ func (a2 *Apple2) RamRead(address uint16) byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a2 *Apple2) Write(address uint16, value byte) {
|
func (a2 *Apple2) Write(address uint16, value byte) {
|
||||||
|
// if address == 0x46 {
|
||||||
|
// fmt.Printf("Write to 0x46: PC==$%04X\n", a2.cpu.PC())
|
||||||
|
// }
|
||||||
if address >= 0xD000 {
|
if address >= 0xD000 {
|
||||||
if a2.cardRomMask > 0 {
|
if a2.cardRomMask > 0 {
|
||||||
if a2.card12kConflict {
|
if a2.card12kConflict {
|
||||||
|
@ -229,21 +240,54 @@ func (a2 *Apple2) Keypress(key byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a2 *Apple2) AddPCAction(address uint16, action PCAction) {
|
func (a2 *Apple2) AddPCAction(address uint16, action PCAction) {
|
||||||
a2.pcActions[address] = append(a2.pcActions[address], action)
|
a2.pcActions[address] = append(a2.pcActions[address], &action)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a2 *Apple2) Step() error {
|
func (a2 *Apple2) Step() error {
|
||||||
|
p := a2.cpu.P()
|
||||||
if actions, ok := a2.pcActions[a2.cpu.PC()]; ok {
|
if actions, ok := a2.pcActions[a2.cpu.PC()]; ok {
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
|
if p&action.Mask != action.Masked {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if action.Delay > 0 {
|
||||||
|
fmt.Printf("Delaying %v: %d\n", action.Type, action.Delay)
|
||||||
|
action.Delay--
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch action.Type {
|
switch action.Type {
|
||||||
case ActionDumpMem:
|
case ActionDumpMem:
|
||||||
a2.DumpRAM(action.String, true)
|
a2.DumpRAM(action.String)
|
||||||
case ActionLogRegisters:
|
case ActionLogRegisters:
|
||||||
a2.LogRegisters()
|
a2.LogRegisters()
|
||||||
|
case ActionTrace:
|
||||||
|
a2.cpu.Print(action.String == "on" || action.String == "true")
|
||||||
|
case ActionSetLimit:
|
||||||
|
if i, err := strconv.Atoi(action.String); err == nil {
|
||||||
|
a2.limit = i
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
case ActionHere:
|
||||||
|
fmt.Printf("$%04X: (%d) %s - A=$%02X X=$%02X Y=$%02X SP=$%02X P=$%08b\n",
|
||||||
|
a2.cpu.PC(), a2.cycle, action.String,
|
||||||
|
a2.cpu.A(), a2.cpu.X(), a2.cpu.Y(), a2.cpu.SP(), a2.cpu.P())
|
||||||
|
case ActionDiskStatus:
|
||||||
|
fmt.Printf("$%04X: %v\n",
|
||||||
|
a2.cpu.PC(), a2.cards[6])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return a2.cpu.Step()
|
err := a2.cpu.Step()
|
||||||
|
if a2.limit > 0 {
|
||||||
|
a2.limit--
|
||||||
|
if a2.limit == 0 {
|
||||||
|
a2.DumpRAM("limit-goa2.bin")
|
||||||
|
panic("Limit reached")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a2.cycle++
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a2 *Apple2) Tick() {
|
func (a2 *Apple2) Tick() {
|
||||||
|
@ -303,18 +347,32 @@ func (a2 *Apple2) LogRegisters() {
|
||||||
log.Printf("Registers: PC=$%04X A=$%02X X=$%02X Y=$%02X SP=$%02X P=$%02X=$%08b",
|
log.Printf("Registers: PC=$%04X A=$%02X X=$%02X Y=$%02X SP=$%02X P=$%02X=$%08b",
|
||||||
c.PC(), c.A(), c.X(), c.Y(), c.SP(), c.P(), c.P())
|
c.PC(), c.A(), c.X(), c.Y(), c.SP(), c.P(), c.P())
|
||||||
}
|
}
|
||||||
func (a2 *Apple2) DumpRAM(filename string, addTimestamp bool) error {
|
|
||||||
|
var dumpCount = 0
|
||||||
|
|
||||||
|
func (a2 *Apple2) DumpRAM(filename string) error {
|
||||||
f := filename
|
f := filename
|
||||||
if addTimestamp {
|
dumpCount++
|
||||||
ts := "-" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
ts := "-" + fmt.Sprintf("%05d", dumpCount)
|
||||||
if ext := filepath.Ext(filename); ext == "" {
|
if ext := filepath.Ext(filename); ext == "" {
|
||||||
f = filename + ts
|
f = filename + ts
|
||||||
} else {
|
} else {
|
||||||
dir, file := filepath.Split(filename[:len(filename)-len(ext)])
|
dir, file := filepath.Split(filename[:len(filename)-len(ext)])
|
||||||
f = dir + file + ts + ext
|
f = dir + file + ts + ext
|
||||||
}
|
|
||||||
}
|
}
|
||||||
log.Printf("Dumping RAM to %s", f)
|
log.Printf("Dumping RAM to %s", f)
|
||||||
a2.LogRegisters()
|
a2.LogRegisters()
|
||||||
return ioutil.WriteFile(f, a2.mem[:0xC000], 0644)
|
buf := make([]byte, 0xC000, 0xC000+20)
|
||||||
|
copy(buf, a2.mem[:0xC000])
|
||||||
|
// LDA $A
|
||||||
|
// LDX $X
|
||||||
|
// LDY $Y
|
||||||
|
buf = append(buf, 0xA9, a2.cpu.A(), 0xA2, a2.cpu.X(), 0xA0, a2.cpu.Y())
|
||||||
|
// PHP, PLA, CMP $P
|
||||||
|
buf = append(buf, 0x08, 0x68, 0xC9, a2.cpu.P())
|
||||||
|
// TSX, CPX $SP
|
||||||
|
buf = append(buf, 0xBA, 0xE0, a2.cpu.SP())
|
||||||
|
// JMP $PC
|
||||||
|
buf = append(buf, 0x4C, byte(a2.cpu.PC()&0xFF), byte(a2.cpu.PC()>>8))
|
||||||
|
return ioutil.WriteFile(f, buf, 0644)
|
||||||
}
|
}
|
||||||
|
|
38
sdl/sdl.go
38
sdl/sdl.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
|
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
|
||||||
|
"github.com/zellyn/go6502/cpu"
|
||||||
"github.com/zellyn/goapple2"
|
"github.com/zellyn/goapple2"
|
||||||
"github.com/zellyn/goapple2/cards"
|
"github.com/zellyn/goapple2/cards"
|
||||||
"github.com/zellyn/goapple2/disk"
|
"github.com/zellyn/goapple2/disk"
|
||||||
|
@ -323,10 +324,10 @@ func RunEmulator() {
|
||||||
if err := a2.AddCard(diskCard); err != nil {
|
if err := a2.AddCard(diskCard); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
disk1 := disk.NewNybble()
|
// disk1, err := disk.DiskFromFile("../data/disks/spedtest.dsk", 0)
|
||||||
// if err = disk1.LoadDosDisk("../data/disks/spedtest.dsk"); err != nil {
|
// disk1, err := disk.DiskFromFile("../data/disks/dung_beetles.dsk", 0)
|
||||||
// if err = disk1.LoadDosDisk("../data/disks/dung_beetles.dsk"); err != nil {
|
disk1, err := disk.DiskFromFile("../data/disks/chivalry.dsk", 0)
|
||||||
if err = disk1.LoadDosDisk("../data/disks/chivalry.dsk"); err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
diskCard.LoadDisk(disk1, 0)
|
diskCard.LoadDisk(disk1, 0)
|
||||||
|
@ -342,8 +343,33 @@ func RunEmulator() {
|
||||||
defer pprof.StopCPUProfile()
|
defer pprof.StopCPUProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
a2.AddPCAction(0xC6EB, goapple2.PCAction{goapple2.ActionDumpMem, "chivalry-dump.bin"})
|
/*
|
||||||
a2.AddPCAction(0x1F76, goapple2.PCAction{goapple2.ActionDumpMem, "chivalry-dump.bin"})
|
a2.AddPCAction(
|
||||||
|
0xB940, goapple2.PCAction{Type: goapple2.ActionDumpMem, String: "0xB940-goa2.bin",
|
||||||
|
Mask: cpu.FLAG_Z, Masked: cpu.FLAG_Z, Delay: 68})
|
||||||
|
a2.AddPCAction(0xB7B5, goapple2.PCAction{Type: goapple2.ActionHere, String: "ENTER.RWTS"})
|
||||||
|
a2.AddPCAction(0xB7BE, goapple2.PCAction{Type: goapple2.ActionHere, String: "ENTER.RWTS - Success"})
|
||||||
|
a2.AddPCAction(0xB7C1, goapple2.PCAction{Type: goapple2.ActionHere, String: "ENTER.RWTS - Fail"})
|
||||||
|
a2.AddPCAction(0xBD00, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS"})
|
||||||
|
a2.AddPCAction(0xBDAF, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS Command"})
|
||||||
|
// a2.AddPCAction(0xBE35, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS READ.SECTOR call"})
|
||||||
|
// a2.AddPCAction(0xBE38, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS READ.SECTOR success"})
|
||||||
|
|
||||||
|
a2.AddPCAction(0xBE46, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS Success"})
|
||||||
|
a2.AddPCAction(0xBE48, goapple2.PCAction{Type: goapple2.ActionHere, String: "RWTS Error"})
|
||||||
|
|
||||||
|
a2.AddPCAction(0xBDAF, goapple2.PCAction{Type: goapple2.ActionDiskStatus})
|
||||||
|
|
||||||
|
a2.AddPCAction(0xBDAF, goapple2.PCAction{Type: goapple2.ActionTrace, String: "on",
|
||||||
|
Delay: 70})
|
||||||
|
|
||||||
|
a2.AddPCAction(
|
||||||
|
0xBE48, goapple2.PCAction{Type: goapple2.ActionSetLimit, String: "1"})
|
||||||
|
|
||||||
|
a2.AddPCAction(
|
||||||
|
0xBDAF, goapple2.PCAction{Type: goapple2.ActionDumpMem, String: "0xBDAF-goa2.bin", Delay: 68})
|
||||||
|
*/
|
||||||
|
_ = cpu.FLAG_Z
|
||||||
|
|
||||||
// go typeProgram(a2)
|
// go typeProgram(a2)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue