From c2b620ec01dc415f87a14beb1b03955dfc6ed162 Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Tue, 11 Aug 2020 23:53:05 +0200 Subject: [PATCH] Support 3.5 disks and HDV format. Generalize the hard disk support to be used for any SmartPort device. --- README.md | 15 +++--- apple2Setup.go | 7 +-- apple2main.go | 22 ++++++-- blockDisk.go | 98 +++++++++++++++++++++++++++++++++++ cardHardDisk.go | 8 +-- file2mg.go | 84 ++++++++++++++++++++++++++++++ hardDisk.go | 135 ------------------------------------------------ 7 files changed, 218 insertions(+), 151 deletions(-) create mode 100644 blockDisk.go create mode 100644 file2mg.go delete mode 100644 hardDisk.go diff --git a/README.md b/README.md index cdadd51..3971cb9 100644 --- a/README.md +++ b/README.md @@ -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 "/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 "/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 diff --git a/apple2Setup.go b/apple2Setup.go index e3c3089..badd046 100644 --- a/apple2Setup.go +++ b/apple2Setup.go @@ -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) } diff --git a/apple2main.go b/apple2main.go index d28e5ca..fcf06db 100644 --- a/apple2main.go +++ b/apple2main.go @@ -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) } diff --git a/blockDisk.go b/blockDisk.go new file mode 100644 index 0000000..9a7b22a --- /dev/null +++ b/blockDisk.go @@ -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 +} diff --git a/cardHardDisk.go b/cardHardDisk.go index 100b767..a10379d 100644 --- a/cardHardDisk.go +++ b/cardHardDisk.go @@ -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 } diff --git a/file2mg.go b/file2mg.go new file mode 100644 index 0000000..5eadd5b --- /dev/null +++ b/file2mg.go @@ -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 +} diff --git a/hardDisk.go b/hardDisk.go deleted file mode 100644 index 2f4b9d2..0000000 --- a/hardDisk.go +++ /dev/null @@ -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 -}