138 lines
2.8 KiB
Go
138 lines
2.8 KiB
Go
|
package script
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"os"
|
||
|
)
|
||
|
|
||
|
func ParseFile(filename string, startAddr int) (*Script, error) {
|
||
|
rawfile, err := os.ReadFile(filename)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("unable to read file: %w", err)
|
||
|
}
|
||
|
|
||
|
return Parse(rawfile, startAddr)
|
||
|
}
|
||
|
|
||
|
func Parse(rawinput []byte, startAddr int) (*Script, error) {
|
||
|
if len(rawinput) < 3 {
|
||
|
return nil, fmt.Errorf("not enough bytes for script")
|
||
|
}
|
||
|
|
||
|
script := &Script{
|
||
|
Tokens: []*Token{},
|
||
|
Warnings: []string{},
|
||
|
StackAddress: (int(rawinput[1])<<8) | int(rawinput[0]),
|
||
|
StartAddress: startAddr,
|
||
|
}
|
||
|
|
||
|
for i := 2; i < len(rawinput); i++ {
|
||
|
raw := rawinput[i]
|
||
|
|
||
|
token := &Token{
|
||
|
Offset: startAddr+i,
|
||
|
Raw: raw,
|
||
|
Inline: []InlineVal{},
|
||
|
}
|
||
|
script.Tokens = append(script.Tokens, token)
|
||
|
|
||
|
if raw < 0x80 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
op, ok := InstrMap[raw]
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("OP %02X not in instruction map", raw)
|
||
|
}
|
||
|
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++ {
|
||
|
args = append(args, WordVal([2]byte{rawinput[i], rawinput[i+1]}))
|
||
|
i+=2
|
||
|
}
|
||
|
|
||
|
case -3: // count then count+1 words (extra is default case)
|
||
|
i++
|
||
|
l := int(rawinput[i])
|
||
|
args = append(args, ByteVal(l))
|
||
|
i++
|
||
|
for c := 0; c < l+1; c++ {
|
||
|
args = append(args, WordVal([2]byte{rawinput[i], rawinput[i+1]}))
|
||
|
i+=2
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
for _, t := range script.Tokens {
|
||
|
switch t.Raw {
|
||
|
case 0x84, 0x85, 0xBF, 0xC0: // jmp/call
|
||
|
if len(t.Inline) == 0 {
|
||
|
return nil, fmt.Errorf("jump/call missing address")
|
||
|
}
|
||
|
|
||
|
addr := t.Inline[0].Int()
|
||
|
found := false
|
||
|
for _, tok := range script.Tokens {
|
||
|
if tok.Offset == addr {
|
||
|
tok.IsTarget = true
|
||
|
found = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !found {
|
||
|
script.Warnings = append(script.Warnings, fmt.Sprintf("Warning: no target found for jump/call at offset $%04X; value $%04X", t.Offset, addr))
|
||
|
}
|
||
|
|
||
|
case 0xC1, 0xEE: // switches
|
||
|
if len(t.Inline) < 2 {
|
||
|
return nil, fmt.Errorf("jump/call switch missing addresses")
|
||
|
}
|
||
|
|
||
|
for _, v := range t.Inline[1:] {
|
||
|
addr := v.Int()
|
||
|
found := false
|
||
|
for _, tok := range script.Tokens {
|
||
|
if tok.Offset == addr {
|
||
|
tok.IsTarget = true
|
||
|
found = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return script, nil
|
||
|
}
|