2019-10-02 21:39:39 +00:00
|
|
|
package apple2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
2019-10-05 13:30:13 +00:00
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"os"
|
2019-10-02 21:39:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
2019-10-05 13:30:13 +00:00
|
|
|
Valid for ProDos hard disks in 2MG format.
|
2019-10-02 21:39:39 +00:00
|
|
|
|
|
|
|
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 {
|
2019-10-05 13:30:13 +00:00
|
|
|
file *os.File
|
|
|
|
readOnly bool
|
|
|
|
header hardDisk2mgHeader
|
2019-10-02 21:39:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-10-05 13:30:13 +00:00
|
|
|
func (hd *hardDisk) read(block uint32) ([]uint8, error) {
|
2019-10-02 21:39:39 +00:00
|
|
|
if block >= hd.header.Blocks {
|
2019-10-05 13:30:13 +00:00
|
|
|
return nil, errors.New("disk block number is too big")
|
2019-10-02 21:39:39 +00:00
|
|
|
}
|
2019-10-05 13:30:13 +00:00
|
|
|
|
|
|
|
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
|
2019-10-02 21:39:39 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 13:30:13 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-10-05 23:26:00 +00:00
|
|
|
func openHardDisk2mg(filename string) (*hardDisk, error) {
|
2019-10-02 21:39:39 +00:00
|
|
|
var hd hardDisk
|
|
|
|
|
2019-10-05 13:30:13 +00:00
|
|
|
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 {
|
2019-10-05 23:26:00 +00:00
|
|
|
return nil, err
|
2019-10-05 13:30:13 +00:00
|
|
|
}
|
|
|
|
hd.file = file
|
|
|
|
|
|
|
|
fileInfo, err := hd.file.Stat()
|
|
|
|
if err != nil {
|
2019-10-05 23:26:00 +00:00
|
|
|
return nil, err
|
2019-10-05 13:30:13 +00:00
|
|
|
}
|
2019-10-02 21:39:39 +00:00
|
|
|
|
2019-10-05 13:30:13 +00:00
|
|
|
minHeaderSize := binary.Size(&hd.header)
|
|
|
|
if fileInfo.Size() < int64(minHeaderSize) {
|
2019-10-05 23:26:00 +00:00
|
|
|
return nil, errors.New("Invalid 2MG file")
|
2019-10-05 13:30:13 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 23:26:00 +00:00
|
|
|
err = readHeader(hd.file, &hd.header)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-10-05 13:30:13 +00:00
|
|
|
|
|
|
|
if fileInfo.Size() < int64(hd.header.OffsetData+hd.header.Blocks*proDosBlockSize) {
|
2019-10-05 23:26:00 +00:00
|
|
|
return nil, errors.New("Thr 2MG file is too small")
|
2019-10-02 21:39:39 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 23:26:00 +00:00
|
|
|
return &hd, nil
|
2019-10-05 13:30:13 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 23:26:00 +00:00
|
|
|
func readHeader(buf io.Reader, header *hardDisk2mgHeader) error {
|
2019-10-05 13:30:13 +00:00
|
|
|
err := binary.Read(buf, binary.LittleEndian, header)
|
2019-10-02 21:39:39 +00:00
|
|
|
if err != nil {
|
2019-10-05 23:26:00 +00:00
|
|
|
return err
|
2019-10-02 21:39:39 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 13:30:13 +00:00
|
|
|
if header.Preamble != hardDisk2mgPreamble {
|
2019-10-05 23:26:00 +00:00
|
|
|
return errors.New("2mg file must start with '2IMG'")
|
2019-10-02 21:39:39 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 13:30:13 +00:00
|
|
|
if header.Format != hardDisk2mgFormatProdos {
|
2019-10-05 23:26:00 +00:00
|
|
|
return errors.New("Only prodos hard disks are supported")
|
2019-10-02 21:39:39 +00:00
|
|
|
}
|
|
|
|
|
2019-10-05 13:30:13 +00:00
|
|
|
if header.Version != hardDisk2mgVersion {
|
2019-10-05 23:26:00 +00:00
|
|
|
return errors.New("Version of 2MG image not supported")
|
2019-10-02 21:39:39 +00:00
|
|
|
}
|
2019-10-05 23:26:00 +00:00
|
|
|
|
|
|
|
return nil
|
2019-10-02 21:39:39 +00:00
|
|
|
}
|