Compare commits

...

26 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
April Ayres-Griffiths
a6d47bbafa Added setvolume for prodos disks 2018-07-18 20:39:11 -05:00
April Ayres-Griffiths
e38cb58d37 fix prodos file delete bug 2018-06-27 17:57:09 +10:00
April Ayres-Griffiths
358c50662f system and extension chunking fixes 2018-06-20 15:58:25 +10:00
April Ayres-Griffiths
0a4cc590d8 file reading and writing fixes 2018-05-12 11:00:57 +10:00
April Ayres-Griffiths
4479f21f0b fix: HGR2 tokenization bug 2018-04-23 22:35:34 +10:00
April
76dfc34fd2
Update README.md 2018-02-15 20:54:14 +11:00
April
c72cc80e5b
Update README.md 2018-02-15 20:50:44 +11:00
April
185caf6c4f
Add new options to README.md 2018-02-15 19:54:50 +11:00
20 changed files with 645 additions and 242 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;
@ -42,11 +42,13 @@ ls List local files
mkdir Create a directory on disk
mount Mount a disk image
move Move files from one volume to another
put Copy local file to disk
prefix Change volume path
put Copy local file to disk (with optional target dir)
quarantine Like report, but allow moving dupes to a backup folder
quit Leave this place
rename Rename a file on the disk
report Run a report
search Run a search
target Select mounted volume as default
unlock Unlock file on the disk
unmount unmount disk image
@ -81,7 +83,7 @@ Command-line flags:
-csv
Output data to CSV format
-datastore string
Database of disk fingerprints for checking (default "/Users/nnnnn/DiskM8/fingerprints")
Database of disk fingerprints for checking (default "/home/myname/DiskM8/fingerprints")
-dir
Directory specified disk (needs -disk)
-dir-create string
@ -142,26 +144,36 @@ Command-line flags:
Run whole disk dupe report
-with-disk string
Perform disk operation (-file-extract,-file-put,-file-delete)
-with-path string
Target path for disk operation (-file-extract,-file-put,-file-delete)
```
Getting Started
## Getting Started
Ingest your disk collection, so diskm8 can report on them:
diskm8 -ingest "C:\Users\myname\LotsOfDisks"
Simple Reports
```diskm8 -ingest "C:\Users\myname\LotsOfDisks"```
### Simple Reports
Find Whole Disk duplicates:
diskm8 -whole-dupes
```diskm8 -whole-dupes```
Find Active Sectors duplicates (inactive sectors can be different):
diskm8 -as-dupes
```diskm8 -as-dupes```
Find Duplicate files across disks:
diskm8 -file-dupes
Limiting reports to subdirectories
```diskm8 -file-dupes```
### Limiting reports to subdirectories
Find Active Sector duplicates but only under a folder:
diskm8 -as-dupes -select "C:\Users\myname\LotsOfDisks\Operating Systems"
```
```diskm8 -as-dupes -select "C:\Users\myname\LotsOfDisks\Operating Systems"```
### Putting a file onto a disk in a particular path
```diskm8 -with-disk prodos_basic.dsk -with-path practice -file-put start#0x0801.BAS```

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)
@ -566,27 +591,116 @@ func ApplesoftTokenize(lines []string) []byte {
linebuffer[0x03] = byte(ln / 0x100)
// PROCESS LINE
var lastKeyword string
var inREM bool
for _, ch := range rest {
switch {
case inqq && ch != '"':
linebuffer = append(linebuffer, byte(ch))
continue
case ch == '"':
linebuffer = append(linebuffer, byte(ch))
inqq = !inqq
continue
case !inqq && breakingChar(ch):
linebuffer = append(linebuffer, []byte(chunk)...)
// 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 = ""
linebuffer = append(linebuffer, byte(ch))
// lastIsTok = true
continue
}
chunk += string(ch)
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) && !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 = ""
if ch != ' ' {
linebuffer = append(linebuffer, byte(ch))
}
lastKeyword = ""
continue
}
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 = ""
continue
}
}
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 = ""
}
}
@ -594,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)

