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 , "\n Options:" )
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