Compare commits

...

18 Commits

Author SHA1 Message Date
April Ayres-Griffiths
a00aeed763 fix(disk/diskimageappledos.go): fix for badly behaved sector size spec 2024-05-25 09:42:33 -07:00
April Ayres-Griffiths
f46d7ff6ee fix(make.sh): migrate to go module, add darwin/arm64 windows/arm64 binaries 2024-05-25 09:23:45 -07:00
April Ayres-Griffiths
6b8517efec fix(main.go): suppress banner in -shell-batch / -with-disk modes 2024-05-25 09:22:40 -07:00
April Ayres-Griffiths
583624a6f8 chore(banner.go): add message about help command 2024-05-25 09:21:46 -07:00
April Ayres-Griffiths
d147f7e61b fix(disk/diskimagerdos.go): trim filenames, fix basic detokenization on extract 2024-05-25 09:06:55 -07:00
April Ayres-Griffiths
5135679d0b feat(disk/diskimagepd.go): start of handling work for non std volumes 2024-05-25 08:51:05 -07:00
April Ayres-Griffiths
7d13e7f8d8 fix(disk/diskimage.go): fix RDOS 33 string 2024-05-25 08:40:11 -07:00
April Ayres-Griffiths
9df9fb6c89 chore(banner.go): update year 2024-05-25 08:33:14 -07:00
April Ayres-Griffiths
613b5f1cd1 fix(disk/diskimagepas.go): trim filename strings 2024-05-25 08:13:50 -07:00
April Ayres-Griffiths
824eac4ce5 fix(disk/diskimage.go): refine detection, fix disk filename case bug 2024-05-25 08:11:18 -07:00
April Ayres-Griffiths
8f395e8360 fix(disk/atokens.go): handle partial keywords, improve detokenization/tokenization 2024-05-25 08:09:29 -07:00
April
2a5efddea2
Merge pull request #14 from OrangeBox72/master
Remove old dskalyzer refs, generalise example home directory.
2020-05-03 19:57:16 +10:00
johnny
6d952cd8a1 Fixed description for file-extract 2019-12-25 21:11:38 -06:00
johnny
6d12d71867 generalized name of home directory 2019-12-25 20:43:29 -06:00
johnny
b605401357 updated app name to 'diskm8' 2019-12-25 20:42:33 -06:00
April Ayres-Griffiths
086a2da973 prodos large volumes block mapping fix 2019-02-19 22:38:20 +11:00
Melody Ayres-Griffiths
2ca1798d95
Update README.md 2019-02-02 07:14:46 +11:00
April Ayres-Griffiths
16c8a2100d fix dos write 2019-01-06 09:19:01 +11:00
15 changed files with 392 additions and 170 deletions

View File

@ -5,7 +5,7 @@ Download from: https://github.com/paleotronic/diskm8/releases
Features include:
- Read from ProDOS, DOS 3.X, RDOS and Pascal disk images;
- ProDOS or DOS ordered; 2MG and NIB; 113-800K
- ProDOS or DOS ordered; DSK, PO, 2MG and NIB; 113-800K
- Write to Prodos and DOS 3.3 disk images;
- Extract and convert binary, text and detokenize BASIC files (Integer and Applesoft);
- Write binary, text and retokenized BASIC (Applesoft) files back to disk images;
@ -83,7 +83,7 @@ Command-line flags:
-csv
Output data to CSV format
-datastore string
Database of disk fingerprints for checking (default "/home/april/DiskM8/fingerprints")
Database of disk fingerprints for checking (default "/home/myname/DiskM8/fingerprints")
-dir
Directory specified disk (needs -disk)
-dir-create string

View File

@ -1,32 +1,32 @@
## Usage examples
Ingest your disk collection, so dskalyzer can report on them:
Ingest your disk collection, so diskm8 can report on them:
```
dskalyzer -ingest C:\Users\myname\LotsOfDisks
diskm8 -ingest C:\Users\myname\LotsOfDisks
```
Find Whole Disk duplicates:
```
dskalyzer -whole-dupes
diskm8 -whole-dupes
```
Find Active Sectors duplicates (inactive sectors can be different):
```
dskalyzer -as-dupes
diskm8 -as-dupes
```
Find Duplicate files across disks:
```
dskalyzer -file-dupes
diskm8 -file-dupes
```
Find Active Sector duplicates but only under a folder:
```
dskalyzer -as-dupes -select "C:\Users\myname\LotsOfDisks\Operating Systems"
diskm8 -as-dupes -select "C:\Users\myname\LotsOfDisks\Operating Systems"
```

View File

@ -12,6 +12,7 @@ func banner() {
t, _ := base64.StdEncoding.DecodeString(text)
os.Stderr.WriteString(string(t) + "\r\n")
os.Stderr.WriteString("(c) 2015 - 2018 Paleotronic.com\n\n")
os.Stderr.WriteString("(c) 2015 - 2024 Paleotronic.com\n\n")
os.Stderr.WriteString("type 'help' to see commands\n\n")
}

17
data.go
View File

