diff --git a/cmd/just-stats.go b/cmd/just-stats.go index 87e2149..628a396 100644 --- a/cmd/just-stats.go +++ b/cmd/just-stats.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" "io/fs" + "slices" "github.com/alexflint/go-arg" @@ -19,6 +20,7 @@ type Arguments struct { type Walker struct { Found []string + CDLs []string } 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) } + if strings.HasSuffix(path, "_scriptData.cdl.json") { + w.CDLs = append(w.CDLs, path) + } + return nil } @@ -46,17 +52,31 @@ func run(args *Arguments) error { 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)) - } + var cdl *script.CodeDataLog + cdlname := file[:len(file)-4]+".cdl.json" + if slices.Contains(w.CDLs, cdlname) { + fmt.Println("", cdlname) + 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) diff --git a/cmd/script-decode.go b/cmd/script-decode.go index 44ddb0a..5856eea 100644 --- a/cmd/script-decode.go +++ b/cmd/script-decode.go @@ -7,6 +7,7 @@ import ( "strconv" "bufio" "slices" + "errors" "github.com/alexflint/go-arg" @@ -19,6 +20,8 @@ type Arguments struct { 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"` + CDL string `arg:"--cdl" help:"CodeDataLog json file"` + Smart bool `arg:"--smart"` start int } @@ -39,19 +42,38 @@ func run(args *Arguments) error { 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 { - 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 != "" { labels, err := parseLabelFile(args.LabelFile) if err != nil { - return err + return fmt.Errorf("Labels parse error: %w", err) } for _, label := range labels { - //fmt.Printf("%#v\n", label) scr.Labels[label.Address] = label } } @@ -66,7 +88,7 @@ func run(args *Arguments) error { } for _, w := range scr.Warnings { - fmt.Fprintln(os.Stderr, w) + //fmt.Fprintln(os.Stderr, w) if args.Output != "" { fmt.Fprintln(outfile, "; "+w) } @@ -75,6 +97,16 @@ func run(args *Arguments) error { fmt.Fprintf(outfile, "; Start address: $%04X\n", scr.StartAddress) 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 _, lbl := range scr.Labels { + // fmt.Println(lbl) + //} + for _, token := range scr.Tokens { fmt.Fprintln(outfile, token.String(scr.Labels)) } @@ -86,7 +118,6 @@ func run(args *Arguments) error { } defer statfile.Close() - //err = scr.WriteStats(statfile) _, err = scr.Stats().WriteTo(statfile) if err != nil { return fmt.Errorf("Error writing stats: %w", err) diff --git a/script/cdl.go b/script/cdl.go new file mode 100644 index 0000000..c773ed1 --- /dev/null +++ b/script/cdl.go @@ -0,0 +1,151 @@ +package script + +import ( + "io" + "os" + "encoding/json" + "strconv" + "fmt" +) + +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 +) + +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 +} diff --git a/script/labels.go b/script/labels.go new file mode 100644 index 0000000..09af803 --- /dev/null +++ b/script/labels.go @@ -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, + } +} + diff --git a/script/parser.go b/script/parser.go index 2264833..8c13e8f 100644 --- a/script/parser.go +++ b/script/parser.go @@ -3,185 +3,296 @@ package script import ( "fmt" "os" + "errors" ) -type Label struct { - Address int - Name string - Comment string - FarLabel bool +var ( + ErrEarlyEOF = errors.New("Unexpected EOF when reading OP arguments") + ErrInvalidInstruction = errors.New("Invalid instruction") + ErrNavigation = errors.New("SmartParse navigation error") +) + +type Parser struct { + rawinput []byte + current int + startAddr int + + script *Script + cdl *CodeDataLog } -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, cdl *CodeDataLog) (*Script, error) { rawfile, err := os.ReadFile(filename) if err != nil { 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 { 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, - Labels: make(map[int]*Label), // map[location]name + p := &Parser{ + // Don't use CDL that was passed in + cdl: &CodeDataLog{}, + + script: &Script{ + Tokens: []*Token{}, + Warnings: []string{}, + StackAddress: (int(rawinput[1])<<8) | int(rawinput[0]), + StartAddress: startAddr, + Labels: make(map[int]*Label), // map[location]name + }, + + rawinput: rawinput, + startAddr: startAddr, + } + + 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.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{ + cdl: cdl, + script: &Script{ + Tokens: []*Token{}, + Warnings: []string{}, + StackAddress: (int(rawinput[1])<<8) | int(rawinput[0]), + StartAddress: startAddr, + Labels: make(map[int]*Label), // map[location]name + }, + rawinput: rawinput, + startAddr: startAddr, } tokenMap := make(map[int]*Token) - for i := 2; i < len(rawinput); i++ { - raw := rawinput[i] + if p.cdl == nil { + p.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{ - Offset: startAddr+i, + Offset: startAddr+p.current, Raw: raw, Inline: []InlineVal{}, } - script.Tokens = append(script.Tokens, token) + p.script.Tokens = append(p.script.Tokens, token) tokenMap[token.Offset] = token - if raw < 0x80 { + if raw < 0x80 || p.cdl.IsData(p.current+startAddr) { // || p.current >= earliestVar { + if p.cdl.IsData(p.current+startAddr) { + token.IsData = true + //fmt.Print(".") + //fmt.Printf("%#v\n", token) + } continue } - op, ok := InstrMap[raw] - if !ok { - return nil, fmt.Errorf("OP 0x%02X not in instruction map", raw) + err := p.parseToken(token, raw) + if err != nil { + 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 - for _, t := range script.Tokens { + for _, t := range p.script.Tokens { + if t.Instruction == nil { + continue + } + switch t.Raw { case 0x84, 0x85, 0xBF, 0xC0: // jmp/call 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() found := false - for _, tok := range script.Tokens { + for _, tok := range p.script.Tokens { if tok.Offset == addr { tok.IsTarget = 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 } } 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 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:] { addr := v.Int() found := false - for _, tok := range script.Tokens { + for _, tok := range p.script.Tokens { if tok.Offset == addr { tok.IsTarget = 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 } } 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: // if word arg, see if it's something in this script if t.Instruction == nil { + //if t.IsData { + // fmt.Print(",") + //} continue } @@ -189,11 +300,76 @@ func Parse(rawinput []byte, startAddr int) (*Script, error) { addr := t.Inline[0].Int() if tok, ok := tokenMap[addr]; ok { 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++ { + 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]) + 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.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.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++ + args = append(args, ByteVal(p.rawinput[p.current])) + } + + token.Inline = args + return nil } diff --git a/script/tokens.go b/script/tokens.go index 2f07b9d..018496b 100644 --- a/script/tokens.go +++ b/script/tokens.go @@ -6,11 +6,12 @@ import ( ) type Token struct { - Offset int + Offset int // in CPU space Raw byte Inline []InlineVal IsTarget bool // target of a call/jump? IsVariable bool // target of something else + IsData bool // from CDL Instruction *Instruction } @@ -18,28 +19,24 @@ type Token struct { func (t Token) String(labels map[int]*Label) string { suffix := "" switch t.Raw { - case 0x86: + case 0x86: // Newline after return suffix = "\n" } prefix := "" - if t.IsTarget || t.IsVariable { - if lbl, ok := labels[t.Offset]; ok { - comment := "" - if lbl.Comment != "" { - comment = "; "+lbl.Comment+"\n" - } - prefix = "\n"+comment+lbl.Name+":\n" - } else { - prefix = fmt.Sprintf("\nL%04X:\n", t.Offset) + if lbl, ok := labels[t.Offset]; ok { + comment := "" + if lbl.Comment != "" { + comment = "; "+lbl.Comment+"\n" } - } else { - if lbl, ok := labels[t.Offset]; ok && lbl.Comment != "" { - suffix = " ; "+lbl.Comment+suffix + name := "" + if lbl.Name != "" { + 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", prefix, t.Offset,