Class: KindleHacks::Update
- Inherits:
-
Object
- Object
- KindleHacks::Update
- Defined in:
- lib/kindle_hacks/update.rb,
lib/kindle_hacks/update_hl.rb
Overview
:nodoc: documented in update_hl.rb
Constant Summary collapse
- DEFAULT_UNKNOWN =
Default value for the unknown byte in the update header.
0x13
- DEFAULT_MIN_VERSION =
Default value for updates’ minimum-allowed version.
0
- DEFAULT_MAX_VERSION =
Default value for updates’ maximum-allowed version.
0x7fffffff
- DEVICE_IDS =
Kindle devices ID (numbers in the 3rd and 4th digits of the serial number).
{:kindle => 1, :kindle2 => 2, :kindle_dx => 4}
- BLOCK_SIZE =
Flash partition block size.
{:kindle => 131072, :kindle2 => 131072, :kindle_dx => 131072 }
- HEADER_SIZES =
Header sizes, based on update types.
{:manual => 131072, :ota => 64}
- TARGET_IDS =
Target IDs in the update manifest.
{ :base_fs => 6, :contents_fs => 7, :temp => 128, :exec => 129 }
- UPDATE_IDS =
Update file signatures.
{:manual => 'FB01', :ota => 'FC02'}
- SCRAMBLE_KEY =
The key used to scramble update bytes.
0x7A
Instance Attribute Summary collapse
-
#device ⇒ Object
readonly
The device that the update targets, e.g.
-
#files ⇒ Object
readonly
The files that constitute the update.
-
#max_version ⇒ Object
readonly
The maximum firmware version that this update applies to.
-
#min_version ⇒ Object
readonly
The minimum firmware version that this update applies to.
-
#name ⇒ Object
readonly
The update’s name, appended at the end of the update image and manifest.
-
#optional ⇒ Object
readonly
Whether this update is optional or not.
-
#update ⇒ Object
readonly
The update’s type, e.g.
Class Method Summary collapse
-
.decode_files(raw_tgz) ⇒ Object
Decodes an update’s file contents.
-
.decode_header(raw_header) ⇒ Object
Decodes an update’s header.
-
.decode_manifest(raw_manifest) ⇒ Object
Decodes an update file’s manifest (update*.dat).
-
.read(raw_update) ⇒ Object
Creates an Update from raw update bytes.
- .read_dir(path) ⇒ Object
-
.scramble!(bytes, key) ⇒ Object
Scrambles update bytes (either the MD5 or the TGZ).
-
.stringify_keys!(obj) ⇒ Object
Recursively converts a hash’s keys and symbol values to strings.
-
.symbolize_keys!(obj) ⇒ Object
Recursively converts a hash’s keys to symbols.
Instance Method Summary collapse
-
#binary_file_name ⇒ Object
The update’s binary file name.
-
#encoded_files ⇒ Object
The encoded file data (tgz) for this update.
-
#encoded_header ⇒ Object
The encoded header for this update.
-
#encoded_manifest ⇒ Object
The encoded manifest for this update.
-
#initialize(manifest = {}) ⇒ Update
constructor
Initializes an update from a potentially incomplete manifest.
-
#manifest ⇒ Object
The update’s manifest.
-
#to_binary ⇒ Object
Raw update bytes corresponding to an update.
- #to_dir(path) ⇒ Object
Constructor Details
#initialize(manifest = {}) ⇒ Update
Initializes an update from a potentially incomplete manifest.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/kindle_hacks/update.rb', line 49 def initialize(manifest = {}) @device = manifest[:device] || :kindle @device = @device.to_sym @files = manifest[:files] || [] @files.each do |file| file[:display] ||= file[:name] + '_file' file[:target] = file[:target].to_sym end @min_version = manifest[:min_version] || DEFAULT_MIN_VERSION @max_version = manifest[:max_version] || DEFAULT_MAX_VERSION @name = manifest[:name] || '' @optional = manifest[:optional] || 0 @unknown = manifest[:unknown] || DEFAULT_UNKNOWN @update = manifest[:signature] || :ota @update = @update.to_sym end |
Instance Attribute Details
#device ⇒ Object (readonly)
The device that the update targets, e.g. :kindle_dx.
20 21 22 |
# File 'lib/kindle_hacks/update.rb', line 20 def device @device end |
#files ⇒ Object (readonly)
The files that constitute the update.
23 24 25 |
# File 'lib/kindle_hacks/update.rb', line 23 def files @files end |
#max_version ⇒ Object (readonly)
The maximum firmware version that this update applies to.
The recommended value is DEFAULT_MAX_VERSION.
33 34 35 |
# File 'lib/kindle_hacks/update.rb', line 33 def max_version @max_version end |
#min_version ⇒ Object (readonly)
The minimum firmware version that this update applies to.
The recommended value is DEFAULT_MIN_VERSION.
28 29 30 |
# File 'lib/kindle_hacks/update.rb', line 28 def min_version @min_version end |
#name ⇒ Object (readonly)
The update’s name, appended at the end of the update image and manifest.
For example, an update named _Savory-0.06 will have its update file named update_Savory-0.06.bin and the manifest will be named update-Savory-0.06.dat.
40 41 42 |
# File 'lib/kindle_hacks/update.rb', line 40 def name @name end |
#optional ⇒ Object (readonly)
Whether this update is optional or not.
43 44 45 |
# File 'lib/kindle_hacks/update.rb', line 43 def optional @optional end |
#update ⇒ Object (readonly)
The update’s type, e.g. :ota.
46 47 48 |
# File 'lib/kindle_hacks/update.rb', line 46 def update @update end |
Class Method Details
.decode_files(raw_tgz) ⇒ Object
Decodes an update’s file contents.
Args:
raw_tgz:: a string containing the raw update tar.gz bytes
Returns a hash that can be used as the options argument to Update’s constructor.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/kindle_hacks/update.rb', line 132 def self.decode_files(raw_tgz) raw_files = {} gz_reader = Zlib::GzipReader.new StringIO.new(raw_tgz) tar_reader = Archive::Tar::Minitar::Reader.new gz_reader tar_reader.each_entry do |entry| raise 'Directories unsupported' if entry.directory? name = entry.full_name contents = entry.read raw_files[name] = contents end tar_reader.close gz_reader.close manifest_file = raw_files.keys.find { |name| /^update.*\.dat$/ =~ name } raise 'No update*.dat manifest found' unless manifest_file files = decode_manifest(raw_files[manifest_file]) files.each { |file| file[:contents] = raw_files[file[:name]] } { :name => manifest_file[6...-4], :files => files } end |
.decode_header(raw_header) ⇒ Object
Decodes an update’s header.
Args:
raw_header:: a string containing the raw update header
Returns a hash that can be used as an argument to Update’s constructor.
107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/kindle_hacks/update.rb', line 107 def self.decode_header(raw_header) update_id, min_version, max_version, device_id, optional, unknown = *raw_header[0, 16].unpack('a4VVvCC') update = UPDATE_IDS.keys.find { |k| UPDATE_IDS[k] == update_id } device = DEVICE_IDS.keys.find { |k| DEVICE_IDS[k] == device_id } { :update => update, :min_version => min_version, :max_version => max_version, :device => device, :optional => optional, :unknown => unknown } end |
.decode_manifest(raw_manifest) ⇒ Object
Decodes an update file’s manifest (update*.dat).
Args:
raw_manifest:: a string containing the raw bytes in the manifest file
Returns a hash that can be used as the :files key in the options argument to Update’s constructor.
184 185 186 187 188 189 190 191 |
# File 'lib/kindle_hacks/update.rb', line 184 def self.decode_manifest(raw_manifest) raw_manifest.split("\n").map do |line| target_id, md5, filename, block_count, display_name = *line.split target = TARGET_IDS.keys.find { |k| TARGET_IDS[k].to_s == target_id } { :target => target, :name => filename, :display => display_name } end end |
.read(raw_update) ⇒ Object
Creates an Update from raw update bytes.
Returns an Update object.
69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/kindle_hacks/update.rb', line 69 def self.read(raw_update) header = decode_header raw_update header_size = HEADER_SIZES[header[:update]] descramble_key = (SCRAMBLE_KEY >> 4 | SCRAMBLE_KEY << 4) & 0xff md5 = scramble! raw_update[16, 32], descramble_key tgz = scramble! raw_update[header_size..-1], descramble_key raise 'Update signature is invalid' unless Digest::MD5.hexdigest(tgz) == md5 Update.new header.merge(decode_files(tgz)) end |
.read_dir(path) ⇒ Object
14 15 16 17 18 19 20 21 22 23 |
# File 'lib/kindle_hacks/update_hl.rb', line 14 def self.read_dir(path) manifest = YAML.load File.read(File.join(path, 'update.yml')) symbolize_keys! manifest manifest[:files].each do |file| file[:contents] = File.read File.join(path, file[:name]) end Update.new manifest end |
.scramble!(bytes, key) ⇒ Object
Scrambles update bytes (either the MD5 or the TGZ).
Args:
bytes:: the bytes to be scrambled (in-place)
key:: the scrambling key (between 0 and 255)
Returns the same array passed in the bytes argument.
210 211 212 213 214 215 216 217 |
# File 'lib/kindle_hacks/update.rb', line 210 def self.scramble!(bytes, key) 0.upto(bytes.length - 1) do |i| b = bytes[i] b = (((b >> 4) | (b << 4)) & 0xFF) ^ key bytes[i] = b end bytes end |
.stringify_keys!(obj) ⇒ Object
Recursively converts a hash’s keys and symbol values to strings.
67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/kindle_hacks/update_hl.rb', line 67 def self.stringify_keys!(obj) if obj.kind_of? Hash obj.keys.each do |key| value = obj.delete key value = value.to_s if value.kind_of? Symbol stringify_keys! value obj[key.to_s] = value end elsif obj.kind_of? Array obj.each { |value| stringify_keys! value } end obj end |
.symbolize_keys!(obj) ⇒ Object
Recursively converts a hash’s keys to symbols.
Returns the same hash given as an argument.
53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/kindle_hacks/update_hl.rb', line 53 def self.symbolize_keys!(obj) if obj.kind_of? Hash obj.keys.each do |key| value = obj.delete key symbolize_keys! value obj[key.to_sym] = value end elsif obj.kind_of? Array obj.each { |value| symbolize_keys! value } end obj end |
Instance Method Details
#binary_file_name ⇒ Object
The update’s binary file name.
97 98 99 |
# File 'lib/kindle_hacks/update.rb', line 97 def binary_file_name "update#{@name}.bin" end |
#encoded_files ⇒ Object
The encoded file data (tgz) for this update.
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/kindle_hacks/update.rb', line 156 def encoded_files raw_tgz = "" gz_writer = Zlib::GzipWriter.new StringIO.new(raw_tgz) tar_writer = Archive::Tar::Minitar::Writer.new gz_writer data = Hash[*@files.map { |file| [file[:name], file[:contents]] }.flatten] data["update#{@name}.dat"] = encoded_manifest mtime = Time.now.to_i data.each do |name, contents| tar_writer.add_file_simple(name, :mode => 0100755, :uid => 0, :gid => 0, :user => 'root', :group => 'root', :mtime => mtime, :size => contents.length) do |file| file.write contents end end tar_writer.close gz_writer.close raw_tgz end |
#encoded_header ⇒ Object
The encoded header for this update.
120 121 122 123 |
# File 'lib/kindle_hacks/update.rb', line 120 def encoded_header [UPDATE_IDS[@update], @min_version, @max_version, DEVICE_IDS[@device], @optional, @unknown].pack('a4VVvCC') end |
#encoded_manifest ⇒ Object
The encoded manifest for this update.
194 195 196 197 198 199 200 201 |
# File 'lib/kindle_hacks/update.rb', line 194 def encoded_manifest @files.map { |file| target_id = TARGET_IDS[file[:target]] md5 = Digest::MD5.hexdigest file[:contents] block_count = file[:contents].length / BLOCK_SIZE[@device] [target_id, md5, file[:name], block_count, file[:display]].join ' ' }.join("\n") + "\n" end |
#manifest ⇒ Object
The update’s manifest.
36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/kindle_hacks/update_hl.rb', line 36 def manifest m = { :device => @device, :files => @files.map { |f| f.clone }, :min_version => @min_version, :max_version => @max_version, :name => @name, :optional => @optional, :update => @update } m[:files].each do |file| file.delete :contents file.delete :display if file[:display] == file[:name] + '_file' end m.delete :min_version if m[:min_version] == DEFAULT_MIN_VERSION m.delete :max_version if m[:max_version] == DEFAULT_MAX_VERSION m end |
#to_binary ⇒ Object
Raw update bytes corresponding to an update.
Returns a string that can be written to a .bin file for updating a device.
84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/kindle_hacks/update.rb', line 84 def to_binary header = encoded_header tgz = encoded_files md5 = Digest::MD5.hexdigest tgz scrambled_md5 = Update.scramble! md5, SCRAMBLE_KEY scrambled_tgz = Update.scramble! tgz, SCRAMBLE_KEY [header, scrambled_md5, "\0" * (HEADER_SIZES[@update] - header.length - md5.length), scrambled_tgz].join end |
#to_dir(path) ⇒ Object
25 26 27 28 29 30 31 32 33 |
# File 'lib/kindle_hacks/update_hl.rb', line 25 def to_dir(path) m = Update.stringify_keys! manifest File.open(File.join(path, 'update.yml'), 'w') { |f| YAML.dump m, f } @files.each do |file| File.open File.join(path, file[:name]), 'w' do |f| f.write file[:contents] end end end |