@ -1,21 +1,16 @@
package main
import (
"crypto/md5"
"encoding/gob"
"encoding/hex"
"errors"
"fmt"
"runtime"
"time"
"crypto/md5"
"encoding/hex"
"os"
"strings"
"encoding/gob"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/paleotronic/diskm8/disk"
"github.com/paleotronic/diskm8/loggy"

View File

@ -120,6 +120,14 @@ var ApplesoftTokens = map[int]string{
0xEA: "MID$",
}
var ASPartials = map[string]bool{
"COLOR": true,
"HCOLOR": true,
"SPEED": true,
"SCALE": true,
"-": true,
}
var ApplesoftReverse map[string]int
var IntegerReverse map[string]int
@ -128,6 +136,9 @@ func init() {
IntegerReverse = make(map[string]int)
for k, v := range ApplesoftTokens {
ApplesoftReverse[v] = k
if v == "PRINT" {
ApplesoftReverse["?"] = k
}
}
for k, v := range IntegerTokens {
IntegerReverse[v] = k
@ -334,6 +345,8 @@ func ApplesoftDetoks(data []byte) []byte {
break
}
// var lineStart = len(out)
nextAddr = Read16(&srcptr, &length, data)
if nextAddr == 0 {
@ -349,7 +362,7 @@ func ApplesoftDetoks(data []byte) []byte {
lineNum = Read16(&srcptr, &length, data)
ln := fmt.Sprintf("%d", lineNum)
out = append(out, []byte(" "+ln+" ")...)
out = append(out, []byte(ln+" ")...)
if length == 0 {
break
@ -393,7 +406,9 @@ func ApplesoftDetoks(data []byte) []byte {
t = Read8(&srcptr, &length, data)
}
out = append(out, []byte("\r\n")...)
out = append(out, []byte("\n")...)
// log.Printf("Line bytes: %+v", out[lineStart:])
inQuote, inRem = false, false
@ -428,6 +443,8 @@ func IntegerDetoks(data []byte) []byte {
var trailingSpace bool
var newTrailingSpace bool = false
// var lineStart = len(out)
// read the line length
lineLen = Read8(&srcptr, &length, data)
@ -548,7 +565,7 @@ func ApplesoftTokenize(lines []string) []byte {
for _, l := range lines {
l = strings.Trim(l, "\r")
l = strings.Trim(l, " \r\n\t")
if l == "" {
continue
}
@ -556,8 +573,16 @@ func ApplesoftTokenize(lines []string) []byte {
chunk := ""
inqq := false
tmp := strings.SplitN(l, " ", 2)
ln, _ := strconv.Atoi(tmp[0])
rest := strings.Trim(tmp[1], " ")
var rest string
var ln int
if len(tmp) == 1 {
ln = 0
rest = ""
} else {
ln, _ = strconv.Atoi(tmp[0])
rest = strings.Trim(tmp[1], " ")
}
// lastIsTok := false
linebuffer := make([]byte, 4)
@ -567,31 +592,101 @@ func ApplesoftTokenize(lines []string) []byte {
// PROCESS LINE
var lastKeyword string
var inREM bool
for _, ch := range rest {
// case for a single character token, not in string
if codech, ok := ApplesoftReverse[strings.ToUpper(string(ch))]; ok && !inREM && !inqq {
// log.Printf("'%s' (%.2x) is a token... (lastKW=%s, chunk=%s)", string(ch), ch, lastKeyword, chunk)
// 1st - is chunk + string a token?
if chunk != "" {
code, ok := ApplesoftReverse[strings.ToUpper(chunk+string(ch))]
if ok {
if strings.ToUpper(chunk+string(ch)) == "REM" || strings.ToUpper(chunk+string(ch)) == "DATA" {
inREM = true
}
linebuffer = append(linebuffer, byte(code))
lastKeyword = chunk + string(ch)
chunk = ""
// lastIsTok = true
continue // we absorbed it ... eg. COLOR=
} else {
// chunk wasn't so treat as a string
// log.Printf("output (%s) by itself...", chunk)
linebuffer = append(linebuffer, []byte(chunk)...)
// lastIsTok = false
// lastKeyword = chunk
chunk = ""
}
}
// just the symbol
linebuffer = append(linebuffer, byte(codech))
lastKeyword = string(ch)
chunk = ""
// lastIsTok = true
continue
}
switch {
case ch < 32 || ch > 127:
continue
case inREM && ch != ':':
chunk += string(ch)
continue
case inREM && ch == ':':
if chunk != "" {
linebuffer = append(linebuffer, []byte(chunk)...)
}
chunk = ""
linebuffer = append(linebuffer, byte(ch))
inREM = false
continue
case inqq && ch != '"':
linebuffer = append(linebuffer, byte(ch))
lastKeyword = ""
// lastIsTok = false
continue
case ch == '"':
linebuffer = append(linebuffer, byte(ch))
lastKeyword = ""
inqq = !inqq
// lastIsTok = false
continue
case !inqq && breakingChar(ch):
case !inqq && breakingChar(ch) && !ASPartials[chunk]:
if chunk != "" {
code, ok := ApplesoftReverse[strings.ToUpper(chunk+string(ch))]
if ok {
linebuffer = append(linebuffer, byte(code))
lastKeyword = strings.ToUpper(chunk + string(ch))
chunk = ""
continue
}
}
linebuffer = append(linebuffer, []byte(chunk)...)
chunk = ""
linebuffer = append(linebuffer, byte(ch))
if ch != ' ' {
linebuffer = append(linebuffer, byte(ch))
}
lastKeyword = ""
continue
}
chunk += string(ch)
if ch != ' ' {
chunk += string(ch)
}
if lastKeyword != "" {
code, ok := ApplesoftReverse[strings.ToUpper(lastKeyword+chunk)]
if ok {
if strings.ToUpper(lastKeyword+chunk) == "REM" || strings.ToUpper(lastKeyword+chunk) == "DATA" {
inREM = true
}
linebuffer[len(linebuffer)-1] = byte(code)
lastKeyword = lastKeyword + chunk
chunk = ""
@ -601,6 +696,9 @@ func ApplesoftTokenize(lines []string) []byte {
code, ok := ApplesoftReverse[strings.ToUpper(chunk)]
if ok {
if strings.ToUpper(lastKeyword+chunk) == "REM" || strings.ToUpper(lastKeyword+chunk) == "DATA" {
inREM = true
}
linebuffer = append(linebuffer, byte(code))
lastKeyword = chunk
chunk = ""
@ -610,6 +708,24 @@ func ApplesoftTokenize(lines []string) []byte {
linebuffer = append(linebuffer, []byte(chunk)...)
}
//~ for i := 5; i < len(linebuffer)-1; i++ {
//~ if linebuffer[i] == 0xc9 {
//~ log.Printf("Found 0xC9 (-) in linebuffer...")
//~ // minus token...
//~ before := rune(linebuffer[i-1])
//~ after := rune(linebuffer[i+1])
//~ log.Printf("Before = %s, After = %s", string(before), string(after))
//~ if after == '.' || (after >= '0' && after <= '9') {
//~ // number part
//~ if before > 128 || before == ',' || before == 0xD0 {
//~ log.Printf("changing - token at %d to symbol", i)
//~ linebuffer[i] = byte('-')
//~ }
//~ }
//~ }
//~ }
// ENDING ZERO BYTE
linebuffer = append(linebuffer, 0x00)

View File

@ -2,20 +2,16 @@ package disk
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"io/ioutil"
"os"
"strings"
"crypto/sha256"
"encoding/hex"
"fmt"
)
//import "math/rand"
const STD_BYTES_PER_SECTOR = 256
const STD_TRACKS_PER_DISK = 35
const STD_SECTORS_PER_TRACK = 16
@ -69,6 +65,11 @@ var LINEAR_SECTOR_ORDER = []int{
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
}
var DIVERSE_SECTOR_ORDER = []int{
0x00, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x0f,
}
var NIBBLE_62 = []byte{
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
@ -98,6 +99,7 @@ const (
SectorOrderDOS33Alt
SectorOrderProDOS
SectorOrderProDOSLinear
SectorOrderDiversiDOS
)
func (so SectorOrder) String() string {
@ -112,6 +114,8 @@ func (so SectorOrder) String() string {
return "ProDOS"
case SectorOrderProDOSLinear:
return "Linear"
case SectorOrderDiversiDOS:
return "DiversiDOS"
}
return "Linear"
@ -141,6 +145,15 @@ type DiskFormat struct {
tpd int
}
func (df DiskFormat) IsOneOf(args ...DiskFormatID) bool {
for _, f := range args {
if f == df.ID {
return true
}
}
return false
}
func GetDiskFormat(id DiskFormatID) DiskFormat {
return DiskFormat{ID: id}
}
@ -176,7 +189,7 @@ func (f DiskFormat) String() string {
case DF_RDOS_32:
return "SSI RDOS 32 (13/13/Physical)"
case DF_RDOS_33:
return "SSI RDOS 32 (16/16/PD)"
return "SSI RDOS 33 (16/16/PD)"
case DF_PRODOS_CUSTOM:
return fmt.Sprintf("ProDOS Custom (%d SPT, %d TPD)", f.SPT(), f.TPD())
}
@ -304,8 +317,15 @@ type DSKWrapper struct {
//Nibbles []byte
Nibbles Nibbler
CurrentSectorOrder []int
CatalogSectorOrder []int // usually nil but for badly behaved OS: looking at YOU, DiversiDOS... :(
WriteProtected bool
NibblesChanged bool
DOSVolumeID int
}
// SectoreMapperDOS33 handles the interleaving for dos sectors
func SectorMapperLinear(wanted int) int {
return wanted
}
// SectoreMapperDOS33 handles the interleaving for dos sectors
@ -313,39 +333,42 @@ func SectorMapperDOS33(wanted int) int {
return wanted
// 0x00, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04,
// 0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x0F,
switch wanted {
case 0:
return 0
case 13:
return 1
case 11:
return 2
case 9:
return 3
case 7:
return 4
case 5:
return 5
case 3:
return 6
case 1:
return 7
case 14:
return 8
case 12:
return 9
case 10:
return 10
case 8:
return 11
case 6:
return 12
case 4:
return 13
case 2:
return 14
case 15:
return 15
case 0x00:
return 0x00
case 0x07:
return 0x01
case 0x0e:
return 0x02
case 0x06:
return 0x03
case 0x0d:
return 0x04
case 0x05:
return 0x05
case 0x0c:
return 0x06
case 0x04:
return 0x07
case 0x0b:
return 0x08
case 0x03:
return 0x09
case 0x0a:
return 0x0a
case 0x02:
return 0x0b
case 0x09:
return 0x0c
case 0x01:
return 0x0d
case 0x08:
return 0x0e
case 0x0f:
return 0x0f
}
return -1 // invalid sector
@ -473,6 +496,46 @@ func SectorMapperProDOS(wanted int) int {
return -1 // invalid sector
}
// SectoreMapperProDOS handles the interleaving for dos sectors
func SectorMapperDiversiDOS(wanted int) int {
switch wanted {
case 0x00:
return 0x00
case 0x01:
return 0x0e
case 0x02:
return 0x0d
case 0x03:
return 0x0c
case 0x04:
return 0x0b
case 0x05:
return 0x0a
case 0x06:
return 0x09
case 0x07:
return 0x08
case 0x08:
return 0x07
case 0x09:
return 0x06
case 0x0a:
return 0x05
case 0x0b:
return 0x04
case 0x0c:
return 0x03
case 0x0d:
return 0x02
case 0x0e:
return 0x01
case 0x0f:
return 0x0f
}
return -1 // invalid sector
}
func (d *DSKWrapper) SetTrack(t int) error {
if t >= 0 && t < d.Format.TPD() {
@ -522,6 +585,10 @@ func (d *DSKWrapper) SetSectorPointer() {
isector = SectorMapperDOS33(sector)
case SectorOrderProDOS:
isector = SectorMapperProDOS(sector)
case SectorOrderProDOSLinear:
isector = SectorMapperLinear(sector)
case SectorOrderDiversiDOS:
isector = SectorMapperDiversiDOS(sector)
}
d.SectorPointer = (track * d.Format.SPT() * STD_BYTES_PER_SECTOR) + (STD_BYTES_PER_SECTOR * isector)
@ -551,12 +618,10 @@ func (d *DSKWrapper) IsChanged() bool {
// Read is a simple function to return the current pointed to sector
func (d *DSKWrapper) Read() []byte {
////fmt.Printf("---> Reading track %d, sector %d\n", d.CurrentTrack, d.CurrentSector)
return d.Data[d.SectorPointer : d.SectorPointer+256]
}
func (d *DSKWrapper) Write(data []byte) {
////fmt.Printf("---> Reading track %d, sector %d\n", d.CurrentTrack, d.CurrentSector)
l := len(data)
if l > STD_BYTES_PER_SECTOR {
l = STD_BYTES_PER_SECTOR
@ -605,6 +670,7 @@ func NewDSKWrapper(nibbler Nibbler, filename string) (*DSKWrapper, error) {
}
w, e := NewDSKWrapperBin(nibbler, data, filename)
return w, e
}
@ -618,7 +684,8 @@ func NewDSKWrapperBin(nibbler Nibbler, data []byte, filename string) (*DSKWrappe
len(data) != PRODOS_400KB_DISK_BYTES+64 &&
len(data) != PRODOS_800KB_DISK_BYTES &&
len(data) != PRODOS_800KB_DISK_BYTES+64 &&
len(data) != STD_DISK_BYTES+64 {
len(data) != STD_DISK_BYTES+64 &&
len(data) < STD_DISK_BYTES {
return nil, errors.New("Incorrect disk bytes")
}
@ -657,26 +724,30 @@ func (dsk *DSKWrapper) SetNibbles(data []byte) {
}
}
func layoutWithHints(l SectorOrder, hint SectorOrder) SectorOrder {
if hint != -1 {
return hint
}
return l
}
func (dsk *DSKWrapper) Identify() {
dsk.Format = GetDiskFormat(DF_NONE)
var hint DiskFormat
var hint SectorOrder = -1
dsk.Filename = strings.ToLower(dsk.Filename)
lowerFilename := strings.ToLower(dsk.Filename)
switch {
case strings.HasSuffix(dsk.Filename, ".po"):
hint = GetDiskFormat(DF_PRODOS)
case strings.HasSuffix(dsk.Filename, ".do"):
hint = GetDiskFormat(DF_DOS_SECTORS_16)
default:
hint = GetDiskFormat(DF_DOS_SECTORS_16)
case strings.HasSuffix(lowerFilename, ".po"):
hint = SectorOrderProDOS
case strings.HasSuffix(lowerFilename, ".do"):
hint = SectorOrderDOS33
}
is2MG, Format, Layout, zdsk := dsk.Is2MG()
if is2MG {
////fmt.Println("repacked", len(zdsk.Data))
dsk.SetData(zdsk.Data)
dsk.Layout = Layout
dsk.Format = Format
@ -685,9 +756,12 @@ func (dsk *DSKWrapper) Identify() {
isPD, Format, Layout := dsk.IsProDOS()
if isPD {
if Format == GetDiskFormat(DF_PRODOS) {
if Format == GetDiskFormat(DF_PRODOS_CUSTOM) {
dsk.Format = GetDiskFormat(DF_PRODOS)
dsk.Layout = Layout
} else if Format == GetDiskFormat(DF_PRODOS) {
dsk.Format = GetDiskFormat(DF_PRODOS)
dsk.Layout = layoutWithHints(Layout, hint)
switch dsk.Layout {
case SectorOrderProDOS:
dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER
@ -739,25 +813,21 @@ func (dsk *DSKWrapper) Identify() {
isAppleDOS, Format, Layout := dsk.IsAppleDOS()
if isAppleDOS {
////fmt.Printf("Format: %s\n", Format.String())
dsk.Format = Format
dsk.Layout = Layout
switch Layout {
dsk.Layout = layoutWithHints(Layout, hint)
switch dsk.Layout {
case SectorOrderProDOS:
////fmt.Println("Sector Order: ProDOS")
dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER
case SectorOrderProDOSLinear:
////fmt.Println("Sector Order: Linear")
dsk.CurrentSectorOrder = LINEAR_SECTOR_ORDER
case SectorOrderDOS33:
////fmt.Println("Sector Order: DOS33")
dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER
case SectorOrderDOS32:
////fmt.Println("Sector Order: DOS32")
//dsk.CurrentSectorOrder = DOS_32_SECTOR_ORDER
dsk.CurrentSectorOrder = DOS_32_SECTOR_ORDER
case SectorOrderDOS33Alt:
////fmt.Println("Sector Order: Alt Linear")
dsk.CurrentSectorOrder = LINEAR_SECTOR_ORDER
case SectorOrderDiversiDOS:
dsk.CurrentSectorOrder = DIVERSE_SECTOR_ORDER
}
dsk.SetNibbles(dsk.Nibblize())
return
@ -766,14 +836,10 @@ func (dsk *DSKWrapper) Identify() {
fp := hex.EncodeToString(dsk.Data[:32])
if dfmt, ok := identity[fp]; ok {
dsk.Format = dfmt
//fmt.Println(dsk.Format.String())
}
//fmt.Printf("Disk name: %s\n", dsk.Filename)
// 1. NIB
if len(dsk.Data) == 232960 {
//fmt.Println("THIS IS A NIB")
dsk.Format = GetDiskFormat(DF_DOS_SECTORS_16)
dsk.SetNibbles(dsk.Data)
return
@ -781,7 +847,6 @@ func (dsk *DSKWrapper) Identify() {
// 2. Wrong size
if len(dsk.Data) != STD_DISK_BYTES && len(dsk.Data) != STD_DISK_BYTES_OLD && len(dsk.Data) != PRODOS_800KB_DISK_BYTES {
//fmt.Println("NOT STANDARD DISK")
dsk.Format = GetDiskFormat(DF_NONE)
dsk.SetNibbles(make([]byte, 232960))
return
@ -789,14 +854,10 @@ func (dsk *DSKWrapper) Identify() {
// 3. DOS 3x Disk
vtoc, e := dsk.AppleDOSGetVTOC()
//fmt.Println(vtoc.GetTracks(), vtoc.GetSectors())
if e == nil && vtoc.GetTracks() == 35 {
//bps := vtoc.BytesPerSector()
t := vtoc.GetTracks()
s := vtoc.GetSectors()
//fmt.Printf("DOS Tracks = %d, Sectors = %d\n", t, s)
if t == 35 && s == 16 {
dsk.Layout = SectorOrderDOS33
dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER
@ -806,13 +867,11 @@ func (dsk *DSKWrapper) Identify() {
dsk.Layout = SectorOrderDOS32
dsk.CurrentSectorOrder = DOS_32_SECTOR_ORDER
dsk.Format = GetDiskFormat(DF_DOS_SECTORS_13)
dsk.SetNibbles(make([]byte, 232960))
dsk.SetNibbles(dsk.Nibblize())
}
return
}
//fmt.Println("Trying prodos / pascal")
isPAS, volName := dsk.IsPascal()
if isPAS && volName != "" {
dsk.Format = GetDiskFormat(DF_PASCAL)
@ -828,28 +887,22 @@ func (dsk *DSKWrapper) Identify() {
dsk.Format = GetDiskFormat(DF_PRODOS_800KB)
}
//fmt.Println(dsk.Format.String())
switch dsk.Format.ID {
case DF_PRODOS:
vdh, e := dsk.PRODOSGetVDH(2)
////fmt.Printf("Blocks = %d\n", vdh.GetTotalBlocks())
if e == nil && vdh.GetStorageType() == 0xf && vdh.GetTotalBlocks() == 280 {
dsk.Format = GetDiskFormat(DF_PRODOS)
dsk.Layout = SectorOrderDOS33
dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER
dsk.SetNibbles(dsk.Nibblize())
//fmt.Println("THIS IS A PRODOS DISKETTE, DOS Ordered")
return
} else {
dsk.Format = GetDiskFormat(DF_PRODOS)
dsk.Layout = SectorOrderDOS33Alt
////fmt.Println("Try again")
vdh, e = dsk.PRODOSGetVDH(2)
if e == nil && vdh.GetStorageType() == 0xf && vdh.GetTotalBlocks() == 280 {
@ -857,7 +910,6 @@ func (dsk *DSKWrapper) Identify() {
dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER
dsk.SetNibbles(dsk.Nibblize())
//fmt.Println("THIS IS A PRODOS DISKETTE, ProDOS Ordered")
return
}
@ -866,41 +918,39 @@ func (dsk *DSKWrapper) Identify() {
case DF_PRODOS_800KB:
vdh, e := dsk.PRODOS800GetVDH(2)
//fmt.Printf("Blocks = %d\n", vdh.GetTotalBlocks())
if e == nil && vdh.GetStorageType() == 0xf && vdh.GetTotalBlocks() == 1600 {
dsk.Format = GetDiskFormat(DF_PRODOS_800KB)
dsk.Layout = SectorOrderDOS33
dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER
//dsk.SetNibbles(dsk.nibblize())
dsk.SetNibbles(make([]byte, 232960))
//fmt.Println("THIS IS A PRODOS DISKETTE, DOS Ordered")
return
}
}
switch hint.ID {
case DF_PRODOS:
switch hint {
case SectorOrderProDOS:
dsk.Format = GetDiskFormat(DF_PRODOS)
dsk.Layout = SectorOrderProDOS
dsk.CurrentSectorOrder = PRODOS_SECTOR_ORDER
dsk.SetNibbles(dsk.Nibblize())
//fmt.Println("VTOC read failed, will nibblize anyway...")
case DF_DOS_SECTORS_16:
//fmt.Println("VTOC read failed, will nibblize anyway...")
dsk.Layout = SectorOrderProDOS
return
case SectorOrderDOS33:
dsk.Layout = SectorOrderDOS33
dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER
dsk.Format = GetDiskFormat(DF_DOS_SECTORS_16)
dsk.SetNibbles(dsk.Nibblize())
return
}
dsk.Format = GetDiskFormat(DF_NONE)
dsk.Layout = SectorOrderDOS33
dsk.CurrentSectorOrder = DOS_33_SECTOR_ORDER
dsk.SetNibbles(dsk.Nibblize())
}
//func (d *DSKWrapper) ReadFileSectorsProDOS(fd ProDOSFileDescriptor) ([]byte, error) {
//}
func Dump(bytes []byte) {
perline := 0xC
base := 0

View File

@ -257,7 +257,11 @@ func (fd *VTOC) GetTrackOrder() int {
}
func (fd *VTOC) BytesPerSector() int {
return int(fd.Data[0x36]) + 256*int(fd.Data[0x37])
size := int(fd.Data[0x36]) + 256*int(fd.Data[0x37])
if size < 256 {
size = 256
}
return size
}
func (fd *VTOC) IsTSFree(t, s int) bool {
@ -917,20 +921,19 @@ func (dsk *DSKWrapper) AppleDOSWriteFile(name string, kind FileType, data []byte
data = append(header, data...)
}
// 1st: check we have sufficient space...
tsBlocks, dataBlocks, err := dsk.AppleDOSGetFreeSectors(len(data))
if err != nil {
return err
}
// try get catalog entry - delete existing if match found
fd, err := dsk.AppleDOSNamedCatalogEntry(name)
//fmt.Println("FD=", fd)
if err == nil {
if kind != fd.Type() {
return errors.New("File type mismatch")
} else {
// need to delete this file...
err = dsk.AppleDOSDeleteFile(name)
err = dsk.AppleDOSRemoveFile(fd)
if err != nil {
return err
}
// reread vtoc here
vtoc, err = dsk.AppleDOSGetVTOC()
if err != nil {
return err
}
@ -942,6 +945,12 @@ func (dsk *DSKWrapper) AppleDOSWriteFile(name string, kind FileType, data []byte
}
}
// 1st: check we have sufficient space...
tsBlocks, dataBlocks, err := dsk.AppleDOSGetFreeSectors(len(data))
if err != nil {
return err
}
// 2nd: check we can get a free catalog entry
sectorCount := len(dataBlocks)
@ -1058,6 +1067,8 @@ func (d *DSKWrapper) AppleDOSRemoveFile(fd *FileDescriptor) error {
vtoc.SetTSFree(pair[0], pair[1], true)
}
vtoc.Publish(d)
fd.Data[0x00] = 0xff
fd.SetName("")
return fd.Publish(d)

View File

@ -137,7 +137,9 @@ func (pvh *PascalVolumeHeader) GetNameLength() int {
func (pvh *PascalVolumeHeader) GetName() string {
l := pvh.GetNameLength()
return string(pvh.data[0x07 : 0x07+l])
s := strings.Trim(string(pvh.data[0x07:0x07+l]), " ")
s += "." + PascalFileType(pvh.GetType()).Ext()
return s
}
func (pvh *PascalVolumeHeader) GetTotalBlocks() int {
@ -182,7 +184,7 @@ func (pvh *PascalFileEntry) GetNameLength() int {
func (pvh *PascalFileEntry) GetName() string {
l := pvh.GetNameLength()
return string(pvh.data[0x07 : 0x07+l])
return strings.Trim(string(pvh.data[0x07:0x07+l]), "")
}
func (pvh *PascalFileEntry) GetBytesRemaining() int {

View File

@ -5,6 +5,7 @@ import (
"regexp"
"strings"
"time"
"fmt"
)
type VDH struct {
@ -264,6 +265,28 @@ func (dsk *DSKWrapper) IsProDOS() (bool, DiskFormat, SectorOrder) {
}
} else {
fmt.Println("Trying alternative format identification")
layouts := []SectorOrder{SectorOrderDOS33, SectorOrderDOS33Alt, SectorOrderProDOS, SectorOrderProDOSLinear}
for _, l := range layouts {
dsk.Layout = l
vdh, err := dsk.PRODOSGetVDH(2)
if err != nil {
return false, oldFormat, oldLayout
}
fmt.Printf("Blocks = %d, Size/512 = %d, Storage Type = %d\n", vdh.GetTotalBlocks(), len(dsk.Data)/512, vdh.GetStorageType())
if vdh.GetStorageType() == 0xf && vdh.GetTotalBlocks() == len(dsk.Data)/512 {
return true, GetDiskFormat(DF_PRODOS_CUSTOM), l
}
}
}
return false, oldFormat, oldLayout
@ -834,6 +857,15 @@ func (fd *ProDOSFileDescriptor) HeaderPointer() int {
func (d *DSKWrapper) PRODOS800GetBlock(block int) ([]byte, error) {
if len(d.Data) > 819200 {
maxblocks := len(d.Data) / 512
if block < maxblocks {
offset := 512 * block
return d.Data[offset : offset+512], nil
}
return nil, errors.New("Invalid block")
}
t, s1, s2 := d.PRODOS800GetBlockSectors(block)
e := d.Seek(t, s1)
@ -977,9 +1009,13 @@ func (d *DSKWrapper) PRODOSGetCatalog(startblock int, pattern string) (*VDH, []P
var e error
var vtoc *VDH
if d.Format.ID == DF_PRODOS_800KB {
fmt.Printf("FormatID = %s", d.Format.String())
if d.Format.ID == DF_PRODOS_800KB || d.Format.ID == DF_PRODOS_CUSTOM {
fmt.Println("HC VDH")
vtoc, e = d.PRODOS800GetVDH(startblock)
} else {
fmt.Println("LC VDH")
vtoc, e = d.PRODOSGetVDH(startblock)
}
if e != nil {
@ -993,7 +1029,7 @@ func (d *DSKWrapper) PRODOSGetCatalog(startblock int, pattern string) (*VDH, []P
refnum := startblock
var data []byte
if d.Format.ID == DF_PRODOS_800KB {
if d.Format.ID == DF_PRODOS_800KB || d.Format.ID == DF_PRODOS_CUSTOM {
data, _ = d.PRODOS800GetBlock(refnum)
} else {
data, _ = d.PRODOSGetBlock(refnum)
@ -1001,7 +1037,7 @@ func (d *DSKWrapper) PRODOSGetCatalog(startblock int, pattern string) (*VDH, []P
nextblock := int(data[2]) + 256*int(data[3])
//fmt.Printf("ActiveCount = %d\n", filecount)
fmt.Printf("ActiveCount = %d\n", filecount)
entrypointer := 4 + PRODOS_ENTRY_SIZE
@ -1017,7 +1053,7 @@ func (d *DSKWrapper) PRODOSGetCatalog(startblock int, pattern string) (*VDH, []P
var skipname bool = false
if re != nil {
//fmt.Printf("Checking [%s] against regex /%s/\n", fd.Name(), patterntmp)
fmt.Printf("Checking [%s] against regex /%s/\n", fd.Name(), patterntmp)
skipname = !re.MatchString(fd.Name())
}
@ -1033,7 +1069,7 @@ func (d *DSKWrapper) PRODOSGetCatalog(startblock int, pattern string) (*VDH, []P
if activeentries < filecount {
if blockentries == entriesperblock {
refnum = nextblock
if d.Format.ID == DF_PRODOS_800KB {
if d.Format.ID == DF_PRODOS_800KB || d.Format.ID == DF_PRODOS_CUSTOM {
data, err = d.PRODOS800GetBlock(refnum)
} else {
data, err = d.PRODOSGetBlock(refnum)
@ -1063,7 +1099,7 @@ func (d *DSKWrapper) PRODOSReadFileSectors(fd ProDOSFileDescriptor, maxblocks in
switch fd.GetStorageType() {
case StorageType_Seedling:
/* single block pointed to */
if d.Format.ID == DF_PRODOS_800KB {
if d.Format.ID == DF_PRODOS_800KB || d.Format.ID == DF_PRODOS_CUSTOM {
data, _ = d.PRODOS800GetBlock(fd.IndexBlock())
} else {
data, _ = d.PRODOSGetBlock(fd.IndexBlock())
@ -1074,7 +1110,7 @@ func (d *DSKWrapper) PRODOSReadFileSectors(fd ProDOSFileDescriptor, maxblocks in
}
return data[:count], e
case StorageType_Sapling:
if d.Format.ID == DF_PRODOS_800KB {
if d.Format.ID == DF_PRODOS_800KB || d.Format.ID == DF_PRODOS_CUSTOM {
index, _ = d.PRODOS800GetBlock(fd.IndexBlock())
} else {
index, _ = d.PRODOSGetBlock(fd.IndexBlock())
@ -1172,11 +1208,13 @@ func (d *DSKWrapper) PRODOS800ChecksumBlock(b int) string {
func (d *DSKWrapper) PRODOSGetBlockSectors(block int) (int, int, int) {
track := block / PRODOS_BLOCKS_PER_TRACK
bpt := d.Format.USPT() / 2
bo := block % PRODOS_BLOCKS_PER_TRACK
track := block / bpt
if d.Layout == SectorOrderProDOSLinear {
bo := block % bpt
if d.Layout == SectorOrderProDOSLinear || len(d.Data) >= PRODOS_800KB_DISK_BYTES {
return track, bo * 2, bo*2 + 1
}
@ -1584,6 +1622,8 @@ func (fd *ProDOSFileDescriptor) Publish(dsk *DSKWrapper) error {
func (dsk *DSKWrapper) PRODOSWrite(b int, data []byte) error {
//log.Printf("====> Request to write block %.4x", b)
for len(data) < 512 {
data = append(data, 0x00)
}

View File

@ -74,7 +74,7 @@ func (f RDOSFormat) Spec() *RDOSFormatSpec {
switch f {
case RDOS_32:
return &RDOSFormatSpec{
SectorStride: 13,
SectorStride: 16,
SectorMax: 13,
CatalogTrack: 1,
CatalogSector: 0,
@ -84,8 +84,8 @@ func (f RDOSFormat) Spec() *RDOSFormatSpec {
return &RDOSFormatSpec{
SectorStride: 16,
SectorMax: 13,
CatalogTrack: 0,
CatalogSector: 1,
CatalogTrack: 1,
CatalogSector: 0,
Ordering: SectorOrderDOS33,
}
case RDOS_33:
@ -167,7 +167,7 @@ const (
var RDOSTypeMap = map[RDOSFileType][2]string{
FileType_RDOS_Unknown: [2]string{"UNK", "Unknown"},
FileType_RDOS_AppleSoft: [2]string{"APP", "Applesoft Basic Program"},
FileType_RDOS_AppleSoft: [2]string{"BAS", "Applesoft Basic Program"},
FileType_RDOS_Binary: [2]string{"BIN", "Binary File"},
FileType_RDOS_Text: [2]string{"TXT", "ASCII Text"},
}
@ -228,11 +228,11 @@ func (fd *RDOSFileDescriptor) Name() string {
str = strings.TrimRight(str, " ")
switch fd.Type() {
case FileType_RDOS_AppleSoft:
str += ".a"
str += ".bas"
case FileType_RDOS_Binary:
str += ".s"
str += ".bin"
case FileType_RDOS_Text:
str += ".t"
str += ".txt"
}
return str
@ -252,7 +252,7 @@ func (fd *RDOSFileDescriptor) NameUnadorned() string {
}
return str
return strings.TrimSpace(str)
}

7
go.mod Normal file
View File

@ -0,0 +1,7 @@
module github.com/paleotronic/diskm8
go 1.22.3
require github.com/chzyer/readline v1.5.1
require golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect

8
go.sum Normal file
View File

@ -0,0 +1,8 @@
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -36,7 +36,7 @@ func Get(id int) *Logger {
func NewLogger(id int, app string) *Logger {
if app == "" {
app = "dskalyzer"
app = "diskm8"
}
filename := fmt.Sprintf("%s_%d_%s.log", app, id, fts())

26
main.go
View File

@ -13,25 +13,19 @@ through as time goes by.
*/
import (
"flag"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"runtime"
"runtime/debug"
"flag"
"os"
"strings"
"time"
"github.com/paleotronic/diskm8/disk"
"github.com/paleotronic/diskm8/loggy"
"runtime"
"strings"
"time"
"github.com/paleotronic/diskm8/panic"
)
@ -95,7 +89,7 @@ var shell = flag.Bool("shell", false, "Start interactive mode")
var shellBatch = flag.String("shell-batch", "", "Execute shell command(s) from file and exit")
var withDisk = flag.String("with-disk", "", "Perform disk operation (-file-extract,-file-put,-file-delete)")
var withPath = flag.String("with-path", "", "Target path for disk operation (-file-extract,-file-put,-file-delete)")
var fileExtract = flag.String("file-extract", "", "File to delete from disk (-with-disk)")
var fileExtract = flag.String("file-extract", "", "File to extract from disk (-with-disk)")
var filePut = flag.String("file-put", "", "File to put on disk (-with-disk)")
var fileDelete = flag.String("file-delete", "", "File to delete (-with-disk)")
var fileMkdir = flag.String("dir-create", "", "Directory to create (-with-disk)")
@ -106,12 +100,12 @@ func main() {
runtime.GOMAXPROCS(8)
banner()
//l.Default.Level = l.LevelCrit
flag.Parse()
if *withDisk == "" && *shellBatch == "" {
banner()
}
var filterpath []string
if *filterPath || *shell {

View File

@ -1,12 +1,10 @@
#!/bin/bash
ARCHES="darwin-amd64 windows-386 windows-amd64 linux-386 linux-amd64 linux-arm freebsd-arm freebsd-amd64 freebsd-386"
ARCHES="darwin-amd64 darwin-arm64 windows-386 windows-amd64 windows-arm64 linux-386 linux-amd64 linux-arm freebsd-arm freebsd-amd64 freebsd-386"
PUBLISH="publish"
mkdir -p "$PUBLISH"
go get github.com/chzyer/readline
exitState=0
for arch in `echo $ARCHES`; do
export GOOS=`echo $arch | awk -F"-" '{print $1}'`