Compare commits

...

9 Commits

Author SHA1 Message Date
Zorchenhimer 045f204a6b
[cmd] Add just-stats command
This command is meant for gathering stats across many script files.
Currently breaks due to an oversight in script parsing (if an inline
argument list goes past EOF, everything breaks).
2025-09-06 22:56:10 -04:00
Zorchenhimer db2e5b5f87
[script] Add InlineImmediate field to Instruction
Setting this field to true will skip generating auto labels for the
inline values for the instruction.
2025-09-06 22:55:06 -04:00
Zorchenhimer 4bd0027c70
[script] Add some statisticts tracking
Track the statistics of used instructions for the script.
2025-09-06 22:53:33 -04:00
Zorchenhimer b3312a8500
[rom] Tweaked packet info output
Tweaked the packet info strings to be a little more clear on what fields
are printed.
2025-09-06 22:46:26 -04:00
Zorchenhimer 1ebe698ac4
[rom] Add NoAudio option to export
Added an option to exclude the audio file from the unpacked rom data.
2025-09-06 22:45:33 -04:00
Zorchenhimer 5a001931e7
Added missing cmd/sbutil.go
Not sure how this was missing for so long.  Whoops.
2025-09-06 22:43:13 -04:00
Zorchenhimer 4b9f04874b
[script] Better auto label support
Labels are now their own object instead of just a string.  This allows
for a bit more control with them.  Labels can also now have comments.

Additionally, add the ability to load user-defined labels from a file.
The format of this file is subject to change, but for now it is just a
simple text file.  Each line of the file is a label definition.  Each
line has three fields, separated by spaces.  The first field is the
address, second is the label name, and the rest of the line is a
comment.  If a label name is just a dollar sign ($) it will not have a
name.  This is used when adding comments without a bespoke label.
2025-09-06 22:38:39 -04:00
Zorchenhimer 29e48f3cac
[script] Fix varible inline instructions; Fix off-by-one
- Fixed instructions that have -3 as the OpCount (count then count
  words).  There is not an extra word that acts as the default
  selection.  These instructions do nothing if the argument is out of
  range.
- Fixed off-by-one eating the byte following the -3 OpCount
  instructions.
- Fixed panic when a -2 op code goes beyond the end of the script.
2025-09-06 22:27:45 -04:00
Zorchenhimer 6a3a51fef7
[rom] Fix exported file modes
Export files with mode 0666 instead of 0777.
2025-09-06 22:24:34 -04:00
12 changed files with 711 additions and 171 deletions

View File

@ -1,9 +1,12 @@
.PHONY: all .PHONY: all
all: bin/script-decode bin/sbutil all: bin/script-decode bin/sbutil bin/just-stats
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 $@ $<

85
cmd/just-stats.go Normal file
View File

@ -0,0 +1,85 @@
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)
}
}

198
cmd/sbutil.go Normal file
View File

@ -0,0 +1,198 @@
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,8 +5,11 @@ 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"
) )
@ -14,6 +17,9 @@ 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
} }
@ -38,6 +44,18 @@ 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)
@ -61,9 +79,79 @@ 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) error { func (sb *StudyBox) Export(directory string, includeAudio bool) error {
sbj := StudyBoxJson{ sbj := StudyBoxJson{
Version: 1, Version: 1,
Pages: []jsonPage{}, Pages: []jsonPage{},
@ -106,7 +106,7 @@ func (sb *StudyBox) Export(directory string) 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, 0777) err = os.WriteFile(jData.File, rawData, 0666)
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,9 +133,11 @@ func (sb *StudyBox) Export(directory string) error {
return fmt.Errorf("Missing audio!") return fmt.Errorf("Missing audio!")
} }
err := sb.Audio.WriteToFile(directory + "/audio") if includeAudio {
if err != nil { err := sb.Audio.WriteToFile(directory + "/audio")
return fmt.Errorf("Error writing audio file: %v", err) if err != nil {
return fmt.Errorf("Error writing audio file: %v", err)
}
} }
rawJson, err := json.MarshalIndent(sbj, "", " ") rawJson, err := json.MarshalIndent(sbj, "", " ")
@ -143,5 +145,5 @@ func (sb *StudyBox) Export(directory string) error {
return err return err
} }
return os.WriteFile(directory+".json", rawJson, 0777) return os.WriteFile(directory+".json", rawJson, 0666)
} }

