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