package monitor import ( "bytes" "code.beefchicken.com/trimtools/dotx" "encoding/binary" "fmt" // "github.com/jacobsa/go-serial/serial" "io" "strconv" "strings" "time" ) const ( readWriteBlockSize = 244 NrmCmdGetSerial = 0x06 NrmCmdRSerial = 0x07 NrmCmdGetOpt = 0x4A NrmCmdRetOpt = 0x4B NrmCmdModeChange = 0x87 MonCmdWriteMem = 0x80 MonCmdReadMem = 0x82 MonCmdBaudRate = 0x86 MonCmdReset = 0x88 MonCmdJmp = 0x89 MonCmdReadMemAck = 0x92 ) type Monitor struct { receiverType ReceiverType Dummy bool inMonitor bool receiver *Receiver seekPos uint32 } type ReceiverState int const ( ReceiverUnexpected ReceiverState = iota ReceiverUnresponsive ReceiverNormal ReceiverInMonitor ) type ReceiverType int const ( ReceiverType4000 ReceiverType = iota ReceiverType4400 ) func NewMonitor(receiver *Receiver) *Monitor { return &Monitor{receiver: receiver} } func (m *Monitor) StartMonitor() error { if m.Dummy { m.inMonitor = true return nil } fmt.Println("Starting monitor mode...") // once we have something that looks like a receiver on the line, figure // out what mode it's in. state, err := m.receiver.ProbeReceiverState() if err != nil { return err } if state == ReceiverUnresponsive { return fmt.Errorf("receiver unresponsive") } if state == ReceiverNormal { // get config conf, err := m.CmdGetRuntimeConfig() if err != nil { return err } fmt.Printf("PPU Version = %.2f\n", conf.BootRomVersion) opts, err := m.CmdGetOptions() if opts.ReceiverType == 3 { // gauss } // switch to monitor mode err = m.receiver.DoAckCommand(NrmCmdModeChange, []byte{0x57}) // 0x57 is what an ack at 38400 looks like at 9600 baud. if err != nil { return err } // When the monitor starts, it reverts back to 9600-N-8-1, so we have to // re-configure the serial port. err = m.receiver.useSlowBaudRate() if err != nil { return err } // It can take a while to boot into monitor mode err = m.receiver.waitForMonitor() if err != nil { return err } } // reconfigure monitor to speak at 38400-N-8-1 // 0x6D = 4800 // 0x37 = 9600 // 0x1B = 19200 // 0x0E = 38400 // This appears to respond with 0xFE, but that is just is what a 38400 // baud ACK (0x06) looks like when received at 9600 baud, and depending // on where in the bit the UART samples, different UARTs might see // different values, so we just ignore any no-ack error. err = m.receiver.DoAckCommand(MonCmdBaudRate, []byte{0x00, 0x0E}) if err != nil { if _, ok := err.(*ProtocolError); !ok { return err } } // re-configure port for 38400 baud err = m.receiver.useFastBaudRate() if err != nil { return err } // Make sure baud switch was successful err = m.receiver.waitForMonitor() if err != nil { return err } // } m.inMonitor = true // we should be in monitor mode now. fmt.Printf("Setting chip selects...\n") err = m.setChipSelects() if err != nil { return err } m.inMonitor = true fmt.Println("Monitor mode active.") return nil } func (m *Monitor) setChipSelects() error { // I don't understand the mechanism the PPU uses to communicate to the // system memory. The chip select registers only apply to signals originating // from the internal bus, so if the PPU is just taking over the bus, then the // chip selects wouldn't matter. But they definitely do, and if they're not // set properly, the PPU ends up seeing the wrong memory. // Maybe the PPU just loads a small ROM monitor into RAM, then hands control // back to the CPU? Hmmm. err := m.WriteMemory(0x00FFFA58, []uint16{ 0x1004, // CSBAR3 128k @ 0x100000 0x7B71, // CSOR3 Change DSACK to 13 wait }) if err != nil { return err } err = m.WriteMemory(0x00100000, []byte{0x55, 0x55, 0xAA, 0xAA}) if err != nil { return err } b, err := m.ReadMemory(0x00100000, 4) if err != nil { return err } if bytes.Compare(b, []byte{0x55, 0x55, 0xAA, 0xAA}) == 0 { fmt.Printf("SE/SSE/SSi H/W target receiver detected.\n") fmt.Printf("Setting SE/SSE/SSi chip selects...\n") err = m.WriteMemory(0x00FFFA54, []uint16{ 0x7007, // CSBAR2 0x7871, // CSOR2 }) if err != nil { return err } err = m.WriteMemory(0x00FFFA46, []uint16{ 0x03F5, // CSPAR1 }) if err != nil { return err } err = m.WriteMemory(0x00FFFA58, []uint16{ 0x1004, // CSBAR3 0x7BF1, // CSOR3 }) if err != nil { return err } m.receiverType = ReceiverType4000 } else { fmt.Printf("Cheetah/7400 MSi target receiver detected.\n") fmt.Printf("Setting Cheetah/7400 MSi chip selects...\n") err = m.WriteMemory(0x00FFFA48, []uint16{ 0x0005, // CSBARBT 0x7831, // CSORBT 0x0405, // CSBAR0 0x7831, // CSOR0 0x7005, // CSBAR1 0x7831, // CSOR1 0x7405, // CSBAR2 0x7831, // CSOR2 }) if err != nil { return err } m.receiverType = ReceiverType4400 } return nil } // Stop sends a reboot, which disconnects the monitor, and reconfigures the port back // to the user configuration. func (m *Monitor) Stop() error { return m.CmdResetReceiver() } // Close closes the serial port associated with the monitor func (m *Monitor) Close() { m.receiver.port.Close() } // ReadMemory reads the device memory, breaking the read up into multiple // commands as needed. func (m *Monitor) ReadMemory(addr uint32, length int) ([]byte, error) { if !m.inMonitor { return nil, fmt.Errorf("receiver not in monitor") } //fmt.Printf("ReadMemory: addr=%08X length=%d\n", addr, length) out := make([]byte, 0) for i := 0; i < length; i += readWriteBlockSize { l := readWriteBlockSize if i+l > length { l = length - i } blockAddr := addr + uint32(i) b, err := m.CmdReadMemory(blockAddr, uint8(l)) if err != nil { return nil, err } out = append(out, b...) } return out, nil } func (m *Monitor) CmdReadMemory(addr uint32, length uint8) ([]byte, error) { b := make([]byte, 5) binary.BigEndian.PutUint32(b[0:4], addr) b[4] = byte(length) //fmt.Printf("CmdReadMemory: addr=%08X length=%d\n", blockAddr, l) var err error const maxRetries = 3 for r := 0; r < maxRetries; r++ { if r > 0 { fmt.Printf("CmdReadMemory: read failed: %v. Retrying.\n", err) } var reply *CommandReply if reply, err = m.receiver.DoStxEtxCommand(MonCmdReadMem, b); err != nil { continue } if reply.Command != MonCmdReadMemAck || reply.Status != 0 { err = newProtocolError("unexpected response from read memory") continue } if replyAddr := binary.BigEndian.Uint32(reply.Data[0:4]); replyAddr != addr { err = newProtocolError("address mismatch in read reply. expected 0x%06X, received 0x%06X", addr, replyAddr) continue } return reply.Data[4:], nil } return nil, err } // WriteMemory writes the device memory, breaking the write up into multiple // commands as needed. func (m *Monitor) WriteMemory(addr uint32, data interface{}) error { if !m.inMonitor { return fmt.Errorf("receiver not in monitor") } buf := &bytes.Buffer{} binary.Write(buf, binary.BigEndian, data) b := buf.Bytes() length := len(b) for i := 0; i < length; i += readWriteBlockSize { l := readWriteBlockSize if i+l > length { l = length - i } blockAddr := addr + uint32(i) err := m.CmdWriteMemory(blockAddr, b[i:i+l]) if err != nil { return err } } return nil } func (m *Monitor) CmdWriteMemory(addr uint32, data []byte) error { const maxRetries = 3 buf := make([]byte, 4) // for writes the address words are in little endian order, but the bytes // within the words are in big endian order binary.BigEndian.PutUint16(buf[0:2], uint16(addr&0xFFFF)) binary.BigEndian.PutUint16(buf[2:4], uint16(addr>>16&0xFFFF)) buf = append(buf, data...) var err error for r := 0; r < maxRetries; r++ { if r > 0 { fmt.Printf("CmdWriteMemory: write failed: %v. Retrying.\n", err) } err = m.receiver.DoAckCommand(MonCmdWriteMem, buf) if err == nil { break } } if err != nil { return err } return nil } // Jump calls the monitor's jump command. func (m *Monitor) CmdJump(addr uint32) error { if !m.inMonitor { return fmt.Errorf("receiver not in monitor") } b := make([]byte, 4) binary.BigEndian.PutUint32(b[0:4], addr) return m.receiver.DoAckCommand(MonCmdJmp, b) } // ResetReceiver calls the monitor's reset command. func (m *Monitor) CmdResetReceiver() error { if !m.inMonitor { return fmt.Errorf("receiver not in monitor") } err := m.receiver.SendStxEtxRequest(MonCmdReset, []byte{}) if err != nil { return err } m.inMonitor = false // it seems that if I switch the baud rate too soon, the UART will still // be transmitting the above data, which will corrupt the command. time.Sleep(2 * time.Second) // revert the serial port err = m.receiver.useUserBaudRate() if err != nil { return err } return nil } // ReceiverInfo holds all the information we can glean from the receiver by // probing its memory. type ReceiverInfo struct { Name1 string Name2 string SerialNumber int SerialNumberString string OptionMemory []byte ConfigMemory []byte FirmwareDate time.Time FirmwareVersionMajor int FirmwareVersionMinor int FirmwareVersion float64 ReceiverType ReceiverType Code1Start uint32 Code1End uint32 Code1Checksum uint32 Code2Start uint32 Code2End uint32 Code2Checksum uint32 } // PrintOptions emulates the output of the loader.exe 'O' monitor mode command func (info *ReceiverInfo) PrintOptions() { // Print receiver identity information fmt.Printf(" name1:%s\n", info.Name1) fmt.Printf(" name2:%s\n", info.Name2) fmt.Printf("ASCII serial #:%14s\n", info.SerialNumberString) fmt.Printf(" int serial #:%14d\n", info.SerialNumber) // Print Header for i := 0; i <= 48; i += 12 { fmt.Printf("Options %02d-%02d ", i, i+11) } fmt.Printf("\n") // Print option values optNames := []string{ "opt #00", "opt #01", "Locator", "opt #03", "RTCMAsci", "CyclPrnt", "PosStats", "TailBuoy", "PFinder", "LandSeis", "RTCMnetw", "CarPhDis", "Caltrans", "MxL1only", "RmtDnlod", "Demo", "opt #16", "RT17dsab", "LclDatum", "opt #19", "DualFreq", "SerPorts", "MulDatum", "ExtFreq", "EventMrk", "1PPS", "opt #26", "opt #27", "RTCM in", "RTCM out", "SyncLimt", "NMEA out", "NGS", "opt #33", "MultWYPT", "LctrCrPh", "Kinematc", "Config'd", "MemLimit", "RTK-L1", "RTK-OTF", "IONOFREE", "opt #42", "opt #43", "opt #44", "opt #45", "opt #46", "opt #47", "opt #48", "opt #49", "opt #50", "opt #51", "opt #52", "opt #53", "opt #54", "opt #55", "opt #56", "opt #57", "opt #58", "opt #59", } for i := 0; i < 12; i++ { for j := i; j <= i+48; j += 12 { fmt.Printf("$%02x-%-11s", info.OptionMemory[j], optNames[j]) } fmt.Printf("\n") } } func (info *ReceiverInfo) PrintVersions() { // TODO: empty string in param 3 should say '(TEST)' when in test mode fmt.Printf(" Code Version: %1d.%02d %s Date: %s\n", info.FirmwareVersionMajor, info.FirmwareVersionMinor, "", info.FirmwareDate.Format("02-Jan-06")) fmt.Printf(" Code1: %06X-%06X Code2: %06X-%06X\n", info.Code1Start, info.Code1End-1, info.Code2Start, info.Code2End-1) fmt.Printf("Checksums: %06X %06X\n", info.Code1Checksum, info.Code2Checksum) } // GetReceiverInfo reads receiver information out of the receiver. Unlike GetOptions // and GetRuntimeConfig, this reads the values straight out of memory. func (m *Monitor) GetReceiverInfo() (*ReceiverInfo, error) { if !m.inMonitor { return nil, fmt.Errorf("receiver not in monitor") } info := &ReceiverInfo{} // Read the receiver-specific configuration. // 0x80600 and 0x22d both contain the same data, but for some reason some // code reads from one area and some from the other. b, err := m.ReadMemory(0x80600, 0x97) // or 0x80600? I'm so confused. if err != nil { return nil, err } info.Name1 = cStrtoGoStr(b[60:96]) info.Name2 = cStrtoGoStr(b[96:132]) info.SerialNumberString = cStrtoGoStr(b[132:147]) info.SerialNumber = int(binary.BigEndian.Uint32(b[147:151])) info.OptionMemory = b[0:60] info.ReceiverType = m.receiverType configBase := uint32(0x000001F0) // read the configuration memory b, err = m.ReadMemory(configBase, 0x3A) if err != nil { return nil, err } info.ConfigMemory = b date := int(binary.BigEndian.Uint32(info.ConfigMemory[0x222-configBase:])) month := date / 10000 day := date / 100 % 100 year := date % 100 if year >= 80 { year += 1900 } else { year += 2000 } info.FirmwareDate = time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) ver := int(binary.BigEndian.Uint16(info.ConfigMemory[0x204-configBase:])) info.FirmwareVersionMajor = ver / 100 info.FirmwareVersionMinor = ver % 100 info.FirmwareVersion = float64(ver) / 100 info.Code1Start = binary.BigEndian.Uint32(info.ConfigMemory[0x1f0-configBase:]) info.Code1End = binary.BigEndian.Uint32(info.ConfigMemory[0x1f4-configBase:]) info.Code2Start = binary.BigEndian.Uint32(info.ConfigMemory[0x1f8-configBase:]) info.Code2End = binary.BigEndian.Uint32(info.ConfigMemory[0x1fc-configBase:]) // read checksums b, err = m.ReadMemory(info.Code1End-12, 4) if err != nil { return nil, err } info.Code1Checksum = binary.BigEndian.Uint32(b) b, err = m.ReadMemory(info.Code2End-12, 4) if err != nil { return nil, err } info.Code2Checksum = binary.BigEndian.Uint32(b) if info.FirmwareVersion > 5.60 { } return info, nil } func cStrtoGoStr(b []byte) string { i := bytes.IndexByte(b, 0) if i < 0 { return string(b) } return string(b[:i]) } type RuntimeConfig struct { SerialNumber string ReceiverType string NavProcessVersion float64 SigProcessVersion float64 BootRomVersion float64 AntennaSerial string AntennaType string Channels string ChannelsL1 string } func progressBar(width, progress, total int) string { progressBlocks := []rune{'▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'} p := float64(progress) / float64(total) blocks := int(float64(width) * p) eighths := int(float64(width)*p*8) % 8 bar := strings.Repeat("█", blocks) blankWidth := width - blocks - 1 if blankWidth < 0 { blankWidth = 0 } blank := strings.Repeat("-", blankWidth) return fmt.Sprintf("(%0.2f%%) %s%c%s\n", p*100, bar, progressBlocks[eighths], blank) } type codeBlock struct { start uint32 end uint32 } func (m *Monitor) CaptureReceiverFirmware() (*dotx.DotXFile, error) { info, err := m.GetReceiverInfo() if err != nil { return nil, err } dotX := dotx.NewDotX() // these chip selects are hard-coded into the first two records in the file dotX.AddBlock(0x00FFFA54, []byte{0x70, 0x07, 0x78, 0x71}) dotX.AddBlock(0x00FFFA46, []byte{0x03, 0xF5}) // then the code blocks codeBlocks := []*dotx.Block{ {StartAddr: info.Code1Start, EndAddr: info.Code1End}, {StartAddr: info.Code2Start, EndAddr: info.Code2End}, } totalBytes := 0 for _, cb := range codeBlocks { totalBytes += cb.Len() } bytesWriten := 0 for _, block := range codeBlocks { blockLen := block.Len() b, err := m.ReadMemory(block.StartAddr, blockLen) if err != nil { return nil, err } block.Bytes = b dotX.Blocks = append(dotX.Blocks, block) bytesWriten += blockLen } // func(i uint32) { // fmt.Printf("$%08X %s\r", i, progressBar(50, int(i-block.StartAddr)+bytesWriten, totalBytes)) // } return dotX, nil } func (m *Monitor) CmdGetRuntimeConfig() (*RuntimeConfig, error) { if m.inMonitor { return nil, fmt.Errorf("receiver not in normal mode") } r, err := m.receiver.DoStxEtxCommand(NrmCmdGetSerial, []byte{}) if err != nil { return nil, err } if r.Command != NrmCmdRSerial || r.Status != 0 { return nil, newProtocolError("unexpected response from read memory") } conf := &RuntimeConfig{} conf.SerialNumber = strings.TrimSpace(cStrtoGoStr(r.Data[0:8])) conf.ReceiverType = strings.TrimSpace(cStrtoGoStr(r.Data[8:16])) conf.NavProcessVersion, _ = strconv.ParseFloat(cStrtoGoStr(r.Data[16:21]), 64) conf.NavProcessVersion /= 100 conf.SigProcessVersion, _ = strconv.ParseFloat(cStrtoGoStr(r.Data[21:26]), 64) conf.SigProcessVersion /= 100 conf.BootRomVersion, _ = strconv.ParseFloat(cStrtoGoStr(r.Data[26:31]), 64) conf.BootRomVersion /= 100 conf.AntennaSerial = strings.TrimSpace(cStrtoGoStr(r.Data[31:39])) conf.AntennaType = strings.TrimSpace(cStrtoGoStr(r.Data[39:41])) conf.Channels = strings.TrimSpace(cStrtoGoStr(r.Data[41:43])) conf.ChannelsL1 = strings.TrimSpace(cStrtoGoStr(r.Data[43:45])) return conf, nil } type Options struct { ElevationMask int PDOPMask float64 SyncTime float64 FastestMeasRate float64 CurrentPortID int PortsAvailable int L1L2Operation int CarrierPhase bool KinematicMode bool LocatorMode bool PowerUpOption bool RTKOption bool NMEA0183Outputs bool RTCM1Input bool RTCM2Input bool RTCM1Output bool RTCM2Output bool NavOption bool FirmwareUpdate bool EventMarker int PulsePerSec int ExtTimeInput int CoCOM bool MemoryInstalled int MemoryUsed int RTCMNetwork bool DataFormat int DataOffset int PositionStatistics bool RemoteDownload bool LocalDatums bool RealTimeSurveyData bool CalTrans bool ReceiverType int } func (m *Monitor) CmdGetOptions() (*Options, error) { if m.inMonitor { return nil, fmt.Errorf("receiver not in normal mode") } r, err := m.receiver.DoStxEtxCommand(NrmCmdGetOpt, []byte{}) if err != nil { return nil, err } if r.Command != NrmCmdRetOpt || r.Status != 0 { return nil, newProtocolError("unexpected response from read memory") } opts := &Options{ ElevationMask: int(r.Data[0]), PDOPMask: float64(r.Data[1]) * 0.1, SyncTime: float64(binary.BigEndian.Uint16(r.Data[2:4])) * 1, FastestMeasRate: float64(binary.BigEndian.Uint16(r.Data[4:6])) * .1, CurrentPortID: int(r.Data[6]), PortsAvailable: int(r.Data[7]), L1L2Operation: int(r.Data[8]), CarrierPhase: r.Data[9] == 1, KinematicMode: r.Data[10] == 1, LocatorMode: r.Data[11] == 1, PowerUpOption: r.Data[12] == 1, RTKOption: r.Data[13] == 1, NMEA0183Outputs: r.Data[18] == 1, RTCM1Input: r.Data[19] == 1, RTCM2Input: r.Data[20] == 1, RTCM1Output: r.Data[21] == 1, RTCM2Output: r.Data[22] == 1, NavOption: r.Data[23] == 1, FirmwareUpdate: r.Data[24] == 1, EventMarker: int(r.Data[25]), PulsePerSec: int(r.Data[26]), ExtTimeInput: int(r.Data[27]), CoCOM: r.Data[28] == 1, MemoryInstalled: int(binary.BigEndian.Uint16(r.Data[29:31])), MemoryUsed: int(r.Data[31]), RTCMNetwork: r.Data[32] == 1, DataFormat: int(r.Data[34]), DataOffset: int(binary.BigEndian.Uint16(r.Data[35:37])), PositionStatistics: r.Data[37] == 1, RemoteDownload: r.Data[38] == 1, LocalDatums: r.Data[39] == 1, RealTimeSurveyData: r.Data[40] == 1, CalTrans: r.Data[41] == 1, ReceiverType: int(r.Data[44]), } return opts, nil } func (m *Monitor) ProgramReceiver(fw *dotx.DotXFile) error { fmt.Printf("Writing receiver program...\n") save := make([]byte, 8) totalBytes := fw.TotalBytes() t := 0 var err error progressBarWidth := 50 progressBlocks := []rune{'▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'} // set fixFailedWrite to true to have the code just correct the first 8 // bytes of memory to remove the boot blocker. This is a kludge for testing. fixFailedWrite := false for i, block := range fw.Blocks { if fixFailedWrite && i > 2 { // all we want are the chip selects and CODE1 continue } recs := block.ToRecords(244) fmt.Printf("Writing block %d ($%08X - $%08X)...\n", i, block.StartAddr, block.EndAddr) for _, rec := range recs { t += len(rec.Bytes) p := float64(t) / float64(totalBytes) blocks := int(float64(progressBarWidth) * p) eighths := int(float64(progressBarWidth)*p*8) % 8 bar := strings.Repeat("█", blocks) blankWidth := progressBarWidth - blocks - 1 if blankWidth < 0 { blankWidth = 0 } blank := strings.Repeat("-", blankWidth) progressBar := fmt.Sprintf("%s%c%s", bar, progressBlocks[eighths], blank) fmt.Printf("$%08X (%0.2f%%) %s\r", rec.Addr, p*100, progressBar) if rec.Addr == 0x00000000 { // This replaces the first 8 bytes of the firmware image with // 4E72270000000000. The first 8 bytes are the stack pointer and // reset vector. The first value is actually a STOP #$2700 // instruction, and the 0x00000000 reset vector means that STOP // is the only thing that will be executed. This saves the intended // value to write to memory at the end of the firmware upgrade // process, guaranteeing that the receiver will not function // with a half-baked firmware. Crafty, Trimble. b2 := make([]byte, len(rec.Bytes)) copy(b2, rec.Bytes) copy(save[0:8], rec.Bytes[0:8]) binary.BigEndian.PutUint32(b2[0:], 0x4E722700) binary.BigEndian.PutUint32(b2[4:], 0x00000000) err = m.WriteMemory(rec.Addr, b2) if fixFailedWrite { // for a fixFailedWrite all I care about is the first record. break } } else { err = m.WriteMemory(rec.Addr, rec.Bytes) } if err != nil { return err } if m.Dummy { time.Sleep(1 * time.Millisecond) } } fmt.Printf("% 80s\r", "") } fmt.Printf("\rDone. %d bytes written.\n", t) err = m.WriteMemory(0x00000000, save) if err != nil { return err } fmt.Printf("Rebooting receiver.\n") return m.CmdResetReceiver() } // Read implements the io.ReadSeeker interface func (m *Monitor) Read(p []byte) (n int, err error) { p, err = m.ReadMemory(m.seekPos, len(p)) m.seekPos += uint32(len(p)) return len(p), err } // Seek implements the io.ReadSeeker interface func (m *Monitor) Seek(offset int64, whence int) (int64, error) { var abs int64 switch whence { case io.SeekStart: abs = offset case io.SeekCurrent: abs = int64(m.seekPos) + offset default: return 0, fmt.Errorf("Seek: invalid whence") } if abs < 0 { return 0, fmt.Errorf("Seek: negative position") } m.seekPos = uint32(abs) return abs, nil } func ReadMemory(f io.ReadSeeker, addr uint32, length int) ([]byte, error) { _, err := f.Seek(int64(addr), io.SeekStart) if err != nil { return nil, err } b := make([]byte, length) _, err = f.Read(b) if err != nil { return nil, err } return b, nil } type RawReceiverInfo struct { Options [60]byte Name1 [36]byte Name2 [36]byte SerialNumberString [15]byte SerialNumber uint32 } // GetReceiverInfo reads receiver information out of the receiver. Unlike GetOptions // and GetRuntimeConfig, this reads the values straight out of memory. func GetReceiverInfo(f io.ReadSeeker) (*ReceiverInfo, error) { info := &ReceiverInfo{} // Read the receiver-specific configuration. // 0x80600 and 0x22d both contain the same data, but for some reason some // code reads from one area and some from the other. b, err := ReadMemory(f, 0x80600, 0x97) if err != nil { return nil, err } info.OptionMemory = b[0:60] info.Name1 = cStrtoGoStr(b[60:96]) info.Name2 = cStrtoGoStr(b[96:132]) info.SerialNumberString = cStrtoGoStr(b[132:147]) info.SerialNumber = int(binary.BigEndian.Uint32(b[147:151])) configBase := uint32(0x000001F0) // read the configuration memory b, err = ReadMemory(f, configBase, 0x3A) if err != nil { return nil, err } info.ConfigMemory = b date := int(binary.BigEndian.Uint32(info.ConfigMemory[0x222-configBase:])) month := date / 10000 day := date / 100 % 100 year := date % 100 if year >= 80 { year += 1900 } else { year += 2000 } info.FirmwareDate = time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) ver := int(binary.BigEndian.Uint16(info.ConfigMemory[0x204-configBase:])) info.FirmwareVersionMajor = ver / 100 info.FirmwareVersionMinor = ver % 100 info.FirmwareVersion = float64(ver) / 100 info.Code1Start = binary.BigEndian.Uint32(info.ConfigMemory[0x1f0-configBase:]) info.Code1End = binary.BigEndian.Uint32(info.ConfigMemory[0x1f4-configBase:]) info.Code2Start = binary.BigEndian.Uint32(info.ConfigMemory[0x1f8-configBase:]) info.Code2End = binary.BigEndian.Uint32(info.ConfigMemory[0x1fc-configBase:]) // read checksums b, err = ReadMemory(f, info.Code1End-12, 4) if err != nil { return nil, err } info.Code1Checksum = binary.BigEndian.Uint32(b) b, err = ReadMemory(f, info.Code2End-12, 4) if err != nil { return nil, err } info.Code2Checksum = binary.BigEndian.Uint32(b) if info.FirmwareVersion > 5.60 { } return info, nil } func CaptureReceiverFirmware(f io.ReadSeeker) (*dotx.DotXFile, error) { info, err := GetReceiverInfo(f) if err != nil { return nil, err } dotX := dotx.NewDotX() // these chip selects are hard-coded into the first two records in the file dotX.AddBlock(0x00FFFA54, []byte{0x70, 0x07, 0x78, 0x71}) dotX.AddBlock(0x00FFFA46, []byte{0x03, 0xF5}) // then the code blocks codeBlocks := []*dotx.Block{ {StartAddr: info.Code1Start, EndAddr: info.Code1End}, {StartAddr: info.Code2Start, EndAddr: info.Code2End}, } totalBytes := 0 for _, cb := range codeBlocks { totalBytes += cb.Len() } bytesWriten := 0 for _, block := range codeBlocks { blockLen := block.Len() b, err := ReadMemory(f, block.StartAddr, blockLen) if err != nil { return nil, err } block.Bytes = b dotX.Blocks = append(dotX.Blocks, block) bytesWriten += blockLen } return dotX, nil }