forked from acidanthera/BrcmPatchRAM
-
Notifications
You must be signed in to change notification settings - Fork 8
/
firmware.rb
executable file
·297 lines (233 loc) · 11.4 KB
/
firmware.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#!/usr/bin/ruby
require 'base64'
require 'fileutils'
require 'optparse'
require 'ostruct'
require 'zlib'
require 'rexml/document'
include REXML
def add_element(parent, name, text)
element = Element.new(name, parent)
element.text = text
end
def add_key_value(dict, key, type, value)
add_element(dict, "key", key)
add_element(dict, type, value)
end
def parse_inf(inf_path)
devices = Array.new
in_device_block = 0
device = nil
if !File.exist?(inf_path)
puts "Error: bcbtums.inf not found in firmware input folder."
exit
end
File.open(inf_path).each do |line|
# When in the device block, parse all devices
if in_device_block == 1 and line =~ /^%([\w\.]*)%=.*(RAMUSB\w*),\s*USB\\VID_([0-9A-F]{4})\&PID_([0-9A-F]{4})\s*;\s(.*?)\r\n$/
# Example match: %BRCM20702.DeviceDesc%=BlueRAMUSB21E8, USB\VID_0A5C&PID_21E8 ; 20702A1 dongles
# new Example: %BRCM20702.DeviceDesc%=RAMUSB21E8, USB\VID_0A5C&PID_21E8 ; 20702A1 dongles
device = OpenStruct.new
device.stringKey = $1
device.deviceKey = $2
device.vendorId = $3.hex
device.productId = $4.hex
device.comment = $5
new_device = devices.find { |f| f.deviceKey.casecmp(device.deviceKey) == 0 }
# Skip drivers from Windows 8 when we have drivers from Windows 10.
if new_device == nil
devices << device
end
device = nil
end
# Extract the firmware file name from the current device block
if device and line =~ /^(BCM.*\.hex)/
device.firmware = $1
device.firmwareVersion = $1[-8..-1].chomp(".hex").to_i
if device.firmwareVersion < 4096
device.firmwareVersion += 4096
end
if device.firmware.start_with?("BCM4356A2")
devices.delete(device)
end
device = nil
end
# Determine the firmware filename for each device
if line =~ /^\[(RAMUSB[0-9A-F_]{4,})\.CopyList\]/
# Example matches:
# [RAMUSB21E8.CopyList]
# [RAMUSB185F_2167.CopyList]
# Locate the device information for this RAMUSB device in the firmware array
device = devices.find { |f| f.deviceKey.casecmp($1) == 0 }
end
# Extract device descriptions
if line =~ /^(\w*\.DeviceDesc)\=\s*"(.*)"/
matches = devices.each.select { |f| f.stringKey.casecmp($1) == 0 }
matches.each do |match|
match.description = $2
end
end
# Found start of Windows 10 drivers block
if line =~ /^\[Broadcom\.NT\w*\.10\.0\]/
in_device_block = 1
end
# Found end of all drivers blocks
if line =~ /^\[DestinationDirs\]/
in_device_block = 0
end
end
return devices
end
def create_firmwares(devices, input_path, output_path)
# Create output folder
FileUtils::makedirs output_path
FileUtils::chdir output_path
# Wipe any existing symbolic links - Ignore exceptions
begin
FileUtils::remove(Dir.glob(File.join(output_path, "*.zhx")))
rescue
end
# Prune and rename existing firmwares
Dir.glob(File.join(input_path, "*.hex")).each do |firmware|
basename = File.basename(firmware)
# Validate if we have a matching firmware definition
device = devices.find { |d| d.firmware != nil && d.firmware.casecmp(basename) == 0 }
if device
output_file = "#{File.basename(firmware, File.extname(firmware))}_v#{device.firmwareVersion}.zhx"
data_to_compress = File.read(firmware)
data_compressed = Zlib::Deflate.deflate(data_to_compress, Zlib::BEST_COMPRESSION)
puts "Compressed firmware #{output_file} (#{data_to_compress.size} --> #{data_compressed.size})"
device_folder = "%04x_%04x" % [ device.vendorId, device.productId ]
device_path = File.join(output_path, device_folder)
FileUtils::makedirs(device_path)
File.write(File.join(device_path, output_file), data_compressed)
# Determine latest firmware for the current device and symlink
latest_firmware = Dir.glob(File.join(device_path, "*.zhx")).sort_by{ |f| f[-8..1] }.reverse.each.first
if File.exist?(File.basename(latest_firmware))
puts "Firmware symlink #{File.basename(latestfirmware)} already created for another device."
else
FileUtils::symlink("./" + File.join(device_folder, File.basename(latest_firmware)), File.basename(latest_firmware))
end
create_injector(device, false, data_compressed, device_path)
create_injector(device, true, data_compressed, device_path)
else
puts "Firmware file %s is not matched against devices in INF file... skipping." % basename
end
end
end
def create_injector(device, for_usbhost, compressed_data, output_path)
xml = Document.new('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"/>');
root_dict = Element.new("dict", xml.root)
add_key_value(root_dict, "CFBundleIdentifier", "string", "as.acidanthera.BrcmInjector.%04x.%04x" % [ device.vendorId, device.productId ])
add_key_value(root_dict, "CFBundleInfoDictionaryVersion", "string", "6.0")
add_key_value(root_dict, "CFBundleName", "string", "BrcmInjector.%04x.%04x" % [ device.vendorId, device.productId ])
add_key_value(root_dict, "CFBundlePackageType", "string", "KEXT")
add_key_value(root_dict, "CFBundleShortVersionString", "string", "2.1.0")
add_key_value(root_dict, "CFBundleSignature", "string", "????")
add_key_value(root_dict, "CFBundleVersion", "string", "2.1.0")
add_element(root_dict, "key", "IOKitPersonalities")
iokit_dict = Element.new("dict", root_dict)
add_element(iokit_dict, "key", "%04x_%04x" % [ device.vendorId, device.productId ])
device_dict = Element.new("dict", iokit_dict);
add_key_value(device_dict, "CFBundleIdentifier", "string", for_usbhost ? "as.acidanthera.BrcmPatchRAM2" : "as.acidanthera.BrcmPatchRAM")
add_key_value(device_dict, "DisplayName", "string", device.description)
add_key_value(device_dict, "FirmwareKey", "string", "%04x_%04x_v%4d" % [ device.vendorId, device.productId, device.firmwareVersion ])
add_key_value(device_dict, "IOClass", "string", for_usbhost ? "BrcmPatchRAM2" : "BrcmPatchRAM" )
add_key_value(device_dict, "IOMatchCategory", "string", for_usbhost ? "BrcmPatchRAM2" : "BrcmPatchRAM")
add_key_value(device_dict, "IOProbeScore", "integer", "2000")
add_key_value(device_dict, "IOProviderClass", "string", for_usbhost ? "IOUSBHostDevice" : "IOUSBDevice")
add_key_value(device_dict, "idProduct", "integer", device.productId.to_i())
add_key_value(device_dict, "idVendor", "integer", device.vendorId.to_i())
add_element(iokit_dict, "key", "BrcmFirmwareStore")
firmware_dict = Element.new("dict", iokit_dict)
add_key_value(firmware_dict, "CFBundleIdentifier", "string", "as.acidanthera.BrcmFirmwareStore")
add_element(firmware_dict, "key", "Firmwares")
firmwares_dict = Element.new("dict", firmware_dict)
add_key_value(firmwares_dict, "%04x_%04x_v%4d" % [ device.vendorId, device.productId, device.firmwareVersion ], "data", Base64.encode64(compressed_data))
add_key_value(firmware_dict, "IOClass", "string", "BrcmFirmwareStore")
add_key_value(firmware_dict, "IOMatchCategory", "string", "BrcmFirmwareStore")
add_key_value(firmware_dict, "IOProbeScore", "integer", "2000")
add_key_value(firmware_dict, "IOProviderClass", "string", "IOResources")
injector_path = File.join(output_path, "BrcmFirmwareInjector%s_%04x_%04x.kext/Contents" % [ for_usbhost ? "2" : "", device.vendorId, device.productId, device.version ])
FileUtils::makedirs(injector_path)
formatter = REXML::Formatters::Pretty.new
formatter.compact = true
File.open(File.join(injector_path, "Info.plist"), "w") { |file| file.puts formatter.write(xml.root, "") }
end
def create_plist(devices, output_path, version)
# Generate plist XML snippet
xml = Document.new "<dict />"
Element.new("key", xml.root).text = "IOKitPersonalities"
fws_xml = Element.new("dict", xml.root)
devices.sort_by{|d| [d.vendorId, d.productId]}.each do |device|
if device.firmware == nil
puts "Failed to parse firmware path for %s, skipping..." % device
next
end
Element.new("key", fws_xml).text = "%04x_%04x" % [ device.vendorId, device.productId ]
device_xml = Element.new("dict", fws_xml)
add_key_value(device_xml, "CFBundleIdentifier", "string", "as.acidanthera.$(PRODUCT_NAME:rfc1034identifier)")
add_key_value(device_xml, "DisplayName", "string", device.description)
add_key_value(device_xml, "FirmwareKey", "string", "#{device.firmware.chomp(".hex")}_v#{device.firmwareVersion}")
if version >= 2
add_key_value(device_xml, "IOClass", "string", "BrcmPatchRAM%d" % version)
add_key_value(device_xml, "IOMatchCategory", "string", "BrcmPatchRAM%d" % version)
add_key_value(device_xml, "IOProbeScore", "integer", 4000)
add_key_value(device_xml, "IOProviderClass", "string", "IOUSBHostDevice")
else
add_key_value(device_xml, "IOClass", "string", "BrcmPatchRAM")
add_key_value(device_xml, "IOMatchCategory", "string", "BrcmPatchRAM")
add_key_value(device_xml, "IOProbeScore", "integer", 4000)
add_key_value(device_xml, "IOProviderClass", "string", "IOUSBDevice")
end
add_key_value(device_xml, "idProduct", "integer", device.productId)
add_key_value(device_xml, "idVendor", "integer", device.vendorId)
end
if version >= 2
Element.new("key", fws_xml).text = "BrcmPatchRAMResidency"
residency_xml = Element.new("dict", fws_xml)
add_key_value(residency_xml, "CFBundleIdentifier", "string", "as.acidanthera.$(PRODUCT_NAME:rfc1034identifier)")
add_key_value(residency_xml, "IOClass", "string", "BrcmPatchRAMResidency")
add_key_value(residency_xml, "IOMatchCategory", "string", "BrcmPatchRAMResidency")
add_key_value(residency_xml, "IOProviderClass", "string", "disabled_IOResources")
end
formatter = REXML::Formatters::Pretty.new
formatter.compact = true
if version >= 2
firmwares = "firmwares%d.plist" % version
else
firmwares = "firmwares.plist"
end
File.open(File.join(output_path, firmwares), "w") { |file| file.puts formatter.write(xml.root, "") }
end
def create_readme(devices, output_path)
File.open(File.join(output_path, "firmwares.md"), "w") do |file|
devices.sort_by{|d| [d.vendorId, d.productId]}.each do |device|
device_folder = "%04x_%04x" % [ device.vendorId, device.productId ]
device_path = File.join(output_path, device_folder)
if Dir.exists?(device_path)
file.puts "* [`%04x:%04x`] %s (%s)" % [ device.vendorId, device.productId, device.comment, device.description ]
Dir.glob(File.join(device_path, "*.zhx")).each do |firmware|
firmware = File.basename(firmware).chomp(".zhx")
file.puts(" * %s (v%s)" % [ firmware[0..-7], firmware[-4..-1] ])
end
end
end
end
end
if ARGV.length != 2
puts "Usage: firmware.rb <input folder> <output folder>"
exit
end
input = File.expand_path(ARGV.shift)
output = File.expand_path(ARGV.shift)
# Parse Windows INF file into device objects
devices = parse_inf(File.join(input, "bcbtums.inf"))
# Extract and compress all device firmwares
create_firmwares(devices, input, output)
# Generate plist extract
create_plist(devices, output, 1)
create_plist(devices, output, 2)
create_plist(devices, output, 3)
# Generate markdown readme with device / firmware information
create_readme(devices, output)