mirror of
https://github.com/zellyn/goapple2.git
synced 2024-11-29 08:49:49 +00:00
Working on disks
This commit is contained in:
parent
a3eced201d
commit
8f3ce4748b
130
cards/disk.go
Normal file
130
cards/disk.go
Normal file
@ -0,0 +1,130 @@
|
||||
package cards
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zellyn/goapple2/disk"
|
||||
)
|
||||
|
||||
type Disk interface {
|
||||
Read() byte
|
||||
Write(byte)
|
||||
SetHalfTrack(byte)
|
||||
HalfTrack() byte
|
||||
SetVolume(byte)
|
||||
Volume() byte
|
||||
Writeable() bool
|
||||
}
|
||||
|
||||
type DiskCard struct {
|
||||
rom [256]byte
|
||||
cm CardManager
|
||||
slot byte
|
||||
slotbit byte
|
||||
disks [2]Disk
|
||||
active int
|
||||
phases byte
|
||||
}
|
||||
|
||||
func NewDiskCard(rom []byte, slot byte, cm CardManager) (*DiskCard, error) {
|
||||
if len(rom) != 256 {
|
||||
return nil, fmt.Errorf("Wrong size ROM: expected 256, got %d", len(rom))
|
||||
}
|
||||
dc := &DiskCard{
|
||||
cm: cm,
|
||||
slot: slot,
|
||||
slotbit: 1 << slot,
|
||||
disks: [2]Disk{disk.NewDummy(disk.DEFAULT_VOLUME), disk.NewDummy(disk.DEFAULT_VOLUME)},
|
||||
}
|
||||
copy(dc.rom[:], rom)
|
||||
return dc, nil
|
||||
}
|
||||
|
||||
func (dc *DiskCard) String() string {
|
||||
return fmt.Sprintf("Disk Card (slot %d)", dc.slot)
|
||||
}
|
||||
|
||||
func (dc *DiskCard) Slot() byte {
|
||||
return dc.slot
|
||||
}
|
||||
|
||||
func (dc *DiskCard) ROMDisabled() {
|
||||
// Disk card doesn't have a $C(8-F)xx ROM
|
||||
}
|
||||
|
||||
func (dc *DiskCard) handlePhase(phase byte, onOff bool) {
|
||||
phaseBit := byte(1 << phase)
|
||||
if onOff {
|
||||
dc.phases |= phaseBit
|
||||
} else {
|
||||
dc.phases &^= phaseBit
|
||||
}
|
||||
|
||||
disk := dc.disks[dc.active]
|
||||
newTrack := int(disk.HalfTrack())
|
||||
switch dc.phases {
|
||||
case 1:
|
||||
newTrack = (newTrack + 1) / 4 * 4
|
||||
case 2:
|
||||
newTrack = (newTrack/4)*4 + 1
|
||||
case 4:
|
||||
if newTrack != 0 {
|
||||
newTrack = (newTrack-1)/4*4 + 2
|
||||
}
|
||||
case 8:
|
||||
if newTrack < 2 {
|
||||
newTrack = 0
|
||||
} else {
|
||||
newTrack = (newTrack-2)/4*4 + 3
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
if newTrack < 0 {
|
||||
newTrack = 0
|
||||
}
|
||||
if newTrack > 68 {
|
||||
newTrack = 68
|
||||
}
|
||||
if disk.HalfTrack() != byte(newTrack) {
|
||||
disk.SetHalfTrack(byte(newTrack))
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DiskCard) handleAccess(address byte) {
|
||||
if address < 8 {
|
||||
phase := address / 2
|
||||
onOff := (address & 1) == 1
|
||||
dc.handlePhase(phase, onOff)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DiskCard) Read16(address byte) byte {
|
||||
dc.handleAccess(address)
|
||||
return dc.cm.EmptyRead()
|
||||
}
|
||||
|
||||
func (dc *DiskCard) Write16(address byte, value byte) {
|
||||
dc.handleAccess(address)
|
||||
}
|
||||
|
||||
func (dc *DiskCard) Read(address uint16) byte {
|
||||
panic(fmt.Sprintf("%s got read to $%04X", dc.String(), address))
|
||||
}
|
||||
|
||||
func (dc *DiskCard) Write(address uint16, value byte) {
|
||||
panic(fmt.Sprintf("%s got write to $%04X", dc.String(), address))
|
||||
}
|
||||
|
||||
func (dc *DiskCard) Read256(address byte) byte {
|
||||
return dc.rom[address]
|
||||
}
|
||||
|
||||
func (dc *DiskCard) Write256(address byte, value byte) {
|
||||
// Firmware is ROM: do nothing
|
||||
}
|
||||
|
||||
func (dc *DiskCard) LoadDisk(d Disk, which int) {
|
||||
dc.disks[which] = d
|
||||
}
|
171
disk/convert.go
Normal file
171
disk/convert.go
Normal file
@ -0,0 +1,171 @@
|
||||
package disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Dos33writeTable is the list of valid DOS 3.3 bytes.
|
||||
// See [UtA2 9-27 Disk Data Formats].
|
||||
var Dos33writeTable = [64]byte{
|
||||
0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
|
||||
0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
|
||||
0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
|
||||
0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
|
||||
0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
|
||||
0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
|
||||
0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
|
||||
0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
|
||||
}
|
||||
|
||||
// Dos33LogicalToPhysicalSectorMap maps logical sector numbers to physical ones.
|
||||
// See [UtA2 9-42 - Read Routines].
|
||||
var Dos33LogicalToPhysicalSectorMap = []byte{
|
||||
0x00, 0x0D, 0x0B, 0x09, 0x07, 0x05, 0x03, 0x01,
|
||||
0x0E, 0x0C, 0x0A, 0x08, 0x06, 0x04, 0x02, 0x0F,
|
||||
}
|
||||
|
||||
// Dos33PhysicalToLogicalSectorMap maps physical sector numbers to logical ones.
|
||||
// See [UtA2 9-42 - Read Routines].
|
||||
var Dos33PhysicalToLogicalSectorMap = []byte{
|
||||
0x00, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04,
|
||||
0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x0F,
|
||||
}
|
||||
|
||||
// ProDosLogicalToPhysicalSectorMap maps logical sector numbers to pysical ones.
|
||||
// See [UtA2e 9-43 - Sectors vs. Blocks].
|
||||
var ProDosLogicalToPhysicalSectorMap = []byte{
|
||||
0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E,
|
||||
0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
|
||||
}
|
||||
|
||||
// ProDosPhysicalToLogicalSectorMap maps physical sector numbers to logical ones.
|
||||
// See [UtA2e 9-43 - Sectors vs. Blocks].
|
||||
var ProDosPhysicalToLogicalSectorMap = []byte{
|
||||
0x00, 0x08, 0x01, 0x09, 0x02, 0x0A, 0x03, 0x0B,
|
||||
0x04, 0x0C, 0x05, 0x0D, 0x06, 0x0E, 0x07, 0x0F,
|
||||
}
|
||||
|
||||
// PreNybble converts from 256 8-bit bytes to 342 6-bit "nybbles".
|
||||
// The equivalent DOS routine, PRE.NYBBLE, would put the 256 MSB-6's into one buffer,
|
||||
// and the 86 LSB-2's into a separate one, but we place the 86 after the 256.
|
||||
// See http://www.txbobsc.com/aal/1981/aal8106.html#a5
|
||||
func PreNybble(source []byte) (target [342]byte) {
|
||||
if len(source) != 256 {
|
||||
panic(fmt.Sprintf("PreNybble expects 256 bytes as input, got %d", len(source)))
|
||||
}
|
||||
y := 2
|
||||
for i := 0; i < 3; i++ {
|
||||
for x := 0; x < 86; x++ {
|
||||
// DEY
|
||||
y = (y + 0xff) & 0xff
|
||||
a := source[y]
|
||||
target[y] = a >> 2
|
||||
target[x+256] = (target[x+256]<<2 + a&2>>1 + a&1<<1) & 0x3F
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
// PostNybble converts from 342 6-bit "nybbles to 255 8-bit bytes.
|
||||
// The equivalent DOS routine, POST.NYBBLE, would read the 256 MSB-6's from one buffer,
|
||||
// and the 86 LSB-2's from a separate one, but we place the 86 after the 256.
|
||||
// See http://www.txbobsc.com/aal/1981/aal8106.html#a5
|
||||
func PostNybble(source []byte) (target [256]byte) {
|
||||
if len(source) != 342 {
|
||||
panic(fmt.Sprintf("PostNybble expects 342 bytes as input, got %d", len(source)))
|
||||
}
|
||||
|
||||
x := 0
|
||||
for y := 0; y < 256; y++ {
|
||||
// DEX
|
||||
x = (x + 85) % 86
|
||||
a := source[256+x]
|
||||
source[256+x] = a >> 2
|
||||
target[y] = source[y]<<2 + a&2>>1 + a&1<<1
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
// appendSyncs appends the given number of sync bytes to the slice, and returns the resulting slice.
|
||||
func appendSyncs(target []byte, count int) []byte {
|
||||
for i := 0; i < count; i++ {
|
||||
target = append(target, 0xFF)
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
// append44 appends bytes using the 4-4 encoded format used for volume, track, sector, checksum.
|
||||
func append44(target []byte, b byte) []byte {
|
||||
return append(target, 0xAA|b<<1, 0xAA|b)
|
||||
}
|
||||
|
||||
// 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)
|
||||
target = append44(target, v)
|
||||
target = append44(target, t)
|
||||
target = append44(target, s)
|
||||
target = append44(target, v^t^s)
|
||||
target = append(target, 0xDE, 0xAA, 0xEB)
|
||||
return target
|
||||
}
|
||||
|
||||
// appendData appends the encoded sector data to the slice, and returns the resulting slice.
|
||||
func appendData(target, source []byte) []byte {
|
||||
target = append(target, 0xD5, 0xAA, 0xAD)
|
||||
nybbles := PreNybble(source)
|
||||
checksum := byte(0)
|
||||
for i := 341; i >= 256; i-- {
|
||||
target = append(target, Dos33writeTable[nybbles[i]^checksum])
|
||||
checksum = nybbles[i]
|
||||
}
|
||||
for i := 0; i < 256; i++ {
|
||||
target = append(target, Dos33writeTable[nybbles[i]^checksum])
|
||||
checksum = nybbles[i]
|
||||
}
|
||||
target = append(target, Dos33writeTable[checksum])
|
||||
target = append(target, 0xDE, 0xAA, 0xEB)
|
||||
return target
|
||||
}
|
||||
|
||||
// dosToNybbleSector appends a single 256-byte DOS sector in .nyb format, and returns the slice.
|
||||
func dosToNybbleSector(target, source []byte, t, s, v byte, preSync, intraSync int) []byte {
|
||||
if len(source) != 256 {
|
||||
panic(fmt.Sprintf("dosToNybbleSector expects 256 bytes, got %d", len(source)))
|
||||
}
|
||||
target = appendSyncs(target, preSync)
|
||||
target = appendAddress(target, t, s, v)
|
||||
target = appendSyncs(target, intraSync)
|
||||
target = appendData(target, source)
|
||||
return target
|
||||
}
|
||||
|
||||
// dos16ToNybbleTrack converts a 16-sector dos image of a track to a nybblized track.
|
||||
func dos16ToNybbleTrack(source []byte, t, v byte, preSync, intraSync int, sectorOrder []byte) []byte {
|
||||
trackSync := NYBBLE_TRACK_BYTES - 16*(preSync+NYBBLE_ADDRESS_BYTES+intraSync+NYBBLE_DATA_BYTES)
|
||||
target := make([]byte, 0, NYBBLE_TRACK_BYTES)
|
||||
target = appendSyncs(target, trackSync)
|
||||
for s := byte(0); s < 16; s++ {
|
||||
start := 256 * int(sectorOrder[s])
|
||||
target = dosToNybbleSector(target, source[start:start+256], t, s, v, preSync, intraSync)
|
||||
}
|
||||
if len(target) != NYBBLE_TRACK_BYTES {
|
||||
panic(fmt.Sprintf("Tracks should be %d bytes, go %d", NYBBLE_TRACK_BYTES, len(target)))
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func dos16ToNybbleTracks(source []byte, v byte, preSync, intraSync int, sectorOrder []byte) (tracks [][]byte, err error) {
|
||||
if preSync+intraSync > MAX_PRE_INTRA_SYNC {
|
||||
return nil, fmt.Errorf("preSync(%d) + intraSync(%d) cannot be more than %d bytes",
|
||||
preSync, intraSync, MAX_PRE_INTRA_SYNC)
|
||||
}
|
||||
|
||||
for t := byte(0); t < 35; t++ {
|
||||
start := DOS_TRACK_BYTES * int(t)
|
||||
trackBytes := source[start : start+DOS_TRACK_BYTES]
|
||||
track := dos16ToNybbleTrack(trackBytes, t, v, preSync, intraSync, sectorOrder)
|
||||
tracks = append(tracks, track)
|
||||
}
|
||||
return tracks, nil
|
||||
}
|
14
disk/disk.go
Normal file
14
disk/disk.go
Normal file
@ -0,0 +1,14 @@
|
||||
package disk
|
||||
|
||||
const (
|
||||
DOS_DISK_BYTES = 143360 // 35 tracks * 16 sectors * 256 bytes
|
||||
DOS_TRACK_BYTES = DOS_DISK_BYTES / 35
|
||||
NYBBLE_DISK_BYTES = 232960
|
||||
NYBBLE_TRACK_BYTES = NYBBLE_DISK_BYTES / 35
|
||||
NYBBLE_ADDRESS_BYTES = 14
|
||||
NYBBLE_DATA_BYTES = 349
|
||||
DEFAULT_VOLUME = 254
|
||||
DEFAULT_PRESYNC = 20
|
||||
DEFAULT_INTRASYNC = 8
|
||||
MAX_PRE_INTRA_SYNC = (NYBBLE_TRACK_BYTES / 16) - NYBBLE_ADDRESS_BYTES - NYBBLE_DATA_BYTES
|
||||
)
|
32
disk/disk_test.go
Normal file
32
disk/disk_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package disk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPreNybble(t *testing.T) {
|
||||
var source [256]byte
|
||||
for i := range source {
|
||||
source[i] = byte(i)
|
||||
}
|
||||
target := PreNybble(source[:])
|
||||
for i := range target {
|
||||
if target[i] > 0x3F {
|
||||
t.Errorf("target[%d] too large: 0x%02X", i, target[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrePostNybble(t *testing.T) {
|
||||
var source [256]byte
|
||||
for i := range source {
|
||||
source[i] = byte(i)
|
||||
}
|
||||
target := PreNybble(source[:])
|
||||
sourceCheck := PostNybble(target[:])
|
||||
for i := range source {
|
||||
if source[i] != sourceCheck[i] {
|
||||
t.Errorf("source, sourceCheck differ at %d: 0x%02X != 0x%02X", i, source[i], sourceCheck[i])
|
||||
}
|
||||
}
|
||||
}
|
35
disk/dummy.go
Normal file
35
disk/dummy.go
Normal file
@ -0,0 +1,35 @@
|
||||
package disk
|
||||
|
||||
type Dummy byte
|
||||
|
||||
func (v Dummy) Read() byte {
|
||||
return 0xFF
|
||||
}
|
||||
|
||||
func (v Dummy) Write(b byte) {
|
||||
// pass
|
||||
}
|
||||
|
||||
func (v Dummy) SetHalfTrack(t byte) {
|
||||
// pass
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func NewDummy(v byte) Dummy {
|
||||
return Dummy(v)
|
||||
}
|
84
disk/nybble.go
Normal file
84
disk/nybble.go
Normal file
@ -0,0 +1,84 @@
|
||||
package disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Nybble struct {
|
||||
Tracks [][]byte
|
||||
volume byte
|
||||
halfTrack byte
|
||||
position int
|
||||
writeable bool
|
||||
}
|
||||
|
||||
func NewNybble() *Nybble {
|
||||
nd := Nybble{
|
||||
volume: DEFAULT_VOLUME,
|
||||
}
|
||||
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)
|
||||
return track[disk.position]
|
||||
}
|
||||
|
||||
func (disk *Nybble) Write(b byte) {
|
||||
track := disk.Tracks[disk.halfTrack/2]
|
||||
disk.position = (disk.position + 1) % len(track)
|
||||
if disk.writeable {
|
||||
track[disk.position] = b
|
||||
}
|
||||
}
|
||||
|
||||
func (disk *Nybble) SetHalfTrack(halfTrack byte) {
|
||||
disk.halfTrack = halfTrack
|
||||
}
|
||||
|
||||
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
|
||||
}
|
180
docs/apple2.org
180
docs/apple2.org
@ -485,3 +485,183 @@ F8 jumper not jumpered: never use F8 ROM on card: use motherboard.
|
||||
* Questions
|
||||
** Color
|
||||
Can a previously rendered dot change colors? Gray?
|
||||
** Disk
|
||||
I thought ProDOS only allowed a read syncing leader from $10 to
|
||||
$1A. But the nybblized disk format seems to have 48 spare bytes for
|
||||
each sector.
|
||||
(- 416 14 5 6 1 342) 48
|
||||
|
||||
* Disk formats
|
||||
** dsk/do - DOS 3.3 order
|
||||
35 tracks * 16 sectors * 256 bytes = 143,360
|
||||
** po - ProDOS order
|
||||
** nib
|
||||
232,960 bytes
|
||||
|
||||
(* 35 16 342)
|
||||
191,520
|
||||
|
||||
(* 35 16 (+ 342 74))
|
||||
232,960 bytes
|
||||
|
||||
Extra bytes per track:
|
||||
1184 (74 PER SECTOR)
|
||||
|
||||
|
||||
https://code.google.com/p/twoapple/source/browse/src/peripheral/diskii.d
|
||||
|
||||
ADDR: 13
|
||||
PROLOGUE: 3 D5,AA,96
|
||||
VOLUME: 2
|
||||
TRACK: 2
|
||||
SECTOR: 2
|
||||
CHECK: 2
|
||||
EPILOGUE: 2 DE,AA
|
||||
|
||||
DATAFIELD: 349
|
||||
PROLOGUE: 3 D5,AA,AD
|
||||
DATA: 343
|
||||
EPILOGUE: 3 DE,AA,EB
|
||||
|
||||
SECTOR:
|
||||
ADDR: 13
|
||||
GAP2: 6
|
||||
DATAFIELD: 349
|
||||
GAP3: 45
|
||||
|
||||
(+ 13 6 349 45)
|
||||
413
|
||||
|
||||
# 48 byte gap, 413 per sector: (+ 48 (* 16 413)) 6656
|
||||
|
||||
|
||||
http://sourceforge.net/p/java-ace/code/285/tree/jace/src/jace/hardware/FloppyDisk.java
|
||||
- 15 junk bytes
|
||||
- Address block: 14
|
||||
- D5,AA,96 - 3
|
||||
- Volume - 2
|
||||
- Track - 2
|
||||
- Sector - 2
|
||||
- Checksum - 2
|
||||
- DE,AA,EB - 3
|
||||
- 4 junk bytes
|
||||
- Data block 349
|
||||
- D5,AA,AD 3
|
||||
- Data: 342
|
||||
- Checksum: 1
|
||||
- DE,AA,EB - 3
|
||||
- 34 junk bytes
|
||||
|
||||
(+ 15 14 4 349 34)
|
||||
416
|
||||
|
||||
|
||||
Writing out:
|
||||
http://sourceforge.net/p/java-ace/code/285/tree/jace/src/jace/hardware/FloppyDisk.java#l260
|
||||
|
||||
- Look for D5,AA,96
|
||||
- Verify track
|
||||
- Read sector
|
||||
- Look for DE,AA
|
||||
- Look for D5,AA,AD
|
||||
-
|
||||
- Look for DE,AA,EB
|
||||
|
||||
http://mrob.com/apple2/extract.txt
|
||||
|
||||
https://code.google.com/p/openemulator/source/browse/trunk/libdiskimage/DIApple525DiskStorage.cpp#590
|
||||
- 128 sync bytes for track 0, 20 for others.
|
||||
- address field
|
||||
- (+ 3 8 3) 14
|
||||
- 8 sync bytes
|
||||
- data field
|
||||
- (+ 3 86 256 1 3) 349
|
||||
|
||||
(+ 108 (* 20 16) (* 16 (+ 14 8 349)))
|
||||
6364
|
||||
|
||||
6656
|
||||
|
||||
|
||||
* Listings
|
||||
* RWTS listing
|
||||
http://www.txbobsc.com/aal/1981/aal8109.html#a5
|
||||
|
||||
* DOS 3.3 boot sector
|
||||
http://www.txbobsc.com/aal/1981/aal8108.html#a9
|
||||
|
||||
* Volume numbers
|
||||
DOS 3.3: Default = 254
|
||||
Prodos: not used. [[https://groups.google.com/d/msg/comp.sys.apple2/YjoOR7gza1w/-2-HcNLLucwJ][1?]]
|
||||
|
||||
* Sector orders
|
||||
|
||||
** DOS 3.3
|
||||
[[ftp://ftp.apple.asimov.net/pub/apple_II/documentation/source_code/Apple2DOS33CSourceListing.pdf][Logical to physical sector mapping source]]
|
||||
|
||||
Logical to physical
|
||||
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
|
||||
0,D,B,9,7,5,3,1,E,C,A,8,6,4,2,F
|
||||
|
||||
Physical to logical:
|
||||
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
|
||||
0,7,E,6,D,5,C,4,B,3,A,2,9,1,8,F
|
||||
|
||||
When writing nybblized track 1, we write track 1 in address header, but read from image track sector 7.
|
||||
|
||||
logical 4 <==> physical 7
|
||||
|
||||
** Prodos
|
||||
[[http://sourceforge.net/p/java-ace/code/285/tree/jace/src/jace/hardware/FloppyDisk.java#l40][Physical to logical - JACE]]
|
||||
|
||||
Logical to Physical:
|
||||
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
|
||||
0,2,4,6,8,A,C,E,1,3,5,7,9,B,D,F
|
||||
|
||||
Physical to logical:
|
||||
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
|
||||
0,8,1,9,2,A,3,B,4,C,5,D,6,E,7,F
|
||||
|
||||
* Formatting...
|
||||
_Prodos note_: tolerance of read syncing leader is $10 to $1A, so drive
|
||||
speed must be close to 300rpm.
|
||||
|
||||
Syncing leader: 5-40 FFs
|
||||
Address
|
||||
Gap 50
|
||||
Gap 53
|
||||
Syncing leader: 5 FFs
|
||||
|
||||
|
||||
Guess 40 FFs.
|
||||
Write 128 FF40's before Sector 0, 40 FF40s before the others.
|
||||
Write sectors in order (physical sectors).
|
||||
|
||||
Sync count reduced by 2s until it reaches 16, then by ones until it reaches 5.
|
||||
If track 0 has >15 sync count, the rest of the tracks will have (sync count - 2).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
* Byte counts
|
||||
|
||||
| | Jace | Twoapple | OpenEmulator-Byte | OpenEmulator-Cycle |
|
||||
|-------------+------------------------+---------------------+----------------------+-----------------------------|
|
||||
| Pre-track-0 | | 48 | 108 | (* 108 40) 4320 |
|
||||
| Pre-sync | 15 | | 20 | (+ (* 19 40) 32) 792 |
|
||||
| Address | 14 | 13 | 14 | (+ (* 13 32) 16) 432 |
|
||||
| Intra-sync | 4 | 6 | 8 | (+ (* 7 40) 36) 316 |
|
||||
| Data | 349 | 349 | (+ 3 86 256 3 1) 349 | (* 349 32) 11168 |
|
||||
| Post-sync | 34 | 45 | 0 | 0 |
|
||||
| | (+ 15 14 4 349 34) 416 | (+ 13 6 349 45) 413 | (+ 20 14 8 349) 391 | (+ 792 432 316 11168) 12708 |
|
||||
| | (* 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
|
||||
|
64
goapple2.go
64
goapple2.go
@ -56,39 +56,41 @@ func (a2 *Apple2) handleCardRom(address uint16, value byte, write bool) byte {
|
||||
}
|
||||
|
||||
func (a2 *Apple2) handleC00X(address uint16, value byte, write bool) byte {
|
||||
switch address & 0xC0F0 {
|
||||
// $C00X: Read keyboard
|
||||
case 0xC000:
|
||||
if a2.key&0x80 == 0 {
|
||||
select {
|
||||
case key := <-a2.keys:
|
||||
a2.key = key
|
||||
default:
|
||||
if address < 0xC080 {
|
||||
switch address & 0xC0F0 {
|
||||
// $C00X: Read keyboard
|
||||
case 0xC000:
|
||||
if a2.key&0x80 == 0 {
|
||||
select {
|
||||
case key := <-a2.keys:
|
||||
a2.key = key
|
||||
default:
|
||||
}
|
||||
}
|
||||
return a2.key
|
||||
// $C01X: Reset keyboard
|
||||
case 0xC010:
|
||||
a2.key &= 0x7F
|
||||
return a2.EmptyRead()
|
||||
}
|
||||
switch address {
|
||||
case 0xC050: // GRAPHICS
|
||||
a2.scanner.SetGraphics(true)
|
||||
case 0xC051: // TEXT
|
||||
a2.scanner.SetGraphics(false)
|
||||
case 0xC052: // NOMIX
|
||||
a2.scanner.SetMix(false)
|
||||
case 0xC053: // MIX
|
||||
a2.scanner.SetMix(true)
|
||||
case 0xC054: // PAGE 1
|
||||
a2.scanner.SetPage(1)
|
||||
case 0xC055: // PAGE 2
|
||||
a2.scanner.SetPage(2)
|
||||
case 0xC056: // LORES
|
||||
a2.scanner.SetHires(false)
|
||||
case 0xC057: // HIRES
|
||||
a2.scanner.SetHires(true)
|
||||
}
|
||||
return a2.key
|
||||
// $C01X: Reset keyboard
|
||||
case 0xC010:
|
||||
a2.key &= 0x7F
|
||||
return a2.EmptyRead()
|
||||
}
|
||||
switch address {
|
||||
case 0xC050: // GRAPHICS
|
||||
a2.scanner.SetGraphics(true)
|
||||
case 0xC051: // TEXT
|
||||
a2.scanner.SetGraphics(false)
|
||||
case 0xC052: // NOMIX
|
||||
a2.scanner.SetMix(false)
|
||||
case 0xC053: // MIX
|
||||
a2.scanner.SetMix(true)
|
||||
case 0xC054: // PAGE 1
|
||||
a2.scanner.SetPage(1)
|
||||
case 0xC055: // PAGE 2
|
||||
a2.scanner.SetPage(2)
|
||||
case 0xC056: // LORES
|
||||
a2.scanner.SetHires(false)
|
||||
case 0xC057: // HIRES
|
||||
a2.scanner.SetHires(true)
|
||||
}
|
||||
|
||||
if address < 0xC080 {
|
||||
|
20
sdl/sdl.go
20
sdl/sdl.go
@ -13,6 +13,7 @@ import (
|
||||
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
|
||||
"github.com/zellyn/goapple2"
|
||||
"github.com/zellyn/goapple2/cards"
|
||||
"github.com/zellyn/goapple2/disk"
|
||||
"github.com/zellyn/goapple2/util"
|
||||
"github.com/zellyn/goapple2/videoscan"
|
||||
)
|
||||
@ -33,12 +34,10 @@ const (
|
||||
func plot(x, y uint, color uint32, screen *sdl.Surface) {
|
||||
x = x + BORDER_W
|
||||
y = y + BORDER_H
|
||||
screen.Lock()
|
||||
pixels := uintptr(screen.Pixels)
|
||||
offset := uintptr(y*uint(screen.Pitch) + x*(SCREEN_BPP/8))
|
||||
addr := pixels + offset
|
||||
*(*uint32)(unsafe.Pointer(addr)) = color
|
||||
screen.Unlock()
|
||||
}
|
||||
|
||||
func Init() (screen *sdl.Surface, err error) {
|
||||
@ -255,7 +254,9 @@ func (s SdlPlotter) Plot(pd videoscan.PlotData) {
|
||||
}
|
||||
|
||||
func (s SdlPlotter) OncePerFrame() {
|
||||
s.screen.Unlock()
|
||||
s.screen.Flip()
|
||||
s.screen.Lock()
|
||||
s.oncePerFrame()
|
||||
}
|
||||
|
||||
@ -305,6 +306,21 @@ func RunEmulator() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
diskCardRom := util.ReadRomOrDie("../data/roms/Apple Disk II 16 Sector Interface Card ROM P5 - 341-0027.bin")
|
||||
diskCard, err := cards.NewDiskCard(diskCardRom, 6, a2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := a2.AddCard(diskCard); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
disk1 := disk.NewNybble()
|
||||
if err = disk1.LoadDosDisk("../data/disks/chivalry.dsk"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
disk1.SetHalfTrack(50)
|
||||
diskCard.LoadDisk(disk1, 0)
|
||||
|
||||
steps := *steplimit
|
||||
|
||||
if *cpuprofile != "" {
|
||||
|
Loading…
Reference in New Issue
Block a user