Files
2026-08-03 19:57:38 +03:00

782 lines
22 KiB
Go

// MTBoss: MT6261 firmware management application
// with zero DA required
// Created by Luxferre in 2026, released into the public domain
package main
import (
"bytes"
"crypto/md5"
"encoding/binary"
"encoding/hex"
"flag"
"fmt"
"os"
"strings"
"time"
"go.bug.st/serial"
)
// ==================== REGISTERS & BITS ====================
const (
SFI_BASE = 0xA0140000
SFI_MAC_CTL = SFI_BASE + 0x0000
SFI_DIRECT_CTL = SFI_BASE + 0x0004
SFI_MISC_CTL = SFI_BASE + 0x0008
SFI_MAC_OUTL = SFI_BASE + 0x0010
SFI_MAC_INL = SFI_BASE + 0x0014
SFI_MISC_CTL3 = SFI_BASE + 0x0044
SFI_GPRAM = SFI_BASE + 0x0800
SFI_WIP = 1 << 0
SFI_WIP_READY = 1 << 1
SFI_TRIG = 1 << 2
SFI_MAC_EN = 1 << 3
SFI_MAC_SEL = 1 << 28
BOOT_ENG_BASE = 0xA0510000
FLASH_SIZE_4MB = 0x400000 // 4MB (4,194,304 bytes)
FLASH_PAGE_SIZE = 0x100 // 256 bytes
FLASH_SECTOR_SZ = 0x1000 // 4KB
SR_WIP = 0x01
SR_WEL = 0x02
SR_BP0 = 0x04
SR_BP1 = 0x08
SR_BP2 = 0x10
SR_BP3 = 0x20
)
// ==================== HELPER PARSER ====================
func parseSizeOrOffset(valStr string) (uint64, error) {
valStr = strings.TrimSpace(valStr)
if valStr == "" {
return 0, fmt.Errorf("empty string")
}
multiplier := uint64(1)
upper := strings.ToUpper(valStr)
if strings.HasSuffix(upper, "MIB") || strings.HasSuffix(upper, "MB") {
multiplier = 1024 * 1024
valStr = valStr[:len(valStr)-2]
if strings.HasSuffix(strings.ToUpper(valStr), "I") {
valStr = valStr[:len(valStr)-1]
}
} else if strings.HasSuffix(upper, "M") {
multiplier = 1024 * 1024
valStr = valStr[:len(valStr)-1]
} else if strings.HasSuffix(upper, "KIB") || strings.HasSuffix(upper, "KB") {
multiplier = 1024
valStr = valStr[:len(valStr)-2]
if strings.HasSuffix(strings.ToUpper(valStr), "I") {
valStr = valStr[:len(valStr)-1]
}
} else if strings.HasSuffix(upper, "K") {
multiplier = 1024
valStr = valStr[:len(valStr)-1]
}
valStr = strings.TrimSpace(valStr)
var val uint64
var err error
if strings.HasPrefix(valStr, "0x") || strings.HasPrefix(valStr, "0X") {
_, err = fmt.Sscanf(valStr, "0x%x", &val)
} else {
_, err = fmt.Sscanf(valStr, "%d", &val)
}
if err != nil {
return 0, err
}
return val * multiplier, nil
}
// ==================== MTBOSS STRUCT ====================
type MTBoss struct {
port serial.Port
portName string
chipID uint16
}
func NewMTBoss(portName string) (*MTBoss, error) {
flasher := &MTBoss{portName: portName}
if err := flasher.connectPort(); err != nil {
return nil, err
}
return flasher, nil
}
func (f *MTBoss) connectPort() error {
var targetPort string
if f.portName == "" || f.portName == "auto" {
ports, err := serial.GetPortsList()
if err == nil {
for _, p := range ports {
if strings.Contains(p, "ttyUSB") || strings.Contains(p, "ttyACM") || strings.Contains(p, "COM") {
targetPort = p
break
}
}
}
if targetPort == "" {
targetPort = "/dev/ttyUSB0"
}
} else {
targetPort = f.portName
}
fmt.Printf("Connecting to serial port %s at 115200 baud (RTS/CTS enabled)...\n", targetPort)
mode := &serial.Mode{
BaudRate: 115200,
DataBits: 8,
Parity: serial.NoParity,
StopBits: serial.OneStopBit,
}
start := time.Now()
for time.Since(start) < 10*time.Minute {
p, err := serial.Open(targetPort, mode)
if err == nil {
// Enable RTSCTS hardware flow control
_ = p.SetRTS(true)
_ = p.SetDTR(true)
f.port = p
f.portName = targetPort
return nil
}
// Also scan for any new port if set to auto
if f.portName == "auto" {
ports, _ := serial.GetPortsList()
for _, pName := range ports {
if strings.Contains(pName, "ttyUSB") || strings.Contains(pName, "ttyACM") {
p, err := serial.Open(pName, mode)
if err == nil {
_ = p.SetRTS(true)
_ = p.SetDTR(true)
f.port = p
f.portName = pName
return nil
}
}
}
}
time.Sleep(50 * time.Millisecond)
}
return fmt.Errorf("timeout waiting for serial port %s", targetPort)
}
func (f *MTBoss) Close() {
if f.port != nil {
_ = f.port.Close()
}
}
// ==================== BASIC SERIAL I/O ====================
func (f *MTBoss) sendRaw(data []byte) error {
_, err := f.port.Write(data)
return err
}
func (f *MTBoss) readExact(length int) ([]byte, error) {
buf := make([]byte, length)
read := 0
for read < length {
n, err := f.port.Read(buf[read:])
if err != nil {
return nil, err
}
if n == 0 {
time.Sleep(2 * time.Millisecond)
}
read += n
}
return buf, nil
}
// ==================== BROM REGISTER OPERATIONS ====================
func (f *MTBoss) readReg16(addr uint32) (uint16, error) {
cmd := make([]byte, 9)
cmd[0] = 0xA2
binary.BigEndian.PutUint32(cmd[1:5], addr)
binary.BigEndian.PutUint32(cmd[5:9], 1)
if err := f.sendRaw(cmd); err != nil {
return 0, err
}
if _, err := f.readExact(9); err != nil { // Echo
return 0, err
}
resp, err := f.readExact(2)
if err != nil {
return 0, err
}
return binary.BigEndian.Uint16(resp), nil
}
func (f *MTBoss) writeReg16(addr uint32, val uint16) error {
cmd := make([]byte, 9)
cmd[0] = 0xD2
binary.BigEndian.PutUint32(cmd[1:5], addr)
binary.BigEndian.PutUint32(cmd[5:9], 1)
if err := f.sendRaw(cmd); err != nil {
return err
}
if _, err := f.readExact(9); err != nil {
return err
}
if ack, err := f.readExact(2); err != nil || !bytes.Equal(ack, []byte{0x00, 0x01}) {
return fmt.Errorf("writeReg16 cmd ACK error: %v", ack)
}
valBuf := make([]byte, 2)
binary.BigEndian.PutUint16(valBuf, val)
if err := f.sendRaw(valBuf); err != nil {
return err
}
if _, err := f.readExact(2); err != nil {
return err
}
if ack, err := f.readExact(2); err != nil || !bytes.Equal(ack, []byte{0x00, 0x01}) {
return fmt.Errorf("writeReg16 data ACK error: %v", ack)
}
return nil
}
func (f *MTBoss) readReg32(addr uint32) (uint32, error) {
cmd := make([]byte, 9)
cmd[0] = 0xD1
binary.BigEndian.PutUint32(cmd[1:5], addr)
binary.BigEndian.PutUint32(cmd[5:9], 1)
if err := f.sendRaw(cmd); err != nil {
return 0, err
}
if _, err := f.readExact(9); err != nil {
return 0, err
}
resp, err := f.readExact(8) // status[2] + data[4] + status[2]
if err != nil {
return 0, err
}
return binary.LittleEndian.Uint32(resp[2:6]), nil
}
func (f *MTBoss) writeReg32(addr uint32, val uint32) error {
cmd := make([]byte, 9)
cmd[0] = 0xD4
binary.BigEndian.PutUint32(cmd[1:5], addr)
binary.BigEndian.PutUint32(cmd[5:9], 1)
if err := f.sendRaw(cmd); err != nil {
return err
}
if _, err := f.readExact(9); err != nil {
return err
}
if ack, err := f.readExact(2); err != nil || !bytes.Equal(ack, []byte{0x00, 0x01}) {
return fmt.Errorf("writeReg32 cmd ACK error: %v", ack)
}
valBuf := make([]byte, 4)
binary.BigEndian.PutUint32(valBuf, val)
if err := f.sendRaw(valBuf); err != nil {
return err
}
if _, err := f.readExact(4); err != nil {
return err
}
if ack, err := f.readExact(2); err != nil || !bytes.Equal(ack, []byte{0x00, 0x01}) {
return fmt.Errorf("writeReg32 data ACK error: %v", ack)
}
return nil
}
// ==================== BOOTROM HANDSHAKE ====================
func (f *MTBoss) WaitAndConnect(timeoutSec int) error {
fmt.Println("\n============================================================")
fmt.Println("Waiting for MTBoss (MT6261) BootROM Connection")
fmt.Println("============================================================")
fmt.Println("1. Power OFF phone completely")
fmt.Println("2. Press and hold BOOT key (or Power button)")
fmt.Println("3. Plug in USB cable now...")
start := time.Now()
dots := 0
for time.Since(start) < time.Duration(timeoutSec)*time.Second {
_ = f.sendRaw([]byte{0xA0})
time.Sleep(5 * time.Millisecond)
b := make([]byte, 1)
n, _ := f.port.Read(b)
if n > 0 && b[0] == 0x5F {
// Second sync packet
_ = f.sendRaw([]byte{0x0A, 0x50, 0x05})
ack3, err := f.readExact(3)
if err == nil && bytes.Equal(ack3, []byte{0xF5, 0xAF, 0xFA}) {
fmt.Println("\nConnected to MT6261 BootROM!")
chip, err := f.readReg16(0x80000008)
if err == nil {
f.chipID = chip
fmt.Printf("Chip ID: 0x%04X\n", chip)
}
// Disable watchdogs & enable USB download
_ = f.writeReg16(0xA0030000, 0x2200) // System watchdog
_ = f.writeReg16(0xA0700A28, 0x8000) // USB download mode
_ = f.writeReg16(0xA0700A24, 0x0002) // Battery watchdog
_ = f.writeReg32(BOOT_ENG_BASE, 2) // Boot engine memory map mode 2
fmt.Println("BootROM initialized & watchdogs disabled.")
return nil
}
}
dots++
if dots%20 == 0 {
fmt.Print(".")
}
}
return fmt.Errorf("timeout waiting for BootROM sync")
}
// ==================== SFI HARDWARE MAC MODE ====================
func (f *MTBoss) sfiMacCmdWrite(cmdByte byte, addr *uint32, data []byte) error {
var payload []byte
payload = append(payload, cmdByte)
if addr != nil {
addrBytes := make([]byte, 4)
binary.BigEndian.PutUint32(addrBytes, *addr)
payload = append(payload, addrBytes[1:]...) // 3-byte big-endian address
}
payload = append(payload, data...)
totalLen := uint32(len(payload))
// 1. Write payload to GPRAM (0xA0140800) in 32-bit LE words
for i := uint32(0); i < totalLen; i += 4 {
end := i + 4
if end > totalLen {
end = totalLen
}
chunk := make([]byte, 4)
copy(chunk, payload[i:end])
valLE := binary.LittleEndian.Uint32(chunk)
if err := f.writeReg32(SFI_GPRAM+i, valLE); err != nil {
return err
}
}
// 2. Mask AHB Channel 2
misc3, _ := f.readReg32(SFI_MISC_CTL3)
_ = f.writeReg32(SFI_MISC_CTL3, misc3|(1<<9))
// 3. Enable MAC Mode FIRST (SFI_MAC_SEL bit 28 | SFI_MAC_EN bit 3)
macVal := uint32(SFI_MAC_SEL | SFI_MAC_EN)
if err := f.writeReg32(SFI_MAC_CTL, macVal); err != nil {
return err
}
// 4. Set OUTL and INL lengths while MAC mode is active
_ = f.writeReg32(SFI_MAC_OUTL, totalLen)
_ = f.writeReg32(SFI_MAC_INL, 0)
// 5. Trigger transaction (SFI_TRIG bit 2)
_ = f.writeReg32(SFI_MAC_CTL, macVal|SFI_TRIG)
// 6. Poll for completion (WIP_READY bit 1 set, WIP bit 0 clear)
for i := 0; i < 300; i++ {
v, err := f.readReg32(SFI_MAC_CTL)
if err == nil && (v&SFI_WIP_READY != 0) && (v&SFI_WIP == 0) {
break
}
time.Sleep(1 * time.Millisecond)
}
// 7. Clean up MAC mode and unmask AHB
_ = f.writeReg32(SFI_MAC_CTL, 0)
_ = f.writeReg32(SFI_MISC_CTL3, misc3&^(1<<9))
return nil
}
func (f *MTBoss) sfiMacCmdRead(cmdByte byte, readLen uint32) ([]byte, error) {
// 1. Write command to GPRAM
if err := f.writeReg32(SFI_GPRAM, uint32(cmdByte)); err != nil {
return nil, err
}
// 2. Mask AHB Channel 2
misc3, _ := f.readReg32(SFI_MISC_CTL3)
_ = f.writeReg32(SFI_MISC_CTL3, misc3|(1<<9))
// 3. Enable MAC Mode FIRST
macVal := uint32(SFI_MAC_SEL | SFI_MAC_EN)
_ = f.writeReg32(SFI_MAC_CTL, macVal)
// 4. Set OUTL and INL lengths
_ = f.writeReg32(SFI_MAC_OUTL, 1)
_ = f.writeReg32(SFI_MAC_INL, readLen)
// 5. Trigger
_ = f.writeReg32(SFI_MAC_CTL, macVal|SFI_TRIG)
// 6. Poll completion
for i := 0; i < 300; i++ {
v, err := f.readReg32(SFI_MAC_CTL)
if err == nil && (v&SFI_WIP_READY != 0) && (v&SFI_WIP == 0) {
break
}
time.Sleep(1 * time.Millisecond)
}
// 7. Read response from GPRAM
totalBytes := 1 + readLen
var resBuf bytes.Buffer
for i := uint32(0); i < (totalBytes + 3); i += 4 {
val, _ := f.readReg32(SFI_GPRAM + i)
wordBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(wordBytes, val)
resBuf.Write(wordBytes)
}
// 8. Clean up
_ = f.writeReg32(SFI_MAC_CTL, 0)
_ = f.writeReg32(SFI_MISC_CTL3, misc3&^(1<<9))
fullData := resBuf.Bytes()
if uint32(len(fullData)) < 1+readLen {
return nil, fmt.Errorf("short SFI MAC read")
}
return fullData[1 : 1+readLen], nil
}
// ==================== SPI FLASH HIGH-LEVEL ====================
func (f *MTBoss) ReadJEDECID() ([]byte, error) {
return f.sfiMacCmdRead(0x9F, 3)
}
func (f *MTBoss) ReadStatusRegister() (byte, error) {
data, err := f.sfiMacCmdRead(0x05, 1)
if err != nil || len(data) == 0 {
return 0, err
}
return data[0], nil
}
func (f *MTBoss) WaitUntilReady(timeoutSec int) error {
start := time.Now()
for time.Since(start) < time.Duration(timeoutSec)*time.Second {
sr, err := f.ReadStatusRegister()
if err == nil && (sr&SR_WIP == 0) {
return nil
}
time.Sleep(5 * time.Millisecond)
}
return fmt.Errorf("timeout waiting for SPI flash ready")
}
func (f *MTBoss) UnlockWriteProtection() error {
sr, err := f.ReadStatusRegister()
if err == nil {
fmt.Printf("SPI Flash Status Register: 0x%02X\n", sr)
if sr&(SR_BP0|SR_BP1|SR_BP2|SR_BP3) != 0 {
fmt.Println("Write protection enabled - disabling...")
_ = f.sfiMacCmdWrite(0x50, nil, nil) // Volatile WREN
_ = f.sfiMacCmdWrite(0x01, nil, []byte{0x00})
_ = f.WaitUntilReady(5)
sr, _ = f.ReadStatusRegister()
fmt.Printf("Status Register after unlocking: 0x%02X\n", sr)
}
}
return nil
}
func (f *MTBoss) EnableWrite() error {
return f.sfiMacCmdWrite(0x06, nil, nil)
}
func (f *MTBoss) EraseSector(addr uint32) error {
if err := f.WaitUntilReady(5); err != nil {
return err
}
if err := f.EnableWrite(); err != nil {
return err
}
if err := f.sfiMacCmdWrite(0x20, &addr, nil); err != nil {
return err
}
return f.WaitUntilReady(5)
}
func (f *MTBoss) PageProgram(addr uint32, data []byte) error {
const maxChunk = 64
offset := 0
for offset < len(data) {
end := offset + maxChunk
if end > len(data) {
end = len(data)
}
chunk := data[offset:end]
currAddr := addr + uint32(offset)
if err := f.WaitUntilReady(5); err != nil {
return err
}
if err := f.EnableWrite(); err != nil {
return err
}
if err := f.sfiMacCmdWrite(0x02, &currAddr, chunk); err != nil {
return err
}
if err := f.WaitUntilReady(5); err != nil {
return err
}
offset += len(chunk)
}
return nil
}
// ==================== BROM MEMORY-MAPPED READING ====================
func (f *MTBoss) ReadFlash(address uint32, length uint32) ([]byte, error) {
var result bytes.Buffer
remaining := length
currAddr := address
const blkSize = 1024
for remaining > 0 {
rsize := remaining
if rsize > blkSize {
rsize = blkSize
}
wordsCnt := rsize >> 2
cmd := make([]byte, 9)
cmd[0] = 0xD1
binary.BigEndian.PutUint32(cmd[1:5], currAddr)
binary.BigEndian.PutUint32(cmd[5:9], wordsCnt)
if err := f.sendRaw(cmd); err != nil {
return nil, err
}
if _, err := f.readExact(9); err != nil {
return nil, err
}
respLen := (int(wordsCnt) * 4) + 4
resp, err := f.readExact(respLen)
if err != nil {
return nil, err
}
// Skip 2-byte header and 2-byte status footer
rawWords := resp[2 : 2+int(wordsCnt)*4]
for i := 0; i < len(rawWords); i += 4 {
wordVal := binary.LittleEndian.Uint32(rawWords[i : i+4])
wordBE := make([]byte, 4)
binary.BigEndian.PutUint32(wordBE, wordVal)
result.Write(wordBE)
}
currAddr += rsize
remaining -= rsize
progress := float64(length-remaining) / float64(length) * 100.0
fmt.Printf("\rReading: %.1f%%", progress)
}
fmt.Println("\nRead complete.")
return result.Bytes(), nil
}
// ==================== HIGH-LEVEL FLASH OPERATIONS ====================
func (f *MTBoss) WriteRegion(address uint32, data []byte, verify bool) error {
fmt.Printf("\n============================================================\n")
fmt.Printf("Flashing Region at 0x%08X (Size: %d bytes / 0x%X)\n", address, len(data), len(data))
fmt.Printf("============================================================\n")
_ = f.UnlockWriteProtection()
startSector := address & ^uint32(FLASH_SECTOR_SZ-1)
endAddr := address + uint32(len(data))
fmt.Printf("Erasing sectors from 0x%08X to 0x%08X...\n", startSector, endAddr)
for sec := startSector; sec < endAddr; sec += FLASH_SECTOR_SZ {
fmt.Printf(" Erasing sector at 0x%08X...\n", sec)
if err := f.EraseSector(sec); err != nil {
return fmt.Errorf("failed erasing sector 0x%08X: %v", sec, err)
}
}
fmt.Println("Sectors erased.")
fmt.Printf("Programming %d bytes to 0x%08X...\n", len(data), address)
offset := 0
for offset < len(data) {
end := offset + 256
if end > len(data) {
end = len(data)
}
chunk := data[offset:end]
currAddr := address + uint32(offset)
if err := f.PageProgram(currAddr, chunk); err != nil {
return fmt.Errorf("failed programming at 0x%08X: %v", currAddr, err)
}
offset += len(chunk)
fmt.Printf("\rProgramming: %.1f%%", float64(offset)/float64(len(data))*100.0)
}
fmt.Println("\nProgramming complete.")
if verify {
fmt.Printf("Verifying %d bytes at 0x%08X...\n", len(data), address)
readData, err := f.ReadFlash(address, uint32(len(data)))
if err != nil {
return fmt.Errorf("verification read failed: %v", err)
}
refMD5 := md5.Sum(data)
readMD5 := md5.Sum(readData)
fmt.Printf("Reference MD5: %s\n", hex.EncodeToString(refMD5[:]))
fmt.Printf("Readback MD5: %s\n", hex.EncodeToString(readMD5[:]))
if bytes.Equal(data, readData) {
fmt.Println("\n*** VERIFICATION PASSED - EXACT BYTE-FOR-BYTE MATCH ***")
} else {
return fmt.Errorf("verification FAILED - binary mismatch!")
}
}
return nil
}
func (f *MTBoss) Reset() {
fmt.Println("Resetting device...")
_ = f.writeReg16(0xA003001C, 0x1209)
}
// ==================== MAIN CLI ====================
func main() {
portFlag := flag.String("port", "auto", "Serial port device (e.g. /dev/ttyUSB0, /dev/ttyUSB1 or auto)")
modeFlag := flag.String("mode", "flash", "Mode of operation: flash, read, erase, identify")
fileFlag := flag.String("file", "", "Input/Output binary file path")
startFlag := flag.String("start", "", "Flash start offset address (e.g. 0, 0x0000, 4K)")
offsetFlag := flag.String("offset", "0x00000000", "Flash offset address (alias for -start, default 0)")
sizeFlag := flag.String("size", "", "Operation size in bytes/hex/units (e.g. 4MB, 64KB, 0x400000)")
lengthFlag := flag.String("length", "0x400000", "Operation size (alias for -size, default 4MiB / 0x400000)")
verifyFlag := flag.Bool("verify", true, "Verify flash write via readback")
timeoutFlag := flag.Int("timeout", 600, "Timeout in seconds waiting for BootROM sync")
flag.Parse()
// Determine start address string (prefer -start if explicitly set)
startStr := *offsetFlag
if *startFlag != "" {
startStr = *startFlag
}
// Determine size string (prefer -size if explicitly set)
sizeStr := *lengthFlag
if *sizeFlag != "" {
sizeStr = *sizeFlag
}
start, err := parseSizeOrOffset(startStr)
if err != nil {
fmt.Printf("Invalid start/offset format '%s': %v\n", startStr, err)
os.Exit(1)
}
size, err := parseSizeOrOffset(sizeStr)
if err != nil {
fmt.Printf("Invalid size/length format '%s': %v\n", sizeStr, err)
os.Exit(1)
}
flasher, err := NewMTBoss(*portFlag)
if err != nil {
fmt.Printf("Error opening serial port: %v\n", err)
os.Exit(1)
}
defer flasher.Close()
if err := flasher.WaitAndConnect(*timeoutFlag); err != nil {
fmt.Printf("BootROM Connection failed: %v\n", err)
os.Exit(1)
}
switch *modeFlag {
case "identify":
id, err := flasher.ReadJEDECID()
if err == nil && len(id) >= 3 {
fmt.Printf("JEDEC ID: %02X %02X %02X\n", id[0], id[1], id[2])
} else {
fmt.Printf("Could not read JEDEC ID: %v\n", err)
}
flasher.Reset()
case "read":
if *fileFlag == "" {
fmt.Println("Error: --file is required for read mode")
os.Exit(1)
}
fmt.Printf("Reading %d (0x%X) bytes from 0x%08X to %s...\n", size, size, uint32(start), *fileFlag)
data, err := flasher.ReadFlash(uint32(start), uint32(size))
if err != nil {
fmt.Printf("Read failed: %v\n", err)
os.Exit(1)
}
if err := os.WriteFile(*fileFlag, data, 0644); err != nil {
fmt.Printf("Failed writing file: %v\n", err)
os.Exit(1)
}
hash := md5.Sum(data)
fmt.Printf("Successfully saved %s (MD5: %s)\n", *fileFlag, hex.EncodeToString(hash[:]))
flasher.Reset()
case "erase":
fmt.Printf("Erasing %d (0x%X) bytes at 0x%08X...\n", size, size, uint32(start))
startSec := uint32(start) & ^uint32(FLASH_SECTOR_SZ-1)
endSec := uint32(start + size)
for sec := startSec; sec < endSec; sec += FLASH_SECTOR_SZ {
fmt.Printf("Erasing sector at 0x%08X...\n", sec)
if err := flasher.EraseSector(sec); err != nil {
fmt.Printf("Erase failed: %v\n", err)
os.Exit(1)
}
}
fmt.Println("Erase complete.")
flasher.Reset()
case "flash":
if *fileFlag == "" {
fmt.Println("Error: --file is required for flash mode")
os.Exit(1)
}
data, err := os.ReadFile(*fileFlag)
if err != nil {
fmt.Printf("Failed reading file %s: %v\n", *fileFlag, err)
os.Exit(1)
}
// If size was explicitly set or if size < len(data), limit payload to specified size
if uint64(len(data)) > size {
fmt.Printf("Limiting file payload from %d to specified size %d (0x%X) bytes\n", len(data), size, size)
data = data[:size]
}
fHash := md5.Sum(data)
fmt.Printf("Loaded %s (%d bytes, MD5: %s)\n", *fileFlag, len(data), hex.EncodeToString(fHash[:]))
if err := flasher.WriteRegion(uint32(start), data, *verifyFlag); err != nil {
fmt.Printf("Flashing failed: %v\n", err)
os.Exit(1)
}
fmt.Println("\n============================================================")
fmt.Println("Operation completed successfully!")
fmt.Println("============================================================")
flasher.Reset()
default:
fmt.Printf("Unknown mode: %s\n", *modeFlag)
os.Exit(1)
}
}