Compare commits

..

3 Commits

Author SHA1 Message Date
Zorchenhimer bfc7beb382
[script] Improve CDL logic
- CDL data can now be output to a file.  If --cdl-output isn't given,
  but --cdl is given, the input file is overwritten with the new data.
  If no CDL file is given no data is written.
- Count arguments as "Code"
- The SmartParse function no longer creates a new CDL.  It will use and
  update what it was given.
- The just-stats command does not write CDL data even though it
  generates it.

Variable length arguments are not properly handled yet.  Currently, just
the OP code and first argument (length) are set at Code.  The rest are
ignored.
2025-09-14 18:06:46 -04:00
Zorchenhimer 11cfad1e1a
[script] Add CDL; Add "smart" parsing
- Added a CDL implementation.  It is still very incomplete and is not
  really used yet.
- Added "smart" parsing.  This will disassemble the code and follow any
  jumps and calls.  Anything not found by this is currently excluded
  from output.
2025-09-14 16:52:06 -04:00
Zorchenhimer d8cc126c04
[script] Move InstrStat to script/stats.go 2025-09-13 14:03:45 -04:00
8 changed files with 702 additions and 151 deletions

View File

@ -6,6 +6,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"io/fs" "io/fs"
"slices"
"github.com/alexflint/go-arg" "github.com/alexflint/go-arg"
@ -19,6 +20,7 @@ type Arguments struct {
type Walker struct { type Walker struct {
Found []string Found []string
CDLs []string
} }
func (w *Walker) WalkFunc(path string, info fs.DirEntry, err error) error { func (w *Walker) WalkFunc(path string, info fs.DirEntry, err error) error {
@ -30,6 +32,10 @@ func (w *Walker) WalkFunc(path string, info fs.DirEntry, err error) error {
w.Found = append(w.Found, path) w.Found = append(w.Found, path)
} }
if strings.HasSuffix(path, "_scriptData.cdl.json") {
w.CDLs = append(w.CDLs, path)
}
return nil return nil
} }
@ -46,17 +52,31 @@ func run(args *Arguments) error {
for _, file := range w.Found { for _, file := range w.Found {
fmt.Println(file) fmt.Println(file)
scr, err := script.ParseFile(file, 0x0000) var cdl *script.CodeDataLog
if err != nil { cdlname := file[:len(file)-4]+".cdl.json"
if scr != nil { if slices.Contains(w.CDLs, cdlname) {
for _, token := range scr.Tokens { fmt.Println("", cdlname)
fmt.Println(token.String(scr.Labels)) cdl, err = script.CdlFromJsonFile(cdlname)
} if err != nil {
fmt.Println(" CDL read error:", err)
cdl = nil
} }
return err
} }
stats.Add(scr.Stats()) scr, err := script.SmartParseFile(file, 0x6000, cdl)
if err != nil {
//if scr != nil {
// for _, token := range scr.Tokens {
// fmt.Println(token.String(scr.Labels))
// }
//}
fmt.Println(err)
//return err
}
if scr != nil {
stats.Add(scr.Stats())
}
} }
outfile, err := os.Create(args.Output) outfile, err := os.Create(args.Output)

View File

@ -7,6 +7,7 @@ import (
"strconv" "strconv"
"bufio" "bufio"
"slices" "slices"
"errors"
"github.com/alexflint/go-arg" "github.com/alexflint/go-arg"
@ -19,6 +20,9 @@ type Arguments struct {
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"` StatsFile string `arg:"--stats" help:"file to write some statistics to"`
LabelFile string `arg:"--labels" help:"file containing address/label pairs"` LabelFile string `arg:"--labels" help:"file containing address/label pairs"`
CDL string `arg:"--cdl" help:"CodeDataLog json file"`
CDLOutput string `arg:"--cdl-output"`
Smart bool `arg:"--smart"`
start int start int
} }
@ -39,19 +43,38 @@ func run(args *Arguments) error {
args.start = int(val) args.start = int(val)
scr, err := script.ParseFile(args.Input, args.start) var cdl *script.CodeDataLog
if args.CDL != "" {
//fmt.Println(" CDL:", args.CDL)
cdl, err = script.CdlFromJsonFile(args.CDL)
if err != nil {
//return fmt.Errorf("CDL Parse error: %w", err)
cdl = nil
}
}
var scr *script.Script
if args.Smart {
scr, err = script.SmartParseFile(args.Input, args.start, cdl)
} else {
scr, err = script.ParseFile(args.Input, args.start, cdl)
}
if err != nil { if err != nil {
return err if errors.Is(err, script.ErrEarlyEOF) || errors.Is(err, script.ErrNavigation) {
fmt.Println(err)
} else {
return fmt.Errorf("Script parse error: %w", err)
}
} }
if args.LabelFile != "" { if args.LabelFile != "" {
labels, err := parseLabelFile(args.LabelFile) labels, err := parseLabelFile(args.LabelFile)
if err != nil { if err != nil {
return err return fmt.Errorf("Labels parse error: %w", err)
} }
for _, label := range labels { for _, label := range labels {
//fmt.Printf("%#v\n", label)
scr.Labels[label.Address] = label scr.Labels[label.Address] = label
} }
} }
@ -66,7 +89,7 @@ func run(args *Arguments) error {
} }
for _, w := range scr.Warnings { for _, w := range scr.Warnings {
fmt.Fprintln(os.Stderr, w) //fmt.Fprintln(os.Stderr, w)
if args.Output != "" { if args.Output != "" {
fmt.Fprintln(outfile, "; "+w) fmt.Fprintln(outfile, "; "+w)
} }
@ -75,6 +98,12 @@ func run(args *Arguments) error {
fmt.Fprintf(outfile, "; Start address: $%04X\n", scr.StartAddress) fmt.Fprintf(outfile, "; Start address: $%04X\n", scr.StartAddress)
fmt.Fprintf(outfile, "; Stack address: $%04X\n\n", scr.StackAddress) fmt.Fprintf(outfile, "; Stack address: $%04X\n\n", scr.StackAddress)
slices.SortFunc(scr.Tokens, func(a, b *script.Token) int {
if a.Offset < b.Offset { return -1 }
if a.Offset > b.Offset { return 1 }
return 0
})
for _, token := range scr.Tokens { for _, token := range scr.Tokens {
fmt.Fprintln(outfile, token.String(scr.Labels)) fmt.Fprintln(outfile, token.String(scr.Labels))
} }
@ -86,13 +115,28 @@ func run(args *Arguments) error {
} }
defer statfile.Close() defer statfile.Close()
//err = scr.WriteStats(statfile)
_, err = scr.Stats().WriteTo(statfile) _, err = scr.Stats().WriteTo(statfile)
if err != nil { if err != nil {
return fmt.Errorf("Error writing stats: %w", err) return fmt.Errorf("Error writing stats: %w", err)
} }
} }
if scr.CDL != nil {
cdlout := args.CDL
if args.CDLOutput != "" {
cdlout = args.CDLOutput
}
if cdlout == "" {
return nil
}
err = scr.CDL.WriteToFile(cdlout)
if err != nil {
return fmt.Errorf("Error writing CDL file: %w", err)
}
}
return nil return nil
} }

