diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..d40acf3 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,3 @@ +brew install sdl sdl_image sdl_mixer sdl_ttf +go get github.com/zellyn/Go-SDL/sdl --with-x11-driver +go get github.com/zellyn/Go-SDL/sdl/audio diff --git a/a2/charrom.go b/a2/charrom.go new file mode 100644 index 0000000..f6b23b7 --- /dev/null +++ b/a2/charrom.go @@ -0,0 +1,50 @@ +package main + +import ( + "io/ioutil" + + "github.com/gonuts/commander" + "github.com/zellyn/goapple2/videoscan" +) + +var cmdCharROM = &commander.Command{ + Run: runCharROM, + UsageLine: "charrom infile outfile", + Short: "convert apple II character ROMs to text and back", + Long: ` +CharROM is a simple character ROM conversion utility. +`, +} + +var decompile bool +var reverse bool + +func init() { + cmdCharROM.Flag.BoolVar(&decompile, "d", false, "decompile: convert from ROM to text.") + cmdCharROM.Flag.BoolVar(&reverse, "r", false, "reverse: put bits in opposite order.") +} + +func runCharROM(cmd *commander.Command, args []string) error { + if len(args) != 2 { + cmd.Usage() + return nil + } + + in, err := ioutil.ReadFile(args[0]) + if err != nil { + panic("Cannot read file: " + args[0]) + } + + if decompile { + out := videoscan.BytesToText(in, reverse) + err := ioutil.WriteFile(args[1], out, 0644) + return err + } + + out, err := videoscan.TextToBytes(in, reverse) + if err != nil { + return err + } + err = ioutil.WriteFile(args[1], out, 0644) + return err +} diff --git a/a2/main.go b/a2/main.go index f727177..94d5105 100644 --- a/a2/main.go +++ b/a2/main.go @@ -16,6 +16,7 @@ func init() { Subcommands: []*commander.Command{ cmdDisasm, cmdDiskConvert, + cmdCharROM, }, Flag: *flag.NewFlagSet("a2", flag.ExitOnError), } diff --git a/videoscan/convert.go b/videoscan/convert.go new file mode 100644 index 0000000..3a38a2b --- /dev/null +++ b/videoscan/convert.go @@ -0,0 +1,94 @@ +// convert.go contains routines to convert from character ROMs to text and back. +package videoscan + +import ( + "fmt" + "strings" +) + +var chars = []rune{'.', '#'} + +func reverseByte(in byte) byte { + hi := in & 0x80 + var out byte + for i := 0; i < 7; i++ { + out = out << 1 + out = out | in&1 + in = in >> 1 + } + out |= hi + return out +} + +func BytesToText(rom []byte, reverse bool) []byte { + result := []byte{} + for i, b := range rom { + if reverse { + b = reverseByte(b) + } + if i > 0 && i%8 == 0 { + result = append(result, '\n') + } + if b >= 0x80 { + result = append(result, 'i') + } else { + result = append(result, 'n') + } + result = append(result, ' ') + for i := 0; i < 7; i++ { + result = append(result, byte(chars[b&1])) + b = b >> 1 + } + result = append(result, '\n') + } + return result +} + +func TextToBytes(text []byte, reverse bool) ([]byte, error) { + result := []byte{} + lines := strings.Split(string(text), "\n") + for i, l := range lines { + l = strings.TrimSpace(l) + lines[i] = l + if i != 0 && i%9 == 8 { + if l != "" { + return nil, fmt.Errorf("line %d: expected empty line, got '%s'", i+1, l) + } + continue + } + if len(l) != 9 { + return nil, fmt.Errorf("line %d: expected 9 characters, got %d: '%s'", i+1, len(l), l) + } + + var hi byte + switch l[0] { + case 'i': + hi = 0x80 + case 'n': + default: + return nil, fmt.Errorf("line %d: should start with 'n' or 'i'; got '%s'", i+1, l[0]) + } + + if l[1] != ' ' { + return nil, fmt.Errorf("line %d: second char should be space; got '%s'", i+1, l[1]) + } + var b byte + var bit byte = 1 + for _, c := range l[2:] { + switch c { + case chars[0]: // do nothing + case chars[1]: + b |= bit + default: + return nil, fmt.Errorf("line %d: expected character: '%s'", i+1, c) + } + bit <<= 1 + } + b |= hi + if reverse { + b = reverseByte(b) + } + result = append(result, b) + } + return result, nil +}