Support 3.5 disks and HDV format. Generalize the hard disk support to be used for any SmartPort device.

This commit is contained in:
Ivan Izaguirre 2020-08-11 23:53:05 +02:00
parent caa312c12a
commit c2b620ec01
7 changed files with 218 additions and 151 deletions

View File

@ -12,9 +12,10 @@ Portable emulator of an Apple II+ or //e. Written in Go.
- Apple //e enhanced with 128Kb of RAM
- Base64A clone with 48Kb of base RAM and paginated ROM
- Storage
- 16 Sector diskettes in NIB, DSK or PO format
- 16 Sector diskettes in WOZ 1.0 or 2.0 format (read only)
- Hard disk with ProDOS and SmartPort support
- 16 Sector 5 1/4 diskettes in NIB, DSK or PO format
- 16 Sector 5 1/4 diskettes in WOZ 1.0 or 2.0 format (read only)
- 3.5 disks in PO or 2MG format
- Hard disk in HDV or 2MG format with ProDOS and SmartPort support
- Emulated extension cards:
- DiskII controller
- 16Kb Language Card
@ -22,7 +23,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
- 1Mb Memory Expansion Card (slinky)
- RAMWorks style expansion Card (up to 16MB additional) (Apple //e only)
- ThunderClock Plus real time clock
- Bootable hard disk card
- Bootable Smartport / ProDOS card
- Apple //e 80 columns with 64Kb extra RAM and optional RGB modes
- VidHd, limited to the ROM signature and SHR as used by Total Replay, only for //e models with 128Kb
- FASTChip, limited to what Total Replay needs to set and clear fast mode
@ -154,6 +155,8 @@ Only valid on SDL mode
file to load on the first disk drive (default "<internal>/dos33.dsk")
-disk2Slot int
slot for the disk driver. -1 for none. (default 6)
-disk35 string
file to load on the SmartPort disk (slot 5)
-diskRom string
rom file for the disk drive controller (default "<internal>/DISK2.rom")
-diskb string
@ -165,7 +168,7 @@ Only valid on SDL mode
-fastDisk
set fast mode when the disks are spinning (default true)
-hd string
file to load on the hard disk
file to load on the boot hard disk
-hdSlot int
slot for the hard drive if present. -1 for none. (default -1)
-languageCardSlot int
@ -195,7 +198,7 @@ Only valid on SDL mode
-traceCpu
dump to the console the CPU execution. Use F11 to toggle.
-traceHD
dump to the console the hd commands
dump to the console the hd/smartport commands
-traceMLI
dump to the console the calls to ProDOS machine language interface calls to $BF00
-traceSS

View File

@ -126,14 +126,14 @@ func (a *Apple2) AddDisk2(slot int, diskRomFile string, diskImage, diskBImage st
return nil
}
// AddHardDisk adds a ProDos hard dirve with a 2MG image
func (a *Apple2) AddHardDisk(slot int, hdImage string, trace bool) error {
// AddSmartPortDisk adds a smart port card and image
func (a *Apple2) AddSmartPortDisk(slot int, hdImage string, trace bool) error {
var c cardHardDisk
c.setTrace(trace)
c.loadRom(buildHardDiskRom(slot))
a.insertCard(&c, slot)
hd, err := openHardDisk2mg(hdImage)
hd, err := openBlockDisk(hdImage)
if err != nil {
return err
}
@ -194,6 +194,7 @@ func (a *Apple2) AddRGBCard() {
setupRGBCard(a)
}
// AddRAMWorks inserts adds RAMWorks style RAM to the Apple IIe 80 col 64KB card
func (a *Apple2) AddRAMWorks(banks int) {
setupRAMWorksCard(a, banks)
}

View File

@ -36,11 +36,15 @@ func MainApple() *Apple2 {
hardDiskImage := flag.String(
"hd",
"",
"file to load on the hard disk")
"file to load on the boot hard disk (slot 7)")
hardDiskSlot := flag.Int(
"hdSlot",
-1,
"slot for the hard drive if present. -1 for none.")
smartPortImage := flag.String(
"disk35",
"",
"file to load on the SmartPort disk (slot 5)")
cpuClock := flag.Float64(
"mhz",
CPUClockMhz,
@ -104,7 +108,7 @@ func MainApple() *Apple2 {
traceHD := flag.Bool(
"traceHD",
false,
"dump to the console the hd commands")
"dump to the console the hd/smarport commands")
dumpChars := flag.Bool(
"dumpChars",
false,
@ -237,6 +241,18 @@ func MainApple() *Apple2 {
if *vidHDCardSlot >= 0 {
a.AddVidHD(*vidHDCardSlot)
}
if *smartPortImage != "" {
err := a.AddSmartPortDisk(5, *smartPortImage, *traceHD)
if err != nil {
panic(err)
}
if *fastChipCardSlot == 5 {
// Don't use fastChipCard if the slot 5 is already in use
*fastChipCardSlot = 0
}
}
if *fastChipCardSlot >= 0 {
a.AddFastChip(*fastChipCardSlot)
}
@ -251,7 +267,7 @@ func MainApple() *Apple2 {
// If there is a hard disk image, but no slot assigned, use slot 7.
*hardDiskSlot = 7
}
err := a.AddHardDisk(*hardDiskSlot, *hardDiskImage, *traceHD)
err := a.AddSmartPortDisk(*hardDiskSlot, *hardDiskImage, *traceHD)
if err != nil {
panic(err)
}

98
blockDisk.go Normal file
View File

@ -0,0 +1,98 @@
package apple2
import (
"errors"
"fmt"
"os"
)
/*
Valid for ProDos disks with 512 bytes blocks. Can be diskettes or hard disks
*/
const (
proDosBlockSize = uint32(512)
proDosMaxBlocks = uint32(65536)
)
type blockDisk struct {
file *os.File
readOnly bool
dataOffset uint32
blocks uint32
}
func openBlockDisk(filename string) (*blockDisk, error) {
var bd blockDisk
bd.readOnly = false
file, err := os.OpenFile(filename, os.O_RDWR, 0)
if os.IsPermission(err) {
// Retry in read-only mode
bd.readOnly = true
file, err = os.OpenFile(filename, os.O_RDONLY, 0)
}
if err != nil {
return nil, err
}
bd.file = file
err2mg := parse2mg(&bd)
if err2mg == nil {
// It's a 2mg file, ready to use
return &bd, nil
}
// Let's try to load as raw ProDOS Blocks
fileInfo, err := bd.file.Stat()
if err != nil {
return nil, err
}
if fileInfo.Size() > int64(proDosBlockSize*proDosMaxBlocks) {
return nil, fmt.Errorf("File is too big OR %s", err2mg.Error())
}
size := uint32(fileInfo.Size())
if size%proDosBlockSize != 0 {
return nil, fmt.Errorf("File size os invalid OR %s", err2mg.Error())
}
// It's a valid raw file
bd.blocks = size / proDosBlockSize
bd.dataOffset = 0
return &bd, nil
}
func (bd *blockDisk) read(block uint32) ([]uint8, error) {
if block >= bd.blocks {
return nil, errors.New("disk block number is too big")
}
buf := make([]uint8, proDosBlockSize)
offset := int64(bd.dataOffset + block*proDosBlockSize)
_, err := bd.file.ReadAt(buf, offset)
if err != nil {
return nil, err
}
return buf, nil
}
func (bd *blockDisk) write(block uint32, data []uint8) error {
if bd.readOnly {
return errors.New("can't write in a readonly disk")
}
if block >= bd.blocks {
return errors.New("disk block number is too big")
}
offset := int64(bd.dataOffset + block*proDosBlockSize)
_, err := bd.file.WriteAt(data, offset)
if err != nil {
return err
}
return nil
}

View File

@ -15,7 +15,7 @@ See:
type cardHardDisk struct {
cardBase
disk *hardDisk
disk *blockDisk
mliParams uint16
trace bool
}
@ -132,11 +132,11 @@ func (c *cardHardDisk) assign(a *Apple2, slot int) {
}, "HDCOMMAND")
c.addCardSoftSwitchR(1, func(*ioC0Page) uint8 {
// Blocks available, low byte
return uint8(c.disk.header.Blocks)
return uint8(c.disk.blocks)
}, "HDBLOCKSLO")
c.addCardSoftSwitchR(2, func(*ioC0Page) uint8 {
// Blocks available, high byte
return uint8(c.disk.header.Blocks >> 8)
return uint8(c.disk.blocks >> 8)
}, "HDBLOCKHI")
c.addCardSoftSwitchR(3, func(*ioC0Page) uint8 {
@ -218,7 +218,7 @@ func (c *cardHardDisk) writeBlock(block uint16, source uint16) uint8 {
return proDosDeviceNoError
}
func (c *cardHardDisk) addDisk(disk *hardDisk) {
func (c *cardHardDisk) addDisk(disk *blockDisk) {
c.disk = disk
}

84
file2mg.go Normal file
View File

@ -0,0 +1,84 @@
package apple2
import (
"encoding/binary"
"errors"
"io"
)
/*
Valid for ProDos disks in 2MG format.
See:
https://apple2.org.za/gswv/a2zine/Docs/DiskImage_2MG_Info.txt
*/
const (
file2mgPreamble = uint32(1196247346) // "2IMG"
file2mgFormatProdos = 1
file2mgVersion = 1
)
type file2mgHeader struct {
Preamble uint32
Creator uint32
HeaderSize uint16
Version uint16
Format uint32
Flags uint32
Blocks uint32
OffsetData uint32
LengthData uint32
OffsetComment uint32
LengthComment uint32
OffsetCreator uint32
LengthCreator uint32
}
func parse2mg(bd *blockDisk) error {
fileInfo, err := bd.file.Stat()
if err != nil {
return err
}
var header file2mgHeader
minHeaderSize := binary.Size(&header)
if fileInfo.Size() < int64(minHeaderSize) {
return errors.New("Invalid 2MG file")
}
err = readHeader(bd.file, &header)
if err != nil {
return err
}
bd.blocks = header.Blocks
bd.dataOffset = header.OffsetData
if fileInfo.Size() < int64(bd.dataOffset+bd.blocks*proDosBlockSize) {
return errors.New("The 2MG file is too small")
}
return nil
}
func readHeader(buf io.Reader, header *file2mgHeader) error {
err := binary.Read(buf, binary.LittleEndian, header)
if err != nil {
return err
}
if header.Preamble != file2mgPreamble {
return errors.New("The 2mg file must start with '2IMG'")
}
if header.Format != file2mgFormatProdos {
return errors.New("Only prodos disks are supported")
}
if header.Version != file2mgVersion {
return errors.New("Version of 2MG image not supported")
}
return nil
}

View File

@ -1,135 +0,0 @@
package apple2
import (
"encoding/binary"
"errors"
"io"
"os"
)
/*
Valid for ProDos hard disks in 2MG format.
See:
https://apple2.org.za/gswv/a2zine/Docs/DiskImage_2MG_Info.txt
*/
const (
proDosBlockSize = uint32(512)
hardDisk2mgPreamble = uint32(1196247346) // "2IMG"
hardDisk2mgFormatProdos = 1
hardDisk2mgVersion = 1
)
type hardDisk struct {
file *os.File
readOnly bool
header hardDisk2mgHeader
}
type hardDisk2mgHeader struct {
Preamble uint32
Creator uint32
HeaderSize uint16
Version uint16
Format uint32
Flags uint32
Blocks uint32
OffsetData uint32
LengthData uint32
OffsetComment uint32
LengthComment uint32
OffsetCreator uint32
LengthCreator uint32
}
func (hd *hardDisk) read(block uint32) ([]uint8, error) {
if block >= hd.header.Blocks {
return nil, errors.New("disk block number is too big")
}
buf := make([]uint8, proDosBlockSize)
offset := int64(hd.header.OffsetData + block*proDosBlockSize)
_, err := hd.file.ReadAt(buf, offset)
if err != nil {
return nil, err
}
return buf, nil
}
func (hd *hardDisk) write(block uint32, data []uint8) error {
if hd.readOnly {
return errors.New("can't write in a readonly disk")
}
if block >= hd.header.Blocks {
return errors.New("disk block number is too big")
}
offset := int64(hd.header.OffsetData + block*proDosBlockSize)
_, err := hd.file.WriteAt(data, offset)
if err != nil {
return err
}
return nil
}
func openHardDisk2mg(filename string) (*hardDisk, error) {
var hd hardDisk
hd.readOnly = false
file, err := os.OpenFile(filename, os.O_RDWR, 0)
if os.IsPermission(err) {
// Retry in read-only mode
hd.readOnly = true
file, err = os.OpenFile(filename, os.O_RDONLY, 0)
}
if err != nil {
return nil, err
}
hd.file = file
fileInfo, err := hd.file.Stat()
if err != nil {
return nil, err
}
minHeaderSize := binary.Size(&hd.header)
if fileInfo.Size() < int64(minHeaderSize) {
return nil, errors.New("Invalid 2MG file")
}
err = readHeader(hd.file, &hd.header)
if err != nil {
return nil, err
}
if fileInfo.Size() < int64(hd.header.OffsetData+hd.header.Blocks*proDosBlockSize) {
return nil, errors.New("Thr 2MG file is too small")
}
return &hd, nil
}
func readHeader(buf io.Reader, header *hardDisk2mgHeader) error {
err := binary.Read(buf, binary.LittleEndian, header)
if err != nil {
return err
}
if header.Preamble != hardDisk2mgPreamble {
return errors.New("2mg file must start with '2IMG'")
}
if header.Format != hardDisk2mgFormatProdos {
return errors.New("Only prodos hard disks are supported")
}
if header.Version != hardDisk2mgVersion {
return errors.New("Version of 2MG image not supported")
}
return nil
}