Lots of work on disks. Also added a2 command-line utility.

This commit is contained in:
Zellyn Hunter 2013-05-06 21:23:36 -07:00
parent 5e28969748
commit b8fb9c6ab8
13 changed files with 3953 additions and 3519 deletions

40
a2/disk_convert.go Normal file
View File

@ -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)
}
}

View File

@ -15,6 +15,7 @@ func init() {
Name: os.Args[0],
Commands: []*commander.Command{
cmdDisasm,
cmdDiskConvert,
},
Flag: flag.NewFlagSet("a2", flag.ExitOnError),
}

View File

@ -12,8 +12,6 @@ type Disk interface {
Write(byte)
SetHalfTrack(byte)
HalfTrack() byte
SetVolume(byte)
Volume() byte
Writeable() bool
}
@ -53,7 +51,11 @@ func NewDiskCard(rom []byte, slot byte, cm CardManager) (*DiskCard, error) {
}
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 {
@ -125,7 +127,7 @@ func (dc *DiskCard) handleAccess(address byte) {
dc.handlePhase(0, dc.phases&1 == 1) // No change: force update
}
case 0xC, 0xD:
dc.mode = dc.mode&^2 | address&1<<2
dc.mode = dc.mode&^2 | address&1<<1
case 0xE, 0xF:
dc.mode = dc.mode&^1 | address&1
}
@ -154,6 +156,8 @@ func (dc *DiskCard) Read16(address byte) byte {
case MODE_WRITE | MODE_LOAD:
// Nonsense for reading: just return last data
return dc.dataRegister
default:
panic(fmt.Sprintf("Unexpected disk card mode: %d", dc.mode))
}
}
return 0xFF
@ -165,17 +169,19 @@ func (dc *DiskCard) Write16(address byte, value byte) {
switch dc.mode {
case MODE_READ | MODE_SHIFT:
// Normal read
panic("Write while in read mode")
// panic("Write while in read mode")
case MODE_READ | MODE_LOAD:
// Check write-protect
panic("Write while in check-write-protect mode")
// panic("Write while in check-write-protect mode")
case MODE_WRITE | MODE_SHIFT:
// Shifting data to disk
panic("Write while in shift mode")
// panic("Write while in shift mode")
case MODE_WRITE | MODE_LOAD:
if dc.disks[dc.active].Writeable() {
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
}
disk := dc.disks[dc.active]
if dc.lastAccess > 300 {
disk.Skip(dc.lastAccess / 36)
}
dc.lastAccess = 0
dc.dataRegister = disk.Read()
return dc.dataRegister

View File

@ -2,6 +2,9 @@ package disk
import (
"fmt"
"io/ioutil"
"log"
"path"
)
// 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,
}
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.
// See [UtA2 9-42 - Read Routines].
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.
func appendAddress(target []byte, t, s, v byte) []byte {
target = append(target, 0xD5, 0xAA, 0x96)
fmt.Println("s=", s)
target = append44(target, v)
target = append44(target, t)
target = append44(target, s)
@ -170,3 +183,177 @@ func dos16ToNybbleTracks(source []byte, v byte, preSync, intraSync int, sectorOr
}
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")
}

View File

@ -1,6 +1,7 @@
package disk
const (
NUM_TRACKS = 35
DOS_DISK_BYTES = 143360 // 35 tracks * 16 sectors * 256 bytes
DOS_TRACK_BYTES = DOS_DISK_BYTES / 35
NYBBLE_DISK_BYTES = 232960

View File

@ -1,6 +1,7 @@
package disk
import (
"math/rand"
"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])
}
}
}

View File

@ -22,14 +22,6 @@ func (v Dummy) HalfTrack() byte {
return 0
}
func (v Dummy) SetVolume(byte) {
// pass
}
func (v Dummy) Volume() byte {
return byte(v)
}
func (v Dummy) Writeable() bool {
return false
}

View File

