mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-12-22 09:30:19 +00:00
Support 3.5 disks and HDV format. Generalize the hard disk support to be used for any SmartPort device.
This commit is contained in:
parent
caa312c12a
commit
c2b620ec01
15
README.md
15
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 "<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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
98
blockDisk.go
Normal 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
|
||||
}
|
@ -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
84
file2mg.go
Normal 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
|
||||
}
|
135
hardDisk.go
135
hardDisk.go
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user