245
script/cdl.go Normal file
View File

@ -0,0 +1,245 @@
package script
import (
"io"
"os"
"encoding/json"
"strconv"
"fmt"
"slices"
)
type CodeDataLog struct {
Code []CdlRange
Data []CdlRange
cache map[int]cdlBit
offset int
}
type CdlRange struct {
// strings cuz json doesn't know wtf hexadecimal is
Start string
End string
}
type cdlBit byte
var (
cdlUnknown cdlBit = 0x00
cdlCode cdlBit = 0x01
cdlData cdlBit = 0x02
//cdlOpCode cdlBit = 0x04
)
func (cdl *CodeDataLog) WriteToFile(filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
_, werr := cdl.WriteTo(file)
err = file.Close()
if err != nil {
return err
}
return werr
}
func getRanges(list []int) []CdlRange {
//fmt.Printf("getRanges(%v)\n", list)
data := []CdlRange{}
start := -1
//end := -1
prev := -1
for _, addr := range list {
if start == -1 {
start = addr
}
if prev != -1 && prev != addr-1 {
data = append(data, CdlRange{
Start: fmt.Sprintf("0x%X", start),
End: fmt.Sprintf("0x%X", prev),
})
//fmt.Printf("start: 0x%X end: 0x%X\n", start, prev)
start = addr
}
prev = addr
}
if start != -1 && prev != -1 {
data = append(data, CdlRange{
Start: fmt.Sprintf("0x%X", start),
End: fmt.Sprintf("0x%X", prev),
})
//fmt.Println("start:", start, "end:", prev)
}
return data
}
func (cdl *CodeDataLog) WriteTo(w io.Writer) (int64, error) {
clean := &CodeDataLog{
Code: []CdlRange{},
Data: []CdlRange{},
}
keys := []int{}
for k, _ := range cdl.cache {
keys = append(keys, k)
}
slices.Sort(keys)
code := []int{}
data := []int{}
for _, k := range keys {
b := cdl.cache[k]
if b & cdlCode == cdlCode {
code = append(code, k)
}
if b & cdlData == cdlData {
data = append(data, k)
}
}
clean.Code = getRanges(code)
clean.Data = getRanges(data)
raw, err := json.MarshalIndent(clean, "", "\t")
if err != nil {
return 0, err
}
n, err := w.Write(raw)
return int64(n), err
}
func (cdl *CodeDataLog) setData(scriptOffset int) {
if cdl.cache == nil {
err := cdl.doCache()
if err != nil {
panic(fmt.Sprintf("CDL data error: %w", err))
}
}
cdl.cache[scriptOffset+cdl.offset] |= cdlData
}
func (cdl *CodeDataLog) setCode(scriptOffset int) {
if cdl.cache == nil {
err := cdl.doCache()
if err != nil {
panic(fmt.Sprintf("CDL data error: %w", err))
}
}
cdl.cache[scriptOffset+cdl.offset] |= cdlCode
}
func (cdl *CodeDataLog) doCache() error {
cdl.cache = make(map[int]cdlBit)
for _, rng := range cdl.Code {
start, err := strconv.ParseInt(rng.Start, 0, 32)
if err != nil {
return fmt.Errorf("Invalid start: %q", rng.Start)
}
end, err := strconv.ParseInt(rng.End, 0, 32)
if err != nil {
return fmt.Errorf("Invalid end: %q", rng.End)
}
for i := int(start); i <= int(end); i++ {
if _, ok := cdl.cache[i]; !ok {
cdl.cache[i] = cdlUnknown
}
cdl.cache[i] |= cdlCode
}
}
for _, rng := range cdl.Data {
start, err := strconv.ParseInt(rng.Start, 0, 32)
if err != nil {
return fmt.Errorf("Invalid start: %q", rng.Start)
}
end, err := strconv.ParseInt(rng.End, 0, 32)
if err != nil {
return fmt.Errorf("Invalid end: %q", rng.End)
}
for i := int(start); i <= int(end); i++ {
if _, ok := cdl.cache[i]; !ok {
cdl.cache[i] = cdlUnknown
}
cdl.cache[i] |= cdlData
}
}
return nil
}
func CdlFromJson(r io.Reader) (*CodeDataLog, error) {
cdl := &CodeDataLog{}
dec := json.NewDecoder(r)
err := dec.Decode(cdl)
if err != nil {
return nil, err
}
return cdl, nil
}
func CdlFromJsonFile(filename string) (*CodeDataLog, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
return CdlFromJson(file)
}
func (cdl *CodeDataLog) IsData(addr int) bool {
if cdl.cache == nil {
err := cdl.doCache()
if err != nil {
panic(fmt.Sprintf("CDL data error: %w", err))
}
}
val, ok := cdl.cache[addr]
if !ok {
return false
}
return val & cdlData == cdlData
}
func (cdl *CodeDataLog) IsCode(addr int) bool {
if cdl.cache == nil {
err := cdl.doCache()
if err != nil {
panic(fmt.Sprintf("CDL data error: %w", err))
}
}
val, ok := cdl.cache[addr]
if !ok {
return false
}
return val & cdlCode == cdlCode
}

