kindle_hacks 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|