izapple2/hardDisk.go
2019-10-06 01:26:00 +02:00

136 lines
2.7 KiB
Go

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
}