mirror of
https://github.com/zellyn/goapple2.git
synced 2024-12-01 21:50:13 +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
|
* Questions
|
||||||
** Color
|
** Color
|
||||||
Can a previously rendered dot change colors? Gray?
|
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
|
||||||
|
@ -56,6 +56,7 @@ func (a2 *Apple2) handleCardRom(address uint16, value byte, write bool) byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a2 *Apple2) handleC00X(address uint16, value byte, write bool) byte {
|
func (a2 *Apple2) handleC00X(address uint16, value byte, write bool) byte {
|
||||||
|
if address < 0xC080 {
|
||||||
switch address & 0xC0F0 {
|
switch address & 0xC0F0 {
|
||||||
// $C00X: Read keyboard
|
// $C00X: Read keyboard
|
||||||
case 0xC000:
|
case 0xC000:
|
||||||
@ -90,6 +91,7 @@ func (a2 *Apple2) handleC00X(address uint16, value byte, write bool) byte {
|
|||||||
case 0xC057: // HIRES
|
case 0xC057: // HIRES
|
||||||
a2.scanner.SetHires(true)
|
a2.scanner.SetHires(true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if address < 0xC080 {
|
if address < 0xC080 {
|
||||||
return a2.EmptyRead()
|
return a2.EmptyRead()
|
||||||
|
20
sdl/sdl.go
20
sdl/sdl.go
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
|
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
|
||||||
"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/util"
|
"github.com/zellyn/goapple2/util"
|
||||||
"github.com/zellyn/goapple2/videoscan"
|
"github.com/zellyn/goapple2/videoscan"
|
||||||
)
|
)
|
||||||
@ -33,12 +34,10 @@ const (
|
|||||||
func plot(x, y uint, color uint32, screen *sdl.Surface) {
|
func plot(x, y uint, color uint32, screen *sdl.Surface) {
|
||||||
x = x + BORDER_W
|
x = x + BORDER_W
|
||||||
y = y + BORDER_H
|
y = y + BORDER_H
|
||||||
screen.Lock()
|
|
||||||
pixels := uintptr(screen.Pixels)
|
pixels := uintptr(screen.Pixels)
|
||||||
offset := uintptr(y*uint(screen.Pitch) + x*(SCREEN_BPP/8))
|
offset := uintptr(y*uint(screen.Pitch) + x*(SCREEN_BPP/8))
|
||||||
addr := pixels + offset
|
addr := pixels + offset
|
||||||
*(*uint32)(unsafe.Pointer(addr)) = color
|
*(*uint32)(unsafe.Pointer(addr)) = color
|
||||||
screen.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init() (screen *sdl.Surface, err error) {
|
func Init() (screen *sdl.Surface, err error) {
|
||||||
@ -255,7 +254,9 @@ func (s SdlPlotter) Plot(pd videoscan.PlotData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s SdlPlotter) OncePerFrame() {
|
func (s SdlPlotter) OncePerFrame() {
|
||||||
|
s.screen.Unlock()
|
||||||
s.screen.Flip()
|
s.screen.Flip()
|
||||||
|
s.screen.Lock()
|
||||||
s.oncePerFrame()
|
s.oncePerFrame()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,6 +306,21 @@ func RunEmulator() {
|
|||||||
log.Fatal(err)
|
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
|
steps := *steplimit
|
||||||
|
|
||||||
if *cpuprofile != "" {
|
if *cpuprofile != "" {
|
||||||
|
Loading…
Reference in New Issue
Block a user