42
script/labels.go Normal file
View File

@ -0,0 +1,42 @@
package script
import (
"fmt"
)
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,
}
}

View File

@ -3,185 +3,300 @@ package script
import ( import (
"fmt" "fmt"
"os" "os"
"errors"
) )
type Label struct { var (
Address int ErrEarlyEOF = errors.New("Unexpected EOF when reading OP arguments")
Name string ErrInvalidInstruction = errors.New("Invalid instruction")
Comment string ErrNavigation = errors.New("SmartParse navigation error")
FarLabel bool )
type Parser struct {
rawinput []byte
current int
startAddr int
script *Script
cdl *CodeDataLog
} }
func AutoLabel(address int) *Label { func ParseFile(filename string, startAddr int, cdl *CodeDataLog) (*Script, error) {
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) {
rawfile, err := os.ReadFile(filename) rawfile, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to read file: %w", err) return nil, fmt.Errorf("unable to read file: %w", err)
} }
return Parse(rawfile, startAddr) return Parse(rawfile, startAddr, cdl)
} }
func Parse(rawinput []byte, startAddr int) (*Script, error) { func SmartParseFile(filename string, startAddr int, cdl *CodeDataLog) (*Script, error) {
rawfile, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("unable to read file: %w", err)
}
return SmartParse(rawfile, startAddr, cdl)
}
func SmartParse(rawinput []byte, startAddr int, cdl *CodeDataLog) (*Script, error) {
if len(rawinput) < 3 { if len(rawinput) < 3 {
return nil, fmt.Errorf("not enough bytes for script") return nil, fmt.Errorf("not enough bytes for script")
} }
script := &Script{ p := &Parser{
Tokens: []*Token{},
Warnings: []string{}, script: &Script{
StackAddress: (int(rawinput[1])<<8) | int(rawinput[0]), Tokens: []*Token{},
StartAddress: startAddr, Warnings: []string{},
Labels: make(map[int]*Label), // map[location]name StackAddress: (int(rawinput[1])<<8) | int(rawinput[0]),
StartAddress: startAddr,
Labels: make(map[int]*Label), // map[location]name
CDL: cdl,
},
rawinput: rawinput,
startAddr: startAddr,
}
if p.script.CDL == nil {
p.script.CDL = &CodeDataLog{}
}
tokenMap := make(map[int]*Token)
// starting point is the third byte in the script.
branches := []int{ 2 }
visited := make([]bool, len(p.rawinput))
for len(branches) > 0 {
//fmt.Printf("start @ $%04X\n", branches[0]+startAddr)
INNER:
for p.current = branches[0]; p.current < len(p.rawinput); p.current++ {
//branches = branches[1:]
if p.current < 0 {
return p.script, errors.Join(ErrNavigation,
fmt.Errorf("HOW IS CURRENT NEGATIVE?????"))
}
if visited[p.current] {
//fmt.Printf("found visited at $%04X\n", p.current+startAddr)
break
}
visited[p.current] = true
raw := p.rawinput[p.current]
token := &Token{
Offset: startAddr+p.current,
Raw: raw,
Inline: []InlineVal{},
}
p.script.Tokens = append(p.script.Tokens, token)
tokenMap[token.Offset] = token
//fmt.Printf("{$%04X} %s\n", token.Offset, token.String(map[int]*Label{}))
p.script.CDL.setCode(p.current)
if raw < 0x80 {
continue
}
err := p.parseToken(token, raw)
if err != nil {
return p.script, err
}
//fmt.Println(token.String(map[int]*Label{}))
switch raw {
case 0x86, 0xAC, 0xFF, 0x81, 0x9B, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xFD: // return, long_return, break_engine & halts
//fmt.Printf("[$%04X] %s\n",
// token.Offset, token.Instruction.Name)
break INNER
case 0x84, 0xBF, 0xC0, 0x85: // jump_abs, jump_not_zero, jump_zero, call_abs
if len(token.Inline) < 1 {
return p.script, errors.Join(ErrNavigation,
fmt.Errorf("jump missing target"))
}
if len(token.Inline) > 1 {
return p.script, errors.Join(ErrNavigation,
fmt.Errorf("jump has too many targets"))
}
val := token.Inline[0].Int()
//fmt.Printf("[$%04X] %s $%04X\n",
// token.Offset, token.Instruction.Name, val)
branches = append(branches, val-startAddr)
p.script.Labels[val] = AutoLabel(val)
if raw == 0x84 { // not jump_abs
break INNER
}
case 0xC1, 0xEE: // jump_switch, call_switch
if len(token.Inline) < 2 {
return p.script, errors.Join(ErrNavigation,
fmt.Errorf("switch missing targets"))
}
count := token.Inline[0].Int()
if len(token.Inline) != count+1 {
return p.script, errors.Join(ErrNavigation,
fmt.Errorf("switch target missmatch (expected %d, got %d)", count, len(token.Inline)-1))
}
for _, val := range token.Inline[1:] {
//fmt.Printf("[$%04X] %s $%04X\n",
// token.Offset, token.Instruction.Name, val.Int())
branches = append(branches, val.Int()-startAddr)
p.script.Labels[val.Int()] = AutoLabel(val.Int())
}
if raw == 0xC1 { // jump_switch
break INNER
}
}
if token.Instruction.OpCount == 2 {
val := token.Inline[0].Int()
if _, ok := p.script.Labels[val]; !ok {
p.script.Labels[val] = AutoLabelVar(val)
}
}
}
if len(branches) == 1 {
break
}
branches = branches[1:]
}
return p.script, nil
}
func Parse(rawinput []byte, startAddr int, cdl *CodeDataLog) (*Script, error) {
if len(rawinput) < 3 {
return nil, fmt.Errorf("not enough bytes for script")
}
p := &Parser{
script: &Script{
Tokens: []*Token{},
Warnings: []string{},
StackAddress: (int(rawinput[1])<<8) | int(rawinput[0]),
StartAddress: startAddr,
Labels: make(map[int]*Label), // map[location]name
CDL: cdl,
},
rawinput: rawinput,
startAddr: startAddr,
} }
tokenMap := make(map[int]*Token) tokenMap := make(map[int]*Token)
for i := 2; i < len(rawinput); i++ { if p.script.CDL == nil {
raw := rawinput[i] p.script.CDL = &CodeDataLog{}
}
//earliestVar := len(p.rawinput)-2
//fmt.Printf("var start bounds: $%04X, $%04X\n", startAddr, startAddr+len(p.rawinput))
for p.current = 2; p.current < len(p.rawinput); p.current++ {
//if p.current >= earliestVar {
// fmt.Printf("Earliest Variable found at offset %d ($%04X)\n", p.current, startAddr+p.current)
// break
//}
raw := p.rawinput[p.current]
token := &Token{ token := &Token{
Offset: startAddr+i, Offset: startAddr+p.current,
Raw: raw, Raw: raw,
Inline: []InlineVal{}, Inline: []InlineVal{},
} }
script.Tokens = append(script.Tokens, token) p.script.Tokens = append(p.script.Tokens, token)
tokenMap[token.Offset] = token tokenMap[token.Offset] = token
if raw < 0x80 { if raw < 0x80 || p.script.CDL.IsData(p.current+startAddr) { // || p.current >= earliestVar {
if p.script.CDL.IsData(p.current+startAddr) {
token.IsData = true
//fmt.Print(".")
//fmt.Printf("%#v\n", token)
}
continue continue
} }
op, ok := InstrMap[raw] err := p.parseToken(token, raw)
if !ok { if err != nil {
return nil, fmt.Errorf("OP 0x%02X not in instruction map", raw) return p.script, err
} }
token.Instruction = op
args := []InlineVal{}
switch op.OpCount {
case -1: // null terminated
for ; i < len(rawinput); i++ {
val := ByteVal(rawinput[i])
args = append(args, val)
if rawinput[i] == 0x00 {
break
}
}
case -2: // count then count words
i++
l := int(rawinput[i])
args = append(args, ByteVal(l))
i++
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]}))
i+=2
}
i--
case -3: // count then count words. "default" is no call (skip Code_Pointer to after args)
i++
l := int(rawinput[i])
args = append(args, ByteVal(l))
i++
for c := 0; c < l; c++ {
args = append(args, WordVal([2]byte{rawinput[i], rawinput[i+1]}))
i+=2
}
i--
case 2:
args = append(args, WordVal([2]byte{rawinput[i+1], rawinput[i+2]}))
i+=2
case 1:
i++
args = append(args, ByteVal(rawinput[i]))
}
token.Inline = args
} }
// Find and mark labels for a few instructions // Find and mark labels for a few instructions
for _, t := range script.Tokens { for _, t := range p.script.Tokens {
if t.Instruction == nil {
continue
}
switch t.Raw { switch t.Raw {
case 0x84, 0x85, 0xBF, 0xC0: // jmp/call case 0x84, 0x85, 0xBF, 0xC0: // jmp/call
if len(t.Inline) == 0 { if len(t.Inline) == 0 {
return nil, fmt.Errorf("jump/call missing address") //return nil, fmt.Errorf("jump/call missing address ($%04X)", t.Offset)
p.script.Warnings = append(p.script.Warnings,
fmt.Sprintf("jump/call missing addresses ($%04X)", t.Offset))
continue
} }
addr := t.Inline[0].Int() addr := t.Inline[0].Int()
found := false found := false
for _, tok := range script.Tokens { for _, tok := range p.script.Tokens {
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) p.script.Labels[addr] = AutoLabel(addr) //fmt.Sprintf("L%04X", addr)
break break
} }
} }
if !found { if !found {
script.Warnings = append(script.Warnings, fmt.Sprintf("Warning: no target found for jump/call at offset $%04X; value $%04X", t.Offset, addr)) p.script.Warnings = append(p.script.Warnings, fmt.Sprintf("Warning: no target found for jump/call at offset $%04X; value $%04X", t.Offset, addr))
} }
case 0xC1, 0xEE: // switches case 0xC1, 0xEE: // switches
if len(t.Inline) < 2 { if len(t.Inline) < 2 {
return nil, fmt.Errorf("jump/call switch missing addresses") //return nil, fmt.Errorf("jump/call switch missing addresses")
p.script.Warnings = append(p.script.Warnings,
fmt.Sprintf("jump/call switch missing addresses ($%04X)", t.Offset))
continue
} }
for _, v := range t.Inline[1:] { for _, v := range t.Inline[1:] {
addr := v.Int() addr := v.Int()
found := false found := false
for _, tok := range script.Tokens { for _, tok := range p.script.Tokens {
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) p.script.Labels[addr] = AutoLabel(addr) //fmt.Sprintf("L%04X", addr)
break break
} }
} }
if !found { if !found {
script.Warnings = append(script.Warnings, fmt.Sprintf("Warning: no target found for jump/call switch at offset $%04X; value: $%04X", t.Offset, addr)) p.script.Warnings = append(p.script.Warnings, fmt.Sprintf("Warning: no target found for jump/call switch at offset $%04X; value: $%04X", t.Offset, addr))
} }
} }
default: default:
// if word arg, see if it's something in this script // if word arg, see if it's something in this script
if t.Instruction == nil { if t.Instruction == nil {
//if t.IsData {
// fmt.Print(",")
//}
continue continue
} }
@ -189,11 +304,83 @@ func Parse(rawinput []byte, startAddr int) (*Script, error) {
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) p.script.Labels[addr] = AutoLabelVar(addr) //fmt.Sprintf("Var_%04X", addr)
} }
} }
} }
} }
return script, nil return p.script, nil
}
func (p *Parser) parseToken(token *Token, raw byte) error {
op, ok := InstrMap[raw]
if !ok {
return errors.Join(ErrInvalidInstruction,
fmt.Errorf("OP 0x%02X not in instruction map", raw))
}
token.Instruction = op
args := []InlineVal{}
switch op.OpCount {
case -1: // null terminated
for ; p.current < len(p.rawinput); p.current++ {
p.script.CDL.setCode(p.current)
val := ByteVal(p.rawinput[p.current])
args = append(args, val)
if p.rawinput[p.current] == 0x00 {
break
}
}
case -2: // count then count words
// FIXME: wtf makes this different from -3??
p.current++
l := int(p.rawinput[p.current])
p.script.CDL.setCode(p.current)
args = append(args, ByteVal(l))
p.current++
for c := 0; c < l; c++ {
if len(p.rawinput) <= p.current+1 {
return errors.Join(ErrEarlyEOF,
fmt.Errorf("OP early end at offset 0x%X (%d) {%d} %#v", p.current, p.current, l, op))
}
args = append(args, WordVal([2]byte{p.rawinput[p.current], p.rawinput[p.current+1]}))
p.current+=2
}
p.current--
case -3: // count then count words. "default" is no call (skip Code_Pointer to after args)
p.current++
l := int(p.rawinput[p.current])
args = append(args, ByteVal(l))
p.script.CDL.setCode(p.current)
p.current++
for c := 0; c < l; c++ {
args = append(args, WordVal([2]byte{p.rawinput[p.current], p.rawinput[p.current+1]}))
p.current+=2
}
p.current--
case 2:
args = append(args, WordVal([2]byte{p.rawinput[p.current+1], p.rawinput[p.current+2]}))
p.script.CDL.setCode(p.current+1)
p.script.CDL.setCode(p.current+2)
p.current+=2
//fmt.Printf("var at $%04X\n", val.Int())
//if val.Int() > p.startAddr && val.Int() < p.startAddr+len(p.rawinput) && p.earliestVar > val.Int() {
// fmt.Printf("new earliest: $%04X\n", val.Int())
// p.earliestVar = val.Int()
//}
case 1:
p.current++
p.script.CDL.setCode(p.current)
args = append(args, ByteVal(p.rawinput[p.current]))
}
token.Inline = args
return nil
} }