View File

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

View File

@ -1,6 +1,7 @@
package script package script
import ( import (
"fmt"
) )
var InstrMap map[byte]*Instruction var InstrMap map[byte]*Instruction
@ -13,197 +14,218 @@ func init() {
} }
var Instructions []*Instruction = []*Instruction{ var Instructions []*Instruction = []*Instruction{
&Instruction{ 0x80, 0, 0, 0, "play_beep"}, &Instruction{ 0x80, 0, 0, 0, false, "play_beep"},
&Instruction{ 0x81, 0, 0, 0, "halt"}, &Instruction{ 0x81, 0, 0, 0, false, "halt"},
&Instruction{ 0x82, 0, 0, 0, "tape_nmi_shenanigans"}, &Instruction{ 0x82, 0, 0, 0, false, "tape_nmi_shenanigans"},
&Instruction{ 0x83, 0, 0, 0, "tape_wait"}, &Instruction{ 0x83, 0, 0, 0, false, "tape_wait"},
// Jump to the inline word, in the VM // Jump to the inline word, in the VM
&Instruction{ 0x84, 0, 2, 0, "jump_abs"}, &Instruction{ 0x84, 0, 2, 0, false, "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, "call_abs"}, &Instruction{ 0x85, 0, 2, 0, false, "call_abs"},
// Return from a previous call // Return from a previous call
&Instruction{ 0x86, 0, 0, 0, "return"}, &Instruction{ 0x86, 0, 0, 0, false, "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{ 0x90, 0, 0, 1, "strings_not_equal"}, &Instruction{ 0x87, 0, 0, 0, false, "loop"},
&Instruction{ 0x91, 0, 0, 1, "string_less_than"}, &Instruction{ 0x88, 0, 0, 0, false, "play_sound"},
&Instruction{ 0x92, 0, 0, 1, "string_less_than_equal"}, &Instruction{ 0x89, 3, 0, 0, false, ""},
&Instruction{ 0x93, 0, 0, 1, "string_greater_than_equal"}, &Instruction{ 0x8A, 0, 2, 0, false, "pop_string_to_addr"},
&Instruction{ 0x94, 0, 0, 1, "string_greater_than"}, &Instruction{ 0x8B, 1, 0, 0, false, ""},
&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.
&Instruction{ 0x95, 1, 0, 0, "tape_nmi_shenigans_set"}, // Will call 0x82 tape_nmi_shenanigans if $0740 != 0
&Instruction{ 0x96, 0, 2, 0, "set_word_4E"}, &Instruction{ 0x95, 1, 0, 0, false, "tape_nmi_shenigans_set"},
&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"},
// Calls 0xEB draw_overlay. Seems to draw a whole screen. &Instruction{ 0x96, 0, 2, 0, true, "set_word_4E"},
&Instruction{ 0x9E, 2, 0, 0, ""}, &Instruction{ 0x97, 2, 0, 0, false, ""},
&Instruction{ 0x9F, 6, 0, 0, ""}, &Instruction{ 0x98, 1, 0, 0, false, ""},
&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"},
&Instruction{ 0xA0, 2, 0, 1, ""}, // Calls 0xEB draw_overlay. Draws the whole screen from data previously
&Instruction{ 0xA1, 1, 0, 0, ""}, // loaded from the tape.
&Instruction{ 0xA2, 1, 0, 0, "buffer_palette"}, &Instruction{ 0x9E, 2, 0, 0, false, "draw_and_show_screen"},
&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, "sprite_setup"}, &Instruction{ 0xA3, 1, 0, 0, false, "sprite_setup"},
&Instruction{ 0xA4, 3, 0, 0, ""}, &Instruction{ 0xA4, 3, 0, 0, false, ""},
&Instruction{ 0xA5, 1, 0, 0, "set_470A"}, &Instruction{ 0xA5, 1, 0, 0, false, "set_470A"},
&Instruction{ 0xA6, 1, 0, 0, "set_470B"}, &Instruction{ 0xA6, 1, 0, 0, false, "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)
&Instruction{ 0xA7, 0, 0, 0, "call_asm"}, // Will not jump to anything at or above $8000 or below $5000.
&Instruction{ 0xA8, 5, 0, 0, ""}, // Addresses in $5000-$5FFF use $470A as the bank ID
&Instruction{ 0xA9, 1, 0, 0, ""}, // Addresses in $6000-$7FFF use $470B as the bank ID
&Instruction{ 0xAA, 1, 0, 0, ""}, &Instruction{ 0xA7, 0, 0, 0, false, "call_asm"},
&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{ 0xB0, 1, 0, 16, ""}, &Instruction{ 0xA8, 5, 0, 0, false, ""},
&Instruction{ 0xB1, 1, 0, 16, "to_hex_string"}, &Instruction{ 0xA9, 1, 0, 0, false, ""},
&Instruction{ 0xB2, 0, 0, 1, ""}, &Instruction{ 0xAA, 1, 0, 0, false, ""},
&Instruction{ 0xB3, 7, 0, 0, ""}, // possible 16-bit inline? &Instruction{ 0xAB, 1, 0, 0, false, "long_call"},
&Instruction{ 0xB4, 0, 0, 0, ""}, &Instruction{ 0xAC, 0, 0, 0, false, "long_return"},
&Instruction{ 0xB5, 0, 0, 0, ""}, &Instruction{ 0xAD, 1, 0, 1, false, "absolute"},
&Instruction{ 0xB6, 0, 0, 0, ""}, &Instruction{ 0xAE, 1, 0, 1, false, "compare"},
&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, "deref_ptr_inline"}, &Instruction{ 0xB7, 0, 2, 0, false, "deref_ptr_inline"},
// Pushes the inline word to the stack // Pushes the inline word to the stack
&Instruction{ 0xB8, 0, 2, 0, "push_word"}, &Instruction{ 0xB8, 0, 2, 0, true, "push_word"},
&Instruction{ 0xB9, 0, 2, 0, "push_word_indexed"}, &Instruction{ 0xB9, 0, 2, 0, false, "push_word_indexed"},
&Instruction{ 0xBA, 0, 2, 0, "push"}, &Instruction{ 0xBA, 0, 2, 0, false, "push"},
&Instruction{ 0xBB, 0, -1, 0, "push_data"}, &Instruction{ 0xBB, 0, -1, 0, false, "push_data"},
&Instruction{ 0xBC, 0, 2, 0, "push_string_from_table"}, &Instruction{ 0xBC, 0, 2, 0, false, "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, "pop"}, &Instruction{ 0xBD, 0, 2, 0, false, "pop_into"},
&Instruction{ 0xBE, 0, 2, 0, "write_to_table"},
&Instruction{ 0xBF, 0, 2, 0, "jump_not_zero"}, &Instruction{ 0xBE, 0, 2, 0, false, "write_to_table"},
&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, "jump_zero"}, &Instruction{ 0xC0, 1, 2, 0, false, "jump_zero"},
&Instruction{ 0xC1, 1, -2, 0, "jump_switch"}, &Instruction{ 0xC1, 1, -2, 0, false, "jump_switch"},
&Instruction{ 0xC2, 1, 0, 1, "equals_zero"}, &Instruction{ 0xC2, 1, 0, 1, false, "equals_zero"},
&Instruction{ 0xC3, 2, 0, 1, "and_a_b"}, &Instruction{ 0xC3, 2, 0, 1, false, "and_a_b"},
&Instruction{ 0xC4, 2, 0, 1, "or_a_b"}, &Instruction{ 0xC4, 2, 0, 1, false, "or_a_b"},
&Instruction{ 0xC5, 2, 0, 1, "equal"}, &Instruction{ 0xC5, 2, 0, 1, false, "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, "not_equal"}, &Instruction{ 0xC6, 2, 0, 1, false, "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{ 0xD0, 1, 0, 1, "modulus"}, &Instruction{ 0xC7, 2, 0, 1, false, "less_than"},
&Instruction{ 0xD1, 2, 0, 1, "expansion_controller"}, &Instruction{ 0xC8, 2, 0, 1, false, "less_than_equal"},
&Instruction{ 0xD2, 2, 0, 1, ""}, &Instruction{ 0xC9, 2, 0, 1, false, "greater_than"},
&Instruction{ 0xD3, 2, 0, 16, ""}, &Instruction{ 0xCA, 2, 0, 1, false, "greater_than_equal"},
&Instruction{ 0xD4, 3, 0, 0, ""}, &Instruction{ 0xCB, 2, 0, 1, false, "sum"},
&Instruction{ 0xD5, 1, 0, 0, "wait_for_tape"}, &Instruction{ 0xCC, 2, 0, 1, false, "subtract"},
&Instruction{ 0xD6, 1, 0, 16, "truncate_string"}, &Instruction{ 0xCD, 2, 0, 1, false, "multiply"},
&Instruction{ 0xD7, 1, 0, 16, "trim_string"}, &Instruction{ 0xCE, 2, 0, 1, false, "signed_divide"},
&Instruction{ 0xD8, 1, 0, 16, "trim_string_start"}, &Instruction{ 0xCF, 1, 0, 1, false, "negate"},
&Instruction{ 0xD9, 2, 0, 16, "trim_string_start"},
&Instruction{ 0xDA, 1, 0, 16, "to_int_string"}, &Instruction{ 0xD0, 1, 0, 1, false, "modulus"},
&Instruction{ 0xDB, 3, 0, 0, ""}, &Instruction{ 0xD1, 2, 0, 1, false, "expansion_controller"},
&Instruction{ 0xDC, 5, 0, 0, ""}, &Instruction{ 0xD2, 2, 0, 1, false, ""},
&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, "fill_box"}, &Instruction{ 0xDD, 5, 0, 0, false, "fill_box"},
&Instruction{ 0xDE, 3, 0, 0, ""}, &Instruction{ 0xDE, 3, 0, 0, false, ""},
&Instruction{ 0xDF, 3, 0, 0, ""}, &Instruction{ 0xDF, 3, 0, 0, false, ""},
&Instruction{ 0xE0, 2, 0, 1, "signed_divide"}, // Divide and return remainder
&Instruction{ 0xE1, 4, 0, 0, ""}, &Instruction{ 0xE0, 2, 0, 1, false, "modulo"},
&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, "deref_ptr_stack"}, &Instruction{ 0xE3, 1, 0, 1, false, "deref_ptr_stack"},
&Instruction{ 0xE4, 2, 0, 0, "swap_ram_bank"}, &Instruction{ 0xE4, 2, 0, 0, false, "swap_ram_bank"},
&Instruction{ 0xE5, 1, 0, 0, "disable_sprite"}, &Instruction{ 0xE5, 1, 0, 0, false, "disable_sprite"},
&Instruction{ 0xE6, 1, 0, 0, "tape_nmi_setup"},
&Instruction{ 0xE7, 7, 0, 0, ""}, // Will call 0x82 tape_nmi_shenanigans if $0740 != 0
&Instruction{ 0xE8, 1, 0, 0, "setup_tape_nmi"}, &Instruction{ 0xE6, 1, 0, 0, false, "tape_nmi_setup"},
&Instruction{ 0xE9, 0, 1, 0, "setup_loop"},
&Instruction{ 0xEA, 0, 0, 0, "string_write_to_table"}, &Instruction{ 0xE7, 7, 0, 0, false, "draw_metasprite"},
&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, "draw_overlay"}, &Instruction{ 0xEB, 4, 0, 0, false, "draw_overlay"},
&Instruction{ 0xEC, 2, 0, 0, "scroll"}, &Instruction{ 0xEC, 2, 0, 0, false, "scroll"},
&Instruction{ 0xED, 1, 0, 0, "disable_sprites"}, &Instruction{ 0xED, 1, 0, 0, false, "disable_sprites"},
&Instruction{ 0xEE, 1, -3, 0, "call_switch"},
&Instruction{ 0xEF, 6, 0, 0, ""},
&Instruction{ 0xF0, 0, 0, 0, "disable_sprites"}, &Instruction{ 0xEE, 1, -3, 0, false, "call_switch"},
&Instruction{ 0xF1, 4, 0, 0, ""}, &Instruction{ 0xEF, 6, 0, 0, false, ""},
&Instruction{ 0xF2, 0, 0, 0, "halt"},
&Instruction{ 0xF3, 0, 0, 0, "halt"}, &Instruction{ 0xF0, 0, 0, 0, false, "disable_sprites"},
&Instruction{ 0xF4, 0, 0, 16, "halt"}, &Instruction{ 0xF1, 4, 0, 0, false, ""},
&Instruction{ 0xF5, 1, 0, 1, "halt"}, &Instruction{ 0xF2, 0, 0, 0, false, "halt"},
&Instruction{ 0xF6, 1, 0, 0, "halt"}, &Instruction{ 0xF3, 0, 0, 0, false, "halt"},
&Instruction{ 0xF7, 0, 0, 0, "halt"}, &Instruction{ 0xF4, 0, 0, 16, false, "halt"},
&Instruction{ 0xF8, 2, 0, 0, "halt"}, &Instruction{ 0xF5, 1, 0, 1, false, "halt"},
&Instruction{ 0xF9, 0, 0, 1, ""}, &Instruction{ 0xF6, 1, 0, 0, false, "halt"},
&Instruction{ 0xFA, 0, 0, 1, ""}, &Instruction{ 0xF7, 0, 0, 0, false, "halt"},
&Instruction{ 0xFB, 1, 0, 0, "jump_arg_a"}, &Instruction{ 0xF8, 2, 0, 0, false, "halt"},
&Instruction{ 0xFC, 2, 0, 1, ""}, &Instruction{ 0xF9, 0, 0, 1, false, ""},
&Instruction{ 0xFD, 0, 0, 16, "halt"}, &Instruction{ 0xFA, 0, 0, 1, false, ""},
&Instruction{ 0xFE, 4, 0, 0, ""}, &Instruction{ 0xFB, 1, 0, 0, false, "jump_arg_a"},
&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, "break_engine"}, &Instruction{ 0xFF, 0, 0, 0, false, "break_engine"},
} }
type Instruction struct { type Instruction struct {
Opcode byte Opcode byte
ArgCount int // stack arguments ArgCount int // stack arguments
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 one additional word // -3: like -2, but with no default. code continues after list on OOB
RetCount int // return count RetCount int // return count
Name string InlineImmediate bool // don't turn the inline value into a variable
Name string
} }
func (i Instruction) String() string { func (i Instruction) String() string {
@ -212,7 +234,7 @@ func (i Instruction) String() string {
return i.Name return i.Name
} }
//return fmt.Sprintf("$%02X_unknown", i.Opcode) return fmt.Sprintf("unknown_0x%02X", i.Opcode)
return "unknown" //return "unknown"
} }

