package main import ( "fmt" "math" "os" // "sort" "strings" //"code.beefchicken.com/68kdisasm" "encoding/json" "path/filepath" ) //var externs = make([]int, 0) type romInfo struct { Roms []struct { File string `json:"file"` Start string `json:"start"` End string `json:"end"` Version string `json:"version"` Device string `json:"device"` Checksum string `json:"checksum"` } `json:"roms"` Labels []struct { Address string `json:"address"` Label string `json:"label"` } `json:"labels"` } type memBlock struct { lowFile string highFile string start uint32 end uint32 } type output struct { mnemonic string operands string comment string } type call struct { function uint32 instrAddr uint32 computedAddr uint32 targetAddr uint32 native bool } func main() { if len(os.Args) < 2 { fmt.Printf("Usage: %s \n", os.Args[0]) os.Exit(1) } romDir := os.Args[1] romMapFilename := filepath.Join(romDir, "map.json") info := &romInfo{} b, _ := os.ReadFile(romMapFilename) fmt.Printf("Loading ROM info from '%s'...\n", romMapFilename) err := json.Unmarshal(b, &info) if err != nil { fmt.Printf("unable to load rom map from '%s': %v\n", romMapFilename, err) os.Exit(1) } d := NewDisassembler() for _, rom := range info.Roms { var start, end uint32 var checksum int _, err := fmt.Sscanf(rom.Start, "0x%X", &start) if err != nil { fmt.Printf("Unable to parse start address '%s' for rom '%s'!\n", rom.Start, rom.File) os.Exit(1) } _, err = fmt.Sscanf(rom.End, "0x%X", &end) if err != nil { fmt.Printf("Unable to parse end address '%s' for rom '%s'!\n", rom.End, rom.File) os.Exit(1) } _, err = fmt.Sscanf(rom.Checksum, "0x%X", &checksum) if err != nil { fmt.Printf("Unable to checksum '%s' for rom '%s'!\n", rom.Checksum, rom.File) os.Exit(1) } file := filepath.Join(romDir, rom.File) fmt.Printf("Loading '%s' (0x%06X-0x%06X)...\n", file, start, end) d.AddRom(file, start, end, checksum) } for _, label := range info.Labels { var address uint32 _, err := fmt.Sscanf(label.Address, "0x%X", &address) if err != nil { fmt.Printf("Unable to parse address '%s' for label '%s!\n", label.Address, label.Label) os.Exit(1) } fmt.Printf("Adding label %s = 0x%06X\n", label.Label, address) d.AddLabel(address, label.Label) } // look for the two kinds of header functions and queue // the vm code up for disassembly queue := []uint32{} //0xe6d // 0xa0435 <- week // 0x00001631 <- WRNO FIX // 0x0009324d for _, block := range d.roms { for i := block.start; i <= block.end-3; i++ { inst := d.read32(i) if inst == 0x4EB82144 || inst == 0x4EB82164 || inst == 0x4EB82102 { queue = append(queue, i) } } } dests := make(map[uint32][]call) // first pass to gather all of the JSR.68Ks for _, addr := range queue { externs, _ := d.disassemble(addr, nil, true) for _, c := range externs { if !c.native { dests[c.targetAddr] = append(dests[c.targetAddr], c) } } } // disassemble with output for _, vpc := range queue { d.disassemble(vpc, dests, false) } } type disassembler struct { pc uint32 labels map[uint32]string roms []*memBlock prog []byte caseStmts map[uint32]caseStmt } type caseStmt struct { switchAddr uint32 keys []byte } func NewDisassembler() *disassembler { return &disassembler{ labels: make(map[uint32]string), roms: make([]*memBlock, 0), prog: make([]byte, 0), caseStmts: make(map[uint32]caseStmt), } } func (d *disassembler) AddLabel(addr uint32, l string) { d.labels[addr] = l } func addx(x, y uint16, xf *uint16) uint16 { t := x + y + *xf if int(t) < int(x)+int(y)+int(*xf) { *xf = 1 } else { *xf = 0 } return t } func (d *disassembler) AddRom(file string, start uint32, end uint32, checksum int) error { if len(d.prog) < int(end)+1 { newProg := make([]byte, end+1) copy(newProg, d.prog) d.prog = newProg } b, err := os.ReadFile(file) if err != nil { return err } var cs uint16 = 0 var cs2 uint16 = 0 var xf uint16 for i := 0; i < len(b); i++ { d.prog[int(start)+i*2] = b[i] if i < len(b)-4 { cs = addx(uint16(b[i]), cs, &xf) } cs2 += uint16(b[i]) } cs = ^cs internalStoredChecksum := uint16(b[len(b)-1])<<8 + uint16(b[len(b)-2]) fmt.Printf("%s INTERNAL CHECKSUM: GOT 0x%04X EXPECTED 0x%04X\n", file, cs, internalStoredChecksum) fmt.Printf("%s EXTERNAL CHECKSUM: GOT 0x%04X EXPECTED 0x%04X\n", file, cs2, checksum) // check if we've already loaded the pair for this rom var block *memBlock for _, rom := range d.roms { if start&0xFFFFFFFE == rom.start&0xFFFFFFFE { block = rom if start < block.start { block.start = start } if end > block.end { block.end = end } break } } if block == nil { // this is the first ROM of this pair, so create the block block = &memBlock{ start: start, end: end, } d.roms = append(d.roms, block) } if start%2 == 0 { block.lowFile = file } else { block.highFile = file } return nil } func (d *disassembler) read8(addr uint32) byte { return d.prog[addr] } func (d *disassembler) read16(addr uint32) uint16 { return uint16(d.prog[addr])<<8 | uint16(d.prog[addr+1]) } func (d *disassembler) read32(addr uint32) uint32 { return uint32(d.prog[addr])<<24 | uint32(d.prog[addr+1])<<16 | uint32(d.prog[addr+2])<<8 | uint32(d.prog[addr+3]) } func (d *disassembler) readFloat64(addr uint32) float64 { return math.Float64frombits(d.read64(addr)) } func (d *disassembler) read64(addr uint32) uint64 { var ret uint64 ret |= uint64(d.prog[addr]) << 56 ret |= uint64(d.prog[addr+1]) << 48 ret |= uint64(d.prog[addr+2]) << 40 ret |= uint64(d.prog[addr+3]) << 32 ret |= uint64(d.prog[addr+4]) << 24 ret |= uint64(d.prog[addr+5]) << 16 ret |= uint64(d.prog[addr+6]) << 8 ret |= uint64(d.prog[addr+7]) return ret } func (d *disassembler) disassemble(start uint32, dests map[uint32][]call, silent bool) ([]call, error) { externs := make([]call, 0) signature := d.read32(start) var headerInfo string // 13fff = no branch // 14000 = branch // 14001 = branch if signature == 0x4EB82102 { if !silent { stackMax := 0x17fff vsp := stackMax + 1 - int(d.read16(start+4)) if vsp < 0x14000 { vsp -= 0xc000 } headerInfo = fmt.Sprintf("; A0 = $%06X; (A0) = $%04X; A6 = $%06X\n\n", 0x4000, vsp, vsp) // A0 = A6 } d.pc = start + 6 } else if signature == 0x4EB82144 { if !silent { a6 := 0 a1 := a6 + int(d.read16(start+4)) - 1 a0 := 0x4000 + (int(d.read8(start+6)) << 2) //(a0) = a1 if a1 >= 0 { headerInfo = fmt.Sprintf("; A0 = $%06X; (A0) = SP + $%X\n\n", a0, a1) } else { headerInfo = fmt.Sprintf("; A0 = $%06X; (A0) = SP - $%X\n\n", a0, -a1) } } d.pc = start + 7 } else if signature == 0x4EB82164 { // movea.l (SP)+,A5 // move.w (A5)+,D0w // move.w (A5)+,D1w // moveq #0x0,D2 // move.b (A5)+,D2b // lsl.w #0x2,D2w // movea.w #0x4000,A0 // adda.w D2w,A0 // move.l (A0),-(SP)=>DAT_00004000 // move.w A0w,-(SP)=>local_2 // movea.l A6,A1 // adda.w D0w,A1 // subq.l #0x1,A1 // move.l A1,(A0)=>DAT_00004000 // movea.l A6,A0 // suba.w D1w,A6 // tst.w D0w // beq.b LAB_00002196 // movea.l A6,A1 // lsr.w #0x1,D0w // subq.w #0x1,D0w // LAB_00002190 // move.w (A0)+,(A1)+ // dbf D0w,LAB_00002190 // LAB_00002196 // bra.w FUN_00002000 if !silent { d0 := uint32(d.read16(start + 4)) a6 := uint32(0) a1 := a6 + d0 - 1 d1 := uint32(d.read16(start + 6)) a0 := 0x4000 + (int(d.read8(start+8)) << 2) headerInfo = fmt.Sprintf("; P0 = $%04X; P1 = $%04X; P2 = $%02X\n", d.read16(start+4), d.read16(start+6), d.read8(start+8)) if d0 > 0 { d0 = (d0 << 1) - 1 } if a1 >= 0 { headerInfo += fmt.Sprintf("; A0 = $%06X; (A0) = A6 + $%X; D1 = $%06X\n\n", a0, a1, d1) } else { headerInfo += fmt.Sprintf("; A0 = $%06X; (A0) = A6 - $%X; D1 = $%06X\n\n", a0, -a1, d1) } } d.pc = start + 9 } if !silent { fmt.Printf("\n; ---------------- Begin %04X Function $%06X ---------------- \n\n%s", signature&0xFFFF, start, headerInfo) for _, dest := range dests[start] { fmt.Printf("; called from $%06X:$%06X\n", dest.function, dest.instrAddr) } fmt.Printf("\n%6s %-14s %-8s\t%-20s%s\n\n", "; addr", "bytes", "opcode", "operands", "comment") } var err error for { instPC := d.pc inst := d.prog[d.pc] d.pc++ o := output{} if cs, ok := d.caseStmts[instPC]; ok { if len(cs.keys) > 0 { o.mnemonic = fmt.Sprintf("CASE") o.comment = fmt.Sprintf("SWITCH $%06X", cs.switchAddr) keysStr := make([]string, len(cs.keys)) for i, v := range cs.keys { keysStr[i] = fmt.Sprintf("$%02X", v) } o.operands = strings.Join(keysStr, ", ") d.pc += 1 + uint32(len(cs.keys)) } else { o.mnemonic = fmt.Sprintf("DEFAULT") o.comment = fmt.Sprintf("SWITCH $%06X", cs.switchAddr) } delete(d.caseStmts, instPC) } else if inst == 0x01 { o.mnemonic = "NOP" } else if inst == 0x02 { o.mnemonic = "INLINE" if d.pc%2 != 0 { d.pc++ } //startPC := d.pc machineCodeBytes := make([]byte, 0) for { machineCodeBytes = append(machineCodeBytes, d.prog[d.pc]) if d.read16(d.pc) == 0x4e75 { machineCodeBytes = append(machineCodeBytes, d.prog[d.pc+1]) d.pc += 2 break } d.pc++ } // bus := disasm.NewAddressBus(d.prog) // doneBytes := 0 // // for { // s, n := disasm.Disassemble(disasm.M68K_CPU_TYPE_68000, int32(startPC+doneBytes), bus) // fmt.Println(s) // doneBytes+=int(n) // if doneBytes == len(machineCodeBytes) { // break // } // } //fmt.Printf("%d % 02X\n",len(machineCodeBytes),machineCodeBytes) o.operands = fmt.Sprintf("%d Bytes", len(machineCodeBytes)) //d.getOpr32()) } else if inst == 0x03 { op := d.getOpr8() if op <= 0x30 { o, externs, err = d.handle2090(d.pc-2, op, externs) } else { err = fmt.Errorf("bad 0x03 instruction") } } else if inst >= 0x04 && inst <= 0x07 { o.mnemonic = "PUSH.10" o.operands = fmt.Sprintf("$%03X", d.getOpr10()) } else if inst >= 0x08 && inst <= 0x0F { o.mnemonic = fmt.Sprintf("?_$%02X", inst) op1 := d.getOpr16() op2 := d.getOpr16() if inst&(1<<2) == 0 && d.prog[d.pc] == 0 { d.pc++ o.operands = fmt.Sprintf("$%04X, $%04X, $0", op1, op2) } else { o.operands = fmt.Sprintf("$%04X, $%04X", op1, op2) } } else if inst >= 0x10 && inst <= 0x17 { o.mnemonic = "BR" jd := int(d.getSignedOpr11()) o.operands = fmt.Sprintf("$%03X", jd) o.comment = fmt.Sprintf("=> $%06X", uint32(int(d.pc)+jd)) } else if inst >= 0x18 && inst <= 0x1F { o.mnemonic = "BRZ" jd := int(d.getSignedOpr11()) o.operands = fmt.Sprintf("$%03X", jd) o.comment = fmt.Sprintf("=> $%06X", uint32(int(d.pc)+jd)) } else if inst >= 0x20 && inst <= 0x3F { o, externs, err = d.handle2090(d.pc-1, inst-0x20, externs) } else if inst >= 0x40 && inst <= 0x7f { o.mnemonic = "PUSH.6" o.operands = fmt.Sprintf("$%02X", d.getOpr6()) } else if inst >= 0x80 && inst <= 0xFF { w := []string{"B", "W", "L", "F"}[inst&0x03] if (inst & 0x10) == 0 { o.mnemonic = fmt.Sprintf("READ.%s", w) } else { o.mnemonic = fmt.Sprintf("WRITE.%s", w) } if inst >= 0xc0 { o.operands = fmt.Sprintf("$%04X", d.getOpr16()) } else { o.operands = fmt.Sprintf("$%02X", d.getOpr8()) } if (inst & 0x10) == 0 { if d.prog[d.pc] == 0 { d.pc++ } } } else { err = fmt.Errorf("bad instruction") } // var hex string // if !silent { // if d.pc-instPC < 6 { // hex = fmt.Sprintf("% 02X", d.prog[instPC:d.pc]) // } else { // hex = fmt.Sprintf("% 02X...", d.prog[instPC:instPC+4]) // } // fmt.Printf("%06X % -14s ", instPC, hex) // } // // if err != nil { // break // } // // if !silent { // var comment string // if o.comment != "" { // comment = "; " + o.comment // } // fmt.Printf("%-8s\t%-20s%s\n", o.mnemonic, o.operands, comment) // } if err == nil { if !silent { d.printInstr(instPC, o) } } if err != nil { if !silent { fmt.Printf("Disassembly failed at $%06X\n", instPC) } break } if o.mnemonic == "RETURN" { break } } for i := range externs { externs[i].function = start } if !silent { if err != nil { fmt.Printf("\n; ---------------- Disassemly Terminated: %v ---------------- \n", err) } else { fmt.Printf("\n; ---------------- End Function $%06X ---------------- \n\n\n", start) } } return externs, err } func (d *disassembler) printInstr(instPC uint32, o output) { // var hex, comment string // if o.comment != "" { // comment = "; " + o.comment // } // // if d.pc-instPC < 6 { // hex = fmt.Sprintf("% 02X", d.prog[instPC:d.pc]) // } else { // hex = fmt.Sprintf("% 02X...", d.prog[instPC:instPC+4]) // } // fmt.Printf("%06X % -14s ", instPC, hex) // // // fmt.Printf("%-8s\t%-20s%s\n", o.mnemonic, o.operands, comment) var hex, comment string if o.comment != "" { comment = "; " + o.comment } if d.pc-instPC < 6 { hex = fmt.Sprintf("% 02X", d.prog[instPC:d.pc]) fmt.Printf("%06X % -14s %-8s\t%-20s%s\n", instPC, hex, o.mnemonic, o.operands, comment) } else { for i := instPC; i < d.pc; i += 5 { if i == instPC { hex = fmt.Sprintf("% 02X", d.prog[i:i+5]) fmt.Printf("%06X % -14s %-8s\t%-20s%s\n", i, hex, o.mnemonic, o.operands, comment) } else { endAddr := i + 5 if endAddr > d.pc { endAddr = d.pc } hex = fmt.Sprintf("% 02X", d.prog[i:endAddr]) //fmt.Printf("%06X % -14s %-8s\n", i, hex, "") fmt.Printf("%6s % -14s %-8s\n", "", hex, "") } } } } func (d *disassembler) getOpr6() byte { return d.prog[d.pc-1] & 0x3F } func (d *disassembler) getOpr8() byte { v := d.prog[d.pc] d.pc += 1 return v } func (d *disassembler) getOpr10() uint16 { v := uint16(d.prog[d.pc-1]&0x03)<<8 | uint16(d.prog[d.pc]) d.pc += 1 return v } func (d *disassembler) getOpr11() uint16 { v := uint16(d.prog[d.pc-1]&0x07)<<8 | uint16(d.prog[d.pc]) d.pc += 1 return v } func (d *disassembler) getSignedOpr11() int16 { v := uint16(d.prog[d.pc-1]&0x07)<<8 | uint16(d.prog[d.pc]) d.pc += 1 if v&0x0400 != 0 { return int16(v | 0xF800) } return int16(v) } func (d *disassembler) getOpr16() uint16 { v := uint16(d.prog[d.pc])<<8 | uint16(d.prog[d.pc+1]) d.pc += 2 return v } func (d *disassembler) getOpr32() uint32 { v := uint32(d.prog[d.pc])<<24 | uint32(d.prog[d.pc+1])<<16 | uint32(d.prog[d.pc+2])<<8 | uint32(d.prog[d.pc+3]) d.pc += 4 return v } func (d *disassembler) getOpr64() uint64 { v := uint64(d.prog[d.pc]) << 56 v |= uint64(d.prog[d.pc+1]) << 48 v |= uint64(d.prog[d.pc+2]) << 40 v |= uint64(d.prog[d.pc+3]) << 32 v |= uint64(d.prog[d.pc+4]) << 24 v |= uint64(d.prog[d.pc+5]) << 16 v |= uint64(d.prog[d.pc+6]) << 8 v |= uint64(d.prog[d.pc+7]) d.pc += 8 return v } func (d *disassembler) handle2090(instAddr uint32, inst byte, externs []call) (output, []call, error) { o := output{} switch inst { case 0x00: o.mnemonic = "ADD.F" case 0x01: o.mnemonic = "SUB.F" case 0x02: o.mnemonic = "MUL.F" case 0x03: o.mnemonic = "DIV.F" case 0x04: o.mnemonic = "GT.F" case 0x05: o.mnemonic = "GTE.F" case 0x06: o.mnemonic = "EQ.F" case 0x07: o.mnemonic = "ADD.L" case 0x08: o.mnemonic = "SUB.L" case 0x09: o.mnemonic = "MUL.L" case 0x0A: o.mnemonic = "EQ.L" case 0x0B: o.mnemonic = "NEQ.L" case 0x0C: o.mnemonic = "AND.L" case 0x0D: o.mnemonic = "OR.L" case 0x0E: o.mnemonic = "NOT.L" case 0x0F: o.mnemonic = "MOD.L" case 0x10: o.mnemonic = "BSR.68K" off := int(d.getOpr16()) o.operands = fmt.Sprintf("$%04X", off) c := call{ instrAddr: instAddr, computedAddr: uint32(int(d.pc) - off), } var targetAddrStr string if d.read16(c.computedAddr) == 0x4EF9 { c.targetAddr = d.read32(c.computedAddr + 2) targetAddrStr = fmt.Sprintf(" => $%06X", c.targetAddr) } else { c.targetAddr = c.computedAddr } label := d.labels[c.targetAddr] if label != "" { label = fmt.Sprintf(" (%s)", label) } var native string if d.read32(c.targetAddr) != 0x4EB82144 && d.read32(c.targetAddr) != 0x4EB82164 { native = " (NATIVE)" c.native = true } o.comment = fmt.Sprintf("=> $%06X%s%s%s", c.computedAddr, targetAddrStr, label, native) externs = append(externs, c) case 0x11: o.mnemonic = "SUBSP.B" o.operands = fmt.Sprintf("$%02X", d.getOpr8()) case 0x12: o.mnemonic = "ADDSP.B" o.operands = fmt.Sprintf("$%02X", d.getOpr8()) case 0x13: o.mnemonic = "_2300" case 0x14: o.mnemonic = "RETURN" case 0x15: o.mnemonic = "SWITCH" switchAddr := d.pc - 1 tpc := d.pc for d.prog[tpc] != 0 { caseAddr := tpc op := d.read16(tpc) tpc += 2 numKey := int(op & 0xF000 >> 12) recLen := int(op & 0xfff) keys := make([]byte, numKey) for i := 0; i < numKey; i++ { keys[i] = d.read8(tpc + uint32(i)) } d.caseStmts[caseAddr] = caseStmt{ switchAddr: switchAddr, keys: keys, } tpc += uint32(recLen) } d.caseStmts[tpc] = caseStmt{ switchAddr: switchAddr, } case 0x16: o.mnemonic = "_2362" op := d.getOpr16() & 0xfff a0 := 0x4000 + ((op & 0x0C00) >> 8) o.comment = fmt.Sprintf("A0 = $%06X; (A0) = A6 + $%X\n\n", a0, 0) o.operands = fmt.Sprintf("$%04X", op) case 0x17: o.mnemonic = "_23d2" op := d.getOpr16() & 0xfff op2 := d.getOpr16() o.operands = fmt.Sprintf("$%04X, $%04X", op, op2) case 0x18: op := d.getOpr16() o.mnemonic = []string{"BLS.B", "BLS.W", "BLS.L", "BLS.W"}[op&0xc000>>14] o.operands = fmt.Sprintf("$%04X", op&0x3fff) o.comment = fmt.Sprintf("=> $%06X", int(d.pc)+int(op&0x3fff)) case 0x19: o.mnemonic = "_23fc" op := d.getOpr16() op2 := d.getOpr16() o.operands = fmt.Sprintf("$%04X, $%04X", op, op2) case 0x1A: op := d.getOpr16() o.mnemonic = []string{"BGT.B", "BGT.W", "BGT.L", "BGT.W"}[op&0xc000>>14] o.operands = fmt.Sprintf("$%04X", op&0x3fff) o.comment = fmt.Sprintf("=> $%06X", int(d.pc)+int(op&0x3fff)) case 0x1B: o.mnemonic = "PUSH.F" v := d.getOpr64() fv := math.Float64frombits(v) o.operands = fmt.Sprintf("$%016X", v) o.comment = fmt.Sprintf("float = %f", fv) case 0x1C: o.mnemonic = "PUSH.W" op := int(d.getOpr16()) o.operands = fmt.Sprintf("$%04X", op) case 0x1D: o.mnemonic = "PUSH.L" op := int(d.getOpr32()) o.operands = fmt.Sprintf("$%08X", op) case 0x1E: o.mnemonic = "PUSH.W" o.operands = "$0000" case 0x1F: o.mnemonic = "DISP" strlen := uint32(d.getOpr8()) str := d.prog[d.pc : d.pc+strlen] o.operands = fmt.Sprintf(`"%s"`, string(str)) d.pc += strlen case 0x20: o.mnemonic = "NEG.W" case 0x21: o.mnemonic = "LT.F" case 0x22: o.mnemonic = "LTE.F" case 0x23: o.mnemonic = "NEQ.F" case 0x24: o.mnemonic = "DIV.L" case 0x25: o.mnemonic = "LT.L" case 0x26: o.mnemonic = "GT.L" case 0x27: o.mnemonic = "LTE.L" case 0x28: o.mnemonic = "GTE.L" case 0x29: o.mnemonic = "FLOAT2LONG" case 0x2A: o.mnemonic = "LONG2FLOAT" case 0x2B: o.mnemonic = "COMPARE" op := d.getOpr16() o.operands = fmt.Sprintf("$%04X", op) case 0x2C: o.mnemonic = "NOTCOMPARE" op := d.getOpr16() o.operands = fmt.Sprintf("$%04X", op) case 0x2D: o.mnemonic = "SUBSP.W" op := d.getOpr16() o.operands = fmt.Sprintf("$%04X", op) case 0x2E: o.mnemonic = "ADDSP.W" op := d.getOpr16() o.operands = fmt.Sprintf("$%04X", op) case 0x2F: o.mnemonic = "XORL.L" case 0x30: o.mnemonic = "TRAP1" strlen := uint32(d.getOpr8()) str := d.prog[d.pc : d.pc+strlen] o.operands = fmt.Sprintf("\"%s\"", string(str)) d.pc += strlen default: return o, externs, fmt.Errorf("unrecognized 2090 instruction $%02X", inst) } return o, externs, nil }