mirror of
https://github.com/zellyn/diskii.git
synced 2024-09-30 01:56:20 +00:00
68ee8a493c
- Loading a .dsk (or any other file type) should yield a SectorMap interface, which reads and writes *physical* sectors. (Auto-detection of sector mapping will come later.) - The various filesystem packages (dos33, supermon, etc.) are responsible for logical sector mapping. - The weird part about this is that .dsk images are already mapped to dos33 logical sector order, so they have to be un-mapped, then re-mapped by the dos33 package. It's still cleaner that way.
212 lines
6.0 KiB
Go
212 lines
6.0 KiB
Go
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
|
|
|
// Package supermon contains routines for working with the on-disk
|
|
// structures of NakedOS/Super-Mon disks.
|
|
package supermon
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/zellyn/diskii/lib/disk"
|
|
)
|
|
|
|
const (
|
|
// FileIllegal (zero) is not allowed in the sector map.
|
|
FileIllegal = 0
|
|
// FileFree signifies unused space in the sector map.
|
|
FileFree = 0xff
|
|
// FileReserved signifies space used by NakedOS in the sector map.
|
|
FileReserved = 0xfe
|
|
)
|
|
|
|
// SectorMap is the list of sectors by file. It's always 560 bytes
|
|
// long (35 tracks * 16 sectors).
|
|
type SectorMap []byte
|
|
|
|
// LoadSectorMap loads a NakedOS sector map.
|
|
func LoadSectorMap(sd disk.SectorDisk) (SectorMap, error) {
|
|
sm := SectorMap(make([]byte, 560))
|
|
sector09, err := sd.ReadPhysicalSector(0, 9)
|
|
if err != nil {
|
|
return sm, err
|
|
}
|
|
sector0A, err := sd.ReadPhysicalSector(0, 0xA)
|
|
if err != nil {
|
|
return sm, err
|
|
}
|
|
sector0B, err := sd.ReadPhysicalSector(0, 0xB)
|
|
if err != nil {
|
|
return sm, err
|
|
}
|
|
copy(sm[0:0x30], sector09[0xd0:])
|
|
copy(sm[0x30:0x130], sector0A)
|
|
copy(sm[0x130:0x230], sector0B)
|
|
return sm, nil
|
|
}
|
|
|
|
// Verify checks that we actually have a NakedOS disk.
|
|
func (sm SectorMap) Verify() error {
|
|
for sector := byte(0); sector <= 0xB; sector++ {
|
|
if file := sm.FileForSector(0, sector); file != FileReserved {
|
|
return fmt.Errorf("Expected track 0, sectors 0-C to be reserved (0xFE), but got 0x%02X in sector %X", file, sector)
|
|
}
|
|
}
|
|
|
|
for track := byte(0); track < 35; track++ {
|
|
for sector := byte(0); sector < 16; sector++ {
|
|
file := sm.FileForSector(track, sector)
|
|
if file == FileIllegal {
|
|
return fmt.Errorf("Found illegal sector map value (%02X), in track %X sector %X", FileIllegal, track, sector)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FileForSector returns the file that owns the given track/sector.
|
|
func (sm SectorMap) FileForSector(track, sector byte) byte {
|
|
if track >= 35 {
|
|
panic(fmt.Sprintf("FileForSector called with track=%d > 34", track))
|
|
}
|
|
if sector >= 16 {
|
|
panic(fmt.Sprintf("FileForSector called with sector=%d > 15", sector))
|
|
}
|
|
return sm[int(track)*16+int(sector)]
|
|
}
|
|
|
|
// SectorsForFile returns the list of sectors that belong to the given
|
|
// file.
|
|
func (sm SectorMap) SectorsForFile(file byte) []disk.TrackSector {
|
|
var result []disk.TrackSector
|
|
for track := byte(0); track < 35; track++ {
|
|
for sector := byte(0); sector < 16; sector++ {
|
|
if file == sm.FileForSector(track, sector) {
|
|
result = append(result, disk.TrackSector{Track: track, Sector: sector})
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// SectorsByFile returns a map of file number to slice of sectors.
|
|
func (sm SectorMap) SectorsByFile() map[byte][]disk.TrackSector {
|
|
result := map[byte][]disk.TrackSector{}
|
|
for file := byte(0x01); file < FileReserved; file++ {
|
|
sectors := sm.SectorsForFile(file)
|
|
if len(sectors) > 0 {
|
|
result[file] = sectors
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ReadFile reads the contents of a file.
|
|
func (sm SectorMap) ReadFile(sd disk.SectorDisk, file byte) []byte {
|
|
var result []byte
|
|
for _, ts := range sm.SectorsForFile(file) {
|
|
bytes, err := sd.ReadPhysicalSector(ts.Track, ts.Sector)
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
result = append(result, bytes...)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Symbol represents a single Super-Mon symbol.
|
|
type Symbol struct {
|
|
// Address is the memory address the symbol points to, or 0 for an
|
|
// empty symbol table entry.
|
|
Address uint16
|
|
// Name is the name of the symbol.
|
|
Name string
|
|
// Link is the index of the next symbol in the symbol chain for this
|
|
// hash key, or -1 if none.
|
|
Link int
|
|
}
|
|
|
|
// decodeSymbol decodes a Super-Mon encoded symbol table entry,
|
|
// returning the string representation.
|
|
func decodeSymbol(five []byte, extra byte) string {
|
|
result := ""
|
|
value := uint64(five[0]) + uint64(five[1])<<8 + uint64(five[2])<<16 + uint64(five[3])<<24 + uint64(five[4])<<32 + uint64(extra)<<40
|
|
for value&0x1f > 0 {
|
|
if value&0x1f < 27 {
|
|
result = result + string(value&0x1f+'@')
|
|
value >>= 5
|
|
continue
|
|
}
|
|
if value&0x20 == 0 {
|
|
result = result + string((value&0x1f)-0x1b+'0')
|
|
} else {
|
|
result = result + string((value&0x1f)-0x1b+'5')
|
|
}
|
|
value >>= 6
|
|
}
|
|
return result
|
|
}
|
|
|
|
// SymbolTable represents an entire Super-Mon symbol table. It'll
|
|
// always be 819 entries long, because it includes blanks.
|
|
type SymbolTable []Symbol
|
|
|
|
// ReadSymbolTable reads the symbol table from a disk. If there are
|
|
// problems with the symbol table (like it doesn't exist, or the link
|
|
// pointers are problematic), it'll return nil and an error.
|
|
func (sm SectorMap) ReadSymbolTable(sd disk.SectorDisk) (SymbolTable, error) {
|
|
table := make(SymbolTable, 0, 819)
|
|
symtbl1 := sm.ReadFile(sd, 3)
|
|
if len(symtbl1) != 0x1000 {
|
|
return nil, fmt.Errorf("expected file FSYMTBL1(0x3) to be 0x1000 bytes long; got 0x%04X", len(symtbl1))
|
|
}
|
|
symtbl2 := sm.ReadFile(sd, 4)
|
|
if len(symtbl2) != 0x1000 {
|
|
return nil, fmt.Errorf("expected file FSYMTBL1(0x4) to be 0x1000 bytes long; got 0x%04X", len(symtbl2))
|
|
}
|
|
|
|
five := []byte{0, 0, 0, 0, 0}
|
|
for i := 0; i < 0x0fff; i += 5 {
|
|
address := uint16(symtbl1[i]) + uint16(symtbl1[i+1])<<8
|
|
if address == 0 {
|
|
table = append(table, Symbol{})
|
|
continue
|
|
}
|
|
linkAddr := uint16(symtbl1[i+2]) + uint16(symtbl1[i+3])<<8
|
|
link := -1
|
|
if linkAddr != 0 {
|
|
if linkAddr < 0xD000 || linkAddr >= 0xDFFF {
|
|
return nil, fmt.Errorf("Expected symbol table link address between 0xD000 and 0xDFFE; got 0x%04X", linkAddr)
|
|
}
|
|
if (linkAddr-0xD000)%5 != 0 {
|
|
return nil, fmt.Errorf("Expected symbol table link address to 0xD000+5x; got 0x%04X", linkAddr)
|
|
}
|
|
link = (int(linkAddr) - 0xD000) % 5
|
|
}
|
|
extra := symtbl1[i+4]
|
|
copy(five, symtbl2[i:i+5])
|
|
name := decodeSymbol(five, extra)
|
|
symbol := Symbol{
|
|
Address: address,
|
|
Name: name,
|
|
Link: link,
|
|
}
|
|
table = append(table, symbol)
|
|
}
|
|
|
|
// TODO(zellyn): check link addresses.
|
|
|
|
return table, nil
|
|
}
|
|
|
|
// SymbolsByAddress returns a map of addresses to slices of symbols.
|
|
func (st SymbolTable) SymbolsByAddress() map[uint16][]Symbol {
|
|
result := map[uint16][]Symbol{}
|
|
for _, symbol := range st {
|
|
if symbol.Address != 0 {
|
|
result[symbol.Address] = append(result[symbol.Address], symbol)
|
|
}
|
|
}
|
|
return result
|
|
}
|