View File

@ -5,6 +5,42 @@ 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 {
@ -24,7 +60,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]string), // map[location]name Labels: make(map[int]*Label), // map[location]name
} }
tokenMap := make(map[int]*Token) tokenMap := make(map[int]*Token)
@ -45,7 +81,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 %02X not in instruction map", raw) return nil, fmt.Errorf("OP 0x%02X not in instruction map", raw)
} }
token.Instruction = op token.Instruction = op
@ -66,19 +102,25 @@ 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+1 words (extra is default case) case -3: // count then count words. "default" is no call (skip Code_Pointer to after args)
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+1; c++ { for c := 0; c < l; 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]}))
@ -92,6 +134,7 @@ 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
@ -105,7 +148,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] = fmt.Sprintf("L%04X", addr) script.Labels[addr] = AutoLabel(addr) //fmt.Sprintf("L%04X", addr)
break break
} }
} }
@ -126,7 +169,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] = fmt.Sprintf("L%04X", addr) script.Labels[addr] = AutoLabel(addr) //fmt.Sprintf("L%04X", addr)
break break
} }
} }
@ -141,11 +184,12 @@ 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] = fmt.Sprintf("Var_%04X", addr) script.Labels[addr] = AutoLabelVar(addr) //fmt.Sprintf("Var_%04X", addr)
} }
} }
} }

View File

@ -1,6 +1,7 @@
package script package script
import ( import (
"fmt"
) )
type Script struct { type Script struct {
@ -10,5 +11,35 @@ type Script struct {
StartAddress int StartAddress int
StackAddress int StackAddress int
Labels map[int]string Labels map[int]*Label
}
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
} }

56
script/stats.go Normal file
View File

@ -0,0 +1,56 @@
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]string) string { func (t Token) String(labels map[int]*Label) string {
suffix := "" suffix := ""
switch t.Raw { switch t.Raw {
case 0x86: case 0x86:
@ -25,10 +25,18 @@ func (t Token) String(labels map[int]string) 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 {
prefix = "\n"+lbl+":\n" comment := ""
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 {
@ -56,7 +64,7 @@ func (t Token) String(labels map[int]string) 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) argstr = append(argstr, lbl.Name)
} else { } else {
argstr = append(argstr, a.HexString()) argstr = append(argstr, a.HexString())
} }