From 0b7db8d43dc1d59f3b6caff9d2ad62efe8ff7d4a Mon Sep 17 00:00:00 2001 From: Zellyn Hunter Date: Mon, 19 Dec 2016 23:51:20 -0500 Subject: [PATCH] add mksd command --- README.md | 19 ++++--- cmd/sd.go | 133 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/disk/dsk.go | 11 ++++ 3 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 cmd/sd.go diff --git a/README.md b/README.md index d36c29b..a07b19f 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,17 @@ diskii functionality, but I'm still experimenting with the command syntax and organization, so don't get too comfy with it. -diskii is a commandline tool for working with Apple II disk images. +diskii-the-tool is a commandline tool for working with Apple II disk +images. Given that +[AppleCommander](http://applecommander.sourceforge.net/) already does +everything, it's not terribly necessary. It is, however, mine. Minor +benefits (right now) are binaries you can copy around (no Java +needed), and support for Super-Mon symbol tables on NakedOS disks. -It is also a library of code that can be used by other Go programs. +diskii-the-library is probably more useful: a library of +disk-image-manipulation code that can be used by other Go programs. -Its major advantage is that it's written in Go, hence -cross-platform. - -Its major disadvantage is that it mostly doesn't exist yet. +diskii's major disadvantage is that it mostly doesn't exist yet. [![Build Status](https://travis-ci.org/zellyn/diskii.svg?branch=master)](https://travis-ci.org/zellyn/diskii) [![Report Card](https://goreportcard.com/badge/github.com/zellyn/diskii)](https://goreportcard.com/report/github.com/zellyn/diskii) @@ -69,8 +72,8 @@ will be likely to get priority. - [x] Build per-platform binaries for Linux, MacOS, Windows. - [ ] Implement `GetFile` for DOS 3.3 - [ ] Add and implement the `-l` flag for `ls` -- [ ] Add `Delete` to the `disk.Operator` interface - - [ ] Implement it for supermon +- [x] Add `Delete` to the `disk.Operator` interface + - [x] Implement it for Super-Mon - [ ] Implement it for DOS 3.3 - [ ] Make 13-sector DOS disks work - [ ] Read/write nybble formats diff --git a/cmd/sd.go b/cmd/sd.go new file mode 100644 index 0000000..170c45d --- /dev/null +++ b/cmd/sd.go @@ -0,0 +1,133 @@ +// Copyright © 2016 Zellyn Hunter + +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/zellyn/diskii/lib/disk" + "github.com/zellyn/diskii/lib/helpers" +) + +var sdAddress uint16 // flag for address to load at +var sdStart uint16 // flag for address to start execution at + +// mksdCmd represents the mksd command +var mksdCmd = &cobra.Command{ + Use: "mksd", + Short: "create a Standard-Delivery disk image", + Long: `diskii mksd creates a "Standard Delivery" disk image containing a binary. +See https://github.com/peterferrie/standard-delivery for details. + +Examples: +mksd test.dsk foo.o # load and run foo.o at the default address, then jump to the start of the loaded code. +mksd test.dsk foo.o --address 0x2000 --start 0x2100 # load foo.o at address 0x2000, then jump to 0x2100.`, + Run: func(cmd *cobra.Command, args []string) { + if err := runMkSd(args); err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(-1) + } + }, +} + +func init() { + RootCmd.AddCommand(mksdCmd) + mksdCmd.Flags().Uint16VarP(&sdAddress, "address", "a", 0x6000, "memory location to load code at") + mksdCmd.Flags().Uint16VarP(&sdStart, "start", "s", 0x6000, "memory location to jump to") +} + +// ----- mksd command ------------------------------------------------------- + +// runMkSd performs the actual mksd logic. +func runMkSd(args []string) error { + if len(args) != 2 { + return fmt.Errorf("usage: diskii mksd ") + } + contents, err := helpers.FileContentsOrStdIn(args[1]) + if err != nil { + return err + } + if sdAddress%256 != 0 { + return fmt.Errorf("address %d (%04X) not on a page boundary", sdAddress, sdAddress) + } + if sdStart < sdAddress { + return fmt.Errorf("start address %d (%04X) < load address %d (%04X)", sdStart, sdStart, sdAddress, sdAddress) + } + + if int(sdStart) >= int(sdAddress)+len(contents) { + end := int(sdAddress) + len(contents) + return fmt.Errorf("start address %d (%04X) is beyond load address %d (%04X) + file length = %d (%04X)", + sdStart, sdStart, sdAddress, sdAddress, end, end) + } + + if int(sdStart)+len(contents) > 0xC000 { + end := int(sdStart) + len(contents) + return fmt.Errorf("start address %d (%04X) + file length %d (%04X) = %d (%04X), but we can't load past page 0xBF00", + sdStart, sdStart, len(contents), len(contents), end, end) + } + + sectors := (len(contents) + 255) / 256 + + loader := []byte{ + 0x01, 0xa8, 0xee, 0x06, 0x08, 0xad, 0x4e, 0x08, 0xc9, 0xc0, 0xf0, 0x40, 0x85, 0x27, 0xc8, + 0xc0, 0x10, 0x90, 0x09, 0xf0, 0x05, 0x20, 0x2f, 0x08, 0xa8, 0x2c, 0xa0, 0x01, 0x84, 0x3d, + 0xc8, 0xa5, 0x27, 0xf0, 0xdf, 0x8a, 0x4a, 0x4a, 0x4a, 0x4a, 0x09, 0xc0, 0x48, 0xa9, 0x5b, + 0x48, 0x60, 0xe6, 0x41, 0x06, 0x40, 0x20, 0x37, 0x08, 0x18, 0x20, 0x3c, 0x08, 0xe6, 0x40, + 0xa5, 0x40, 0x29, 0x03, 0x2a, 0x05, 0x2b, 0xa8, 0xb9, 0x80, 0xc0, 0xa9, 0x30, 0x4c, 0xa8, + 0xfc, 0x4c, byte(sdStart), byte(sdStart >> 8), + } + + if len(loader)+sectors+1 > 256 { + return fmt.Errorf("file %q is %d bytes long, max is %d", args[1], len(contents), (255-len(loader))*256) + } + + for len(contents)%256 != 0 { + contents = append(contents, 0) + } + + sd := disk.Empty() + + var track, sector byte + for i := 0; i < len(contents); i += 256 { + sector += 2 + if sector >= sd.Sectors() { + sector = (sd.Sectors() + 1) - sector + if sector == 0 { + track++ + if track >= sd.Tracks() { + return fmt.Errorf("ran out of tracks") + } + } + } + + address := int(sdAddress) + i + loader = append(loader, byte(address>>8)) + if err := sd.WritePhysicalSector(track, sector, contents[i:i+256]); err != nil { + return err + } + } + + loader = append(loader, 0xC0) + for len(loader) < 256 { + loader = append(loader, 0) + } + + if err := sd.WritePhysicalSector(0, 0, loader); err != nil { + return err + } + + f, err := os.Create(args[0]) + if err != nil { + return err + } + _, err = sd.Write(f) + if err != nil { + return err + } + if err = f.Close(); err != nil { + return err + } + return nil +} diff --git a/lib/disk/dsk.go b/lib/disk/dsk.go index 015a712..de0c197 100644 --- a/lib/disk/dsk.go +++ b/lib/disk/dsk.go @@ -40,6 +40,17 @@ func LoadDSK(filename string) (DSK, error) { }, nil } +// Empty creates a .dsk image that is all zeros. +func Empty() DSK { + return DSK{ + data: make([]byte, DOS33DiskBytes), + sectors: 16, + physicalToStored: Dos33PhysicalToLogicalSectorMap, + bytesPerTrack: 16 * 256, + tracks: DOS33Tracks, + } +} + // ReadPhysicalSector reads a single physical sector from the disk. It // always returns 256 byes. func (d DSK) ReadPhysicalSector(track byte, sector byte) ([]byte, error) {