@ -1,54 +1,24 @@
package disk
import (
"fmt"
"io/ioutil"
"strings"
)
type SavedPos struct {
halfTrack byte
position int
}
type Nybble struct {
Tracks [][]byte
volume byte
halfTrack byte
position int
writeable bool
}
func NewNybble() *Nybble {
func NewNybble(tracks [][]byte) *Nybble {
nd := Nybble{
volume: DEFAULT_VOLUME,
Tracks: tracks,
}
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 {
track := disk.Tracks[disk.halfTrack/2]
disk.position = (disk.position + 1) % len(track)
@ -76,14 +46,15 @@ func (disk *Nybble) HalfTrack() byte {
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 {
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
}

View File

@ -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 |
| | 6656 | (+ 6608 48) 6656 | (+ 6256 108) 6364 | (+ 203328 4320) 207648 |
| | | | | (/ 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 |

View File

@ -89,3 +89,165 @@ $1DCA: 85 77 STA $77
$1DCC: BD 5E 1E LDA $1E5E,X
$1DCF: 85 64 STA $64
$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

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,6 @@ import (
"log"
"path/filepath"
"strconv"
"time"
"github.com/zellyn/go6502/cpu"
"github.com/zellyn/goapple2/cards"
@ -18,11 +17,18 @@ type PCActionType int
const (
ActionDumpMem PCActionType = iota + 1
ActionLogRegisters
ActionTrace
ActionSetLimit
ActionHere
ActionDiskStatus
)
type PCAction struct {
Type PCActionType
String string
Mask byte
Masked byte
Delay uint64
}
// Apple II struct
@ -44,14 +50,16 @@ type Apple2 struct {
card12kConflict bool "True if more than one card is handling the 12k ROM area"
card12kHandler 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 {
a2 := Apple2{
// BUG(zellyn): this is not how the apple2 keyboard actually works
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)
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) {
// if address == 0x46 {
// fmt.Printf("Write to 0x46: PC==$%04X\n", a2.cpu.PC())
// }
if address >= 0xD000 {
if a2.cardRomMask > 0 {
if a2.card12kConflict {
@ -229,21 +240,54 @@ func (a2 *Apple2) Keypress(key byte) {
}
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 {
p := a2.cpu.P()
if actions, ok := a2.pcActions[a2.cpu.PC()]; ok {
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 {
case ActionDumpMem:
a2.DumpRAM(action.String, true)
a2.DumpRAM(action.String)
case ActionLogRegisters:
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() {
@ -303,18 +347,32 @@ func (a2 *Apple2) LogRegisters() {
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())
}
func (a2 *Apple2) DumpRAM(filename string, addTimestamp bool) error {
var dumpCount = 0
func (a2 *Apple2) DumpRAM(filename string) error {
f := filename
if addTimestamp {
ts := "-" + strconv.FormatInt(time.Now().UnixNano(), 10)
if ext := filepath.Ext(filename); ext == "" {
f = filename + ts
} else {
dir, file := filepath.Split(filename[:len(filename)-len(ext)])
f = dir + file + ts + ext
}
dumpCount++
ts := "-" + fmt.Sprintf("%05d", dumpCount)
if ext := filepath.Ext(filename); ext == "" {
f = filename + ts
} else {
dir, file := filepath.Split(filename[:len(filename)-len(ext)])
f = dir + file + ts + ext
}
log.Printf("Dumping RAM to %s", f)
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)
}

View File

@ -11,6 +11,7 @@ import (
"unsafe"
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
"github.com/zellyn/go6502/cpu"
"github.com/zellyn/goapple2"
"github.com/zellyn/goapple2/cards"
"github.com/zellyn/goapple2/disk"
@ -323,10 +324,10 @@ func RunEmulator() {
if err := a2.AddCard(diskCard); err != nil {
log.Fatal(err)
}
disk1 := disk.NewNybble()
// if err = disk1.LoadDosDisk("../data/disks/spedtest.dsk"); err != nil {
// if err = disk1.LoadDosDisk("../data/disks/dung_beetles.dsk"); err != nil {
if err = disk1.LoadDosDisk("../data/disks/chivalry.dsk"); err != nil {
// disk1, err := disk.DiskFromFile("../data/disks/spedtest.dsk", 0)
// disk1, err := disk.DiskFromFile("../data/disks/dung_beetles.dsk", 0)
disk1, err := disk.DiskFromFile("../data/disks/chivalry.dsk", 0)
if err != nil {
log.Fatal(err)
}
diskCard.LoadDisk(disk1, 0)
@ -342,8 +343,33 @@ func RunEmulator() {
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)