mirror of
https://github.com/zellyn/goapple2.git
synced 2024-12-26 01:29:52 +00:00
Lots of work on disks. Also added a2 command-line utility.
This commit is contained in:
parent
5e28969748
commit
b8fb9c6ab8
40
a2/disk_convert.go
Normal file
40
a2/disk_convert.go
Normal 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)
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ func init() {
|
||||
Name: os.Args[0],
|
||||
Commands: []*commander.Command{
|
||||
cmdDisasm,
|
||||
cmdDiskConvert,
|
||||
},
|
||||
Flag: flag.NewFlagSet("a2", flag.ExitOnError),
|
||||
}
|
||||
|
@ -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
|
||||
|
189
disk/convert.go
189
disk/convert.go
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 |
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
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"
|
||||
"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)
|
||||
}
|
||||
|
38
sdl/sdl.go
38
sdl/sdl.go
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user