44
disk/atokens_test.go Normal file
View File

@ -0,0 +1,44 @@
package disk
import (
"strings"
"testing"
)
func TestHGR2Tokenise(t *testing.T) {
lines := []string{
"10 HGR2 : REM SOMETHING",
"20 REM SOMETHING ELSE",
}
a := ApplesoftTokenize(lines)
s := string(ApplesoftDetoks(a))
t.Logf("code: %s", s)
if !strings.Contains(s, "HGR2 ") {
t.Fatalf("Expected HGR2")
}
}
func TestHGRTokenise(t *testing.T) {
lines := []string{
"10 HGR : REM SOMETHING",
"20 REM SOMETHING ELSE",
}
a := ApplesoftTokenize(lines)
s := string(ApplesoftDetoks(a))
t.Logf("code: %s", s)
if !strings.Contains(s, "HGR ") {
t.Fatalf("Expected HGR")
}
}

View File

@ -1,31 +0,0 @@
package disk
import (
//"strings"
"testing"
)
import "fmt"
//import "os"
func TestDisk(t *testing.T) {
if STD_DISK_BYTES != 143360 {
t.Error(fmt.Sprintf("Wrong size got %d", STD_DISK_BYTES))
}
dsk, e := NewDSKWrapper("g19.dsk")
if e != nil {
t.Error(e)
}
fmt.Printf("Disk format is %d\n", dsk.Format)
_, fdlist, e := dsk.GetCatalogProDOSPathed(2, "GAMES", "")
for _, fd := range fdlist {
fmt.Printf("[%s]\n", fd.Name())
}
t.Fail()
}

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

