kindle_hacks 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1 -0
- data/LICENSE +21 -0
- data/Manifest +17 -0
- data/README.textile +18 -0
- data/Rakefile +31 -0
- data/bin/kindle_update +30 -0
- data/kindle_hacks.gemspec +42 -0
- data/lib/kindle_hacks.rb +8 -0
- data/lib/kindle_hacks/update.rb +248 -0
- data/lib/kindle_hacks/update_hl.rb +82 -0
- data/test/fixtures/update.dat +3 -0
- data/test/fixtures/update/001-savory.sh +52 -0
- data/test/fixtures/update/999-reboot.sh +18 -0
- data/test/fixtures/update/savoryctl +35 -0
- data/test/fixtures/update/update.yml +12 -0
- data/test/fixtures/update_dx_web.bin +0 -0
- data/test/fixtures/update_k2_pdf.bin +0 -0
- data/test/update_test.rb +124 -0
- metadata +112 -0
data/CHANGELOG
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
v0.1. Initial release.
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2009 Zergling.Net
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
LICENSE
|
3
|
+
Manifest
|
4
|
+
README.textile
|
5
|
+
Rakefile
|
6
|
+
bin/kindle_update
|
7
|
+
lib/kindle_hacks.rb
|
8
|
+
lib/kindle_hacks/update.rb
|
9
|
+
lib/kindle_hacks/update_hl.rb
|
10
|
+
test/fixtures/update.dat
|
11
|
+
test/fixtures/update/001-savory.sh
|
12
|
+
test/fixtures/update/999-reboot.sh
|
13
|
+
test/fixtures/update/savoryctl
|
14
|
+
test/fixtures/update/update.yml
|
15
|
+
test/fixtures/update_dx_web.bin
|
16
|
+
test/fixtures/update_k2_pdf.bin
|
17
|
+
test/update_test.rb
|
data/README.textile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
h1. KindleHacks
|
2
|
+
|
3
|
+
This gem contains code that's useful for hacking on the kindle.
|
4
|
+
|
5
|
+
h2. Features
|
6
|
+
|
7
|
+
Currently, the gem installs the command-line tool kindle_update that can be used
|
8
|
+
to build and unpack Kindle updates. KindleHacks::Update implements the API used
|
9
|
+
by the command-line tool.
|
10
|
+
|
11
|
+
h2. Acknowledgments
|
12
|
+
|
13
|
+
The update packing and unpacking is based on igorsk's article available at
|
14
|
+
http://igorsk.blogspot.com/2007/12/hacking-kindle-part-2-bootloader-and.html
|
15
|
+
|
16
|
+
h2. Contributions
|
17
|
+
|
18
|
+
Please don't hesitate to fork the project and send pull requests.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Rakefile that uses echoe to manage kindle_hacks' gemspec.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2009 Zergling.Net
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'echoe'
|
9
|
+
|
10
|
+
Echoe.new('kindle_hacks') do |p|
|
11
|
+
p.project = 'zerglings' # rubyforge project
|
12
|
+
|
13
|
+
p.author = 'Victor Costan'
|
14
|
+
p.email = 'victor@zergling.net'
|
15
|
+
p.summary = "Assorted toolbox for hacking into Kindles."
|
16
|
+
p.url = 'http://github.com/costan/kindle_hacks'
|
17
|
+
p.dependencies = ['archive-tar-minitar >=0.5.2']
|
18
|
+
p.development_dependencies = ["echoe >=3.1.1", "flexmock >=0.8.6"]
|
19
|
+
p.eval = proc do |p|
|
20
|
+
p.default_executable = 'bin/kindle_update'
|
21
|
+
end
|
22
|
+
|
23
|
+
p.need_tar_gz = true
|
24
|
+
p.need_zip = true
|
25
|
+
p.rdoc_pattern = /^(lib|bin|tasks|ext)|^BUILD|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
|
26
|
+
end
|
27
|
+
|
28
|
+
if $0 == __FILE__
|
29
|
+
Rake.application = Rake::Application.new
|
30
|
+
Rake.application.run
|
31
|
+
end
|
data/bin/kindle_update
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Command-line tool for packing and unpacking updates.
|
4
|
+
#
|
5
|
+
# Author:: Victor Costan
|
6
|
+
# Copyright:: Copyright (C) 2009 Zergling.Net
|
7
|
+
# License:: MIT
|
8
|
+
|
9
|
+
require 'fileutils'
|
10
|
+
require 'open-uri'
|
11
|
+
|
12
|
+
require 'rubygems'
|
13
|
+
require 'kindle_hacks'
|
14
|
+
|
15
|
+
case ARGV[0]
|
16
|
+
when 'pack'
|
17
|
+
update = KindleHacks::Update.read_dir ARGV[1] || '.'
|
18
|
+
File.open(File.join(ARGV[2] || '.', update.binary_file_name), 'w') do |f|
|
19
|
+
f.write update.to_binary
|
20
|
+
end
|
21
|
+
when 'unpack'
|
22
|
+
update = KindleHacks::Update.read File.read(ARGV[1])
|
23
|
+
FileUtils.mkdir_p ARGV[2] if ARGV[2]
|
24
|
+
update.to_dir ARGV[2] || '.'
|
25
|
+
else
|
26
|
+
print <<END_USAGE
|
27
|
+
Pack an update: #{$0} unpack [update_directory] [target_directory]
|
28
|
+
Unpack an update: #{$0} pack update_file [target_directory]
|
29
|
+
END_USAGE
|
30
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{kindle_hacks}
|
5
|
+
s.version = "0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Victor Costan"]
|
9
|
+
s.date = %q{2009-10-09}
|
10
|
+
s.default_executable = %q{bin/kindle_update}
|
11
|
+
s.description = %q{Assorted toolbox for hacking into Kindles.}
|
12
|
+
s.email = %q{victor@zergling.net}
|
13
|
+
s.executables = ["kindle_update"]
|
14
|
+
s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README.textile", "bin/kindle_update", "lib/kindle_hacks.rb", "lib/kindle_hacks/update.rb", "lib/kindle_hacks/update_hl.rb"]
|
15
|
+
s.files = ["CHANGELOG", "LICENSE", "Manifest", "README.textile", "Rakefile", "bin/kindle_update", "lib/kindle_hacks.rb", "lib/kindle_hacks/update.rb", "lib/kindle_hacks/update_hl.rb", "test/fixtures/update.dat", "test/fixtures/update/001-savory.sh", "test/fixtures/update/999-reboot.sh", "test/fixtures/update/savoryctl", "test/fixtures/update/update.yml", "test/fixtures/update_dx_web.bin", "test/fixtures/update_k2_pdf.bin", "test/update_test.rb", "kindle_hacks.gemspec"]
|
16
|
+
s.homepage = %q{http://github.com/costan/kindle_hacks}
|
17
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Kindle_hacks", "--main", "README.textile"]
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
s.rubyforge_project = %q{zerglings}
|
20
|
+
s.rubygems_version = %q{1.3.5}
|
21
|
+
s.summary = %q{Assorted toolbox for hacking into Kindles.}
|
22
|
+
s.test_files = ["test/update_test.rb"]
|
23
|
+
|
24
|
+
if s.respond_to? :specification_version then
|
25
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
26
|
+
s.specification_version = 3
|
27
|
+
|
28
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
29
|
+
s.add_runtime_dependency(%q<archive-tar-minitar>, [">= 0.5.2"])
|
30
|
+
s.add_development_dependency(%q<echoe>, [">= 3.1.1"])
|
31
|
+
s.add_development_dependency(%q<flexmock>, [">= 0.8.6"])
|
32
|
+
else
|
33
|
+
s.add_dependency(%q<archive-tar-minitar>, [">= 0.5.2"])
|
34
|
+
s.add_dependency(%q<echoe>, [">= 3.1.1"])
|
35
|
+
s.add_dependency(%q<flexmock>, [">= 0.8.6"])
|
36
|
+
end
|
37
|
+
else
|
38
|
+
s.add_dependency(%q<archive-tar-minitar>, [">= 0.5.2"])
|
39
|
+
s.add_dependency(%q<echoe>, [">= 3.1.1"])
|
40
|
+
s.add_dependency(%q<flexmock>, [">= 0.8.6"])
|
41
|
+
end
|
42
|
+
end
|
data/lib/kindle_hacks.rb
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
# Code for dealing with the binary update file format.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2009 Zergling.Net
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
require 'digest/md5'
|
8
|
+
require 'zlib'
|
9
|
+
|
10
|
+
require 'rubygems'
|
11
|
+
require 'archive/tar/minitar'
|
12
|
+
|
13
|
+
# :nodoc: namespace
|
14
|
+
module KindleHacks
|
15
|
+
|
16
|
+
|
17
|
+
# :nodoc: documented in update_hl.rb
|
18
|
+
class Update
|
19
|
+
# The device that the update targets, e.g. :kindle_dx.
|
20
|
+
attr_reader :device
|
21
|
+
|
22
|
+
# The files that constitute the update.
|
23
|
+
attr_reader :files
|
24
|
+
|
25
|
+
# The minimum firmware version that this update applies to.
|
26
|
+
#
|
27
|
+
# The recommended value is DEFAULT_MIN_VERSION.
|
28
|
+
attr_reader :min_version
|
29
|
+
|
30
|
+
# The maximum firmware version that this update applies to.
|
31
|
+
#
|
32
|
+
# The recommended value is DEFAULT_MAX_VERSION.
|
33
|
+
attr_reader :max_version
|
34
|
+
|
35
|
+
# The update's name, appended at the end of the update image and manifest.
|
36
|
+
#
|
37
|
+
# For example, an update named _Savory-0.06 will have its update file named
|
38
|
+
# update_Savory-0.06.bin and the manifest will be named
|
39
|
+
# update-Savory-0.06.dat.
|
40
|
+
attr_reader :name
|
41
|
+
|
42
|
+
# Whether this update is optional or not.
|
43
|
+
attr_reader :optional
|
44
|
+
|
45
|
+
# The update's type, e.g. :ota.
|
46
|
+
attr_reader :update
|
47
|
+
|
48
|
+
# Initializes an update from a potentially incomplete manifest.
|
49
|
+
def initialize(manifest = {})
|
50
|
+
@device = manifest[:device] || :kindle
|
51
|
+
@device = @device.to_sym
|
52
|
+
@files = manifest[:files] || []
|
53
|
+
@files.each do |file|
|
54
|
+
file[:display] ||= file[:name] + '_file'
|
55
|
+
file[:target] = file[:target].to_sym
|
56
|
+
end
|
57
|
+
@min_version = manifest[:min_version] || DEFAULT_MIN_VERSION
|
58
|
+
@max_version = manifest[:max_version] || DEFAULT_MAX_VERSION
|
59
|
+
@name = manifest[:name] || ''
|
60
|
+
@optional = manifest[:optional] || 0
|
61
|
+
@unknown = manifest[:unknown] || DEFAULT_UNKNOWN
|
62
|
+
@update = manifest[:signature] || :ota
|
63
|
+
@update = @update.to_sym
|
64
|
+
end
|
65
|
+
|
66
|
+
# Creates an Update from raw update bytes.
|
67
|
+
#
|
68
|
+
# Returns an Update object.
|
69
|
+
def self.read(raw_update)
|
70
|
+
header = decode_header raw_update
|
71
|
+
|
72
|
+
header_size = HEADER_SIZES[header[:update]]
|
73
|
+
descramble_key = (SCRAMBLE_KEY >> 4 | SCRAMBLE_KEY << 4) & 0xff
|
74
|
+
md5 = scramble! raw_update[16, 32], descramble_key
|
75
|
+
tgz = scramble! raw_update[header_size..-1], descramble_key
|
76
|
+
raise 'Update signature is invalid' unless Digest::MD5.hexdigest(tgz) == md5
|
77
|
+
|
78
|
+
Update.new header.merge(decode_files(tgz))
|
79
|
+
end
|
80
|
+
|
81
|
+
# Raw update bytes corresponding to an update.
|
82
|
+
#
|
83
|
+
# Returns a string that can be written to a .bin file for updating a device.
|
84
|
+
def to_binary
|
85
|
+
header = encoded_header
|
86
|
+
tgz = encoded_files
|
87
|
+
md5 = Digest::MD5.hexdigest tgz
|
88
|
+
scrambled_md5 = Update.scramble! md5, SCRAMBLE_KEY
|
89
|
+
scrambled_tgz = Update.scramble! tgz, SCRAMBLE_KEY
|
90
|
+
|
91
|
+
[header, scrambled_md5,
|
92
|
+
"\0" * (HEADER_SIZES[@update] - header.length - md5.length),
|
93
|
+
scrambled_tgz].join
|
94
|
+
end
|
95
|
+
|
96
|
+
# The update's binary file name.
|
97
|
+
def binary_file_name
|
98
|
+
"update#{@name}.bin"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Decodes an update's header.
|
102
|
+
#
|
103
|
+
# Args:
|
104
|
+
# raw_header:: a string containing the raw update header
|
105
|
+
#
|
106
|
+
# Returns a hash that can be used as an argument to Update's constructor.
|
107
|
+
def self.decode_header(raw_header)
|
108
|
+
update_id, min_version, max_version, device_id, optional, unknown =
|
109
|
+
*raw_header[0, 16].unpack('a4VVvCC')
|
110
|
+
|
111
|
+
update = UPDATE_IDS.keys.find { |k| UPDATE_IDS[k] == update_id }
|
112
|
+
device = DEVICE_IDS.keys.find { |k| DEVICE_IDS[k] == device_id }
|
113
|
+
|
114
|
+
{ :update => update, :min_version => min_version,
|
115
|
+
:max_version => max_version, :device => device, :optional => optional,
|
116
|
+
:unknown => unknown }
|
117
|
+
end
|
118
|
+
|
119
|
+
# The encoded header for this update.
|
120
|
+
def encoded_header
|
121
|
+
[UPDATE_IDS[@update], @min_version, @max_version, DEVICE_IDS[@device],
|
122
|
+
@optional, @unknown].pack('a4VVvCC')
|
123
|
+
end
|
124
|
+
|
125
|
+
# Decodes an update's file contents.
|
126
|
+
#
|
127
|
+
# Args:
|
128
|
+
# raw_tgz:: a string containing the raw update tar.gz bytes
|
129
|
+
#
|
130
|
+
# Returns a hash that can be used as the options argument to Update's
|
131
|
+
# constructor.
|
132
|
+
def self.decode_files(raw_tgz)
|
133
|
+
raw_files = {}
|
134
|
+
|
135
|
+
gz_reader = Zlib::GzipReader.new StringIO.new(raw_tgz)
|
136
|
+
tar_reader = Archive::Tar::Minitar::Reader.new gz_reader
|
137
|
+
tar_reader.each_entry do |entry|
|
138
|
+
raise 'Directories unsupported' if entry.directory?
|
139
|
+
|
140
|
+
name = entry.full_name
|
141
|
+
contents = entry.read
|
142
|
+
raw_files[name] = contents
|
143
|
+
end
|
144
|
+
tar_reader.close
|
145
|
+
gz_reader.close
|
146
|
+
|
147
|
+
manifest_file = raw_files.keys.find { |name| /^update.*\.dat$/ =~ name }
|
148
|
+
raise 'No update*.dat manifest found' unless manifest_file
|
149
|
+
|
150
|
+
files = decode_manifest(raw_files[manifest_file])
|
151
|
+
files.each { |file| file[:contents] = raw_files[file[:name]] }
|
152
|
+
{ :name => manifest_file[6...-4], :files => files }
|
153
|
+
end
|
154
|
+
|
155
|
+
# The encoded file data (tgz) for this update.
|
156
|
+
def encoded_files
|
157
|
+
raw_tgz = ""
|
158
|
+
gz_writer = Zlib::GzipWriter.new StringIO.new(raw_tgz)
|
159
|
+
tar_writer = Archive::Tar::Minitar::Writer.new gz_writer
|
160
|
+
|
161
|
+
data = Hash[*@files.map { |file| [file[:name], file[:contents]] }.flatten]
|
162
|
+
data["update#{@name}.dat"] = encoded_manifest
|
163
|
+
mtime = Time.now.to_i
|
164
|
+
data.each do |name, contents|
|
165
|
+
tar_writer.add_file_simple(name, :mode => 0100755, :uid => 0, :gid => 0,
|
166
|
+
:user => 'root', :group => 'root', :mtime => mtime,
|
167
|
+
:size => contents.length) do |file|
|
168
|
+
file.write contents
|
169
|
+
end
|
170
|
+
end
|
171
|
+
tar_writer.close
|
172
|
+
gz_writer.close
|
173
|
+
|
174
|
+
raw_tgz
|
175
|
+
end
|
176
|
+
|
177
|
+
# Decodes an update file's manifest (update*.dat).
|
178
|
+
#
|
179
|
+
# Args:
|
180
|
+
# raw_manifest:: a string containing the raw bytes in the manifest file
|
181
|
+
#
|
182
|
+
# Returns a hash that can be used as the :files key in the options argument to
|
183
|
+
# Update's constructor.
|
184
|
+
def self.decode_manifest(raw_manifest)
|
185
|
+
raw_manifest.split("\n").map do |line|
|
186
|
+
target_id, md5, filename, block_count, display_name = *line.split
|
187
|
+
|
188
|
+
target = TARGET_IDS.keys.find { |k| TARGET_IDS[k].to_s == target_id }
|
189
|
+
{ :target => target, :name => filename, :display => display_name }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# The encoded manifest for this update.
|
194
|
+
def encoded_manifest
|
195
|
+
@files.map { |file|
|
196
|
+
target_id = TARGET_IDS[file[:target]]
|
197
|
+
md5 = Digest::MD5.hexdigest file[:contents]
|
198
|
+
block_count = file[:contents].length / BLOCK_SIZE[@device]
|
199
|
+
[target_id, md5, file[:name], block_count, file[:display]].join ' '
|
200
|
+
}.join("\n") + "\n"
|
201
|
+
end
|
202
|
+
|
203
|
+
# Scrambles update bytes (either the MD5 or the TGZ).
|
204
|
+
#
|
205
|
+
# Args:
|
206
|
+
# bytes:: the bytes to be scrambled (in-place)
|
207
|
+
# key:: the scrambling key (between 0 and 255)
|
208
|
+
#
|
209
|
+
# Returns the same array passed in the bytes argument.
|
210
|
+
def self.scramble!(bytes, key)
|
211
|
+
0.upto(bytes.length - 1) do |i|
|
212
|
+
b = bytes[i]
|
213
|
+
b = (((b >> 4) | (b << 4)) & 0xFF) ^ key
|
214
|
+
bytes[i] = b
|
215
|
+
end
|
216
|
+
bytes
|
217
|
+
end
|
218
|
+
|
219
|
+
# Default value for the unknown byte in the update header.
|
220
|
+
DEFAULT_UNKNOWN = 0x13
|
221
|
+
|
222
|
+
# Default value for updates' minimum-allowed version.
|
223
|
+
DEFAULT_MIN_VERSION = 0
|
224
|
+
|
225
|
+
# Default value for updates' maximum-allowed version.
|
226
|
+
DEFAULT_MAX_VERSION = 0x7fffffff
|
227
|
+
|
228
|
+
# Kindle devices ID (numbers in the 3rd and 4th digits of the serial number).
|
229
|
+
DEVICE_IDS = {:kindle => 1, :kindle2 => 2, :kindle_dx => 4}
|
230
|
+
|
231
|
+
# Flash partition block size.
|
232
|
+
BLOCK_SIZE = {:kindle => 131072, :kindle2 => 131072, :kindle_dx => 131072 }
|
233
|
+
|
234
|
+
# Header sizes, based on update types.
|
235
|
+
HEADER_SIZES = {:manual => 131072, :ota => 64}
|
236
|
+
|
237
|
+
# Target IDs in the update manifest.
|
238
|
+
TARGET_IDS = { :base_fs => 6, :contents_fs => 7, :temp => 128,
|
239
|
+
:exec => 129 }
|
240
|
+
|
241
|
+
# Update file signatures.
|
242
|
+
UPDATE_IDS = {:manual => 'FB01', :ota => 'FC02'}
|
243
|
+
|
244
|
+
# The key used to scramble update bytes.
|
245
|
+
SCRAMBLE_KEY = 0x7A
|
246
|
+
end # class
|
247
|
+
|
248
|
+
end # namespace
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Code for dealing with the high-level update file format.
|
2
|
+
#
|
3
|
+
# Author:: Victor Costan
|
4
|
+
# Copyright:: Copyright (C) 2009 Zergling.Net
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
# :nodoc: namespace
|
10
|
+
module KindleHacks
|
11
|
+
|
12
|
+
|
13
|
+
class Update
|
14
|
+
def self.read_dir(path)
|
15
|
+
manifest = YAML.load File.read(File.join(path, 'update.yml'))
|
16
|
+
symbolize_keys! manifest
|
17
|
+
|
18
|
+
manifest[:files].each do |file|
|
19
|
+
file[:contents] = File.read File.join(path, file[:name])
|
20
|
+
end
|
21
|
+
|
22
|
+
Update.new manifest
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_dir(path)
|
26
|
+
m = Update.stringify_keys! manifest
|
27
|
+
File.open(File.join(path, 'update.yml'), 'w') { |f| YAML.dump m, f }
|
28
|
+
@files.each do |file|
|
29
|
+
File.open File.join(path, file[:name]), 'w' do |f|
|
30
|
+
f.write file[:contents]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# The update's manifest.
|
36
|
+
def manifest
|
37
|
+
m = { :device => @device, :files => @files.map { |f| f.clone },
|
38
|
+
:min_version => @min_version, :max_version => @max_version,
|
39
|
+
:name => @name, :optional => @optional, :update => @update }
|
40
|
+
|
41
|
+
m[:files].each do |file|
|
42
|
+
file.delete :contents
|
43
|
+
file.delete :display if file[:display] == file[:name] + '_file'
|
44
|
+
end
|
45
|
+
m.delete :min_version if m[:min_version] == DEFAULT_MIN_VERSION
|
46
|
+
m.delete :max_version if m[:max_version] == DEFAULT_MAX_VERSION
|
47
|
+
m
|
48
|
+
end
|
49
|
+
|
50
|
+
# Recursively converts a hash's keys to symbols.
|
51
|
+
#
|
52
|
+
# Returns the same hash given as an argument.
|
53
|
+
def self.symbolize_keys!(obj)
|
54
|
+
if obj.kind_of? Hash
|
55
|
+
obj.keys.each do |key|
|
56
|
+
value = obj.delete key
|
57
|
+
symbolize_keys! value
|
58
|
+
obj[key.to_sym] = value
|
59
|
+
end
|
60
|
+
elsif obj.kind_of? Array
|
61
|
+
obj.each { |value| symbolize_keys! value }
|
62
|
+
end
|
63
|
+
obj
|
64
|
+
end
|
65
|
+
|
66
|
+
# Recursively converts a hash's keys and symbol values to strings.
|
67
|
+
def self.stringify_keys!(obj)
|
68
|
+
if obj.kind_of? Hash
|
69
|
+
obj.keys.each do |key|
|
70
|
+
value = obj.delete key
|
71
|
+
value = value.to_s if value.kind_of? Symbol
|
72
|
+
stringify_keys! value
|
73
|
+
obj[key.to_s] = value
|
74
|
+
end
|
75
|
+
elsif obj.kind_of? Array
|
76
|
+
obj.each { |value| stringify_keys! value }
|
77
|
+
end
|
78
|
+
obj
|
79
|
+
end
|
80
|
+
end # class
|
81
|
+
|
82
|
+
end # namespace
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
_FUNCTIONS=/etc/rc.d/functions
|
4
|
+
[ -f ${_FUNCTIONS} ] && . ${_FUNCTIONS}
|
5
|
+
|
6
|
+
. /etc/sysconfig/mntus
|
7
|
+
|
8
|
+
VERSION_FILE=/etc/prettyversion.txt
|
9
|
+
SAVE_PRETTY=${VERSION_FILE}-beforesavory
|
10
|
+
NF_INI=/opt/amazon/ebook/config/netfront.ini
|
11
|
+
SAVE_NF_INI=${NF_INI}-beforesavory
|
12
|
+
SAVORYCTL=/etc/init.d/savoryctl
|
13
|
+
|
14
|
+
# If savory is already there, bail out
|
15
|
+
if [ -f $SAVORYCTL ]; then
|
16
|
+
update_progressbar 90
|
17
|
+
return 0
|
18
|
+
fi
|
19
|
+
|
20
|
+
|
21
|
+
update_progressbar 20
|
22
|
+
|
23
|
+
cp savoryctl $SAVORYCTL
|
24
|
+
chown root.root $SAVORYCTL
|
25
|
+
chmod 755 $SAVORYCTL
|
26
|
+
ln -s $SAVORYCTL /etc/rc5.d/S99savoryctl
|
27
|
+
|
28
|
+
update_progressbar 50
|
29
|
+
|
30
|
+
|
31
|
+
if [ -f $NF_INI ]; then
|
32
|
+
cp $NF_INI $SAVE_NF_INI
|
33
|
+
sed 's/dlexts=prc mobi txt azw$/dlexts=prc mobi txt azw epub lrf lit pdf fb2 odt/' $SAVE_NF_INI > /tmp/netfront-temp
|
34
|
+
sed 's/dlmimes=application\/x-mobipocket-ebook text\/x-prc$/dlmimes=application\/x-mobipocket-ebook text\/x-prc application\/pdf/' /tmp/netfront-temp > /tmp/netfront-temp-2
|
35
|
+
|
36
|
+
if [ -s /tmp/netfront-temp-2 ]; then
|
37
|
+
cp /tmp/netfront-temp-2 $NF_INI
|
38
|
+
fi
|
39
|
+
|
40
|
+
fi
|
41
|
+
|
42
|
+
update_progressbar 60
|
43
|
+
|
44
|
+
|
45
|
+
cp $VERSION_FILE $SAVE_PRETTY
|
46
|
+
sed 's/$/ UNSUPPORTED SAVORY-0.06/' $SAVE_PRETTY > $VERSION_FILE
|
47
|
+
|
48
|
+
update_progressbar 100
|
49
|
+
|
50
|
+
return 0
|
51
|
+
|
52
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
PATH=/tmp/savory/bin:$PATH
|
3
|
+
NAME="savory file conversion daemon"
|
4
|
+
DAEMON=/tmp/savory/bin/savory_daemon
|
5
|
+
PIDFILE=/var/run/savory-daemon.pid
|
6
|
+
_FUNCTIONS=/etc/rc.d/functions
|
7
|
+
[ -f ${_FUNCTIONS} ] && . ${_FUNCTIONS}
|
8
|
+
|
9
|
+
case "$1" in
|
10
|
+
|
11
|
+
start)
|
12
|
+
mkdir /tmp/savory
|
13
|
+
mount -o loop -o ro /mnt/us/system/savory-image.ext3 /tmp/savory
|
14
|
+
if [ -x "$DAEMON" ]; then
|
15
|
+
msg "starting $NAME" I
|
16
|
+
$DAEMON -p $PIDFILE &
|
17
|
+
fi
|
18
|
+
;;
|
19
|
+
|
20
|
+
stop)
|
21
|
+
if [ -r "$PIDFILE" ]; then
|
22
|
+
msg "stopping $NAME" I
|
23
|
+
kill `cat $PIDFILE`
|
24
|
+
rm -f $PIDFILE
|
25
|
+
umount /tmp/savory
|
26
|
+
fi
|
27
|
+
;;
|
28
|
+
*)
|
29
|
+
msg "Usage: /etc/init.d/$NAME {start|stop}" W >&2
|
30
|
+
exit 1
|
31
|
+
;;
|
32
|
+
esac
|
33
|
+
|
34
|
+
exit 0
|
35
|
+
|
Binary file
|
Binary file
|
data/test/update_test.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# Author:: Victor Costan
|
2
|
+
# Copyright:: Copyright (C) 2009 Zergling.Net
|
3
|
+
# License:: MIT
|
4
|
+
|
5
|
+
require 'kindle_hacks'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'test/unit'
|
8
|
+
|
9
|
+
class UpdateTest < Test::Unit::TestCase
|
10
|
+
Update = KindleHacks::Update
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@fixtures_path = File.join(File.dirname(__FILE__), 'fixtures')
|
14
|
+
@update_path = File.join(@fixtures_path, 'update')
|
15
|
+
|
16
|
+
@kindle2_update = File.read File.join(@fixtures_path, 'update_k2_pdf.bin')
|
17
|
+
@kindledx_update = File.read File.join(@fixtures_path, 'update_dx_web.bin')
|
18
|
+
|
19
|
+
@manifest = File.read File.join(@fixtures_path, 'update.dat')
|
20
|
+
|
21
|
+
FileUtils.mkdir_p 'test_tmp'
|
22
|
+
end
|
23
|
+
|
24
|
+
def teardown
|
25
|
+
FileUtils.rm_r 'test_tmp'
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_header_encoding_decoding
|
29
|
+
k2_golden = { :optional => 0, :update => :ota, :device => :kindle2,
|
30
|
+
:unknown => Update::DEFAULT_UNKNOWN,
|
31
|
+
:min_version => Update::DEFAULT_MIN_VERSION,
|
32
|
+
:max_version => Update::DEFAULT_MAX_VERSION }
|
33
|
+
dx_golden = { :optional => 0, :update => :ota, :device => :kindle_dx,
|
34
|
+
:unknown => Update::DEFAULT_UNKNOWN,
|
35
|
+
:min_version => Update::DEFAULT_MIN_VERSION,
|
36
|
+
:max_version => Update::DEFAULT_MAX_VERSION }
|
37
|
+
|
38
|
+
assert_equal k2_golden, Update.decode_header(@kindle2_update),
|
39
|
+
'decoding kindle2_pdf'
|
40
|
+
assert_equal dx_golden, Update.decode_header(@kindledx_update),
|
41
|
+
'decoding kindle_dx_web'
|
42
|
+
|
43
|
+
assert_equal @kindle2_update[0, 16], Update.new(k2_golden).encoded_header,
|
44
|
+
'encoding kindle2_pdf'
|
45
|
+
assert_equal @kindledx_update[0, 16], Update.new(dx_golden).encoded_header,
|
46
|
+
'encoding kindle_dx_pdf'
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_decode_manifest
|
50
|
+
golden = [{ :display => "savoryctl_file", :name => "savoryctl",
|
51
|
+
:target => :temp },
|
52
|
+
{ :display => "001-savory.sh_file", :name => "001-savory.sh",
|
53
|
+
:target => :exec },
|
54
|
+
{ :display => "999-reboot.sh_file", :name => "999-reboot.sh",
|
55
|
+
:target => :exec }]
|
56
|
+
assert_equal golden, Update.decode_manifest(@manifest), 'decoding manifest'
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_read
|
60
|
+
update = Update.read @kindle2_update
|
61
|
+
assert_equal :kindle2, update.device, 'Update device'
|
62
|
+
assert_equal '_Savory-0.06', update.name, 'Update name'
|
63
|
+
assert_equal :ota, update.update, 'Update update (type)'
|
64
|
+
update.files.each do |file|
|
65
|
+
golden = File.read File.join(@update_path, file[:name])
|
66
|
+
assert_equal "#{file[:name]}_file", file[:display],
|
67
|
+
"File #{file[:name]} display name"
|
68
|
+
assert_equal golden, file[:contents], "File #{file[:name]} contents"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_manifest
|
73
|
+
golden_file = File.read File.join(@update_path, 'update.yml')
|
74
|
+
|
75
|
+
golden = Update.symbolize_keys! YAML.load(golden_file)
|
76
|
+
golden[:device] = golden[:device].to_sym
|
77
|
+
golden[:update] = golden[:update].to_sym
|
78
|
+
golden[:files].each { |f| f[:target] = f[:target].to_sym }
|
79
|
+
|
80
|
+
assert_equal golden, Update.read(@kindle2_update).manifest
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_to_dir
|
84
|
+
update = Update.read @kindle2_update
|
85
|
+
update.to_dir 'test_tmp'
|
86
|
+
|
87
|
+
Dir.entries(@update_path).each do |entry|
|
88
|
+
next if File.directory?(entry)
|
89
|
+
|
90
|
+
assert File.exist?(File.join('test_tmp', entry)),
|
91
|
+
"Serialized update doesn't have #{entry}"
|
92
|
+
assert_equal File.read(File.join(@update_path, entry)),
|
93
|
+
File.read(File.join('test_tmp', entry)),
|
94
|
+
"Bad contents in serialized update's #{entry}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_read_dir
|
99
|
+
golden_update = Update.read @kindle2_update
|
100
|
+
update = Update.read_dir @update_path
|
101
|
+
|
102
|
+
assert_equal golden_update.manifest, update.manifest
|
103
|
+
assert_equal golden_update.files, update.files
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_encoded_manifest
|
107
|
+
update = Update.read @kindle2_update
|
108
|
+
assert_equal @manifest, update.encoded_manifest
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_to_binary
|
112
|
+
golden_update = Update.read @kindle2_update
|
113
|
+
binary = golden_update.to_binary
|
114
|
+
update = Update.read binary
|
115
|
+
|
116
|
+
assert_equal golden_update.manifest, update.manifest
|
117
|
+
assert_equal golden_update.files, update.files
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_binary_file_name
|
121
|
+
update = Update.read @kindle2_update
|
122
|
+
assert_equal "update_Savory-0.06.bin", update.binary_file_name
|
123
|
+
end
|
124
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kindle_hacks
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Victor Costan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-09 00:00:00 -04:00
|
13
|
+
default_executable: bin/kindle_update
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: archive-tar-minitar
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.5.2
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: echoe
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.1.1
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: flexmock
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.8.6
|
44
|
+
version:
|
45
|
+
description: Assorted toolbox for hacking into Kindles.
|
46
|
+
email: victor@zergling.net
|
47
|
+
executables:
|
48
|
+
- kindle_update
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- CHANGELOG
|
53
|
+
- LICENSE
|
54
|
+
- README.textile
|
55
|
+
- bin/kindle_update
|
56
|
+
- lib/kindle_hacks.rb
|
57
|
+
- lib/kindle_hacks/update.rb
|
58
|
+
- lib/kindle_hacks/update_hl.rb
|
59
|
+
files:
|
60
|
+
- CHANGELOG
|
61
|
+
- LICENSE
|
62
|
+
- Manifest
|
63
|
+
- README.textile
|
64
|
+
- Rakefile
|
65
|
+
- bin/kindle_update
|
66
|
+
- lib/kindle_hacks.rb
|
67
|
+
- lib/kindle_hacks/update.rb
|
68
|
+
- lib/kindle_hacks/update_hl.rb
|
69
|
+
- test/fixtures/update.dat
|
70
|
+
- test/fixtures/update/001-savory.sh
|
71
|
+
- test/fixtures/update/999-reboot.sh
|
72
|
+
- test/fixtures/update/savoryctl
|
73
|
+
- test/fixtures/update/update.yml
|
74
|
+
- test/fixtures/update_dx_web.bin
|
75
|
+
- test/fixtures/update_k2_pdf.bin
|
76
|
+
- test/update_test.rb
|
77
|
+
- kindle_hacks.gemspec
|
78
|
+
has_rdoc: true
|
79
|
+
homepage: http://github.com/costan/kindle_hacks
|
80
|
+
licenses: []
|
81
|
+
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options:
|
84
|
+
- --line-numbers
|
85
|
+
- --inline-source
|
86
|
+
- --title
|
87
|
+
- Kindle_hacks
|
88
|
+
- --main
|
89
|
+
- README.textile
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: "0"
|
97
|
+
version:
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: "1.2"
|
103
|
+
version:
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project: zerglings
|
107
|
+
rubygems_version: 1.3.5
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: Assorted toolbox for hacking into Kindles.
|
111
|
+
test_files:
|
112
|
+
- test/update_test.rb
|