Skip to content

Commit 9ee3929

Browse files
committed
extra: add bin2uf2 tool
1 parent 8490908 commit 9ee3929

File tree

3 files changed

+185
-0
lines changed

3 files changed

+185
-0
lines changed

extra/bin2uf2/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bin2uf2

extra/bin2uf2/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/pennam/bin2uf2
2+
3+
go 1.21

extra/bin2uf2/main.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package main
2+
3+
import (
4+
"encoding/binary"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"os"
9+
"strconv"
10+
"strings"
11+
"unsafe"
12+
)
13+
14+
// UF2 block constants with fixed-size types.
15+
const (
16+
magic1 uint32 = 0x0A324655
17+
magic2 uint32 = 0x9E5D5157
18+
magic3 uint32 = 0x0AB16F30
19+
flags uint32 = 0x00002000 // familyID present
20+
payloadSize uint32 = 256
21+
blockSize uint32 = 512
22+
dataSectionSize uint32 = 476
23+
)
24+
25+
// UF2Block defines the structure of a UF2 block, used as a data container.
26+
// The Payload array is sized to hold the entire data section, so the unused
27+
// portion of the array acts as our padding.
28+
type UF2Block struct {
29+
Magic1 uint32
30+
Magic2 uint32
31+
Flags uint32
32+
TargetAddr uint32
33+
PayloadSize uint32
34+
BlockNo uint32
35+
NumBlocks uint32
36+
FamilyID uint32
37+
Payload [dataSectionSize]byte
38+
Magic3 uint32
39+
}
40+
41+
// Calculate the offset of the NumBlocks field within the block struct.
42+
const numBlocksOffset = unsafe.Offsetof(UF2Block{}.NumBlocks)
43+
44+
func main() {
45+
// Define optional string flags for address and family ID
46+
addrStr := flag.String("addr", "0x100E0000", "The starting memory address in hexadecimal format.")
47+
familyIDStr := flag.String("familyID", "0xe48bff56", "The family ID of the target device in hexadecimal format.")
48+
49+
// Customize the default usage message to be more explicit.
50+
flag.Usage = func() {
51+
fmt.Fprintf(os.Stderr, "Usage: %s [options] <source file> <destination file>\n", os.Args[0])
52+
fmt.Fprintln(os.Stderr, "Converts a binary file to the UF2 format.")
53+
fmt.Fprintln(os.Stderr, "\nOptions:")
54+
flag.PrintDefaults()
55+
}
56+
57+
flag.Parse()
58+
59+
// Check for the correct number of positional arguments.
60+
if len(flag.Args()) != 2 {
61+
flag.Usage()
62+
os.Exit(1)
63+
}
64+
65+
// Parse the address string from the flag.
66+
parsedAddr, err := strconv.ParseUint(strings.TrimPrefix(*addrStr, "0x"), 16, 32)
67+
if err != nil {
68+
fmt.Fprintf(os.Stderr, "Error: Invalid address format: %v\n", err)
69+
os.Exit(1)
70+
}
71+
address := uint32(parsedAddr)
72+
73+
// Parse the familyID string from the flag.
74+
parsedFamilyID, err := strconv.ParseUint(strings.TrimPrefix(*familyIDStr, "0x"), 16, 32)
75+
if err != nil {
76+
fmt.Fprintf(os.Stderr, "Error: Invalid familyID format: %v\n", err)
77+
os.Exit(1)
78+
}
79+
familyID := uint32(parsedFamilyID)
80+
81+
srcPath := flag.Arg(0)
82+
dstPath := flag.Arg(1)
83+
84+
// Open source file
85+
src, err := os.Open(srcPath)
86+
if err != nil {
87+
fmt.Fprintf(os.Stderr, "Error: Could not open source file %s: %v\n", srcPath, err)
88+
os.Exit(1)
89+
}
90+
defer src.Close()
91+
92+
// Create destination file
93+
dst, err := os.Create(dstPath)
94+
if err != nil {
95+
fmt.Fprintf(os.Stderr, "Error: Could not create destination file %s: %v\n", dstPath, err)
96+
os.Exit(1)
97+
}
98+
defer dst.Close()
99+
100+
var blockNum uint32
101+
var totalBlocks uint32
102+
// This slice is a temporary buffer for reading one payload-worth of data.
103+
readBuffer := make([]byte, payloadSize)
104+
105+
// Main loop to read source and write UF2 blocks
106+
for {
107+
bytesRead, err := io.ReadFull(src, readBuffer)
108+
if err == io.EOF {
109+
break
110+
}
111+
if err != nil && err != io.ErrUnexpectedEOF {
112+
fmt.Fprintf(os.Stderr, "Error: Failed reading from source file %s: %v\n", srcPath, err)
113+
os.Exit(1)
114+
}
115+
116+
// Create the block struct and populate its fields.
117+
block := UF2Block{
118+
Magic1: magic1,
119+
Magic2: magic2,
120+
Flags: flags,
121+
TargetAddr: address,
122+
PayloadSize: payloadSize,
123+
BlockNo: blockNum,
124+
NumBlocks: 0, // Placeholder, will be updated later.
125+
FamilyID: familyID,
126+
Magic3: magic3,
127+
}
128+
// Copy the data from our read buffer into the beginning of the
129+
// larger Payload array. The rest of the array remains zero, acting as padding.
130+
copy(block.Payload[:], readBuffer)
131+
132+
// --- Write the block to disk piece-by-piece ---
133+
// 1. Write the header fields
134+
binary.Write(dst, binary.LittleEndian, block.Magic1)
135+
binary.Write(dst, binary.LittleEndian, block.Magic2)
136+
binary.Write(dst, binary.LittleEndian, block.Flags)
137+
binary.Write(dst, binary.LittleEndian, block.TargetAddr)
138+
binary.Write(dst, binary.LittleEndian, block.PayloadSize)
139+
binary.Write(dst, binary.LittleEndian, block.BlockNo)
140+
binary.Write(dst, binary.LittleEndian, block.NumBlocks)
141+
binary.Write(dst, binary.LittleEndian, block.FamilyID)
142+
143+
// 2. Write the entire 476-byte data section (payload + padding) in one go.
144+
if _, err := dst.Write(block.Payload[:]); err != nil {
145+
fmt.Fprintf(os.Stderr, "Error: Failed writing data section to %s: %v\n", dstPath, err)
146+
os.Exit(1)
147+
}
148+
149+
// 3. Write the final magic number
150+
if err := binary.Write(dst, binary.LittleEndian, block.Magic3); err != nil {
151+
fmt.Fprintf(os.Stderr, "Error: Failed writing final magic to %s: %v\n", dstPath, err)
152+
os.Exit(1)
153+
}
154+
155+
address += payloadSize
156+
blockNum++
157+
158+
if err == io.EOF || bytesRead < int(payloadSize) {
159+
break
160+
}
161+
}
162+
163+
totalBlocks = blockNum
164+
165+
// After writing all blocks, seek back and update the totalBlocks field in each header
166+
for i := uint32(0); i < totalBlocks; i++ {
167+
// Calculate the offset using our safe constant instead of a magic number.
168+
offset := int64(i)*int64(blockSize) + int64(numBlocksOffset)
169+
_, err := dst.Seek(offset, io.SeekStart)
170+
if err != nil {
171+
fmt.Fprintf(os.Stderr, "Error: Failed seeking in destination file %s: %v\n", dstPath, err)
172+
os.Exit(1)
173+
}
174+
if err := binary.Write(dst, binary.LittleEndian, totalBlocks); err != nil {
175+
fmt.Fprintf(os.Stderr, "Error: Failed updating total blocks in %s: %v\n", dstPath, err)
176+
os.Exit(1)
177+
}
178+
}
179+
180+
fmt.Printf("Successfully converted %s to %s (%d blocks written).\n", srcPath, dstPath, totalBlocks)
181+
}

0 commit comments

Comments
 (0)