@ -205,8 +205,10 @@ func (fd *FileDescriptor) TotalSectors() int {
}
func (fd *FileDescriptor) SetTotalSectors(v int) {
fmt.Printf("Call to set sector count to %d\n", v)
fd.Data[0x21] = byte(v & 0xff)
fd.Data[0x22] = byte(v / 0x100)
fmt.Printf("Sector count bytes are %d, %d\n", fd.Data[0x21], fd.Data[0x22])
}
func (fd *FileDescriptor) SetTrackSectorListStart(t, s int) {
@ -255,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 {
@ -901,25 +907,33 @@ func (dsk *DSKWrapper) AppleDOSWriteFile(name string, kind FileType, data []byte
return err
}
fmt.Printf("Writing file with type %s\n", kind.Ext())
if kind == FileTypeBIN {
l := len(data)
fmt.Printf("Length is %d\n", l)
header := []byte{byte(l % 256), byte(l / 256)}
data = append(header, data...)
}
if kind != FileTypeTXT {
header := []byte{byte(loadAddr % 256), byte(loadAddr / 256)}
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
}
@ -931,7 +945,14 @@ 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)
// 3rd: Write the datablocks
var block int = 0
@ -1010,7 +1031,8 @@ func (dsk *DSKWrapper) AppleDOSWriteFile(name string, kind FileType, data []byte
fd.SetName(name)
fd.SetTrackSectorListStart(tsBlocks[0][0], tsBlocks[0][1])
fd.SetType(kind)
fd.SetTotalSectors(len(dataBlocks))
fd.SetTotalSectors(sectorCount)
fd.Publish(dsk)
return nil
@ -1045,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

@ -2,10 +2,10 @@ package disk
import (
"errors"
"fmt"
"regexp"
"strings"
"time"
"fmt"
)
type VDH struct {
@ -75,6 +75,10 @@ func (fd *VDH) GetNameLength() int {
return int(fd.Data[0] & 0xf)
}
func (fd *VDH) SetNameLength(l int) {
fd.Data[0] = (fd.Data[0] & 0xf0) | byte(l&0x0f)
}
func (fd *VDH) GetStorageType() ProDOSStorageType {
return ProDOSStorageType((fd.Data[0]) >> 4)
}
@ -83,6 +87,21 @@ func (fd *VDH) GetDirName() string {
return fd.GetVolumeName()
}
func (fd *VDH) SetVolumeName(s string) {
if len(s) > 15 {
s = s[:15]
}
l := len(s)
for i := 0; i < 15; i++ {
if i < l {
fd.Data[1+i] = byte(s[i])
} else {
fd.Data[1+i] = 0x00
}
}
fd.SetNameLength(l)
}
func (fd *VDH) GetVolumeName() string {
l := fd.GetNameLength()
@ -193,9 +212,9 @@ func (fd *VDH) Publish(dsk *DSKWrapper) error {
for i, v := range fd.Data {
bd[fd.blockoffset+i] = v
}
fmt.Printf("Writing dir header at block %d\n", fd.blockid)
//fmt.Printf("Writing dir header at block %d\n", fd.blockid)
fmt.Printf("Data=%v\n", bd)
//fmt.Printf("Data=%v\n", bd)
return dsk.PRODOSWrite(fd.blockid, bd)
}
@ -246,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
@ -324,6 +365,9 @@ func (vb ProDOSVolumeBitmap) IsBlockFree(b int) bool {
}
func (vb ProDOSVolumeBitmap) SetBlockFree(b int, free bool) {
//log.Printf("Freeing block %d", b)
bidx := b / 8
bit := 7 - (b % 8)
setmask := byte(1 << uint(bit))
@ -813,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)
@ -881,6 +934,20 @@ func (d *DSKWrapper) PRODOSGetVDH(b int) (*VDH, error) {
}
func (d *DSKWrapper) PRODOSSetVDH(b int, vdh *VDH) error {
data, e := d.PRODOSGetBlock(b)
if e != nil {
return e
}
for i, v := range vdh.Data {
data[4+i] = v
}
return d.PRODOSWrite(b, data)
}
func (d *DSKWrapper) PRODOSGetCatalogPathed(start int, path string, pattern string) (*VDH, []ProDOSFileDescriptor, error) {
path = strings.Trim(path, "/")
@ -942,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 {
@ -958,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)
@ -966,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
@ -982,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())
}
@ -998,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)
@ -1028,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())
@ -1039,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())
@ -1137,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
}
@ -1390,10 +1463,16 @@ func (dsk *DSKWrapper) PRODOSDeleteFile(path string, name string) error {
return err
}
maxBlocks := 1600
if len(dsk.Data) == STD_DISK_BYTES {
maxBlocks = 280
}
i := 0
b := int(ib[2*i+0]) + 256*int(ib[2*i+1])
for i < 256 && b != 0 {
b = int(ib[2*i+0]) + 256*int(ib[2*i+1])
b := int(ib[i]) + 256*int(ib[256+i])
for i < 256 && b != 0 && b <= maxBlocks {
removeBlocks = append(removeBlocks, b)
b = int(ib[i]) + 256*int(ib[256+i])
i++
}
}
@ -1543,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)
}

View File

@ -102,11 +102,11 @@ func analyzeDOS13(id int, dsk *disk.DSKWrapper, info *Disk) {
Modified: time.Now(),
}
_, _, data, err := dsk.AppleDOSReadFileRaw(fd)
size, addr, data, err := dsk.AppleDOSReadFileRaw(fd)
if err == nil {
sum := sha256.Sum256(data)
file.SHA256 = hex.EncodeToString(sum[:])
file.Size = len(data)
file.Size = size
if *ingestMode&1 == 1 {
if fd.Type() == disk.FileTypeAPP {
file.Text = disk.ApplesoftDetoks(data)
@ -124,8 +124,8 @@ func analyzeDOS13(id int, dsk *disk.DSKWrapper, info *Disk) {
file.TypeCode = TypeMask_AppleDOS | TypeCode(fd.Type())
file.LoadAddress = 0x0000
} else if fd.Type() == disk.FileTypeBIN && len(data) >= 2 {
file.LoadAddress = int(data[0]) + 256*int(data[1])
file.Data = data[2:]
file.LoadAddress = addr
file.Data = data
file.TypeCode = TypeMask_AppleDOS | TypeCode(fd.Type())
} else {
file.LoadAddress = 0x0000

View File

@ -136,11 +136,11 @@ func analyzeDOS16(id int, dsk *disk.DSKWrapper, info *Disk) {
}
//l.Log("start read")
_, _, data, err := dsk.AppleDOSReadFileRaw(fd)
size, addr, data, err := dsk.AppleDOSReadFileRaw(fd)
if err == nil {
sum := sha256.Sum256(data)
file.SHA256 = hex.EncodeToString(sum[:])
file.Size = len(data)
file.Size = size
if *ingestMode&1 == 1 {
if fd.Type() == disk.FileTypeAPP {
file.Text = disk.ApplesoftDetoks(data)
@ -158,8 +158,8 @@ func analyzeDOS16(id int, dsk *disk.DSKWrapper, info *Disk) {
file.TypeCode = TypeMask_AppleDOS | TypeCode(fd.Type())
file.LoadAddress = 0x0000
} else if fd.Type() == disk.FileTypeBIN && len(data) >= 2 {
file.LoadAddress = int(data[0]) + 256*int(data[1])
file.Data = data[2:]
file.LoadAddress = addr
file.Data = data
file.TypeCode = TypeMask_AppleDOS | TypeCode(fd.Type())
} else {
file.LoadAddress = 0x0000

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}'`

100
shell.go
View File

@ -262,6 +262,20 @@ func init() {
"Mounts disk and switches to the new slot",
},
},
"setvolume": &shellCommand{
Name: "setvolume",
Description: "Sets the ProDOS volume name",
MinArgs: 1,
MaxArgs: 1,
Code: shellVolumeName,
NeedsMount: true,
Context: sccNone,
Text: []string{
"setvolume <volume name>",
"",
"Set ProDOS volume name. Truncated to 15 chars if too long.",
},
},
"unmount": &shellCommand{
Name: "unmount",
Description: "unmount disk image",
@ -820,10 +834,15 @@ func shellCat(args []string) int {
}
bs := 256
volumename := "no-name"
if info.FormatID.ID == disk.DF_PASCAL || info.FormatID.ID == disk.DF_PRODOS ||
info.FormatID.ID == disk.DF_PRODOS_800KB || info.FormatID.ID == disk.DF_PRODOS_400KB ||
info.FormatID.ID == disk.DF_PRODOS_CUSTOM {
bs = 512
vdh, err := commandVolumes[commandTarget].PRODOSGetVDH(2)
if err == nil {
volumename = vdh.GetVolumeName()
}
}
pattern := "*"
@ -833,6 +852,8 @@ func shellCat(args []string) int {
files, _ := globDisk(commandTarget, pattern)
fmt.Printf("Volume Name is %s\n\n", volumename)
fmt.Printf("%-33s %6s %2s %-23s %s\n", "NAME", "BLOCKS", "RO", "KIND", "ADDITONAL")
for _, f := range files {
add := ""
@ -1042,6 +1063,36 @@ func shellMkdir(args []string) int {
}
func shellVolumeName(args []string) int {
fullpath, _ := filepath.Abs(commandVolumes[commandTarget].Filename)
_, err := analyze(0, fullpath)
if err != nil {
return 1
}
name := strings.ToUpper(args[0])
if formatIn(commandVolumes[commandTarget].Format.ID, []disk.DiskFormatID{disk.DF_PRODOS, disk.DF_PRODOS_800KB, disk.DF_PRODOS_400KB, disk.DF_PRODOS_CUSTOM}) {
vdh, err := commandVolumes[commandTarget].PRODOSGetVDH(2)
if err != nil {
fmt.Printf("Failed to get Volume Directory Header: %v\n", err)
return -1
}
vdh.SetVolumeName(name)
commandVolumes[commandTarget].PRODOSSetVDH(2, vdh)
fmt.Printf("Volume name is now %s\n", vdh.GetVolumeName())
saveDisk(commandVolumes[commandTarget], fullpath)
} else {
fmt.Println("Do not support setvolume on " + commandVolumes[commandTarget].Format.String() + ".")
return 0
}
return 0
}
func isASCII(in []byte) bool {
for _, v := range in {
if v > 128 {
@ -1060,15 +1111,42 @@ func shellPut(args []string) int {
return 1
}
data, err := ioutil.ReadFile(args[0])
parts := strings.Split(args[0], ",")
data, err := ioutil.ReadFile(parts[0])
if err != nil {
return -1
}
addr := int64(0x0801)
name := filepath.Base(args[0])
reTrailAddr := regexp.MustCompile("(?i)^([^,]+)([,]A(([$]|0x)[0-9a-f]+))?([,]L(([$]|0x)[0-9a-f]+))?$")
if reTrailAddr.MatchString(name) {
m := reTrailAddr.FindAllStringSubmatch(name, -1)
name = m[0][1]
saddr := m[0][3]
slen := m[0][6]
if saddr != "" {
if strings.HasPrefix(saddr, "$") {
saddr = "0x" + saddr[1:]
}
addr, _ = strconv.ParseInt(saddr, 0, 32)
}
if slen != "" {
if strings.HasPrefix(slen, "$") {
slen = "0x" + slen[1:]
}
nlen, _ := strconv.ParseInt(slen, 0, 32)
if int(nlen) < len(data) {
data = data[:int(nlen)]
}
}
}
if formatIn(commandVolumes[commandTarget].Format.ID, []disk.DiskFormatID{disk.DF_DOS_SECTORS_13, disk.DF_DOS_SECTORS_16}) {
addr := int64(0x0801)
name := filepath.Base(args[0])
kind := disk.FileTypeAPP
reSpecial := regexp.MustCompile("(?i)^(.+)[#](0x[a-fA-F0-9]+)[.]([A-Za-z]+)$")
ext := strings.Trim(filepath.Ext(name), ".")
if reSpecial.MatchString(name) {
@ -1078,7 +1156,9 @@ func shellPut(args []string) int {
addrStr := m[0][2]
addr, _ = strconv.ParseInt(addrStr, 0, 32)
} else {
name = strings.Replace(name, "."+ext, "", -1)
//name = strings.Replace(name, "."+ext, "", -1)
l := len(ext) + 1
name = name[:len(name)-l]
}
kind = disk.AppleDOSFileTypeFromExt(ext)
@ -1106,8 +1186,7 @@ func shellPut(args []string) int {
saveDisk(commandVolumes[commandTarget], fullpath)
} else if formatIn(commandVolumes[commandTarget].Format.ID, []disk.DiskFormatID{disk.DF_PRODOS, disk.DF_PRODOS_800KB, disk.DF_PRODOS_400KB, disk.DF_PRODOS_CUSTOM}) {
addr := int64(0x0801)
name := filepath.Base(args[0])
ext := strings.Trim(filepath.Ext(name), ".")
reSpecial := regexp.MustCompile("(?i)^(.+)[#](0x[a-fA-F0-9]+)[.]([A-Za-z]+)$")
if reSpecial.MatchString(name) {
@ -1117,11 +1196,18 @@ func shellPut(args []string) int {
addrStr := m[0][2]
addr, _ = strconv.ParseInt(addrStr, 0, 32)
} else {
name = strings.Replace(name, "."+ext, "", -1)
l := len(ext) + 1
name = name[:len(name)-l]
}
kind := disk.ProDOSFileTypeFromExt(ext)
if strings.ToLower(ext) == "system" {
name += "." + ext
ext = ""
kind = disk.ProDOSFileTypeFromExt("SYS")
}
if strings.HasSuffix(args[0], ".INT.ASC") {
kind = disk.FileType_PD_INT
} else if strings.HasSuffix(args[0], ".APP.ASC") {