Compare commits

..

No commits in common. "045f204a6bc2dcde5ab14cc4b90a72468635c009" and "468f7c780af684d74bfe0d20a1d176016eea6651" have entirely different histories.

12 changed files with 171 additions and 711 deletions

View File

@ -1,12 +1,9 @@
.PHONY: all .PHONY: all
all: bin/script-decode bin/sbutil bin/just-stats all: bin/script-decode bin/sbutil
bin/script-decode: cmd/script-decode.go script/*.go bin/script-decode: cmd/script-decode.go script/*.go
go build -o $@ $< go build -o $@ $<
bin/sbutil: cmd/sbutil.go rom/*.go bin/sbutil: cmd/sbutil.go rom/*.go
go build -o $@ $< go build -o $@ $<
bin/just-stats: cmd/just-stats.go script/*.go
go build -o $@ $<

View File

@ -1,85 +0,0 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"io/fs"
"github.com/alexflint/go-arg"
"git.zorchenhimer.com/Zorchenhimer/go-studybox/script"
)
type Arguments struct {
BaseDir string `arg:"positional,required"`
Output string `arg:"positional,required"`
}
type Walker struct {
Found []string
}
func (w *Walker) WalkFunc(path string, info fs.DirEntry, err error) error {
if info.IsDir() {
return nil
}
if strings.HasSuffix(path, "_scriptData.dat") {
w.Found = append(w.Found, path)
}
return nil
}
func run(args *Arguments) error {
w := &Walker{Found: []string{}}
err := filepath.WalkDir(args.BaseDir, w.WalkFunc)
if err != nil {
return err
}
fmt.Printf("found %d scripts\n", len(w.Found))
stats := make(script.Stats)
for _, file := range w.Found {
fmt.Println(file)
scr, err := script.ParseFile(file, 0x0000)
if err != nil {
if scr != nil {
for _, token := range scr.Tokens {
fmt.Println(token.String(scr.Labels))
}
}
return err
}
stats.Add(scr.Stats())
}
outfile, err := os.Create(args.Output)
if err != nil {
return err
}
defer outfile.Close()
_, err = stats.WriteTo(outfile)
if err != nil {
return err
}
return nil
}
func main() {
args := &Arguments{}
arg.MustParse(args)
err := run(args)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@ -1,198 +0,0 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/alexflint/go-arg"
"git.zorchenhimer.com/Zorchenhimer/go-studybox/rom"
)
type Arguments struct {
Pack *ArgPack `arg:"subcommand:pack"`
UnPack *ArgUnPack `arg:"subcommand:unpack"`
}
type ArgPack struct {
Input string `arg:"positional,required"`
}
type ArgUnPack struct {
Input string `arg:"positional,required" help:".json metadata file"`
NoAudio bool `arg:"--no-audio" help:"Do not unpack the audio portion"`
OutDir string `arg:"--dir" help:"Base directory to unpack into (json file will be here)"`
}
func main() {
args := &Arguments{}
arg.MustParse(args)
var err error
switch {
case args.Pack != nil:
err = pack(args.Pack)
case args.UnPack != nil:
err = unpack(args.UnPack)
default:
fmt.Fprintln(os.Stderr, "Missing command")
os.Exit(1)
}
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
}
func pack(args *ArgPack) error {
if !strings.HasSuffix(args.Input, ".json") {
return fmt.Errorf("Pack needs a json file as input")
}
//fmt.Println("-- Processing " + args.Input)
sb, err := rom.Import(args.Input)
if err != nil {
return err
}
//outDir := filepath.Base(args.Input)
//outDir = strings.ReplaceAll(outDir, ".json", "_output")
//err = os.MkdirAll(outDir, 0777)
//if err != nil {
// return err
//}
//err = sb.Export(outDir)
//if err != nil {
// return err
//}
// TODO: put this in the json file?
outname := args.Input[:len(args.Input)-len(".json")]+".studybox"
fmt.Println(outname)
err = sb.Write(outname)
if err != nil {
return err
}
return nil
}
func unpack(args *ArgUnPack) error {
//fmt.Println("-- Processing " + file)
if !strings.HasSuffix(args.Input, ".studybox") {
return fmt.Errorf("Input needs to be a .studybox file.")
}
//outDir := filepath.Base(args.Input)
outbase := filepath.Base(args.Input[:len(args.Input)-len(".studybox")])
outdir := filepath.Dir(args.Input)
if args.OutDir != "" {
outdir = args.OutDir
}
outname := filepath.Join(outdir, outbase)
fmt.Println(outname)
//outDir = strings.ReplaceAll(outDir, ".studybox", "")
err := os.MkdirAll(outname, 0777)
if err != nil {
return err
}
sb, err := rom.ReadFile(args.Input)
if err != nil {
return err
}
err = sb.Export(outname, !args.NoAudio)
if err != nil {
return err
}
return nil
}
//func main_old() {
// if len(os.Args) < 3 {
// fmt.Println("Missing command")
// os.Exit(1)
// }
//
// matches := []string{}
// for _, glob := range os.Args[2:len(os.Args)] {
// m, err := filepath.Glob(glob)
// if err != nil {
// fmt.Println(err)
// os.Exit(1)
// }
// matches = append(matches, m...)
// }
//
// if len(matches) == 0 {
// fmt.Println("No files found!")
// }
//
// switch strings.ToLower(os.Args[1]) {
// case "unpack":
// for _, file := range matches {
// fmt.Println("-- Processing " + file)
// outDir := filepath.Base(file)
// outDir = strings.ReplaceAll(outDir, ".studybox", "")
//
// err := os.MkdirAll(outDir, 0777)
// if err != nil {
// fmt.Println(err)
// continue
// }
//
// sb, err := rom.ReadFile(file)
// if err != nil {
// fmt.Println(err)
// continue
// }
//
// err = sb.Export(outDir)
// if err != nil {
// fmt.Println(err)
// }
// }
// case "pack":
// for _, file := range matches {
// fmt.Println("-- Processing " + file)
// sb, err := rom.Import(file)
// if err != nil {
// fmt.Println(err)
// continue
// }
//
// outDir := filepath.Base(file)
// outDir = strings.ReplaceAll(outDir, ".json", "_output")
//
// err = os.MkdirAll(outDir, 0777)
// if err != nil {
// fmt.Println(err)
// continue
// }
//
// err = sb.Export(outDir)
// if err != nil {
// fmt.Println(err)
// continue
// }
//
// // TODO: put this in the json file?
//
// err = sb.Write(outDir + ".studybox")
// if err != nil {
// fmt.Println(err)
// continue
// }
//
// }
// }
//}

View File

@ -5,11 +5,8 @@ import (
"os" "os"
"strings" "strings"
"strconv" "strconv"
"bufio"
"slices"
"github.com/alexflint/go-arg" "github.com/alexflint/go-arg"
"git.zorchenhimer.com/Zorchenhimer/go-studybox/script" "git.zorchenhimer.com/Zorchenhimer/go-studybox/script"
) )
@ -17,9 +14,6 @@ type Arguments struct {
Input string `arg:"positional,required"` Input string `arg:"positional,required"`
Output string `arg:"positional"` Output string `arg:"positional"`
StartAddr string `arg:"--start" default:"0x6000" help:"base address for the start of the script"` StartAddr string `arg:"--start" default:"0x6000" help:"base address for the start of the script"`
StatsFile string `arg:"--stats" help:"file to write some statistics to"`
LabelFile string `arg:"--labels" help:"file containing address/label pairs"`
start int start int
} }
@ -44,18 +38,6 @@ func run(args *Arguments) error {
return err return err
} }
if args.LabelFile != "" {
labels, err := parseLabelFile(args.LabelFile)
if err != nil {
return err
}
for _, label := range labels {
//fmt.Printf("%#v\n", label)
scr.Labels[label.Address] = label
}
}
outfile := os.Stdout outfile := os.Stdout
if args.Output != "" { if args.Output != "" {
outfile, err = os.Create(args.Output) outfile, err = os.Create(args.Output)
@ -79,79 +61,9 @@ func run(args *Arguments) error {
fmt.Fprintln(outfile, token.String(scr.Labels)) fmt.Fprintln(outfile, token.String(scr.Labels))
} }
if args.StatsFile != "" {
statfile, err := os.Create(args.StatsFile)
if err != nil {
return fmt.Errorf("Unable to create stats file: %w", err)
}
defer statfile.Close()
//err = scr.WriteStats(statfile)
_, err = scr.Stats().WriteTo(statfile)
if err != nil {
return fmt.Errorf("Error writing stats: %w", err)
}
}
return nil return nil
} }
func parseLabelFile(filename string) ([]*script.Label, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
labels := []*script.Label{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
line = strings.ReplaceAll(line, "\t", " ")
parts := strings.Split(line, " ")
parts = slices.DeleteFunc(parts, func(str string) bool {
return str == ""
})
if len(parts) < 2 {
fmt.Println("Ignoring", line)
continue
}
if strings.HasPrefix(parts[0], "$") {
parts[0] = "0x"+parts[0][1:]
}
addr, err := strconv.ParseInt(parts[0], 0, 32)
if err != nil {
fmt.Printf("Address parse error for %q: %s\n", line, err)
continue
}
lbl := &script.Label{
Name: parts[1],
Address: int(addr),
}
if lbl.Name == "$" {
lbl.Name = ""
}
if len(parts) > 2 {
lbl.Comment = strings.Join(parts[2:], " ")
}
labels = append(labels, lbl)
}
return labels, nil
}
func main() { func main() {
args := &Arguments{} args := &Arguments{}
arg.MustParse(args) arg.MustParse(args)

View File

@ -6,7 +6,7 @@ import (
"os" "os"
) )
func (sb *StudyBox) Export(directory string, includeAudio bool) error { func (sb *StudyBox) Export(directory string) error {
sbj := StudyBoxJson{ sbj := StudyBoxJson{
Version: 1, Version: 1,
Pages: []jsonPage{}, Pages: []jsonPage{},
@ -106,7 +106,7 @@ func (sb *StudyBox) Export(directory string, includeAudio bool) error {
return fmt.Errorf("[WARN] unknown end data type: %s\n", jData.Type) return fmt.Errorf("[WARN] unknown end data type: %s\n", jData.Type)
} }
err = os.WriteFile(jData.File, rawData, 0666) err = os.WriteFile(jData.File, rawData, 0777)
if err != nil { if err != nil {
return fmt.Errorf("Unable to write data to file [%q]: %v", jData.File, err) return fmt.Errorf("Unable to write data to file [%q]: %v", jData.File, err)
} }
@ -133,17 +133,15 @@ func (sb *StudyBox) Export(directory string, includeAudio bool) error {
return fmt.Errorf("Missing audio!") return fmt.Errorf("Missing audio!")
} }
if includeAudio {
err := sb.Audio.WriteToFile(directory + "/audio") err := sb.Audio.WriteToFile(directory + "/audio")
if err != nil { if err != nil {
return fmt.Errorf("Error writing audio file: %v", err) return fmt.Errorf("Error writing audio file: %v", err)
} }
}
rawJson, err := json.MarshalIndent(sbj, "", " ") rawJson, err := json.MarshalIndent(sbj, "", " ")
if err != nil { if err != nil {
return err return err
} }
return os.WriteFile(directory+".json", rawJson, 0666) return os.WriteFile(directory+".json", rawJson, 0777)
} }

View File

@ -26,8 +26,7 @@ func (ph *packetHeader) RawBytes() []byte {
} }
func (ph *packetHeader) Asm() string { func (ph *packetHeader) Asm() string {
return fmt.Sprintf("header %d [Page %d] ; Checksum: %02X", return fmt.Sprintf("header %d [Page %d] ; Checksum: %02X", ph.PageNumber, ph.PageNumber+1, ph.Checksum)
ph.PageNumber, ph.PageNumber+1, ph.Checksum)
} }
func (ph *packetHeader) Address() int { func (ph *packetHeader) Address() int {
@ -128,8 +127,7 @@ func (p *packetBulkData) Asm() string {
// data = append(data, fmt.Sprintf("$%02X", b)) // data = append(data, fmt.Sprintf("$%02X", b))
//} //}
//return fmt.Sprintf("[%08X] data %s ; Length %d Checksum: %02X", p.address, strings.Join(data, ", "), len(p.Data), p.checksum) //return fmt.Sprintf("[%08X] data %s ; Length %d Checksum: %02X", p.address, strings.Join(data, ", "), len(p.Data), p.checksum)
return fmt.Sprintf("data $%02X, [...], $%02X ; Length:%d Checksum:%02X", return fmt.Sprintf("data $%02X, [...], $%02X ; Length %d Checksum: %02X", p.Data[0], p.Data[len(p.Data)-1], len(p.Data), p.checksum)
p.Data[0], p.Data[len(p.Data)-1], len(p.Data), p.checksum)
} }
func (p *packetBulkData) RawBytes() []byte { func (p *packetBulkData) RawBytes() []byte {
@ -180,7 +178,7 @@ func (p *packetMarkDataStart) dataType() string {
} }
func (p *packetMarkDataStart) Asm() string { func (p *packetMarkDataStart) Asm() string {
return fmt.Sprintf("mark_datatype_start Type:%s ArgA:$%02X ArgB:$%02X ; Checksum:%02X", return fmt.Sprintf("mark_datatype_start %s $%02X $%02X ; Checksum: %02X",
p.dataType(), p.ArgA, p.ArgB, p.checksum) p.dataType(), p.ArgA, p.ArgB, p.checksum)
} }
@ -231,31 +229,30 @@ func (p *packetMarkDataEnd) RawBytes() []byte {
} }
func (p *packetMarkDataEnd) Asm() string { func (p *packetMarkDataEnd) Asm() string {
var typeStr string var tstr string
switch p.Type & 0x0F { switch p.Type & 0x0F {
case 2: case 2:
typeStr = "script" tstr = "script"
case 3: case 3:
typeStr = "nametable" tstr = "nametable"
case 4: case 4:
typeStr = "pattern" tstr = "pattern"
case 5: case 5:
typeStr = "delay" tstr = "delay"
default: default:
typeStr = fmt.Sprintf("unknown $%02X", p.Type) tstr = fmt.Sprintf("unknown $%02X", p.Type)
} }
if p.Reset { if p.Reset {
typeStr += " reset_state" tstr += " reset_state"
} }
s := []string{} s := []string{}
for _, b := range p.RawBytes() { for _, b := range p.RawBytes() {
s = append(s, fmt.Sprintf("%02X", b)) s = append(s, fmt.Sprintf("%02X", b))
} }
return fmt.Sprintf("mark_datatype_end %s ; %s Checksum: %02X",
return fmt.Sprintf("mark_datatype_end %s ; Raw:[%s] Checksum:%02X", tstr, strings.Join(s, " "), p.checksum)
typeStr, strings.Join(s, " "), p.checksum)
} }
func (p *packetMarkDataEnd) Address() int { func (p *packetMarkDataEnd) Address() int {

View File

@ -123,7 +123,7 @@ func (ta TapeAudio) String() string {
func (ta *TapeAudio) WriteToFile(basename string) error { func (ta *TapeAudio) WriteToFile(basename string) error {
ext := "." + strings.ToLower(string(ta.Format)) ext := "." + strings.ToLower(string(ta.Format))
return os.WriteFile(basename+ext, ta.Data, 0666) return os.WriteFile(basename+ext, ta.Data, 0777)
} }
func (ta *TapeAudio) ext() string { func (ta *TapeAudio) ext() string {

View File

@ -1,7 +1,6 @@
package script package script
import ( import (
"fmt"
) )
var InstrMap map[byte]*Instruction var InstrMap map[byte]*Instruction
@ -14,206 +13,186 @@ func init() {
} }
var Instructions []*Instruction = []*Instruction{ var Instructions []*Instruction = []*Instruction{
&Instruction{ 0x80, 0, 0, 0, false, "play_beep"}, &Instruction{ 0x80, 0, 0, 0, "play_beep"},
&Instruction{ 0x81, 0, 0, 0, false, "halt"}, &Instruction{ 0x81, 0, 0, 0, "halt"},
&Instruction{ 0x82, 0, 0, 0, false, "tape_nmi_shenanigans"}, &Instruction{ 0x82, 0, 0, 0, "tape_nmi_shenanigans"},
&Instruction{ 0x83, 0, 0, 0, false, "tape_wait"}, &Instruction{ 0x83, 0, 0, 0, "tape_wait"},
// Jump to the inline word, in the VM // Jump to the inline word, in the VM
&Instruction{ 0x84, 0, 2, 0, false, "jump_abs"}, &Instruction{ 0x84, 0, 2, 0, "jump_abs"},
// Call a routine at the inline word, in the VM // Call a routine at the inline word, in the VM
&Instruction{ 0x85, 0, 2, 0, false, "call_abs"}, &Instruction{ 0x85, 0, 2, 0, "call_abs"},
// Return from a previous call // Return from a previous call
&Instruction{ 0x86, 0, 0, 0, false, "return"}, &Instruction{ 0x86, 0, 0, 0, "return"},
&Instruction{ 0x87, 0, 0, 0, "loop"},
&Instruction{ 0x88, 0, 0, 0, "play_sound"},
&Instruction{ 0x89, 3, 0, 0, ""},
&Instruction{ 0x8A, 0, 2, 0, "pop_string_to_addr"},
&Instruction{ 0x8B, 1, 0, 0, ""},
&Instruction{ 0x8C, 0, 0, 1, "string_length"},
&Instruction{ 0x8D, 0, 0, 1, "string_to_int"},
&Instruction{ 0x8E, 0, 0, 16, "string_concat"},
&Instruction{ 0x8F, 0, 0, 1, "strings_equal"},
&Instruction{ 0x87, 0, 0, 0, false, "loop"}, &Instruction{ 0x90, 0, 0, 1, "strings_not_equal"},
&Instruction{ 0x88, 0, 0, 0, false, "play_sound"}, &Instruction{ 0x91, 0, 0, 1, "string_less_than"},
&Instruction{ 0x89, 3, 0, 0, false, ""}, &Instruction{ 0x92, 0, 0, 1, "string_less_than_equal"},
&Instruction{ 0x8A, 0, 2, 0, false, "pop_string_to_addr"}, &Instruction{ 0x93, 0, 0, 1, "string_greater_than_equal"},
&Instruction{ 0x8B, 1, 0, 0, false, ""}, &Instruction{ 0x94, 0, 0, 1, "string_greater_than"},
&Instruction{ 0x8C, 0, 0, 1, false, "string_length"},
&Instruction{ 0x8D, 0, 0, 1, false, "string_to_int"},
&Instruction{ 0x8E, 0, 0, 16, false, "string_concat"},
&Instruction{ 0x8F, 0, 0, 1, false, "strings_equal"},
&Instruction{ 0x90, 0, 0, 1, false, "strings_not_equal"},
&Instruction{ 0x91, 0, 0, 1, false, "string_less_than"},
&Instruction{ 0x92, 0, 0, 1, false, "string_less_than_equal"},
&Instruction{ 0x93, 0, 0, 1, false, "string_greater_than_equal"},
&Instruction{ 0x94, 0, 0, 1, false, "string_greater_than"},
// Sets some tape NMI stuff if the byte at $0740 is not zero. // Sets some tape NMI stuff if the byte at $0740 is not zero.
// Will call 0x82 tape_nmi_shenanigans if $0740 != 0 &Instruction{ 0x95, 1, 0, 0, "tape_nmi_shenigans_set"},
&Instruction{ 0x95, 1, 0, 0, false, "tape_nmi_shenigans_set"}, &Instruction{ 0x96, 0, 2, 0, "set_word_4E"},
&Instruction{ 0x97, 2, 0, 0, ""},
&Instruction{ 0x98, 1, 0, 0, ""},
&Instruction{ 0x99, 1, 0, 0, ""},
&Instruction{ 0x9A, 0, 0, 0, ""},
&Instruction{ 0x9B, 0, 0, 0, "halt"},
&Instruction{ 0x9C, 0, 0, 0, "toggle_44FE"},
&Instruction{ 0x9D, 2, 0, 0, "something_tape"},
&Instruction{ 0x96, 0, 2, 0, true, "set_word_4E"}, // Calls 0xEB draw_overlay. Seems to draw a whole screen.
&Instruction{ 0x97, 2, 0, 0, false, ""}, &Instruction{ 0x9E, 2, 0, 0, ""},
&Instruction{ 0x98, 1, 0, 0, false, ""}, &Instruction{ 0x9F, 6, 0, 0, ""},
&Instruction{ 0x99, 1, 0, 0, false, ""},
&Instruction{ 0x9A, 0, 0, 0, false, ""},
&Instruction{ 0x9B, 0, 0, 0, false, "halt"},
&Instruction{ 0x9C, 0, 0, 0, false, "toggle_44FE"},
&Instruction{ 0x9D, 2, 0, 0, false, "something_tape"},
// Calls 0xEB draw_overlay. Draws the whole screen from data previously &Instruction{ 0xA0, 2, 0, 1, ""},
// loaded from the tape. &Instruction{ 0xA1, 1, 0, 0, ""},
&Instruction{ 0x9E, 2, 0, 0, false, "draw_and_show_screen"}, &Instruction{ 0xA2, 1, 0, 0, "buffer_palette"},
&Instruction{ 0x9F, 6, 0, 0, false, ""},
&Instruction{ 0xA0, 2, 0, 1, false, ""},
&Instruction{ 0xA1, 1, 0, 0, false, ""},
&Instruction{ 0xA2, 1, 0, 0, false, "buffer_palette"},
// Possibly a sprite setup routine. loads up some CHR data and some palette // Possibly a sprite setup routine. loads up some CHR data and some palette
// data. // data.
&Instruction{ 0xA3, 1, 0, 0, false, "sprite_setup"}, &Instruction{ 0xA3, 1, 0, 0, "sprite_setup"},
&Instruction{ 0xA4, 3, 0, 0, false, ""}, &Instruction{ 0xA4, 3, 0, 0, ""},
&Instruction{ 0xA5, 1, 0, 0, false, "set_470A"}, &Instruction{ 0xA5, 1, 0, 0, "set_470A"},
&Instruction{ 0xA6, 1, 0, 0, false, "set_470B"}, &Instruction{ 0xA6, 1, 0, 0, "set_470B"},
// jump to the inline address, in assembly, not in the VM // jump to the inline address, in assembly, not in the VM
// (built-in ACE, lmao) // (built-in ACE, lmao)
// Will not jump to anything at or above $8000 or below $5000. &Instruction{ 0xA7, 0, 0, 0, "call_asm"},
// Addresses in $5000-$5FFF use $470A as the bank ID &Instruction{ 0xA8, 5, 0, 0, ""},
// Addresses in $6000-$7FFF use $470B as the bank ID &Instruction{ 0xA9, 1, 0, 0, ""},
&Instruction{ 0xA7, 0, 0, 0, false, "call_asm"}, &Instruction{ 0xAA, 1, 0, 0, ""},
&Instruction{ 0xAB, 1, 0, 0, "long_call"},
&Instruction{ 0xAC, 0, 0, 0, "long_return"},
&Instruction{ 0xAD, 1, 0, 1, "absolute"},
&Instruction{ 0xAE, 1, 0, 1, "compare"},
&Instruction{ 0xAF, 0, 0, 1, ""},
&Instruction{ 0xA8, 5, 0, 0, false, ""}, &Instruction{ 0xB0, 1, 0, 16, ""},
&Instruction{ 0xA9, 1, 0, 0, false, ""}, &Instruction{ 0xB1, 1, 0, 16, "to_hex_string"},
&Instruction{ 0xAA, 1, 0, 0, false, ""}, &Instruction{ 0xB2, 0, 0, 1, ""},
&Instruction{ 0xAB, 1, 0, 0, false, "long_call"}, &Instruction{ 0xB3, 7, 0, 0, ""}, // possible 16-bit inline?
&Instruction{ 0xAC, 0, 0, 0, false, "long_return"}, &Instruction{ 0xB4, 0, 0, 0, ""},
&Instruction{ 0xAD, 1, 0, 1, false, "absolute"}, &Instruction{ 0xB5, 0, 0, 0, ""},
&Instruction{ 0xAE, 1, 0, 1, false, "compare"}, &Instruction{ 0xB6, 0, 0, 0, ""},
&Instruction{ 0xAF, 0, 0, 1, false, ""},
&Instruction{ 0xB0, 1, 0, 16, false, ""},
&Instruction{ 0xB1, 1, 0, 16, false, "to_hex_string"},
&Instruction{ 0xB2, 0, 0, 1, false, ""},
&Instruction{ 0xB3, 7, 0, 0, false, ""}, // possible 16-bit inline?
&Instruction{ 0xB4, 0, 0, 0, false, ""},
&Instruction{ 0xB5, 0, 0, 0, false, ""},
&Instruction{ 0xB6, 0, 0, 0, false, ""},
// Uses the inline word as a pointer, and pushes the byte value at that // Uses the inline word as a pointer, and pushes the byte value at that
// address to the stack. // address to the stack.
&Instruction{ 0xB7, 0, 2, 0, false, "deref_ptr_inline"}, &Instruction{ 0xB7, 0, 2, 0, "deref_ptr_inline"},
// Pushes the inline word to the stack // Pushes the inline word to the stack
&Instruction{ 0xB8, 0, 2, 0, true, "push_word"}, &Instruction{ 0xB8, 0, 2, 0, "push_word"},
&Instruction{ 0xB9, 0, 2, 0, false, "push_word_indexed"}, &Instruction{ 0xB9, 0, 2, 0, "push_word_indexed"},
&Instruction{ 0xBA, 0, 2, 0, false, "push"}, &Instruction{ 0xBA, 0, 2, 0, "push"},
&Instruction{ 0xBB, 0, -1, 0, false, "push_data"}, &Instruction{ 0xBB, 0, -1, 0, "push_data"},
&Instruction{ 0xBC, 0, 2, 0, false, "push_string_from_table"}, &Instruction{ 0xBC, 0, 2, 0, "push_string_from_table"},
// Pops a byte off the stack and stores it at the inline address. // Pops a byte off the stack and stores it at the inline address.
&Instruction{ 0xBD, 0, 2, 0, false, "pop_into"}, &Instruction{ 0xBD, 0, 2, 0, "pop"},
&Instruction{ 0xBE, 0, 2, 0, "write_to_table"},
&Instruction{ 0xBE, 0, 2, 0, false, "write_to_table"}, &Instruction{ 0xBF, 0, 2, 0, "jump_not_zero"},
&Instruction{ 0xBF, 0, 2, 0, false, "jump_not_zero"},
// One byte off stack; jumps to inline if byte is zero // One byte off stack; jumps to inline if byte is zero
&Instruction{ 0xC0, 1, 2, 0, false, "jump_zero"}, &Instruction{ 0xC0, 1, 2, 0, "jump_zero"},
&Instruction{ 0xC1, 1, -2, 0, false, "jump_switch"}, &Instruction{ 0xC1, 1, -2, 0, "jump_switch"},
&Instruction{ 0xC2, 1, 0, 1, false, "equals_zero"}, &Instruction{ 0xC2, 1, 0, 1, "equals_zero"},
&Instruction{ 0xC3, 2, 0, 1, false, "and_a_b"}, &Instruction{ 0xC3, 2, 0, 1, "and_a_b"},
&Instruction{ 0xC4, 2, 0, 1, false, "or_a_b"}, &Instruction{ 0xC4, 2, 0, 1, "or_a_b"},
&Instruction{ 0xC5, 2, 0, 1, false, "equal"}, &Instruction{ 0xC5, 2, 0, 1, "equal"},
// Two bytes off stack; result pushed back; 1 if A == B, 0 if A != B // Two bytes off stack; result pushed back; 1 if A == B, 0 if A != B
&Instruction{ 0xC6, 2, 0, 1, false, "not_equal"}, &Instruction{ 0xC6, 2, 0, 1, "not_equal"},
&Instruction{ 0xC7, 2, 0, 1, "less_than"},
&Instruction{ 0xC8, 2, 0, 1, "less_than_equal"},
&Instruction{ 0xC9, 2, 0, 1, "greater_than"},
&Instruction{ 0xCA, 2, 0, 1, "greater_than_equal"},
&Instruction{ 0xCB, 2, 0, 1, "sum"},
&Instruction{ 0xCC, 2, 0, 1, "subtract"},
&Instruction{ 0xCD, 2, 0, 1, "multiply"},
&Instruction{ 0xCE, 2, 0, 1, "signed_divide"},
&Instruction{ 0xCF, 1, 0, 1, "negate"},
&Instruction{ 0xC7, 2, 0, 1, false, "less_than"}, &Instruction{ 0xD0, 1, 0, 1, "modulus"},
&Instruction{ 0xC8, 2, 0, 1, false, "less_than_equal"}, &Instruction{ 0xD1, 2, 0, 1, "expansion_controller"},
&Instruction{ 0xC9, 2, 0, 1, false, "greater_than"}, &Instruction{ 0xD2, 2, 0, 1, ""},
&Instruction{ 0xCA, 2, 0, 1, false, "greater_than_equal"}, &Instruction{ 0xD3, 2, 0, 16, ""},
&Instruction{ 0xCB, 2, 0, 1, false, "sum"}, &Instruction{ 0xD4, 3, 0, 0, ""},
&Instruction{ 0xCC, 2, 0, 1, false, "subtract"}, &Instruction{ 0xD5, 1, 0, 0, "wait_for_tape"},
&Instruction{ 0xCD, 2, 0, 1, false, "multiply"}, &Instruction{ 0xD6, 1, 0, 16, "truncate_string"},
&Instruction{ 0xCE, 2, 0, 1, false, "signed_divide"}, &Instruction{ 0xD7, 1, 0, 16, "trim_string"},
&Instruction{ 0xCF, 1, 0, 1, false, "negate"}, &Instruction{ 0xD8, 1, 0, 16, "trim_string_start"},
&Instruction{ 0xD9, 2, 0, 16, "trim_string_start"},
&Instruction{ 0xD0, 1, 0, 1, false, "modulus"}, &Instruction{ 0xDA, 1, 0, 16, "to_int_string"},
&Instruction{ 0xD1, 2, 0, 1, false, "expansion_controller"}, &Instruction{ 0xDB, 3, 0, 0, ""},
&Instruction{ 0xD2, 2, 0, 1, false, ""}, &Instruction{ 0xDC, 5, 0, 0, ""},
&Instruction{ 0xD3, 2, 0, 16, false, ""},
&Instruction{ 0xD4, 3, 0, 0, false, "set_cursor_location"},
// Wait for ArgA itterations. "itterations" is undefined as of now. (data from tape?)
&Instruction{ 0xD5, 1, 0, 0, false, "wait_for_tape"},
&Instruction{ 0xD6, 1, 0, 16, false, "truncate_string"},
&Instruction{ 0xD7, 1, 0, 16, false, "trim_string"},
&Instruction{ 0xD8, 1, 0, 16, false, "trim_string_start"},
&Instruction{ 0xD9, 2, 0, 16, false, "trim_string_start"},
&Instruction{ 0xDA, 1, 0, 16, false, "to_int_string"},
&Instruction{ 0xDB, 3, 0, 0, false, ""},
&Instruction{ 0xDC, 5, 0, 0, false, ""},
// ArgA, ArgB: X,Y of corner A // ArgA, ArgB: X,Y of corner A
// ArgC, ArgD: X,Y of corner B // ArgC, ArgD: X,Y of corner B
// ArgE: fill value. This is an index into // ArgE: fill value. This is an index into
// the table at $B451. // the table at $B451.
// Fills a box with a tile // Fills a box with a tile
&Instruction{ 0xDD, 5, 0, 0, false, "fill_box"}, &Instruction{ 0xDD, 5, 0, 0, "fill_box"},
&Instruction{ 0xDE, 3, 0, 0, false, ""}, &Instruction{ 0xDE, 3, 0, 0, ""},
&Instruction{ 0xDF, 3, 0, 0, false, ""}, &Instruction{ 0xDF, 3, 0, 0, ""},
// Divide and return remainder &Instruction{ 0xE0, 2, 0, 1, "signed_divide"},
&Instruction{ 0xE0, 2, 0, 1, false, "modulo"}, &Instruction{ 0xE1, 4, 0, 0, ""},
&Instruction{ 0xE2, 7, 0, 0, "setup_sprite"},
&Instruction{ 0xE1, 4, 0, 0, false, ""},
&Instruction{ 0xE2, 7, 0, 0, false, "setup_sprite"},
// Pops a word off the stack, uses it as a pointer, and pushes the byte // Pops a word off the stack, uses it as a pointer, and pushes the byte
// value at that address to the stack. // value at that address to the stack.
&Instruction{ 0xE3, 1, 0, 1, false, "deref_ptr_stack"}, &Instruction{ 0xE3, 1, 0, 1, "deref_ptr_stack"},
&Instruction{ 0xE4, 2, 0, 0, false, "swap_ram_bank"}, &Instruction{ 0xE4, 2, 0, 0, "swap_ram_bank"},
&Instruction{ 0xE5, 1, 0, 0, false, "disable_sprite"}, &Instruction{ 0xE5, 1, 0, 0, "disable_sprite"},
&Instruction{ 0xE6, 1, 0, 0, "tape_nmi_setup"},
// Will call 0x82 tape_nmi_shenanigans if $0740 != 0 &Instruction{ 0xE7, 7, 0, 0, ""},
&Instruction{ 0xE6, 1, 0, 0, false, "tape_nmi_setup"}, &Instruction{ 0xE8, 1, 0, 0, "setup_tape_nmi"},
&Instruction{ 0xE9, 0, 1, 0, "setup_loop"},
&Instruction{ 0xE7, 7, 0, 0, false, "draw_metasprite"}, &Instruction{ 0xEA, 0, 0, 0, "string_write_to_table"},
&Instruction{ 0xE8, 1, 0, 0, false, "setup_tape_nmi"},
&Instruction{ 0xE9, 0, 1, 0, false, "setup_loop"},
&Instruction{ 0xEA, 0, 0, 0, false, "string_write_to_table"},
// Reads and saves tiles from the PPU, then draws over them. // Reads and saves tiles from the PPU, then draws over them.
// This is used to draw dialog boxes, so saving what it overwrites // This is used to draw dialog boxes, so saving what it overwrites
// so it can re-draw them later makes sense. // so it can re-draw them later makes sense.
// Not sure what the arguments actually mean. // Not sure what the arguments actually mean.
// ArgB and ArgC are probably coordinates. // ArgB and ArgC are probably coordinates.
&Instruction{ 0xEB, 4, 0, 0, false, "draw_overlay"}, &Instruction{ 0xEB, 4, 0, 0, "draw_overlay"},
&Instruction{ 0xEC, 2, 0, 0, false, "scroll"}, &Instruction{ 0xEC, 2, 0, 0, "scroll"},
&Instruction{ 0xED, 1, 0, 0, false, "disable_sprites"}, &Instruction{ 0xED, 1, 0, 0, "disable_sprites"},
&Instruction{ 0xEE, 1, -3, 0, "call_switch"},
&Instruction{ 0xEF, 6, 0, 0, ""},
&Instruction{ 0xEE, 1, -3, 0, false, "call_switch"}, &Instruction{ 0xF0, 0, 0, 0, "disable_sprites"},
&Instruction{ 0xEF, 6, 0, 0, false, ""}, &Instruction{ 0xF1, 4, 0, 0, ""},
&Instruction{ 0xF2, 0, 0, 0, "halt"},
&Instruction{ 0xF0, 0, 0, 0, false, "disable_sprites"}, &Instruction{ 0xF3, 0, 0, 0, "halt"},
&Instruction{ 0xF1, 4, 0, 0, false, ""}, &Instruction{ 0xF4, 0, 0, 16, "halt"},
&Instruction{ 0xF2, 0, 0, 0, false, "halt"}, &Instruction{ 0xF5, 1, 0, 1, "halt"},
&Instruction{ 0xF3, 0, 0, 0, false, "halt"}, &Instruction{ 0xF6, 1, 0, 0, "halt"},
&Instruction{ 0xF4, 0, 0, 16, false, "halt"}, &Instruction{ 0xF7, 0, 0, 0, "halt"},
&Instruction{ 0xF5, 1, 0, 1, false, "halt"}, &Instruction{ 0xF8, 2, 0, 0, "halt"},
&Instruction{ 0xF6, 1, 0, 0, false, "halt"}, &Instruction{ 0xF9, 0, 0, 1, ""},
&Instruction{ 0xF7, 0, 0, 0, false, "halt"}, &Instruction{ 0xFA, 0, 0, 1, ""},
&Instruction{ 0xF8, 2, 0, 0, false, "halt"}, &Instruction{ 0xFB, 1, 0, 0, "jump_arg_a"},
&Instruction{ 0xF9, 0, 0, 1, false, ""}, &Instruction{ 0xFC, 2, 0, 1, ""},
&Instruction{ 0xFA, 0, 0, 1, false, ""}, &Instruction{ 0xFD, 0, 0, 16, "halt"},
&Instruction{ 0xFB, 1, 0, 0, false, "jump_arg_a"}, &Instruction{ 0xFE, 4, 0, 0, ""},
&Instruction{ 0xFC, 2, 0, 1, false, ""},
&Instruction{ 0xFD, 0, 0, 16, false, "halt"},
&Instruction{ 0xFE, 4, 2, 0, false, "draw_rom_char"},
// code handler is $FFFF // code handler is $FFFF
&Instruction{ 0xFF, 0, 0, 0, false, "break_engine"}, &Instruction{ 0xFF, 0, 0, 0, "break_engine"},
} }
type Instruction struct { type Instruction struct {
@ -222,9 +201,8 @@ type Instruction struct {
OpCount int // inline operands. length in bytes. OpCount int // inline operands. length in bytes.
// -1: nul-terminated // -1: nul-terminated
// -2: first byte is count, followed by that number of words // -2: first byte is count, followed by that number of words
// -3: like -2, but with no default. code continues after list on OOB // -3: like -2, but with one additional word
RetCount int // return count RetCount int // return count
InlineImmediate bool // don't turn the inline value into a variable
Name string Name string
} }
@ -234,7 +212,7 @@ func (i Instruction) String() string {
return i.Name return i.Name
} }
return fmt.Sprintf("unknown_0x%02X", i.Opcode) //return fmt.Sprintf("$%02X_unknown", i.Opcode)
//return "unknown" return "unknown"
} }

View File

@ -5,42 +5,6 @@ import (
"os" "os"
) )
type Label struct {
Address int
Name string
Comment string
FarLabel bool
}
func AutoLabel(address int) *Label {
return &Label{
Address: address,
Name: fmt.Sprintf("L%04X", address),
}
}
func AutoLabelVar(address int) *Label {
return &Label{
Address: address,
Name: fmt.Sprintf("Var_%04X", address),
}
}
func AutoLabelFar(address int) *Label {
return &Label{
Address: address,
Name: fmt.Sprintf("F%04X", address),
FarLabel: true,
}
}
func NewLabel(address int, name string) *Label {
return &Label{
Address: address,
Name: name,
}
}
func ParseFile(filename string, startAddr int) (*Script, error) { func ParseFile(filename string, startAddr int) (*Script, error) {
rawfile, err := os.ReadFile(filename) rawfile, err := os.ReadFile(filename)
if err != nil { if err != nil {
@ -60,7 +24,7 @@ func Parse(rawinput []byte, startAddr int) (*Script, error) {
Warnings: []string{}, Warnings: []string{},
StackAddress: (int(rawinput[1])<<8) | int(rawinput[0]), StackAddress: (int(rawinput[1])<<8) | int(rawinput[0]),
StartAddress: startAddr, StartAddress: startAddr,
Labels: make(map[int]*Label), // map[location]name Labels: make(map[int]string), // map[location]name
} }
tokenMap := make(map[int]*Token) tokenMap := make(map[int]*Token)
@ -81,7 +45,7 @@ func Parse(rawinput []byte, startAddr int) (*Script, error) {
op, ok := InstrMap[raw] op, ok := InstrMap[raw]
if !ok { if !ok {
return nil, fmt.Errorf("OP 0x%02X not in instruction map", raw) return nil, fmt.Errorf("OP %02X not in instruction map", raw)
} }
token.Instruction = op token.Instruction = op
@ -102,25 +66,19 @@ func Parse(rawinput []byte, startAddr int) (*Script, error) {
args = append(args, ByteVal(l)) args = append(args, ByteVal(l))
i++ i++
for c := 0; c < l; c++ { for c := 0; c < l; c++ {
if len(rawinput) <= i+1 {
return script, fmt.Errorf("OP early end at offset 0x%X (%d) {%d} %#v", i, i, l, op)
}
args = append(args, WordVal([2]byte{rawinput[i], rawinput[i+1]})) args = append(args, WordVal([2]byte{rawinput[i], rawinput[i+1]}))
i+=2 i+=2
} }
i--
case -3: // count then count words. "default" is no call (skip Code_Pointer to after args) case -3: // count then count+1 words (extra is default case)
i++ i++
l := int(rawinput[i]) l := int(rawinput[i])
args = append(args, ByteVal(l)) args = append(args, ByteVal(l))
i++ i++
for c := 0; c < l; c++ { for c := 0; c < l+1; c++ {
args = append(args, WordVal([2]byte{rawinput[i], rawinput[i+1]})) args = append(args, WordVal([2]byte{rawinput[i], rawinput[i+1]}))
i+=2 i+=2
} }
i--
case 2: case 2:
args = append(args, WordVal([2]byte{rawinput[i+1], rawinput[i+2]})) args = append(args, WordVal([2]byte{rawinput[i+1], rawinput[i+2]}))
@ -134,7 +92,6 @@ func Parse(rawinput []byte, startAddr int) (*Script, error) {
token.Inline = args token.Inline = args
} }
// Find and mark labels for a few instructions
for _, t := range script.Tokens { for _, t := range script.Tokens {
switch t.Raw { switch t.Raw {
case 0x84, 0x85, 0xBF, 0xC0: // jmp/call case 0x84, 0x85, 0xBF, 0xC0: // jmp/call
@ -148,7 +105,7 @@ func Parse(rawinput []byte, startAddr int) (*Script, error) {
if tok.Offset == addr { if tok.Offset == addr {
tok.IsTarget = true tok.IsTarget = true
found = true found = true
script.Labels[addr] = AutoLabel(addr) //fmt.Sprintf("L%04X", addr) script.Labels[addr] = fmt.Sprintf("L%04X", addr)
break break
} }
} }
@ -169,7 +126,7 @@ func Parse(rawinput []byte, startAddr int) (*Script, error) {
if tok.Offset == addr { if tok.Offset == addr {
tok.IsTarget = true tok.IsTarget = true
found = true found = true
script.Labels[addr] = AutoLabel(addr) //fmt.Sprintf("L%04X", addr) script.Labels[addr] = fmt.Sprintf("L%04X", addr)
break break
} }
} }
@ -184,12 +141,11 @@ func Parse(rawinput []byte, startAddr int) (*Script, error) {
if t.Instruction == nil { if t.Instruction == nil {
continue continue
} }
if t.Instruction.OpCount == 2 {
if t.Instruction.OpCount == 2 && !t.Instruction.InlineImmediate {
addr := t.Inline[0].Int() addr := t.Inline[0].Int()
if tok, ok := tokenMap[addr]; ok { if tok, ok := tokenMap[addr]; ok {
tok.IsVariable = true tok.IsVariable = true
script.Labels[addr] = AutoLabelVar(addr) //fmt.Sprintf("Var_%04X", addr) script.Labels[addr] = fmt.Sprintf("Var_%04X", addr)
} }
} }
} }

View File

@ -1,7 +1,6 @@
package script package script
import ( import (
"fmt"
) )
type Script struct { type Script struct {
@ -11,35 +10,5 @@ type Script struct {
StartAddress int StartAddress int
StackAddress int StackAddress int
Labels map[int]*Label Labels map[int]string
}
type InstrStat struct {
Instr *Instruction
Count int
}
func (is InstrStat) String() string {
return fmt.Sprintf("0x%02X %3d %s", is.Instr.Opcode, is.Count, is.Instr.String())
}
func (s *Script) Stats() Stats {
st := make(Stats)
for _, t := range s.Tokens {
if t.Instruction == nil {
continue
}
op := t.Instruction.Opcode
if _, ok := st[op]; !ok {
st[op] = &InstrStat{
Instr: t.Instruction,
Count: 0,
}
}
st[op].Count++
}
return st
} }

View File

@ -1,56 +0,0 @@
package script
import (
"io"
"slices"
"maps"
"fmt"
)
type Stats map[byte]*InstrStat
func (this Stats) Add(that Stats) {
for _, st := range that {
op := st.Instr.Opcode
if _, ok := this[op]; !ok {
this[op] = st
} else {
this[op].Count += that[op].Count
}
}
}
func (s Stats) WriteTo(w io.Writer) (int64, error) {
count := int64(0)
keys := slices.Sorted(maps.Keys(s))
unknownInstr := 0
unknownUses := 0
for _, key := range keys {
n, err := fmt.Fprintln(w, s[key])
count += int64(n)
if err != nil {
return count, err
}
if s[key].Instr.Name == "" {
unknownInstr++
unknownUses += s[key].Count
}
}
n, err := fmt.Fprintln(w, "\nUnknown uses:", unknownUses)
count += int64(n)
if err != nil {
return count, err
}
n, err = fmt.Fprintln(w, "Unknown instructions:", unknownInstr)
count += int64(n)
if err != nil {
return count, err
}
return count, nil
}

View File

@ -15,7 +15,7 @@ type Token struct {
Instruction *Instruction Instruction *Instruction
} }
func (t Token) String(labels map[int]*Label) string { func (t Token) String(labels map[int]string) string {
suffix := "" suffix := ""
switch t.Raw { switch t.Raw {
case 0x86: case 0x86:
@ -25,18 +25,10 @@ func (t Token) String(labels map[int]*Label) string {
prefix := "" prefix := ""
if t.IsTarget || t.IsVariable { if t.IsTarget || t.IsVariable {
if lbl, ok := labels[t.Offset]; ok { if lbl, ok := labels[t.Offset]; ok {
comment := "" prefix = "\n"+lbl+":\n"
if lbl.Comment != "" {
comment = "; "+lbl.Comment+"\n"
}
prefix = "\n"+comment+lbl.Name+":\n"
} else { } else {
prefix = fmt.Sprintf("\nL%04X:\n", t.Offset) prefix = fmt.Sprintf("\nL%04X:\n", t.Offset)
} }
} else {
if lbl, ok := labels[t.Offset]; ok && lbl.Comment != "" {
suffix = " ; "+lbl.Comment+suffix
}
} }
if t.Raw < 0x80 { if t.Raw < 0x80 {
@ -64,7 +56,7 @@ func (t Token) String(labels map[int]*Label) string {
argstr := []string{} argstr := []string{}
for _, a := range t.Inline { for _, a := range t.Inline {
if lbl, ok := labels[a.Int()]; ok { if lbl, ok := labels[a.Int()]; ok {
argstr = append(argstr, lbl.Name) argstr = append(argstr, lbl)
} else { } else {
argstr = append(argstr, a.HexString()) argstr = append(argstr, a.HexString())
} }