View File

@ -1,7 +1,6 @@
package script package script
import ( import (
"fmt"
) )
type Script struct { type Script struct {
@ -12,15 +11,7 @@ type Script struct {
StackAddress int StackAddress int
Labels map[int]*Label Labels map[int]*Label
} CDL *CodeDataLog
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 { func (s *Script) Stats() Stats {

View File

@ -9,6 +9,15 @@ import (
type Stats map[byte]*InstrStat type Stats map[byte]*InstrStat
type InstrStat struct {
Instr *Instruction
Count int
}
func (is InstrStat) String() string {
return fmt.Sprintf("0x%02X %6d %s", is.Instr.Opcode, is.Count, is.Instr.String())
}
func (this Stats) Add(that Stats) { func (this Stats) Add(that Stats) {
for _, st := range that { for _, st := range that {
op := st.Instr.Opcode op := st.Instr.Opcode
@ -40,7 +49,23 @@ func (s Stats) WriteTo(w io.Writer) (int64, error) {
} }
} }
n, err := fmt.Fprintln(w, "\nUnknown uses:", unknownUses) n, err := fmt.Fprintln(w, "\nUnused OpCodes:")
count += int64(n)
if err != nil {
return count, err
}
for i := byte(0x80); i <= 0xFF && i >= 0x80; i++ {
if _, ok := s[i]; !ok {
n, err = fmt.Fprintf(w, "0x%02X %s\n", i, InstrMap[i].Name)
count += int64(n)
if err != nil {
return count, err
}
}
}
n, err = fmt.Fprintln(w, "\nUnknown uses:", unknownUses)
count += int64(n) count += int64(n)
if err != nil { if err != nil {
return count, err return count, err

View File

@ -6,11 +6,12 @@ import (
) )
type Token struct { type Token struct {
Offset int Offset int // in CPU space
Raw byte Raw byte
Inline []InlineVal Inline []InlineVal
IsTarget bool // target of a call/jump? IsTarget bool // target of a call/jump?
IsVariable bool // target of something else IsVariable bool // target of something else
IsData bool // from CDL
Instruction *Instruction Instruction *Instruction
} }
@ -18,28 +19,24 @@ type Token struct {
func (t Token) String(labels map[int]*Label) string { func (t Token) String(labels map[int]*Label) string {
suffix := "" suffix := ""
switch t.Raw { switch t.Raw {
case 0x86: case 0x86: // Newline after return
suffix = "\n" suffix = "\n"
} }
prefix := "" prefix := ""
if t.IsTarget || t.IsVariable { if lbl, ok := labels[t.Offset]; ok {
if lbl, ok := labels[t.Offset]; ok { comment := ""
comment := "" if lbl.Comment != "" {
if lbl.Comment != "" { comment = "; "+lbl.Comment+"\n"
comment = "; "+lbl.Comment+"\n"
}
prefix = "\n"+comment+lbl.Name+":\n"
} else {
prefix = fmt.Sprintf("\nL%04X:\n", t.Offset)
} }
} else { name := ""
if lbl, ok := labels[t.Offset]; ok && lbl.Comment != "" { if lbl.Name != "" {
suffix = " ; "+lbl.Comment+suffix name = lbl.Name+":\n"
} }
prefix = "\n"+comment+name
} }
if t.Raw < 0x80 { if t.Instruction == nil {
return fmt.Sprintf("%s[%04X] %02X %-5s : %d%s", return fmt.Sprintf("%s[%04X] %02X %-5s : %d%s",
prefix, prefix,
t.Offset, t.Offset,