mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-12-22 09:30:19 +00:00
Support WOZ files read only
This commit is contained in:
parent
5523c0429a
commit
9e2fd7e824
@ -105,7 +105,9 @@ func (a *Apple2) AddDisk2(slot int, diskRomFile string, diskImage string) error
|
|||||||
a.insertCard(&c, slot)
|
a.insertCard(&c, slot)
|
||||||
|
|
||||||
if diskImage != "" {
|
if diskImage != "" {
|
||||||
diskette, err := loadDisquette(diskImage)
|
//diskette, err := loadDisquette(diskImage)
|
||||||
|
//diskette, err := loadDisquetteTimed(diskImage)
|
||||||
|
diskette, err := loadDisquetteWoz(diskImage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -104,11 +104,11 @@ func MainApple() *Apple2 {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *wozImage != "" {
|
if *wozImage != "" {
|
||||||
d, err := loadDisquetteWoz(*wozImage)
|
f, err := loadFileWoz(*wozImage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
d.dump()
|
f.dump()
|
||||||
panic("Woz loaded")
|
panic("Woz loaded")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -168,6 +168,10 @@ func (c *cardDisk2) processQ6Q7(in uint8) {
|
|||||||
c.dataLatch = in
|
c.dataLatch = in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.dataLatch >= 0x80 {
|
||||||
|
//fmt.Printf("Datalacth: 0x%.2x in cycle %v\n", c.dataLatch, c.a.cpu.GetCycles())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1,67 +1,25 @@
|
|||||||
package apple2
|
package apple2
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
See:
|
See:
|
||||||
"Beneath Apple DOS" https://fabiensanglard.net/fd_proxy/prince_of_persia/Beneath%20Apple%20DOS.pdf
|
"Beneath Apple DOS" https://fabiensanglard.net/fd_proxy/prince_of_persia/Beneath%20Apple%20DOS.pdf
|
||||||
https://github.com/TomHarte/CLK/wiki/Apple-GCR-disk-encoding
|
https://github.com/TomHarte/CLK/wiki/Apple-GCR-disk-encoding
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const (
|
|
||||||
numberOfTracks = 35
|
|
||||||
numberOfSectors = 16
|
|
||||||
bytesPerSector = 256
|
|
||||||
bytesPerTrack = numberOfSectors * bytesPerSector
|
|
||||||
nibBytesPerTrack = 6656
|
|
||||||
nibImageSize = numberOfTracks * nibBytesPerTrack
|
|
||||||
dskImageSize = numberOfTracks * numberOfSectors * bytesPerSector
|
|
||||||
defaultVolumeTag = 254
|
|
||||||
cyclesPerBit = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
type diskette16sector struct {
|
type diskette16sector struct {
|
||||||
track [numberOfTracks][]byte
|
nib *fileNib
|
||||||
timeBased bool
|
position int
|
||||||
// Not time based implementation
|
|
||||||
position int // For not time based implemenation
|
|
||||||
// Time based implementation, expermiental
|
|
||||||
cycleOn uint64 // Cycle when the disk was last turned on
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *diskette16sector) powerOn(cycle uint64) {
|
func (d *diskette16sector) powerOn(cycle uint64) {
|
||||||
d.cycleOn = cycle
|
// Not used
|
||||||
}
|
}
|
||||||
func (d *diskette16sector) powerOff(_ uint64) {
|
func (d *diskette16sector) powerOff(_ uint64) {
|
||||||
// Not needed
|
// Not used
|
||||||
}
|
|
||||||
|
|
||||||
func (d *diskette16sector) getBitPositionInTrack(cycle uint64) int {
|
|
||||||
// Calculate how long the disk has been spinning. We move one bit every 4 cycles.
|
|
||||||
// In this implementation we don't take into account hot long the motor takes to be at full speed.
|
|
||||||
cycles := cycle - d.cycleOn
|
|
||||||
position := cycles / cyclesPerBit
|
|
||||||
return int(position % (8 * nibBytesPerTrack)) // Ignore full turns
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *diskette16sector) read(quarterTrack int, cycle uint64) uint8 {
|
func (d *diskette16sector) read(quarterTrack int, cycle uint64) uint8 {
|
||||||
track := d.track[quarterTrack/stepsPerTrack]
|
track := d.nib.track[quarterTrack/stepsPerTrack]
|
||||||
if d.timeBased {
|
|
||||||
bitPosition := d.getBitPositionInTrack(cycle)
|
|
||||||
bytePosition := bitPosition / 8
|
|
||||||
shift := uint(bitPosition % 8)
|
|
||||||
if shift == 1 {
|
|
||||||
// We continue having the previous data for a little longer
|
|
||||||
shift = 0
|
|
||||||
}
|
|
||||||
value := track[bytePosition]
|
|
||||||
value >>= shift
|
|
||||||
//fmt.Printf("%v, %v, %v, %x\n", bitPosition, shift, bytePosition, uint8(data))
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
value := track[d.position]
|
value := track[d.position]
|
||||||
d.position = (d.position + 1) % nibBytesPerTrack
|
d.position = (d.position + 1) % nibBytesPerTrack
|
||||||
//fmt.Printf("%v, %v, %v, %x\n", 0, 0, d.position, uint8(value))
|
//fmt.Printf("%v, %v, %v, %x\n", 0, 0, d.position, uint8(value))
|
||||||
@ -69,155 +27,18 @@ func (d *diskette16sector) read(quarterTrack int, cycle uint64) uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *diskette16sector) write(quarterTrack int, value uint8, _ uint64) {
|
func (d *diskette16sector) write(quarterTrack int, value uint8, _ uint64) {
|
||||||
if d.timeBased {
|
|
||||||
panic("Write not implmented on time based disk implementation")
|
|
||||||
}
|
|
||||||
track := quarterTrack / stepsPerTrack
|
track := quarterTrack / stepsPerTrack
|
||||||
d.track[track][d.position] = value
|
d.nib.track[track][d.position] = value
|
||||||
d.position = (d.position + 1) % nibBytesPerTrack
|
d.position = (d.position + 1) % nibBytesPerTrack
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDisquette(filename string) (*diskette16sector, error) {
|
func loadDisquette(filename string) (*diskette16sectorTimed, error) {
|
||||||
var d diskette16sector
|
var d diskette16sectorTimed
|
||||||
|
|
||||||
// Experimental
|
f, err := loadNibOrDsk(filename)
|
||||||
d.timeBased = true
|
|
||||||
|
|
||||||
data, err := loadResource(filename)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
size := len(data)
|
d.nib = f
|
||||||
|
|
||||||
if size == nibImageSize {
|
|
||||||
// Load file already in nib format
|
|
||||||
for i := 0; i < numberOfTracks; i++ {
|
|
||||||
d.track[i] = data[nibBytesPerTrack*i : nibBytesPerTrack*(i+1)]
|
|
||||||
}
|
|
||||||
} else if size == dskImageSize {
|
|
||||||
// Convert to nib
|
|
||||||
for i := 0; i < numberOfTracks; i++ {
|
|
||||||
trackData := data[i*bytesPerTrack : (i+1)*bytesPerTrack]
|
|
||||||
d.track[i] = nibEncodeTrack(trackData, defaultVolumeTag, byte(i))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("Invalid disk size")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &d, nil
|
return &d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *diskette16sector) saveNib(filename string) error {
|
|
||||||
f, err := os.Create(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
for _, v := range d.track {
|
|
||||||
_, err := f.Write(v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var dos33SectorsLogicOrder = [16]int{
|
|
||||||
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4,
|
|
||||||
0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF,
|
|
||||||
}
|
|
||||||
|
|
||||||
var sixAndTwoTranslateTable = [0x40]byte{
|
|
||||||
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
|
|
||||||
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
|
|
||||||
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
|
|
||||||
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
|
|
||||||
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
|
|
||||||
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
|
|
||||||
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
|
|
||||||
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
gap1Len = 48
|
|
||||||
gap2Len = 5
|
|
||||||
primaryBufferSize = bytesPerSector
|
|
||||||
secondaryBufferSize = bytesPerSector/3 + 1
|
|
||||||
)
|
|
||||||
|
|
||||||
func oddEvenEncodeByte(b byte) []byte {
|
|
||||||
/*
|
|
||||||
A byte is encoded in two bytes to make sure the bytes start with 1 and
|
|
||||||
does not have two consecutive zeros.
|
|
||||||
Data byte: D7-D6-D5-D4-D3-D2-D1-D0
|
|
||||||
resutl[0]: 1-D7- 1-D5- 1-D3-1 -D1
|
|
||||||
resutl[1]: 1-D6- 1-D4- 1-D2-1 -D0
|
|
||||||
*/
|
|
||||||
e := make([]byte, 2)
|
|
||||||
e[0] = ((b >> 1) & 0x55) | 0xaa
|
|
||||||
e[1] = (b & 0x55) | 0xaa
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func nibEncodeTrack(data []byte, volume byte, track byte) []byte {
|
|
||||||
b := make([]byte, 0, nibBytesPerTrack) // Buffer slice with enough capacity
|
|
||||||
// Initialize gaps to be copied for each sector
|
|
||||||
gap1 := make([]byte, gap1Len)
|
|
||||||
for i := range gap1 {
|
|
||||||
gap1[i] = 0xff
|
|
||||||
}
|
|
||||||
gap2 := make([]byte, gap2Len)
|
|
||||||
for i := range gap2 {
|
|
||||||
gap2[i] = 0xff
|
|
||||||
}
|
|
||||||
for physicalSector := byte(0); physicalSector < numberOfSectors; physicalSector++ {
|
|
||||||
/* On the DSK file the sectors are in DOS3.3 logical order
|
|
||||||
but on the physical encoded track as well as in the nib
|
|
||||||
files they are in phisical order.
|
|
||||||
*/
|
|
||||||
logicalSector := dos33SectorsLogicOrder[physicalSector]
|
|
||||||
sectorData := data[logicalSector*bytesPerSector : (logicalSector+1)*bytesPerSector]
|
|
||||||
|
|
||||||
// 6and2 prenibbilizing.
|
|
||||||
primaryBuffer := make([]byte, primaryBufferSize)
|
|
||||||
secondaryBuffer := make([]byte, secondaryBufferSize)
|
|
||||||
for i, v := range sectorData {
|
|
||||||
// Primary buffer is easy: the 6 MSB
|
|
||||||
primaryBuffer[i] = v >> 2
|
|
||||||
// Secondary buffer: the 2 LSB reversed, shifted and in their place
|
|
||||||
shift := uint((i / secondaryBufferSize) * 2)
|
|
||||||
bit0 := ((v & 0x01) << 1) << shift
|
|
||||||
bit1 := ((v & 0x02) >> 1) << shift
|
|
||||||
position := i % secondaryBufferSize
|
|
||||||
secondaryBuffer[position] |= bit0 | bit1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render sector
|
|
||||||
// Address field
|
|
||||||
b = append(b, gap1...)
|
|
||||||
b = append(b, 0xd5, 0xaa, 0x96) // Address prolog
|
|
||||||
b = append(b, oddEvenEncodeByte(volume)...) // 4-4 encoded volume
|
|
||||||
b = append(b, oddEvenEncodeByte(track)...) // 4-4 encoded track
|
|
||||||
b = append(b, oddEvenEncodeByte(physicalSector)...) // 4-4 encoded sector
|
|
||||||
b = append(b, oddEvenEncodeByte(volume^track^physicalSector)...) // Checksum
|
|
||||||
b = append(b, 0xde, 0xaa, 0xeb) // Epilog
|
|
||||||
// Data field
|
|
||||||
b = append(b, gap2...)
|
|
||||||
b = append(b, 0xd5, 0xaa, 0xad) // Data prolog
|
|
||||||
prevV := byte(0)
|
|
||||||
for _, v := range secondaryBuffer {
|
|
||||||
b = append(b, sixAndTwoTranslateTable[v^prevV])
|
|
||||||
prevV = v
|
|
||||||
}
|
|
||||||
for _, v := range primaryBuffer {
|
|
||||||
b = append(b, sixAndTwoTranslateTable[v^prevV])
|
|
||||||
prevV = v
|
|
||||||
}
|
|
||||||
b = append(b, sixAndTwoTranslateTable[prevV]) // Checksum
|
|
||||||
b = append(b, 0xde, 0xaa, 0xeb) // Data epilog
|
|
||||||
}
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
52
diskette16sectorTimed.go
Normal file
52
diskette16sectorTimed.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package apple2
|
||||||
|
|
||||||
|
type diskette16sectorTimed struct {
|
||||||
|
nib *fileNib
|
||||||
|
cycleOn uint64 // Cycle when the disk was last turned on
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *diskette16sectorTimed) powerOn(cycle uint64) {
|
||||||
|
d.cycleOn = cycle
|
||||||
|
}
|
||||||
|
func (d *diskette16sectorTimed) powerOff(_ uint64) {
|
||||||
|
// Not needed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *diskette16sectorTimed) getBitPositionInTrack(cycle uint64) int {
|
||||||
|
// Calculate how long the disk has been spinning. We move one bit every 4 cycles.
|
||||||
|
// In this implementation we don't take into account how long the motor takes to be at full speed.
|
||||||
|
cycles := cycle - d.cycleOn
|
||||||
|
position := cycles / cyclesPerBit
|
||||||
|
return int(position % (8 * nibBytesPerTrack)) // Ignore full turns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *diskette16sectorTimed) read(quarterTrack int, cycle uint64) uint8 {
|
||||||
|
track := d.nib.track[quarterTrack/stepsPerTrack]
|
||||||
|
bitPosition := d.getBitPositionInTrack(cycle)
|
||||||
|
bytePosition := bitPosition / 8
|
||||||
|
shift := uint(bitPosition % 8)
|
||||||
|
if shift == 1 {
|
||||||
|
// We continue having the unshifted byte for a little longer (4 cycles)
|
||||||
|
shift = 0
|
||||||
|
}
|
||||||
|
value := track[bytePosition]
|
||||||
|
value >>= shift
|
||||||
|
//fmt.Printf("%v, %v, %v, %x\n", bitPosition, shift, bytePosition, uint8(value))
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *diskette16sectorTimed) write(quarterTrack int, value uint8, _ uint64) {
|
||||||
|
panic("Write not implemented on time based disk implementation")
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDisquetteTimed(filename string) (*diskette16sectorTimed, error) {
|
||||||
|
var d diskette16sectorTimed
|
||||||
|
|
||||||
|
f, err := loadNibOrDsk(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d.nib = f
|
||||||
|
|
||||||
|
return &d, nil
|
||||||
|
}
|
248
disketteWoz.go
248
disketteWoz.go
@ -1,218 +1,74 @@
|
|||||||
package apple2
|
package apple2
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
See:
|
|
||||||
https://applesaucefdc.com/woz/
|
|
||||||
*/
|
|
||||||
|
|
||||||
type disketteWoz struct {
|
type disketteWoz struct {
|
||||||
version int
|
data *fileWoz
|
||||||
info woz2Info
|
cycleOn uint64 // Cycle when the disk was last turned on
|
||||||
trackMap []uint8
|
turning bool
|
||||||
tracks [wozMaxTrack]disketteTrackWoz
|
|
||||||
meta map[string]string
|
latch uint8
|
||||||
|
position uint32
|
||||||
|
cycle uint64
|
||||||
|
trackSize uint32
|
||||||
|
|
||||||
|
visibleLatch uint8
|
||||||
|
visibleLatchCountDown int8 // The visible latch stores a valid latch reading for 2 bit timings
|
||||||
}
|
}
|
||||||
|
|
||||||
type disketteTrackWoz struct {
|
func (d *disketteWoz) powerOn(cycle uint64) {
|
||||||
bitCount uint32
|
d.turning = true
|
||||||
data []uint8
|
d.cycleOn = cycle
|
||||||
}
|
}
|
||||||
|
|
||||||
// Structures from the WOZ Disk Image Reference for deserialization
|
func (d *disketteWoz) powerOff(_ uint64) {
|
||||||
type wozChunkHeader struct {
|
d.turning = false
|
||||||
ID [4]byte
|
|
||||||
Size uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type woz1Info struct {
|
func (d *disketteWoz) read(quarterTrack int, cycle uint64) uint8 {
|
||||||
Version uint8 // 2
|
// Count cycles to know how many bits have been read
|
||||||
DiskType uint8
|
cycles := cycle - d.cycle
|
||||||
WriteProtected uint8
|
deltaBits := cycles / cyclesPerBit // TODO: Use Woz optimal bit timing
|
||||||
Synchronized uint8
|
|
||||||
Cleaned uint8
|
// Process bits from woz
|
||||||
Creator [32]byte
|
// TODO: avoid processing too many bits if delta is big
|
||||||
|
for i := uint64(0); i < deltaBits; i++ {
|
||||||
|
d.position++
|
||||||
|
bit := d.data.getBit(d.position, quarterTrack)
|
||||||
|
d.latch = (d.latch << 1) + bit
|
||||||
|
if d.latch >= 0x80 {
|
||||||
|
// Valid byte, store value a bit longer and clear the internal latch
|
||||||
|
//fmt.Printf("Valid 0x%.2x\n", d.latch)
|
||||||
|
d.visibleLatch = d.latch
|
||||||
|
d.visibleLatchCountDown = 1
|
||||||
|
d.latch = 0
|
||||||
|
} else if d.visibleLatchCountDown > 0 {
|
||||||
|
// Continue showing the valid byte
|
||||||
|
d.visibleLatchCountDown--
|
||||||
|
} else {
|
||||||
|
// The valid byte is lost, show the internal latch
|
||||||
|
d.visibleLatch = d.latch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Printf("Visible: 0x%.2x, latch: 0x%.2x, bits: %v, cycles: %v\n", d.visibleLatch, d.latch, deltaBits, cycle-d.cycle)
|
||||||
|
|
||||||
|
// Update the internal last cycle without losing the remainder not processed
|
||||||
|
d.cycle += deltaBits * cyclesPerBit
|
||||||
|
|
||||||
|
return d.visibleLatch
|
||||||
}
|
}
|
||||||
|
|
||||||
type woz2Info struct {
|
func (d *disketteWoz) write(quarterTrack int, value uint8, _ uint64) {
|
||||||
woz1Info
|
panic("Write not implemented on woz disk implementation")
|
||||||
DiskSides uint8
|
|
||||||
BootSectorFormat uint8
|
|
||||||
OptimalBitTiming uint8
|
|
||||||
CompatibleHardware uint16
|
|
||||||
RequiredRAM uint16
|
|
||||||
LargestTrack uint16
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type woz1TrackFooter struct {
|
|
||||||
BytesUsed uint16
|
|
||||||
BitCount uint16
|
|
||||||
SplicePoint uint16
|
|
||||||
SpliceNibble uint8
|
|
||||||
SpliceBitCount uint8
|
|
||||||
Reserved uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type woz2TrackHeader struct {
|
|
||||||
StartingBlock uint16
|
|
||||||
BlockCount uint16
|
|
||||||
BitCount uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
wozFirstChunkPos = 12
|
|
||||||
wozChunkHeaderLen = 8
|
|
||||||
wozMaxTrack = 160
|
|
||||||
woz1TrackDataSize = 6656
|
|
||||||
woz1TrackFooterOffset = 6646
|
|
||||||
woz2TrackBlockSize = 512
|
|
||||||
woz2FirstTrackBlock = 3 // The bits on the TRKS block start on 3*512
|
|
||||||
woz2TrackBitsOffset = 1280
|
|
||||||
)
|
|
||||||
|
|
||||||
var headerWoz1 = []uint8{0x57, 0x4f, 0x5A, 0x31, 0xFF, 0x0A, 0x0D, 0x0A}
|
|
||||||
var headerWoz2 = []uint8{0x57, 0x4f, 0x5A, 0x32, 0xFF, 0x0A, 0x0D, 0x0A}
|
|
||||||
|
|
||||||
func loadDisquetteWoz(filename string) (*disketteWoz, error) {
|
func loadDisquetteWoz(filename string) (*disketteWoz, error) {
|
||||||
var d disketteWoz
|
var d disketteWoz
|
||||||
|
|
||||||
data, err := loadResource(filename)
|
f, err := loadFileWoz(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
d.data = f
|
||||||
// Verify header. Note, the CRC is not verified
|
|
||||||
header := data[:len(headerWoz2)]
|
|
||||||
if bytes.Equal(headerWoz1, header) {
|
|
||||||
d.version = 1
|
|
||||||
} else if bytes.Equal(headerWoz2, header) {
|
|
||||||
d.version = 2
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("Invalid WOZ header")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the chunks
|
|
||||||
i := wozFirstChunkPos
|
|
||||||
var chunkHeader wozChunkHeader
|
|
||||||
chunks := make(map[string][]uint8)
|
|
||||||
for i+wozChunkHeaderLen < len(data) {
|
|
||||||
binary.Read(bytes.NewReader(data[i:]), binary.LittleEndian, &chunkHeader)
|
|
||||||
|
|
||||||
i += wozChunkHeaderLen
|
|
||||||
iNext := i + int(chunkHeader.Size)
|
|
||||||
if i == iNext || iNext > len(data) {
|
|
||||||
return nil, errors.New("Invalid chunk in WOZ file")
|
|
||||||
}
|
|
||||||
|
|
||||||
id := string(chunkHeader.ID[:])
|
|
||||||
chunks[id] = data[i:iNext]
|
|
||||||
i = iNext
|
|
||||||
|
|
||||||
//fmt.Printf("Chunk %v, size %v - %v\n", id, chunkHeader.Size, len(chunks[id]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the INFO chunk
|
|
||||||
infoData, ok := chunks["INFO"]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("Chunk INFO missing from WOZ file")
|
|
||||||
}
|
|
||||||
switch d.version {
|
|
||||||
case 1:
|
|
||||||
binary.Read(bytes.NewReader(infoData), binary.LittleEndian, &d.info.woz1Info)
|
|
||||||
case 2:
|
|
||||||
binary.Read(bytes.NewReader(infoData), binary.LittleEndian, &d.info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the optional META chunk
|
|
||||||
metaData, ok := chunks["META"]
|
|
||||||
if ok {
|
|
||||||
d.meta = make(map[string]string)
|
|
||||||
text := string(metaData)
|
|
||||||
entries := strings.Split(text, "\n")
|
|
||||||
for _, entry := range entries {
|
|
||||||
parts := strings.Split(entry, "\t")
|
|
||||||
if len(parts) >= 2 {
|
|
||||||
d.meta[parts[0]] = parts[1]
|
|
||||||
//fmt.Printf("*** %v: %v\n", parts[0], parts[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the TMAP chunk
|
|
||||||
trackMap, ok := chunks["TMAP"]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("Chunk INFO missing from WOZ file")
|
|
||||||
}
|
|
||||||
d.trackMap = trackMap
|
|
||||||
|
|
||||||
// Read the TRKS chunk
|
|
||||||
tracksData, ok := chunks["TRKS"]
|
|
||||||
if d.version == 1 {
|
|
||||||
i := 0
|
|
||||||
track := 0
|
|
||||||
for i+woz1TrackDataSize <= len(tracksData) {
|
|
||||||
var trackFooter woz1TrackFooter
|
|
||||||
binary.Read(bytes.NewReader(tracksData[i+woz1TrackFooterOffset:]), binary.LittleEndian, &trackFooter)
|
|
||||||
d.tracks[track].bitCount = uint32(trackFooter.BitCount)
|
|
||||||
d.tracks[track].data = tracksData[i : i+int(trackFooter.BytesUsed)]
|
|
||||||
i += woz1TrackDataSize
|
|
||||||
track++
|
|
||||||
}
|
|
||||||
} else if d.version == 2 {
|
|
||||||
reader := bytes.NewReader(tracksData)
|
|
||||||
for i := 0; i < wozMaxTrack; i++ {
|
|
||||||
var trackHeader woz2TrackHeader
|
|
||||||
binary.Read(reader, binary.LittleEndian, &trackHeader)
|
|
||||||
if trackHeader.BitCount != 0 {
|
|
||||||
d.tracks[i].bitCount = trackHeader.BitCount
|
|
||||||
|
|
||||||
dataPos := woz2TrackBlockSize*(int(trackHeader.StartingBlock)-woz2FirstTrackBlock) + woz2TrackBitsOffset
|
|
||||||
dataSize := woz2TrackBlockSize * int(trackHeader.BlockCount)
|
|
||||||
//fmt.Printf("@%v %v:%v (%v) of %v\n", trackHeader.StartingBlock, dataPos, dataPos+dataSize, dataSize, len(tracksData))
|
|
||||||
d.tracks[i].data = tracksData[dataPos : dataPos+dataSize]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("Woz version not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &d, nil
|
return &d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *disketteWoz) dump() {
|
|
||||||
fmt.Printf("Woz image:\n")
|
|
||||||
fmt.Printf(" Version: %v\n", d.info.Version)
|
|
||||||
fmt.Printf(" Disk type: %v\n", d.info.DiskType)
|
|
||||||
fmt.Printf(" Write protected: %v\n", d.info.WriteProtected)
|
|
||||||
fmt.Printf(" Synchronized: %v\n", d.info.Synchronized)
|
|
||||||
fmt.Printf(" Cleaned: %v\n", d.info.Cleaned)
|
|
||||||
fmt.Printf(" Creator: %v\n", string(d.info.Creator[:]))
|
|
||||||
if d.info.Version >= 2 {
|
|
||||||
fmt.Printf(" Disk sides: %v\n", d.info.DiskSides)
|
|
||||||
fmt.Printf(" Boot sector format: %v\n", d.info.BootSectorFormat)
|
|
||||||
fmt.Printf(" Optimal bit timing: %v ns\n", 125*int(d.info.OptimalBitTiming))
|
|
||||||
fmt.Printf(" Compatible hardware: 0x%x\n", d.info.CompatibleHardware)
|
|
||||||
fmt.Printf(" Required RAM: %vKB\n", d.info.RequiredRAM)
|
|
||||||
fmt.Printf(" Largest track: %v blocks\n", d.info.LargestTrack)
|
|
||||||
}
|
|
||||||
if d.meta != nil {
|
|
||||||
fmt.Printf(" Metadata:\n")
|
|
||||||
for k, v := range d.meta {
|
|
||||||
fmt.Printf(" %v: %v\n", k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Printf(" Tracks:\n")
|
|
||||||
for i, track := range d.trackMap {
|
|
||||||
if track != 255 {
|
|
||||||
fmt.Printf(" Track %.2f: %v (%v bits, %v bytes)\n",
|
|
||||||
0.25*float32(i), track, d.tracks[track].bitCount, len(d.tracks[track].data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
170
fileNib.go
Normal file
170
fileNib.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
package apple2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
See:
|
||||||
|
"Beneath Apple DOS" https://fabiensanglard.net/fd_proxy/prince_of_persia/Beneath%20Apple%20DOS.pdf
|
||||||
|
https://github.com/TomHarte/CLK/wiki/Apple-GCR-disk-encoding
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
numberOfTracks = 35
|
||||||
|
numberOfSectors = 16
|
||||||
|
bytesPerSector = 256
|
||||||
|
bytesPerTrack = numberOfSectors * bytesPerSector
|
||||||
|
nibBytesPerTrack = 6656
|
||||||
|
nibImageSize = numberOfTracks * nibBytesPerTrack
|
||||||
|
dskImageSize = numberOfTracks * numberOfSectors * bytesPerSector
|
||||||
|
defaultVolumeTag = 254
|
||||||
|
cyclesPerBit = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileNib struct {
|
||||||
|
track [numberOfTracks][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadNibOrDsk(filename string) (*fileNib, error) {
|
||||||
|
var f fileNib
|
||||||
|
|
||||||
|
data, err := loadResource(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
size := len(data)
|
||||||
|
|
||||||
|
if size == nibImageSize {
|
||||||
|
// Load file already in nib format
|
||||||
|
for i := 0; i < numberOfTracks; i++ {
|
||||||
|
f.track[i] = data[nibBytesPerTrack*i : nibBytesPerTrack*(i+1)]
|
||||||
|
}
|
||||||
|
} else if size == dskImageSize {
|
||||||
|
// Convert to nib
|
||||||
|
for i := 0; i < numberOfTracks; i++ {
|
||||||
|
trackData := data[i*bytesPerTrack : (i+1)*bytesPerTrack]
|
||||||
|
f.track[i] = nibEncodeTrack(trackData, defaultVolumeTag, byte(i))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Invalid disk size")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileNib) saveNib(filename string) error {
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
for _, v := range f.track {
|
||||||
|
_, err := file.Write(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var dos33SectorsLogicOrder = [16]int{
|
||||||
|
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4,
|
||||||
|
0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF,
|
||||||
|
}
|
||||||
|
|
||||||
|
var sixAndTwoTranslateTable = [0x40]byte{
|
||||||
|
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
|
||||||
|
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
|
||||||
|
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
|
||||||
|
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
|
||||||
|
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
|
||||||
|
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
|
||||||
|
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
|
||||||
|
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
gap1Len = 48
|
||||||
|
gap2Len = 5
|
||||||
|
primaryBufferSize = bytesPerSector
|
||||||
|
secondaryBufferSize = bytesPerSector/3 + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func oddEvenEncodeByte(b byte) []byte {
|
||||||
|
/*
|
||||||
|
A byte is encoded in two bytes to make sure the bytes start with 1 and
|
||||||
|
does not have two consecutive zeros.
|
||||||
|
Data byte: D7-D6-D5-D4-D3-D2-D1-D0
|
||||||
|
resutl[0]: 1-D7- 1-D5- 1-D3-1 -D1
|
||||||
|
resutl[1]: 1-D6- 1-D4- 1-D2-1 -D0
|
||||||
|
*/
|
||||||
|
e := make([]byte, 2)
|
||||||
|
e[0] = ((b >> 1) & 0x55) | 0xaa
|
||||||
|
e[1] = (b & 0x55) | 0xaa
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func nibEncodeTrack(data []byte, volume byte, track byte) []byte {
|
||||||
|
b := make([]byte, 0, nibBytesPerTrack) // Buffer slice with enough capacity
|
||||||
|
// Initialize gaps to be copied for each sector
|
||||||
|
gap1 := make([]byte, gap1Len)
|
||||||
|
for i := range gap1 {
|
||||||
|
gap1[i] = 0xff
|
||||||
|
}
|
||||||
|
gap2 := make([]byte, gap2Len)
|
||||||
|
for i := range gap2 {
|
||||||
|
gap2[i] = 0xff
|
||||||
|
}
|
||||||
|
for physicalSector := byte(0); physicalSector < numberOfSectors; physicalSector++ {
|
||||||
|
/* On the DSK file the sectors are in DOS3.3 logical order
|
||||||
|
but on the physical encoded track as well as in the nib
|
||||||
|
files they are in phisical order.
|
||||||
|
*/
|
||||||
|
logicalSector := dos33SectorsLogicOrder[physicalSector]
|
||||||
|
sectorData := data[logicalSector*bytesPerSector : (logicalSector+1)*bytesPerSector]
|
||||||
|
|
||||||
|
// 6and2 prenibbilizing.
|
||||||
|
primaryBuffer := make([]byte, primaryBufferSize)
|
||||||
|
secondaryBuffer := make([]byte, secondaryBufferSize)
|
||||||
|
for i, v := range sectorData {
|
||||||
|
// Primary buffer is easy: the 6 MSB
|
||||||
|
primaryBuffer[i] = v >> 2
|
||||||
|
// Secondary buffer: the 2 LSB reversed, shifted and in their place
|
||||||
|
shift := uint((i / secondaryBufferSize) * 2)
|
||||||
|
bit0 := ((v & 0x01) << 1) << shift
|
||||||
|
bit1 := ((v & 0x02) >> 1) << shift
|
||||||
|
position := i % secondaryBufferSize
|
||||||
|
secondaryBuffer[position] |= bit0 | bit1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render sector
|
||||||
|
// Address field
|
||||||
|
b = append(b, gap1...)
|
||||||
|
b = append(b, 0xd5, 0xaa, 0x96) // Address prolog
|
||||||
|
b = append(b, oddEvenEncodeByte(volume)...) // 4-4 encoded volume
|
||||||
|
b = append(b, oddEvenEncodeByte(track)...) // 4-4 encoded track
|
||||||
|
b = append(b, oddEvenEncodeByte(physicalSector)...) // 4-4 encoded sector
|
||||||
|
b = append(b, oddEvenEncodeByte(volume^track^physicalSector)...) // Checksum
|
||||||
|
b = append(b, 0xde, 0xaa, 0xeb) // Epilog
|
||||||
|
// Data field
|
||||||
|
b = append(b, gap2...)
|
||||||
|
b = append(b, 0xd5, 0xaa, 0xad) // Data prolog
|
||||||
|
prevV := byte(0)
|
||||||
|
for _, v := range secondaryBuffer {
|
||||||
|
b = append(b, sixAndTwoTranslateTable[v^prevV])
|
||||||
|
prevV = v
|
||||||
|
}
|
||||||
|
for _, v := range primaryBuffer {
|
||||||
|
b = append(b, sixAndTwoTranslateTable[v^prevV])
|
||||||
|
prevV = v
|
||||||
|
}
|
||||||
|
b = append(b, sixAndTwoTranslateTable[prevV]) // Checksum
|
||||||
|
b = append(b, 0xde, 0xaa, 0xeb) // Data epilog
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
246
fileWoz.go
Normal file
246
fileWoz.go
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
package apple2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
See:
|
||||||
|
https://applesaucefdc.com/woz/
|
||||||
|
*/
|
||||||
|
|
||||||
|
type fileWoz struct {
|
||||||
|
version int
|
||||||
|
info woz2Info
|
||||||
|
trackMap []uint8
|
||||||
|
tracks [wozMaxTrack]disketteTrackWoz
|
||||||
|
meta map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type disketteTrackWoz struct {
|
||||||
|
bitCount uint32
|
||||||
|
data []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structures from the WOZ Disk Image Reference for deserialization
|
||||||
|
type wozChunkHeader struct {
|
||||||
|
ID [4]byte
|
||||||
|
Size uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type woz1Info struct {
|
||||||
|
Version uint8
|
||||||
|
DiskType uint8
|
||||||
|
WriteProtected uint8
|
||||||
|
Synchronized uint8
|
||||||
|
Cleaned uint8
|
||||||
|
Creator [32]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type woz2Info struct {
|
||||||
|
woz1Info
|
||||||
|
DiskSides uint8
|
||||||
|
BootSectorFormat uint8
|
||||||
|
OptimalBitTiming uint8
|
||||||
|
CompatibleHardware uint16
|
||||||
|
RequiredRAM uint16
|
||||||
|
LargestTrack uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type woz1TrackFooter struct {
|
||||||
|
BytesUsed uint16
|
||||||
|
BitCount uint16
|
||||||
|
SplicePoint uint16
|
||||||
|
SpliceNibble uint8
|
||||||
|
SpliceBitCount uint8
|
||||||
|
Reserved uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type woz2TrackHeader struct {
|
||||||
|
StartingBlock uint16
|
||||||
|
BlockCount uint16
|
||||||
|
BitCount uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
wozFirstChunkPos = 12
|
||||||
|
wozChunkHeaderLen = 8
|
||||||
|
wozMaxTrack = 160
|
||||||
|
woz1TrackDataSize = 6656
|
||||||
|
woz1TrackFooterOffset = 6646
|
||||||
|
woz2TrackBlockSize = 512
|
||||||
|
woz2FirstTrackBlock = 3 // The bits on the TRKS block start on 3*512
|
||||||
|
woz2TrackBitsOffset = 1280
|
||||||
|
)
|
||||||
|
|
||||||
|
var headerWoz1 = []uint8{0x57, 0x4f, 0x5A, 0x31, 0xFF, 0x0A, 0x0D, 0x0A}
|
||||||
|
var headerWoz2 = []uint8{0x57, 0x4f, 0x5A, 0x32, 0xFF, 0x0A, 0x0D, 0x0A}
|
||||||
|
|
||||||
|
func (f *fileWoz) getBit(position uint32, quarterTrack int) uint8 {
|
||||||
|
trackWoz := f.tracks[f.trackMap[quarterTrack]]
|
||||||
|
position %= trackWoz.bitCount
|
||||||
|
return trackWoz.data[position/8] >> (7 - position%8) & 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFileWoz(filename string) (*fileWoz, error) {
|
||||||
|
var f fileWoz
|
||||||
|
|
||||||
|
data, err := loadResource(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify header. Note, the CRC is not verified
|
||||||
|
header := data[:len(headerWoz2)]
|
||||||
|
if bytes.Equal(headerWoz1, header) {
|
||||||
|
f.version = 1
|
||||||
|
} else if bytes.Equal(headerWoz2, header) {
|
||||||
|
f.version = 2
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Invalid WOZ header")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the chunks
|
||||||
|
i := wozFirstChunkPos
|
||||||
|
var chunkHeader wozChunkHeader
|
||||||
|
chunks := make(map[string][]uint8)
|
||||||
|
for i+wozChunkHeaderLen < len(data) {
|
||||||
|
binary.Read(bytes.NewReader(data[i:]), binary.LittleEndian, &chunkHeader)
|
||||||
|
|
||||||
|
i += wozChunkHeaderLen
|
||||||
|
iNext := i + int(chunkHeader.Size)
|
||||||
|
if i == iNext || iNext > len(data) {
|
||||||
|
return nil, errors.New("Invalid chunk in WOZ file")
|
||||||
|
}
|
||||||
|
|
||||||
|
id := string(chunkHeader.ID[:])
|
||||||
|
chunks[id] = data[i:iNext]
|
||||||
|
i = iNext
|
||||||
|
|
||||||
|
//fmt.Printf("Chunk %v, size %v - %v\n", id, chunkHeader.Size, len(chunks[id]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the INFO chunk
|
||||||
|
infoData, ok := chunks["INFO"]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("Chunk INFO missing from WOZ file")
|
||||||
|
}
|
||||||
|
switch f.version {
|
||||||
|
case 1:
|
||||||
|
binary.Read(bytes.NewReader(infoData), binary.LittleEndian, &f.info.woz1Info)
|
||||||
|
case 2:
|
||||||
|
binary.Read(bytes.NewReader(infoData), binary.LittleEndian, &f.info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the optional META chunk
|
||||||
|
metaData, ok := chunks["META"]
|
||||||
|
if ok {
|
||||||
|
f.meta = make(map[string]string)
|
||||||
|
text := string(metaData)
|
||||||
|
entries := strings.Split(text, "\n")
|
||||||
|
for _, entry := range entries {
|
||||||
|
parts := strings.Split(entry, "\t")
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
f.meta[parts[0]] = parts[1]
|
||||||
|
//fmt.Printf("*** %v: %v\n", parts[0], parts[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the TMAP chunk
|
||||||
|
trackMap, ok := chunks["TMAP"]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("Chunk TMAP missing from WOZ file")
|
||||||
|
}
|
||||||
|
f.trackMap = trackMap
|
||||||
|
|
||||||
|
// Read the TRKS chunk
|
||||||
|
tracksData, ok := chunks["TRKS"]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("Chunk TRKS missing from WOZ file")
|
||||||
|
}
|
||||||
|
if f.version == 1 {
|
||||||
|
i := 0
|
||||||
|
track := 0
|
||||||
|
for i+woz1TrackDataSize <= len(tracksData) {
|
||||||
|
var trackFooter woz1TrackFooter
|
||||||
|
binary.Read(bytes.NewReader(tracksData[i+woz1TrackFooterOffset:]), binary.LittleEndian, &trackFooter)
|
||||||
|
f.tracks[track].bitCount = uint32(trackFooter.BitCount)
|
||||||
|
f.tracks[track].data = tracksData[i : i+int(trackFooter.BytesUsed)]
|
||||||
|
i += woz1TrackDataSize
|
||||||
|
track++
|
||||||
|
}
|
||||||
|
} else if f.version == 2 {
|
||||||
|
reader := bytes.NewReader(tracksData)
|
||||||
|
for i := 0; i < wozMaxTrack; i++ {
|
||||||
|
var trackHeader woz2TrackHeader
|
||||||
|
binary.Read(reader, binary.LittleEndian, &trackHeader)
|
||||||
|
if trackHeader.BitCount != 0 {
|
||||||
|
f.tracks[i].bitCount = trackHeader.BitCount
|
||||||
|
|
||||||
|
dataPos := woz2TrackBlockSize*(int(trackHeader.StartingBlock)-woz2FirstTrackBlock) + woz2TrackBitsOffset
|
||||||
|
dataSize := woz2TrackBlockSize * int(trackHeader.BlockCount)
|
||||||
|
//fmt.Printf("@%v %v:%v (%v) of %v\n", trackHeader.StartingBlock, dataPos, dataPos+dataSize, dataSize, len(tracksData))
|
||||||
|
f.tracks[i].data = tracksData[dataPos : dataPos+dataSize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Woz version not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileWoz) dumpTrackAsNib(quarterTrack int) []uint8 {
|
||||||
|
trackWoz := f.tracks[f.trackMap[quarterTrack]]
|
||||||
|
out := make([]uint8, 0, trackWoz.bitCount/8)
|
||||||
|
latch := uint8(0)
|
||||||
|
for iBit := uint32(0); iBit < trackWoz.bitCount; iBit++ {
|
||||||
|
bit := trackWoz.data[iBit/8] >> (7 - iBit%8) & 1
|
||||||
|
latch = (latch << 1) + bit
|
||||||
|
if latch >= 0x80 {
|
||||||
|
// Valid reading
|
||||||
|
out = append(out, latch)
|
||||||
|
latch = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileWoz) dump() {
|
||||||
|
fmt.Printf("Woz image:\n")
|
||||||
|
fmt.Printf(" Version: %v\n", f.info.Version)
|
||||||
|
fmt.Printf(" Disk type: %v\n", f.info.DiskType)
|
||||||
|
fmt.Printf(" Write protected: %v\n", f.info.WriteProtected)
|
||||||
|
fmt.Printf(" Synchronized: %v\n", f.info.Synchronized)
|
||||||
|
fmt.Printf(" Cleaned: %v\n", f.info.Cleaned)
|
||||||
|
fmt.Printf(" Creator: %v\n", string(f.info.Creator[:]))
|
||||||
|
if f.info.Version >= 2 {
|
||||||
|
fmt.Printf(" Disk sides: %v\n", f.info.DiskSides)
|
||||||
|
fmt.Printf(" Boot sector format: %v\n", f.info.BootSectorFormat)
|
||||||
|
fmt.Printf(" Optimal bit timing: %v ns\n", 125*int(f.info.OptimalBitTiming))
|
||||||
|
fmt.Printf(" Compatible hardware: 0x%x\n", f.info.CompatibleHardware)
|
||||||
|
fmt.Printf(" Required RAM: %vKB\n", f.info.RequiredRAM)
|
||||||
|
fmt.Printf(" Largest track: %v blocks\n", f.info.LargestTrack)
|
||||||
|
}
|
||||||
|
if f.meta != nil {
|
||||||
|
fmt.Printf(" Metadata:\n")
|
||||||
|
for k, v := range f.meta {
|
||||||
|
fmt.Printf(" %v: %v\n", k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf(" Tracks:\n")
|
||||||
|
for i, track := range f.trackMap {
|
||||||
|
if track != 255 {
|
||||||
|
fmt.Printf(" Track %.2f: %v (%v bits, %v bytes)\n",
|
||||||
|
0.25*float32(i), track, f.tracks[track].bitCount, len(f.tracks[track].data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//nibs := f.dumpTrackAsNib(0)
|
||||||
|
//fmt.Printf(" Zero track: {%v} %x\n", len(nibs), nibs)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user