diff --git a/.gitignore b/.gitignore index b34785a..a2dddcb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -opt/ +opt/**/ hex2bin # If you prefer the allow list template instead of the deny list, see community template: diff --git a/README.md b/README.md index 3347bd2..114c413 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ The tool supports two conversion modes: - `input_file`: Path to the input file (**required**) - `output_file`: Path to the output file (**optional**) - `mode`: Conversion mode (`hex2bin` or `bin2hex`, **optional**) +- `--all`: (optional, for `bin2hex` only) Write all data, including 0xFF fill bytes, to the HEX file. By default, the tool writes only non-0xFF data (sparse HEX output). +- `--record-bytes=N`: (optional, for `bin2hex` only) Set the number of bytes per HEX record (default: 16, e.g., 16 or 32). If `output_file` is not provided, it will be automatically determined from the input file name and mode. If `mode` is not provided, it will be inferred from the input file extension: @@ -60,6 +62,30 @@ If `mode` is not provided, it will be inferred from the input file extension: If both `output_file` and `mode` are omitted, both will be inferred from the input file extension. +### HEX Output Modes + +By default, when converting from binary to Intel HEX (`bin2hex`), the tool writes only non-0xFF data to the HEX file (sparse output). This matches the structure of reference HEX files and avoids unnecessary fill records. + +To force the tool to write all data (including 0xFF fill bytes) in the HEX file, use the `--all` flag: + +```bash +hex2bin firmware.bin --all +``` + +To control the number of bytes per HEX record, use the `--record-bytes=N` flag (e.g., 16 or 32): + +```bash +hex2bin firmware.bin --record-bytes=16 +``` + +You can combine both flags: + +```bash +hex2bin firmware.bin --all --record-bytes=16 +``` + +This will produce a HEX file with records of the specified size (default is 16 bytes per record). + ### Examples #### Minimal (auto mode and output): @@ -107,3 +133,7 @@ hex2bin firmware.bin out.hex bin2hex ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgement + +This project was generated using only AI via the Cursor app. diff --git a/converter/converter.go b/converter/converter.go index f0947ab..9fab3ab 100644 --- a/converter/converter.go +++ b/converter/converter.go @@ -46,50 +46,6 @@ func writeHexRecord(w io.Writer, recordType byte, address uint16, data []byte) e return err } -func BinToIntelHex(inputFile string, outputFile string) error { - // Read the binary input file - binData, err := os.ReadFile(inputFile) - if err != nil { - return fmt.Errorf("error reading input file: %v", err) - } - - // Create output file - ofw, err := os.Create(outputFile) - if err != nil { - return fmt.Errorf("error creating output file: %v", err) - } - defer ofw.Close() - - w := bufio.NewWriter(ofw) - - // Write extended linear address record for the first segment - err = writeHexRecord(w, ExtLinearAddrRecord, 0x0000, []byte{0x00, 0x00}) - if err != nil { - return fmt.Errorf("error writing extended address: %v", err) - } - - // Write data records in 32-byte chunks - for i := 0; i < len(binData); i += 32 { - end := i + 32 - if end > len(binData) { - end = len(binData) - } - chunk := binData[i:end] - err = writeHexRecord(w, DataRecord, uint16(i), chunk) - if err != nil { - return fmt.Errorf("error writing data chunk: %v", err) - } - } - - // Write end of file record - err = writeHexRecord(w, EndOfFileRecord, 0x0000, nil) - if err != nil { - return fmt.Errorf("error writing EOF: %v", err) - } - - return w.Flush() -} - func IntelHexToBin(inputFile string, outputFile string) error { file, err := os.Open(inputFile) if err != nil { @@ -154,3 +110,74 @@ func IntelHexToBin(inputFile string, outputFile string) error { return nil } + +// BinToIntelHexWithMode converts binary to Intel HEX, with a mode to write all data or only non-0xFF data +// recordSize controls the number of bytes per HEX record (e.g., 16, 32) +func BinToIntelHexWithMode(inputFile string, outputFile string, writeAll bool, recordSize int) error { + if recordSize <= 0 { + recordSize = 32 + } + binData, err := os.ReadFile(inputFile) + if err != nil { + return fmt.Errorf("error reading input file: %v", err) + } + + of, err := os.Create(outputFile) + if err != nil { + return fmt.Errorf("error creating output file: %v", err) + } + defer of.Close() + + w := bufio.NewWriter(of) + + // Write extended linear address record for the first segment + err = writeHexRecord(w, ExtLinearAddrRecord, 0x0000, []byte{0x00, 0x00}) + if err != nil { + return fmt.Errorf("error writing extended address: %v", err) + } + + if writeAll { + // Write all data in recordSize-byte chunks + for i := 0; i < len(binData); i += recordSize { + end := i + recordSize + if end > len(binData) { + end = len(binData) + } + chunk := binData[i:end] + err = writeHexRecord(w, DataRecord, uint16(i), chunk) + if err != nil { + return fmt.Errorf("error writing data chunk: %v", err) + } + } + } else { + // Improved sparse mode: write any chunk with at least one non-0xFF byte + for i := 0; i < len(binData); i += recordSize { + end := i + recordSize + if end > len(binData) { + end = len(binData) + } + chunk := binData[i:end] + write := false + for _, b := range chunk { + if b != 0xFF { + write = true + break + } + } + if write { + err = writeHexRecord(w, DataRecord, uint16(i), chunk) + if err != nil { + return fmt.Errorf("error writing data chunk: %v", err) + } + } + } + } + + // Write end of file record + err = writeHexRecord(w, EndOfFileRecord, 0x0000, nil) + if err != nil { + return fmt.Errorf("error writing EOF: %v", err) + } + + return w.Flush() +} diff --git a/main.go b/main.go index 4c14310..a2bbe21 100644 --- a/main.go +++ b/main.go @@ -58,24 +58,47 @@ func getModeFromInputExt(inputFile string) (string, error) { } func main() { - if len(os.Args) < 2 || len(os.Args) > 4 { + writeAll := false + recordSize := 16 + args := os.Args[1:] + // Check for --all and --record-bytes flags + for i := 0; i < len(args); { + if args[i] == "--all" { + writeAll = true + args = append(args[:i], args[i+1:]...) + continue + } + if strings.HasPrefix(args[i], "--record-bytes=") { + var n int + _, err := fmt.Sscanf(args[i], "--record-bytes=%d", &n) + if err == nil && n > 0 { + recordSize = n + } + args = append(args[:i], args[i+1:]...) + continue + } + i++ + } + + if len(args) < 1 || len(args) > 3 { fmt.Printf("hex2bin v%s (built %s)\n", version, buildTime) - fmt.Println("Usage: hex2bin [output_file] [mode]") + fmt.Println("Usage: hex2bin [output_file] [mode] [--all] [--record-bytes=N]") fmt.Println("Mode: 'bin2hex' for binary to Intel HEX conversion") fmt.Println(" 'hex2bin' for Intel HEX to binary conversion") + fmt.Println(" '--all' to write all data (no sparse HEX, for bin2hex mode)") + fmt.Println(" '--record-bytes=N' to set bytes per HEX record (default 16, e.g., 16 or 32)") os.Exit(1) } - inputFile := os.Args[1] + inputFile := args[0] outputFile := "" mode := "" - if len(os.Args) == 4 { - outputFile = os.Args[2] - mode = os.Args[3] - } else if len(os.Args) == 3 { - // Could be output file or mode - arg := os.Args[2] + if len(args) == 3 { + outputFile = args[1] + mode = args[2] + } else if len(args) == 2 { + arg := args[1] if arg == "bin2hex" || arg == "hex2bin" { mode = arg var err error @@ -94,7 +117,6 @@ func main() { } } } else { - // Only input file provided var err error mode, err = getModeFromInputExt(inputFile) if err != nil { @@ -111,7 +133,7 @@ func main() { var err error switch mode { case "bin2hex": - err = converter.BinToIntelHex(inputFile, outputFile) + err = converter.BinToIntelHexWithMode(inputFile, outputFile, writeAll, recordSize) case "hex2bin": err = converter.IntelHexToBin(inputFile, outputFile) default: