Compare commits

...

4 Commits
v3.5 ... v3.8

Author SHA1 Message Date
Apprentice Alf
529dd3f160 tools v3.8
version 2 - a minor change to one script.
2015-03-05 17:54:25 +00:00
Apprentice Alf
4163d5ccf4 tools v3.8 2015-03-05 17:48:25 +00:00
Apprentice Alf
867ac35b45 tools v3.7 2015-03-05 17:42:55 +00:00
Apprentice Alf
427137b0fe tools v3.6 2015-03-05 17:37:44 +00:00
38 changed files with 712 additions and 514 deletions

View File

@@ -323,12 +323,12 @@ def generateBook(bookDir, raw, fixedimage):
meta_array = getMetaArray(metaFile) meta_array = getMetaArray(metaFile)
# replace special chars in title and authors like & < > # replace special chars in title and authors like & < >
title = meta_array['Title'] title = meta_array.get('Title','No Title Provided')
title = title.replace('&','&amp;') title = title.replace('&','&amp;')
title = title.replace('<','&lt;') title = title.replace('<','&lt;')
title = title.replace('>','&gt;') title = title.replace('>','&gt;')
meta_array['Title'] = title meta_array['Title'] = title
authors = meta_array['Authors'] authors = meta_array.get('Authors','No Authors Provided')
authors = authors.replace('&','&amp;') authors = authors.replace('&','&amp;')
authors = authors.replace('<','&lt;') authors = authors.replace('<','&lt;')
authors = authors.replace('>','&gt;') authors = authors.replace('>','&gt;')
@@ -413,8 +413,10 @@ def generateBook(bookDir, raw, fixedimage):
htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n'
htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' if 'ASIN' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n' htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n'
htmlstr += '</head>\n<body>\n' htmlstr += '</head>\n<body>\n'
@@ -430,8 +432,10 @@ def generateBook(bookDir, raw, fixedimage):
svgindex += '<title>' + meta_array['Title'] + '</title>\n' svgindex += '<title>' + meta_array['Title'] + '</title>\n'
svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' if 'ASIN' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
svgindex += '</head>\n' svgindex += '</head>\n'
svgindex += '<body>\n' svgindex += '<body>\n'
@@ -485,9 +489,12 @@ def generateBook(bookDir, raw, fixedimage):
opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n' opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n'
# adding metadata # adding metadata
opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n' opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n'
opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n' if 'GUID' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n'
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n' if 'ASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n'
if 'oASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n'
opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n' opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n'
opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n' opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n'
opfstr += ' <dc:language>en</dc:language>\n' opfstr += ' <dc:language>en</dc:language>\n'

View File

@@ -18,9 +18,9 @@ global charMap3
global charMap4 global charMap4
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@@ -67,22 +67,6 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result return result
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = openKindleInfo(kInfoFile)
infoReader.read(1)
data = infoReader.read()
if sys.platform.startswith('win'):
items = data.split('{')
else :
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). # Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded).
# Return the decoded and decrypted record # Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey): def getKindleInfoValueForHash(hashedKey):
@@ -224,13 +208,11 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. # Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum): def getKindlePid(pidlst, rec209, token, serialnum):
# Compute book PID
if rec209 != None and token != None: pidHash = SHA1(serialnum+rec209+token)
# Compute book PID bookPID = encodePID(pidHash)
pidHash = SHA1(serialnum+rec209+token) bookPID = checksumPid(bookPID)
bookPID = encodePID(pidHash) pidlst.append(bookPID)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
# compute fixed pid for old pre 2.5 firmware update pid as well # compute fixed pid for old pre 2.5 firmware update pid as well
bookPID = pidFromSerial(serialnum, 7) + "*" bookPID = pidFromSerial(serialnum, 7) + "*"
@@ -243,7 +225,7 @@ def getKindlePid(pidlst, rec209, token, serialnum):
# Parse the EXTH header records and parse the Kindleinfo # Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid. # file to calculate the book pid.
def getK4Pids(pidlst, rec209, token, kInfoFile=None): def getK4Pids(pidlst, rec209, token, kInfoFile):
global kindleDatabase global kindleDatabase
global charMap1 global charMap1
kindleDatabase = None kindleDatabase = None
@@ -256,10 +238,17 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
if kindleDatabase == None : if kindleDatabase == None :
return pidlst return pidlst
try:
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the Mazama Random number # Get the kindle account token
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
except KeyError:
print "Keys not found in " + kInfoFile
return pidlst
# Get the HDD serial # Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
@@ -275,13 +264,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
devicePID = checksumPid(devicePID) devicePID = checksumPid(devicePID)
pidlst.append(devicePID) pidlst.append(devicePID)
# Compute book PID # Compute book PIDs
if rec209 == None or token == None:
print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?"
return pidlst
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
# book pid # book pid
pidHash = SHA1(DSN+kindleAccountToken+rec209+token) pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
@@ -305,8 +288,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
def getPidList(md1, md2, k4, pids, serials, kInfoFiles): def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
pidlst = [] pidlst = []
if kInfoFiles is None:
kInfoFiles = []
if k4: if k4:
pidlst = getK4Pids(pidlst, md1, md2) kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles: for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile) pidlst = getK4Pids(pidlst, md1, md2, infoFile)
for serialnum in serials: for serialnum in serials:

View File

@@ -157,18 +157,22 @@ class TopazBook:
raise TpzDRMError("Parse Error : Record Names Don't Match") raise TpzDRMError("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1)) flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1))
# print nbRecords
for i in range (0,nbRecords) : for i in range (0,nbRecords) :
record = [bookReadString(self.fo), bookReadString(self.fo)] keyval = bookReadString(self.fo)
self.bookMetadata[record[0]] = record[1] content = bookReadString(self.fo)
# print keyval
# print content
self.bookMetadata[keyval] = content
return self.bookMetadata return self.bookMetadata
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
keysRecord = None keysRecord = self.bookMetadata.get('keys','')
keysRecordRecord = None keysRecordRecord = ''
if 'keys' in self.bookMetadata: if keysRecord != '':
keysRecord = self.bookMetadata['keys'] keylst = keysRecord.split(',')
if keysRecord in self.bookMetadata: for keyval in keylst:
keysRecordRecord = self.bookMetadata[keysRecord] keysRecordRecord += self.bookMetadata.get(keyval,'')
return keysRecord, keysRecordRecord return keysRecord, keysRecordRecord
def getBookTitle(self): def getBookTitle(self):

View File

@@ -29,7 +29,7 @@ from __future__ import with_statement
# and import that ZIP into Calibre using its plugin configuration GUI. # and import that ZIP into Calibre using its plugin configuration GUI.
__version__ = '2.4' __version__ = '2.8'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -231,6 +231,9 @@ def main(argv=sys.argv):
# try with built in Kindle Info files # try with built in Kindle Info files
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
kInfoFiles = None
infile = args[0] infile = args[0]
outdir = args[1] outdir = args[1]
@@ -250,7 +253,7 @@ if not __name__ == "__main__" and inCalibre:
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 2, 4) # The version number of this plugin version = (0, 2, 8) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
@@ -267,6 +270,8 @@ if not __name__ == "__main__" and inCalibre:
import mobidedrm import mobidedrm
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
pids = [] pids = []
serials = [] serials = []
kInfoFiles = [] kInfoFiles = []
@@ -366,4 +371,4 @@ if not __name__ == "__main__" and inCalibre:
return of.name return of.name
def customization_help(self, gui=False): def customization_help(self, gui=False):
return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.' return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.'

View File

@@ -168,27 +168,33 @@ def CryptUnprotectData(encryptedData):
return cleartext return cleartext
# Locate and open the .kindle-info file # Locate the .kindle-info files
def openKindleInfo(kInfoFile=None): def getKindleInfoFiles(kInfoFiles):
if kInfoFile == None: home = os.getenv('HOME')
home = os.getenv('HOME') cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' cmdline = cmdline.encode(sys.getfilesystemencoding())
cmdline = cmdline.encode(sys.getfilesystemencoding()) p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) out1, out2 = p1.communicate()
out1, out2 = p1.communicate() reslst = out1.split('\n')
reslst = out1.split('\n') kinfopath = 'NONE'
kinfopath = 'NONE' found = False
cnt = len(reslst) cnt = len(reslst)
for j in xrange(cnt): for resline in reslst:
resline = reslst[j] if os.path.isfile(resline):
pp = resline.find('.kindle-info') kInfoFiles.append(resline)
if pp >= 0: found = True
kinfopath = resline if not found:
break print('No .kindle-info files have been found.')
if not os.path.isfile(kinfopath): return kInfoFiles
raise DrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r') # Parse the Kindle.info file and return the records as a list of key-values
else: def parseKindleInfo(kInfoFile):
if not os.path.isfile(kInfoFile): DB = {}
raise DrmException('Error: kindle-info file can not be found') infoReader = open(kInfoFile, 'r')
return open(kInfoFile, 'r') infoReader.read(1)
data = infoReader.read()
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -93,18 +93,25 @@ def CryptUnprotectData():
return CryptUnprotectData return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData() CryptUnprotectData = CryptUnprotectData()
# # Locate the .kindle-info files
# Locate and open the Kindle.info file. def getKindleInfoFiles(kInfoFiles):
# regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
def openKindleInfo(kInfoFile=None): path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
if kInfoFile == None: kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") if not os.path.isfile(kinfopath):
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] print('The kindle.info files has not been found.')
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath):
raise DrmException('Error: kindle.info file can not be found')
return open(kinfopath,'r')
else: else:
if not os.path.isfile(kInfoFile): kInfoFiles.append(kinfopath)
raise DrmException('Error: kindle.info file can not be found') return kInfoFiles
return open(kInfoFile, 'r')
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = open(kInfoFile, 'r')
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -47,8 +47,12 @@
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) # 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.27' __version__ = '0.29'
import sys import sys
@@ -176,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -191,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -237,12 +242,11 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
token = ''
# The 209 data comes in five byte groups. Interpret the last four bytes # The 209 data comes in five byte groups. Interpret the last four bytes
# of each group as a big endian unsigned integer to get a key value # of each group as a big endian unsigned integer to get a key value
# if that key exists in the meta_array, append its contents to the token # if that key exists in the meta_array, append its contents to the token

View File

@@ -24,7 +24,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>droplet</string> <string>droplet</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>DeDRM 2.3, Copyright © 20102011 by Apprentice Alf and others.</string> <string>DeDRM 2.6, Written 20102011 by Apprentice Alf and others.</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>droplet</string> <string>droplet</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
@@ -34,7 +34,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.3</string> <string>2.6</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>dplt</string> <string>dplt</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
@@ -50,7 +50,7 @@
<key>name</key> <key>name</key>
<string>ScriptWindowState</string> <string>ScriptWindowState</string>
<key>positionOfDivider</key> <key>positionOfDivider</key>
<real>0.0</real> <real>0</real>
<key>savedFrame</key> <key>savedFrame</key>
<string>1578 27 862 788 1440 -150 1680 1050 </string> <string>1578 27 862 788 1440 -150 1680 1050 </string>
<key>selectedTabView</key> <key>selectedTabView</key>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 362 B

View File

@@ -323,12 +323,12 @@ def generateBook(bookDir, raw, fixedimage):
meta_array = getMetaArray(metaFile) meta_array = getMetaArray(metaFile)
# replace special chars in title and authors like & < > # replace special chars in title and authors like & < >
title = meta_array['Title'] title = meta_array.get('Title','No Title Provided')
title = title.replace('&','&amp;') title = title.replace('&','&amp;')
title = title.replace('<','&lt;') title = title.replace('<','&lt;')
title = title.replace('>','&gt;') title = title.replace('>','&gt;')
meta_array['Title'] = title meta_array['Title'] = title
authors = meta_array['Authors'] authors = meta_array.get('Authors','No Authors Provided')
authors = authors.replace('&','&amp;') authors = authors.replace('&','&amp;')
authors = authors.replace('<','&lt;') authors = authors.replace('<','&lt;')
authors = authors.replace('>','&gt;') authors = authors.replace('>','&gt;')
@@ -413,8 +413,10 @@ def generateBook(bookDir, raw, fixedimage):
htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n'
htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' if 'ASIN' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n' htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n'
htmlstr += '</head>\n<body>\n' htmlstr += '</head>\n<body>\n'
@@ -430,8 +432,10 @@ def generateBook(bookDir, raw, fixedimage):
svgindex += '<title>' + meta_array['Title'] + '</title>\n' svgindex += '<title>' + meta_array['Title'] + '</title>\n'
svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' if 'ASIN' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
svgindex += '</head>\n' svgindex += '</head>\n'
svgindex += '<body>\n' svgindex += '<body>\n'
@@ -485,9 +489,12 @@ def generateBook(bookDir, raw, fixedimage):
opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n' opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n'
# adding metadata # adding metadata
opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n' opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n'
opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n' if 'GUID' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n'
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n' if 'ASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n'
if 'oASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n'
opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n' opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n'
opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n' opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n'
opfstr += ' <dc:language>en</dc:language>\n' opfstr += ' <dc:language>en</dc:language>\n'

View File

@@ -29,7 +29,7 @@ from __future__ import with_statement
# and import that ZIP into Calibre using its plugin configuration GUI. # and import that ZIP into Calibre using its plugin configuration GUI.
__version__ = '2.4' __version__ = '2.8'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -231,6 +231,9 @@ def main(argv=sys.argv):
# try with built in Kindle Info files # try with built in Kindle Info files
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
kInfoFiles = None
infile = args[0] infile = args[0]
outdir = args[1] outdir = args[1]
@@ -250,7 +253,7 @@ if not __name__ == "__main__" and inCalibre:
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 2, 4) # The version number of this plugin version = (0, 2, 8) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
@@ -267,6 +270,8 @@ if not __name__ == "__main__" and inCalibre:
import mobidedrm import mobidedrm
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
pids = [] pids = []
serials = [] serials = []
kInfoFiles = [] kInfoFiles = []
@@ -366,4 +371,4 @@ if not __name__ == "__main__" and inCalibre:
return of.name return of.name
def customization_help(self, gui=False): def customization_help(self, gui=False):
return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.' return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.'

View File

@@ -168,27 +168,33 @@ def CryptUnprotectData(encryptedData):
return cleartext return cleartext
# Locate and open the .kindle-info file # Locate the .kindle-info files
def openKindleInfo(kInfoFile=None): def getKindleInfoFiles(kInfoFiles):
if kInfoFile == None: home = os.getenv('HOME')
home = os.getenv('HOME') cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' cmdline = cmdline.encode(sys.getfilesystemencoding())
cmdline = cmdline.encode(sys.getfilesystemencoding()) p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) out1, out2 = p1.communicate()
out1, out2 = p1.communicate() reslst = out1.split('\n')
reslst = out1.split('\n') kinfopath = 'NONE'
kinfopath = 'NONE' found = False
cnt = len(reslst) cnt = len(reslst)
for j in xrange(cnt): for resline in reslst:
resline = reslst[j] if os.path.isfile(resline):
pp = resline.find('.kindle-info') kInfoFiles.append(resline)
if pp >= 0: found = True
kinfopath = resline if not found:
break print('No .kindle-info files have been found.')
if not os.path.isfile(kinfopath): return kInfoFiles
raise DrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r') # Parse the Kindle.info file and return the records as a list of key-values
else: def parseKindleInfo(kInfoFile):
if not os.path.isfile(kInfoFile): DB = {}
raise DrmException('Error: kindle-info file can not be found') infoReader = open(kInfoFile, 'r')
return open(kInfoFile, 'r') infoReader.read(1)
data = infoReader.read()
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -93,18 +93,25 @@ def CryptUnprotectData():
return CryptUnprotectData return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData() CryptUnprotectData = CryptUnprotectData()
# # Locate the .kindle-info files
# Locate and open the Kindle.info file. def getKindleInfoFiles(kInfoFiles):
# regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
def openKindleInfo(kInfoFile=None): path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
if kInfoFile == None: kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") if not os.path.isfile(kinfopath):
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] print('The kindle.info files has not been found.')
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath):
raise DrmException('Error: kindle.info file can not be found')
return open(kinfopath,'r')
else: else:
if not os.path.isfile(kInfoFile): kInfoFiles.append(kinfopath)
raise DrmException('Error: kindle.info file can not be found') return kInfoFiles
return open(kInfoFile, 'r')
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = open(kInfoFile, 'r')
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -18,9 +18,9 @@ global charMap3
global charMap4 global charMap4
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@@ -67,22 +67,6 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result return result
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = openKindleInfo(kInfoFile)
infoReader.read(1)
data = infoReader.read()
if sys.platform.startswith('win'):
items = data.split('{')
else :
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). # Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded).
# Return the decoded and decrypted record # Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey): def getKindleInfoValueForHash(hashedKey):
@@ -224,13 +208,11 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. # Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum): def getKindlePid(pidlst, rec209, token, serialnum):
# Compute book PID
if rec209 != None and token != None: pidHash = SHA1(serialnum+rec209+token)
# Compute book PID bookPID = encodePID(pidHash)
pidHash = SHA1(serialnum+rec209+token) bookPID = checksumPid(bookPID)
bookPID = encodePID(pidHash) pidlst.append(bookPID)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
# compute fixed pid for old pre 2.5 firmware update pid as well # compute fixed pid for old pre 2.5 firmware update pid as well
bookPID = pidFromSerial(serialnum, 7) + "*" bookPID = pidFromSerial(serialnum, 7) + "*"
@@ -243,7 +225,7 @@ def getKindlePid(pidlst, rec209, token, serialnum):
# Parse the EXTH header records and parse the Kindleinfo # Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid. # file to calculate the book pid.
def getK4Pids(pidlst, rec209, token, kInfoFile=None): def getK4Pids(pidlst, rec209, token, kInfoFile):
global kindleDatabase global kindleDatabase
global charMap1 global charMap1
kindleDatabase = None kindleDatabase = None
@@ -256,10 +238,17 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
if kindleDatabase == None : if kindleDatabase == None :
return pidlst return pidlst
try:
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the Mazama Random number # Get the kindle account token
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
except KeyError:
print "Keys not found in " + kInfoFile
return pidlst
# Get the HDD serial # Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
@@ -275,13 +264,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
devicePID = checksumPid(devicePID) devicePID = checksumPid(devicePID)
pidlst.append(devicePID) pidlst.append(devicePID)
# Compute book PID # Compute book PIDs
if rec209 == None or token == None:
print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?"
return pidlst
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
# book pid # book pid
pidHash = SHA1(DSN+kindleAccountToken+rec209+token) pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
@@ -305,8 +288,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
def getPidList(md1, md2, k4, pids, serials, kInfoFiles): def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
pidlst = [] pidlst = []
if kInfoFiles is None:
kInfoFiles = []
if k4: if k4:
pidlst = getK4Pids(pidlst, md1, md2) kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles: for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile) pidlst = getK4Pids(pidlst, md1, md2, infoFile)
for serialnum in serials: for serialnum in serials:

View File

@@ -47,8 +47,12 @@
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) # 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.27' __version__ = '0.29'
import sys import sys
@@ -176,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -191,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -237,12 +242,11 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
token = ''
# The 209 data comes in five byte groups. Interpret the last four bytes # The 209 data comes in five byte groups. Interpret the last four bytes
# of each group as a big endian unsigned integer to get a key value # of each group as a big endian unsigned integer to get a key value
# if that key exists in the meta_array, append its contents to the token # if that key exists in the meta_array, append its contents to the token

View File

@@ -157,18 +157,22 @@ class TopazBook:
raise TpzDRMError("Parse Error : Record Names Don't Match") raise TpzDRMError("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1)) flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1))
# print nbRecords
for i in range (0,nbRecords) : for i in range (0,nbRecords) :
record = [bookReadString(self.fo), bookReadString(self.fo)] keyval = bookReadString(self.fo)
self.bookMetadata[record[0]] = record[1] content = bookReadString(self.fo)
# print keyval
# print content
self.bookMetadata[keyval] = content
return self.bookMetadata return self.bookMetadata
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
keysRecord = None keysRecord = self.bookMetadata.get('keys','')
keysRecordRecord = None keysRecordRecord = ''
if 'keys' in self.bookMetadata: if keysRecord != '':
keysRecord = self.bookMetadata['keys'] keylst = keysRecord.split(',')
if keysRecord in self.bookMetadata: for keyval in keylst:
keysRecordRecord = self.bookMetadata[keysRecord] keysRecordRecord += self.bookMetadata.get(keyval,'')
return keysRecord, keysRecordRecord return keysRecord, keysRecordRecord
def getBookTitle(self): def getBookTitle(self):

View File

@@ -323,12 +323,12 @@ def generateBook(bookDir, raw, fixedimage):
meta_array = getMetaArray(metaFile) meta_array = getMetaArray(metaFile)
# replace special chars in title and authors like & < > # replace special chars in title and authors like & < >
title = meta_array['Title'] title = meta_array.get('Title','No Title Provided')
title = title.replace('&','&amp;') title = title.replace('&','&amp;')
title = title.replace('<','&lt;') title = title.replace('<','&lt;')
title = title.replace('>','&gt;') title = title.replace('>','&gt;')
meta_array['Title'] = title meta_array['Title'] = title
authors = meta_array['Authors'] authors = meta_array.get('Authors','No Authors Provided')
authors = authors.replace('&','&amp;') authors = authors.replace('&','&amp;')
authors = authors.replace('<','&lt;') authors = authors.replace('<','&lt;')
authors = authors.replace('>','&gt;') authors = authors.replace('>','&gt;')
@@ -413,8 +413,10 @@ def generateBook(bookDir, raw, fixedimage):
htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n'
htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' if 'ASIN' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n' htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n'
htmlstr += '</head>\n<body>\n' htmlstr += '</head>\n<body>\n'
@@ -430,8 +432,10 @@ def generateBook(bookDir, raw, fixedimage):
svgindex += '<title>' + meta_array['Title'] + '</title>\n' svgindex += '<title>' + meta_array['Title'] + '</title>\n'
svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' if 'ASIN' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
svgindex += '</head>\n' svgindex += '</head>\n'
svgindex += '<body>\n' svgindex += '<body>\n'
@@ -485,9 +489,12 @@ def generateBook(bookDir, raw, fixedimage):
opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n' opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n'
# adding metadata # adding metadata
opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n' opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n'
opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n' if 'GUID' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n'
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n' if 'ASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n'
if 'oASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n'
opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n' opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n'
opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n' opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n'
opfstr += ' <dc:language>en</dc:language>\n' opfstr += ' <dc:language>en</dc:language>\n'

View File

@@ -29,7 +29,7 @@ from __future__ import with_statement
# and import that ZIP into Calibre using its plugin configuration GUI. # and import that ZIP into Calibre using its plugin configuration GUI.
__version__ = '2.4' __version__ = '2.8'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -231,6 +231,9 @@ def main(argv=sys.argv):
# try with built in Kindle Info files # try with built in Kindle Info files
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
kInfoFiles = None
infile = args[0] infile = args[0]
outdir = args[1] outdir = args[1]
@@ -250,7 +253,7 @@ if not __name__ == "__main__" and inCalibre:
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 2, 4) # The version number of this plugin version = (0, 2, 8) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
@@ -267,6 +270,8 @@ if not __name__ == "__main__" and inCalibre:
import mobidedrm import mobidedrm
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
pids = [] pids = []
serials = [] serials = []
kInfoFiles = [] kInfoFiles = []
@@ -366,4 +371,4 @@ if not __name__ == "__main__" and inCalibre:
return of.name return of.name
def customization_help(self, gui=False): def customization_help(self, gui=False):
return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.' return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.'

View File

@@ -168,27 +168,33 @@ def CryptUnprotectData(encryptedData):
return cleartext return cleartext
# Locate and open the .kindle-info file # Locate the .kindle-info files
def openKindleInfo(kInfoFile=None): def getKindleInfoFiles(kInfoFiles):
if kInfoFile == None: home = os.getenv('HOME')
home = os.getenv('HOME') cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' cmdline = cmdline.encode(sys.getfilesystemencoding())
cmdline = cmdline.encode(sys.getfilesystemencoding()) p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) out1, out2 = p1.communicate()
out1, out2 = p1.communicate() reslst = out1.split('\n')
reslst = out1.split('\n') kinfopath = 'NONE'
kinfopath = 'NONE' found = False
cnt = len(reslst) cnt = len(reslst)
for j in xrange(cnt): for resline in reslst:
resline = reslst[j] if os.path.isfile(resline):
pp = resline.find('.kindle-info') kInfoFiles.append(resline)
if pp >= 0: found = True
kinfopath = resline if not found:
break print('No .kindle-info files have been found.')
if not os.path.isfile(kinfopath): return kInfoFiles
raise DrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r') # Parse the Kindle.info file and return the records as a list of key-values
else: def parseKindleInfo(kInfoFile):
if not os.path.isfile(kInfoFile): DB = {}
raise DrmException('Error: kindle-info file can not be found') infoReader = open(kInfoFile, 'r')
return open(kInfoFile, 'r') infoReader.read(1)
data = infoReader.read()
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -93,18 +93,25 @@ def CryptUnprotectData():
return CryptUnprotectData return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData() CryptUnprotectData = CryptUnprotectData()
# # Locate the .kindle-info files
# Locate and open the Kindle.info file. def getKindleInfoFiles(kInfoFiles):
# regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
def openKindleInfo(kInfoFile=None): path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
if kInfoFile == None: kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") if not os.path.isfile(kinfopath):
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] print('The kindle.info files has not been found.')
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath):
raise DrmException('Error: kindle.info file can not be found')
return open(kinfopath,'r')
else: else:
if not os.path.isfile(kInfoFile): kInfoFiles.append(kinfopath)
raise DrmException('Error: kindle.info file can not be found') return kInfoFiles
return open(kInfoFile, 'r')
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = open(kInfoFile, 'r')
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -18,9 +18,9 @@ global charMap3
global charMap4 global charMap4
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@@ -67,22 +67,6 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result return result
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = openKindleInfo(kInfoFile)
infoReader.read(1)
data = infoReader.read()
if sys.platform.startswith('win'):
items = data.split('{')
else :
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). # Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded).
# Return the decoded and decrypted record # Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey): def getKindleInfoValueForHash(hashedKey):
@@ -224,13 +208,11 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. # Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum): def getKindlePid(pidlst, rec209, token, serialnum):
# Compute book PID
if rec209 != None and token != None: pidHash = SHA1(serialnum+rec209+token)
# Compute book PID bookPID = encodePID(pidHash)
pidHash = SHA1(serialnum+rec209+token) bookPID = checksumPid(bookPID)
bookPID = encodePID(pidHash) pidlst.append(bookPID)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
# compute fixed pid for old pre 2.5 firmware update pid as well # compute fixed pid for old pre 2.5 firmware update pid as well
bookPID = pidFromSerial(serialnum, 7) + "*" bookPID = pidFromSerial(serialnum, 7) + "*"
@@ -243,7 +225,7 @@ def getKindlePid(pidlst, rec209, token, serialnum):
# Parse the EXTH header records and parse the Kindleinfo # Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid. # file to calculate the book pid.
def getK4Pids(pidlst, rec209, token, kInfoFile=None): def getK4Pids(pidlst, rec209, token, kInfoFile):
global kindleDatabase global kindleDatabase
global charMap1 global charMap1
kindleDatabase = None kindleDatabase = None
@@ -256,10 +238,17 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
if kindleDatabase == None : if kindleDatabase == None :
return pidlst return pidlst
try:
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the Mazama Random number # Get the kindle account token
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
except KeyError:
print "Keys not found in " + kInfoFile
return pidlst
# Get the HDD serial # Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
@@ -275,13 +264,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
devicePID = checksumPid(devicePID) devicePID = checksumPid(devicePID)
pidlst.append(devicePID) pidlst.append(devicePID)
# Compute book PID # Compute book PIDs
if rec209 == None or token == None:
print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?"
return pidlst
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
# book pid # book pid
pidHash = SHA1(DSN+kindleAccountToken+rec209+token) pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
@@ -305,8 +288,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
def getPidList(md1, md2, k4, pids, serials, kInfoFiles): def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
pidlst = [] pidlst = []
if kInfoFiles is None:
kInfoFiles = []
if k4: if k4:
pidlst = getK4Pids(pidlst, md1, md2) kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles: for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile) pidlst = getK4Pids(pidlst, md1, md2, infoFile)
for serialnum in serials: for serialnum in serials:

View File

@@ -47,8 +47,12 @@
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) # 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.27' __version__ = '0.29'
import sys import sys
@@ -176,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -191,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -237,12 +242,11 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
token = ''
# The 209 data comes in five byte groups. Interpret the last four bytes # The 209 data comes in five byte groups. Interpret the last four bytes
# of each group as a big endian unsigned integer to get a key value # of each group as a big endian unsigned integer to get a key value
# if that key exists in the meta_array, append its contents to the token # if that key exists in the meta_array, append its contents to the token

View File

@@ -157,18 +157,22 @@ class TopazBook:
raise TpzDRMError("Parse Error : Record Names Don't Match") raise TpzDRMError("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1)) flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1))
# print nbRecords
for i in range (0,nbRecords) : for i in range (0,nbRecords) :
record = [bookReadString(self.fo), bookReadString(self.fo)] keyval = bookReadString(self.fo)
self.bookMetadata[record[0]] = record[1] content = bookReadString(self.fo)
# print keyval
# print content
self.bookMetadata[keyval] = content
return self.bookMetadata return self.bookMetadata
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
keysRecord = None keysRecord = self.bookMetadata.get('keys','')
keysRecordRecord = None keysRecordRecord = ''
if 'keys' in self.bookMetadata: if keysRecord != '':
keysRecord = self.bookMetadata['keys'] keylst = keysRecord.split(',')
if keysRecord in self.bookMetadata: for keyval in keylst:
keysRecordRecord = self.bookMetadata[keysRecord] keysRecordRecord += self.bookMetadata.get(keyval,'')
return keysRecord, keysRecordRecord return keysRecord, keysRecordRecord
def getBookTitle(self): def getBookTitle(self):

View File

@@ -1,4 +1,4 @@
ReadMe_DeDRM_WinApp_v1.5 ReadMe_DeDRM_WinApp_vX.X
----------------------- -----------------------
DeDRM_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto theDeDRM_Drop_Target to have the DRM removed. It repackages the"tools" python software in one easy to use program. DeDRM_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto theDeDRM_Drop_Target to have the DRM removed. It repackages the"tools" python software in one easy to use program.

View File

@@ -323,12 +323,12 @@ def generateBook(bookDir, raw, fixedimage):
meta_array = getMetaArray(metaFile) meta_array = getMetaArray(metaFile)
# replace special chars in title and authors like & < > # replace special chars in title and authors like & < >
title = meta_array['Title'] title = meta_array.get('Title','No Title Provided')
title = title.replace('&','&amp;') title = title.replace('&','&amp;')
title = title.replace('<','&lt;') title = title.replace('<','&lt;')
title = title.replace('>','&gt;') title = title.replace('>','&gt;')
meta_array['Title'] = title meta_array['Title'] = title
authors = meta_array['Authors'] authors = meta_array.get('Authors','No Authors Provided')
authors = authors.replace('&','&amp;') authors = authors.replace('&','&amp;')
authors = authors.replace('<','&lt;') authors = authors.replace('<','&lt;')
authors = authors.replace('>','&gt;') authors = authors.replace('>','&gt;')
@@ -413,8 +413,10 @@ def generateBook(bookDir, raw, fixedimage):
htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n' htmlstr += '<title>' + meta_array['Title'] + ' by ' + meta_array['Authors'] + '</title>\n'
htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' htmlstr += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' htmlstr += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' if 'ASIN' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' htmlstr += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
htmlstr += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n' htmlstr += '<link href="style.css" rel="stylesheet" type="text/css" />\n'
htmlstr += '</head>\n<body>\n' htmlstr += '</head>\n<body>\n'
@@ -430,8 +432,10 @@ def generateBook(bookDir, raw, fixedimage):
svgindex += '<title>' + meta_array['Title'] + '</title>\n' svgindex += '<title>' + meta_array['Title'] + '</title>\n'
svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n' svgindex += '<meta name="Author" content="' + meta_array['Authors'] + '" />\n'
svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n' svgindex += '<meta name="Title" content="' + meta_array['Title'] + '" />\n'
svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n' if 'ASIN' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n' svgindex += '<meta name="ASIN" content="' + meta_array['ASIN'] + '" />\n'
if 'GUID' in meta_array:
svgindex += '<meta name="GUID" content="' + meta_array['GUID'] + '" />\n'
svgindex += '</head>\n' svgindex += '</head>\n'
svgindex += '<body>\n' svgindex += '<body>\n'
@@ -485,9 +489,12 @@ def generateBook(bookDir, raw, fixedimage):
opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n' opfstr += '<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n'
# adding metadata # adding metadata
opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n' opfstr += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n'
opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n' if 'GUID' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n' opfstr += ' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n'
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n' if 'ASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n'
if 'oASIN' in meta_array:
opfstr += ' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n'
opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n' opfstr += ' <dc:title>' + meta_array['Title'] + '</dc:title>\n'
opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n' opfstr += ' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n'
opfstr += ' <dc:language>en</dc:language>\n' opfstr += ' <dc:language>en</dc:language>\n'

View File

@@ -29,7 +29,7 @@ from __future__ import with_statement
# and import that ZIP into Calibre using its plugin configuration GUI. # and import that ZIP into Calibre using its plugin configuration GUI.
__version__ = '2.4' __version__ = '2.8'
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -231,6 +231,9 @@ def main(argv=sys.argv):
# try with built in Kindle Info files # try with built in Kindle Info files
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
kInfoFiles = None
infile = args[0] infile = args[0]
outdir = args[1] outdir = args[1]
@@ -250,7 +253,7 @@ if not __name__ == "__main__" and inCalibre:
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.' Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
author = 'DiapDealer, SomeUpdates' # The author of this plugin author = 'DiapDealer, SomeUpdates' # The author of this plugin
version = (0, 2, 4) # The version number of this plugin version = (0, 2, 8) # The version number of this plugin
file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to file_types = set(['prc','mobi','azw','azw1','tpz']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import on_import = True # Run this plugin during the import
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
@@ -267,6 +270,8 @@ if not __name__ == "__main__" and inCalibre:
import mobidedrm import mobidedrm
k4 = True k4 = True
if sys.platform.startswith('linux'):
k4 = False
pids = [] pids = []
serials = [] serials = []
kInfoFiles = [] kInfoFiles = []
@@ -366,4 +371,4 @@ if not __name__ == "__main__" and inCalibre:
return of.name return of.name
def customization_help(self, gui=False): def customization_help(self, gui=False):
return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.' return 'Enter 10 character PIDs and/or Kindle serial numbers, separated by commas.'

View File

@@ -168,27 +168,33 @@ def CryptUnprotectData(encryptedData):
return cleartext return cleartext
# Locate and open the .kindle-info file # Locate the .kindle-info files
def openKindleInfo(kInfoFile=None): def getKindleInfoFiles(kInfoFiles):
if kInfoFile == None: home = os.getenv('HOME')
home = os.getenv('HOME') cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"' cmdline = cmdline.encode(sys.getfilesystemencoding())
cmdline = cmdline.encode(sys.getfilesystemencoding()) p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
p1 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False) out1, out2 = p1.communicate()
out1, out2 = p1.communicate() reslst = out1.split('\n')
reslst = out1.split('\n') kinfopath = 'NONE'
kinfopath = 'NONE' found = False
cnt = len(reslst) cnt = len(reslst)
for j in xrange(cnt): for resline in reslst:
resline = reslst[j] if os.path.isfile(resline):
pp = resline.find('.kindle-info') kInfoFiles.append(resline)
if pp >= 0: found = True
kinfopath = resline if not found:
break print('No .kindle-info files have been found.')
if not os.path.isfile(kinfopath): return kInfoFiles
raise DrmException('Error: .kindle-info file can not be found')
return open(kinfopath,'r') # Parse the Kindle.info file and return the records as a list of key-values
else: def parseKindleInfo(kInfoFile):
if not os.path.isfile(kInfoFile): DB = {}
raise DrmException('Error: kindle-info file can not be found') infoReader = open(kInfoFile, 'r')
return open(kInfoFile, 'r') infoReader.read(1)
data = infoReader.read()
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -93,18 +93,25 @@ def CryptUnprotectData():
return CryptUnprotectData return CryptUnprotectData
CryptUnprotectData = CryptUnprotectData() CryptUnprotectData = CryptUnprotectData()
# # Locate the .kindle-info files
# Locate and open the Kindle.info file. def getKindleInfoFiles(kInfoFiles):
# regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
def openKindleInfo(kInfoFile=None): path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
if kInfoFile == None: kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\") if not os.path.isfile(kinfopath):
path = winreg.QueryValueEx(regkey, 'Local AppData')[0] print('The kindle.info files has not been found.')
kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
if not os.path.isfile(kinfopath):
raise DrmException('Error: kindle.info file can not be found')
return open(kinfopath,'r')
else: else:
if not os.path.isfile(kInfoFile): kInfoFiles.append(kinfopath)
raise DrmException('Error: kindle.info file can not be found') return kInfoFiles
return open(kInfoFile, 'r')
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = open(kInfoFile, 'r')
infoReader.read(1)
data = infoReader.read()
items = data.split('{')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB

View File

@@ -18,9 +18,9 @@ global charMap3
global charMap4 global charMap4
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4pcutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2 from k4mutils import getKindleInfoFiles, parseKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap2
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M" charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@@ -67,22 +67,6 @@ def decode(data,map):
result += pack("B",value) result += pack("B",value)
return result return result
# Parse the Kindle.info file and return the records as a list of key-values
def parseKindleInfo(kInfoFile):
DB = {}
infoReader = openKindleInfo(kInfoFile)
infoReader.read(1)
data = infoReader.read()
if sys.platform.startswith('win'):
items = data.split('{')
else :
items = data.split('[')
for item in items:
splito = item.split(':')
DB[splito[0]] =splito[1]
return DB
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). # Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded).
# Return the decoded and decrypted record # Return the decoded and decrypted record
def getKindleInfoValueForHash(hashedKey): def getKindleInfoValueForHash(hashedKey):
@@ -224,13 +208,11 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid. # Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum): def getKindlePid(pidlst, rec209, token, serialnum):
# Compute book PID
if rec209 != None and token != None: pidHash = SHA1(serialnum+rec209+token)
# Compute book PID bookPID = encodePID(pidHash)
pidHash = SHA1(serialnum+rec209+token) bookPID = checksumPid(bookPID)
bookPID = encodePID(pidHash) pidlst.append(bookPID)
bookPID = checksumPid(bookPID)
pidlst.append(bookPID)
# compute fixed pid for old pre 2.5 firmware update pid as well # compute fixed pid for old pre 2.5 firmware update pid as well
bookPID = pidFromSerial(serialnum, 7) + "*" bookPID = pidFromSerial(serialnum, 7) + "*"
@@ -243,7 +225,7 @@ def getKindlePid(pidlst, rec209, token, serialnum):
# Parse the EXTH header records and parse the Kindleinfo # Parse the EXTH header records and parse the Kindleinfo
# file to calculate the book pid. # file to calculate the book pid.
def getK4Pids(pidlst, rec209, token, kInfoFile=None): def getK4Pids(pidlst, rec209, token, kInfoFile):
global kindleDatabase global kindleDatabase
global charMap1 global charMap1
kindleDatabase = None kindleDatabase = None
@@ -256,10 +238,17 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
if kindleDatabase == None : if kindleDatabase == None :
return pidlst return pidlst
try:
# Get the Mazama Random number
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
# Get the Mazama Random number # Get the kindle account token
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber") kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
except KeyError:
print "Keys not found in " + kInfoFile
return pidlst
# Get the HDD serial # Get the HDD serial
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1) encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
@@ -275,13 +264,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
devicePID = checksumPid(devicePID) devicePID = checksumPid(devicePID)
pidlst.append(devicePID) pidlst.append(devicePID)
# Compute book PID # Compute book PIDs
if rec209 == None or token == None:
print "\nNo EXTH record type 209 or token - Perhaps not a K4 file?"
return pidlst
# Get the kindle account token
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
# book pid # book pid
pidHash = SHA1(DSN+kindleAccountToken+rec209+token) pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
@@ -305,8 +288,10 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
def getPidList(md1, md2, k4, pids, serials, kInfoFiles): def getPidList(md1, md2, k4, pids, serials, kInfoFiles):
pidlst = [] pidlst = []
if kInfoFiles is None:
kInfoFiles = []
if k4: if k4:
pidlst = getK4Pids(pidlst, md1, md2) kInfoFiles = getKindleInfoFiles(kInfoFiles)
for infoFile in kInfoFiles: for infoFile in kInfoFiles:
pidlst = getK4Pids(pidlst, md1, md2, infoFile) pidlst = getK4Pids(pidlst, md1, md2, infoFile)
for serialnum in serials: for serialnum in serials:

View File

@@ -47,8 +47,12 @@
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) # 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.27' __version__ = '0.29'
import sys import sys
@@ -176,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -191,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -237,12 +242,11 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
token = ''
# The 209 data comes in five byte groups. Interpret the last four bytes # The 209 data comes in five byte groups. Interpret the last four bytes
# of each group as a big endian unsigned integer to get a key value # of each group as a big endian unsigned integer to get a key value
# if that key exists in the meta_array, append its contents to the token # if that key exists in the meta_array, append its contents to the token

View File

@@ -157,18 +157,22 @@ class TopazBook:
raise TpzDRMError("Parse Error : Record Names Don't Match") raise TpzDRMError("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1)) flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1)) nbRecords = ord(self.fo.read(1))
# print nbRecords
for i in range (0,nbRecords) : for i in range (0,nbRecords) :
record = [bookReadString(self.fo), bookReadString(self.fo)] keyval = bookReadString(self.fo)
self.bookMetadata[record[0]] = record[1] content = bookReadString(self.fo)
# print keyval
# print content
self.bookMetadata[keyval] = content
return self.bookMetadata return self.bookMetadata
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
keysRecord = None keysRecord = self.bookMetadata.get('keys','')
keysRecordRecord = None keysRecordRecord = ''
if 'keys' in self.bookMetadata: if keysRecord != '':
keysRecord = self.bookMetadata['keys'] keylst = keysRecord.split(',')
if keysRecord in self.bookMetadata: for keyval in keylst:
keysRecordRecord = self.bookMetadata[keysRecord] keysRecordRecord += self.bookMetadata.get(keyval,'')
return keysRecord, keysRecordRecord return keysRecord, keysRecordRecord
def getBookTitle(self): def getBookTitle(self):

View File

@@ -47,8 +47,12 @@
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) # 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.27' __version__ = '0.29'
import sys import sys
@@ -176,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -191,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -237,12 +242,11 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
token = ''
# The 209 data comes in five byte groups. Interpret the last four bytes # The 209 data comes in five byte groups. Interpret the last four bytes
# of each group as a big endian unsigned integer to get a key value # of each group as a big endian unsigned integer to get a key value
# if that key exists in the meta_array, append its contents to the token # if that key exists in the meta_array, append its contents to the token

View File

@@ -24,7 +24,7 @@
# 0.14 - Working out when the extra data flags are present has been problematic # 0.14 - Working out when the extra data flags are present has been problematic
# Versions 7 through 9 have tried to tweak the conditions, but have been # Versions 7 through 9 have tried to tweak the conditions, but have been
# only partially successful. Closer examination of lots of sample # only partially successful. Closer examination of lots of sample
# files reveals that a confusin has arisen because trailing data entries # files reveals that a confusion has arisen because trailing data entries
# are not encrypted, but it turns out that the multibyte entries # are not encrypted, but it turns out that the multibyte entries
# in utf8 file are encrypted. (Although neither kind gets compressed.) # in utf8 file are encrypted. (Although neither kind gets compressed.)
# This knowledge leads to a simplification of the test for the # This knowledge leads to a simplification of the test for the
@@ -39,13 +39,22 @@
# Removed the disabled Calibre plug-in code # Removed the disabled Calibre plug-in code
# Permit use of 8-digit PIDs # Permit use of 8-digit PIDs
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either. # 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
# 0.20 - Corretion: It seems that multibyte entries are encrypted in a v6 file. # 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
# 0.21 - Added support for multiple pids
# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
# 0.23 - fixed problem with older files with no EXTH section
# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.20' __version__ = '0.29'
import sys import sys
import struct
import binascii
class Unbuffered: class Unbuffered:
def __init__(self, stream): def __init__(self, stream):
@@ -55,10 +64,20 @@ class Unbuffered:
self.stream.flush() self.stream.flush()
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)
sys.stdout=Unbuffered(sys.stdout)
import os
import struct
import binascii
class DrmException(Exception): class DrmException(Exception):
pass pass
#
# MobiBook Utility Routines
#
# Implementation of Pukall Cipher 1 # Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True): def PC1(key, src, decryption=True):
sum1 = 0; sum1 = 0;
@@ -70,7 +89,6 @@ def PC1(key, src, decryption=True):
wkey = [] wkey = []
for i in xrange(8): for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1])) wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
dst = "" dst = ""
for i in xrange(len(src)): for i in xrange(len(src)):
temp1 = 0; temp1 = 0;
@@ -131,7 +149,9 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
num += (ord(ptr[size - num - 1]) & 0x3) + 1 num += (ord(ptr[size - num - 1]) & 0x3) + 1
return num return num
class DrmStripper:
class MobiBook:
def loadSection(self, section): def loadSection(self, section):
if (section + 1 == self.num_sections): if (section + 1 == self.num_sections):
endoff = len(self.data_file) endoff = len(self.data_file)
@@ -140,6 +160,102 @@ class DrmStripper:
off = self.sections[section][0] off = self.sections[section][0]
return self.data_file[off:endoff] return self.data_file[off:endoff]
def __init__(self, infile):
# initial sanity check on file
self.data_file = file(infile, 'rb').read()
self.header = self.data_file[0:78]
if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
raise DrmException("invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
# build up section offset and flag info
self.num_sections, = struct.unpack('>H', self.header[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
flags, val = a1, a2<<16|a3<<8|a4
self.sections.append( (offset, flags, val) )
# parse information from section 0
self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_version = -1
self.meta_array = {}
return
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
self.extra_data_flags = 0
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags
if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE
# if exth region exists parse it for metadata array
self.meta_array = {}
try:
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
exth = 'NONE'
if exth_flag & 0x40:
exth = self.sect[16 + self.mobi_length:]
if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
nitems, = struct.unpack('>I', exth[8:12])
pos = 12
for i in xrange(nitems):
type, size = struct.unpack('>II', exth[pos: pos + 8])
content = exth[pos + 8: pos + size]
self.meta_array[type] = content
# reset the text to speech flag and clipping limit, if present
if type == 401 and size == 9:
# set clipping limit to 100%
self.patchSection(0, "\144", 16 + self.mobi_length + pos + 8)
elif type == 404 and size == 9:
# make sure text to speech is enabled
self.patchSection(0, "\0", 16 + self.mobi_length + pos + 8)
# print type, size, content, content.encode('hex')
pos += size
except:
self.meta_array = {}
pass
def getBookTitle(self):
title = ''
if 503 in self.meta_array:
title = self.meta_array[503]
else :
toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
tend = toff + tlen
title = self.sect[toff:tend]
if title == '':
title = self.header[:32]
title = title.split("\0")[0]
return title
def getPIDMetaInfo(self):
rec209 = ''
token = ''
if 209 in self.meta_array:
rec209 = self.meta_array[209]
data = rec209
# The 209 data comes in five byte groups. Interpret the last four bytes
# of each group as a big endian unsigned integer to get a key value
# if that key exists in the meta_array, append its contents to the token
for i in xrange(0,len(data),5):
val, = struct.unpack('>I',data[i+1:i+5])
sval = self.meta_array.get(val,'')
token += sval
return rec209, token
def patch(self, off, new): def patch(self, off, new):
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):] self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
@@ -152,134 +268,136 @@ class DrmStripper:
assert off + in_off + len(new) <= endoff assert off + in_off + len(new) <= endoff
self.patch(off + in_off, new) self.patch(off + in_off, new)
def parseDRM(self, data, count, pid): def parseDRM(self, data, count, pidlist):
pid = pid.ljust(16,'\0')
keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
temp_key = PC1(keyvec1, pid, False)
temp_key_sum = sum(map(ord,temp_key)) & 0xff
found_key = None found_key = None
for i in xrange(count): keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) for pid in pidlist:
cookie = PC1(temp_key, cookie) bigpid = pid.ljust(16,'\0')
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) temp_key = PC1(keyvec1, bigpid, False)
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1: temp_key_sum = sum(map(ord,temp_key)) & 0xff
found_key = finalkey found_key = None
for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
if cksum == temp_key_sum:
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
if verification == ver and (flags & 0x1F) == 1:
found_key = finalkey
break
if found_key != None:
break break
if not found_key: if not found_key:
# Then try the default encoding that doesn't require a PID # Then try the default encoding that doesn't require a PID
pid = "00000000"
temp_key = keyvec1 temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count): for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30]) verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie) if cksum == temp_key_sum:
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie) cookie = PC1(temp_key, cookie)
if verification == ver and cksum == temp_key_sum: ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
found_key = finalkey if verification == ver:
break found_key = finalkey
return found_key break
return [found_key,pid]
def __init__(self, data_file, pid): def processBook(self, pidlist):
if len(pid)==10: crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
if checksumPid(pid[0:-2]) != pid: print 'Crypto Type is: ', crypto_type
raise DrmException("invalid PID checksum") self.crypto_type = crypto_type
pid = pid[0:-2]
elif len(pid)==8:
print "PID without checksum given. With checksum PID is "+checksumPid(pid)
else:
raise DrmException("Invalid PID length")
self.data_file = data_file
header = data_file[0:72]
if header[0x3C:0x3C+8] != 'BOOKMOBI':
raise DrmException("invalid file format")
self.num_sections, = struct.unpack('>H', data_file[76:78])
self.sections = []
for i in xrange(self.num_sections):
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', data_file[78+i*8:78+i*8+8])
flags, val = a1, a2<<16|a3<<8|a4
self.sections.append( (offset, flags, val) )
sect = self.loadSection(0)
records, = struct.unpack('>H', sect[0x8:0x8+2])
mobi_length, = struct.unpack('>L',sect[0x14:0x18])
mobi_version, = struct.unpack('>L',sect[0x68:0x6C])
extra_data_flags = 0
print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length)
if (mobi_length >= 0xE4) and (mobi_version >= 5):
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
print "Extra Data Flags = %d" %extra_data_flags
if mobi_version < 7:
# multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted.
extra_data_flags &= 0xFFFE
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
if crypto_type == 0: if crypto_type == 0:
print "This book is not encrypted." print "This book is not encrypted."
else: return self.data_file
if crypto_type == 1: if crypto_type != 2 and crypto_type != 1:
raise DrmException("cannot decode Mobipocket encryption type 1") raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
if crypto_type != 2:
raise DrmException("unknown encryption type: %d" % crypto_type)
goodpids = []
for pid in pidlist:
if len(pid)==10:
if checksumPid(pid[0:-2]) != pid:
print "Warning: PID " + pid + " has incorrect checksum, should have been "+checksumPid(pid[0:-2])
goodpids.append(pid[0:-2])
elif len(pid)==8:
goodpids.append(pid)
if self.crypto_type == 1:
t1_keyvec = "QDCVEPMU675RUBSZ"
if self.magic == 'TEXtREAd':
bookkey_data = self.sect[0x0E:0x0E+16]
elif self.mobi_version < 0:
bookkey_data = self.sect[0x90:0x90+16]
else:
bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
pid = "00000000"
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys # calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16]) drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0: if drm_count == 0:
raise DrmException("no PIDs found in this file") raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid) found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
if not found_key: if not found_key:
raise DrmException("no key found. maybe the PID is incorrect") raise DrmException("No key found. Most likely the correct PID has not been given.")
# kill the drm keys # kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr) self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers # kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8) self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC) if pid=="00000000":
print "File has default encryption, no specific PID."
else:
print "File is encoded with PID "+checksumPid(pid)+"."
# decrypt sections # clear the crypto type
print "Decrypting. Please wait . . .", self.patchSection(0, "\0" * 2, 0xC)
new_data = self.data_file[:self.sections[1][0]]
for i in xrange(1, records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
if i%100 == 0:
print ".",
# print "record %d, extra_size %d" %(i,extra_size)
new_data += PC1(found_key, data[0:len(data) - extra_size])
if extra_size > 0:
new_data += data[-extra_size:]
#self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
if self.num_sections > records+1:
new_data += self.data_file[self.sections[records+1][0]:]
self.data_file = new_data
print "done"
def getResult(self): # decrypt sections
print "Decrypting. Please wait . . .",
new_data = self.data_file[:self.sections[1][0]]
for i in xrange(1, self.records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
if i%100 == 0:
print ".",
# print "record %d, extra_size %d" %(i,extra_size)
new_data += PC1(found_key, data[0:len(data) - extra_size])
if extra_size > 0:
new_data += data[-extra_size:]
if self.num_sections > self.records+1:
new_data += self.data_file[self.sections[self.records+1][0]:]
self.data_file = new_data
print "done"
return self.data_file return self.data_file
def getUnencryptedBook(infile,pid): def getUnencryptedBook(infile,pid):
sys.stdout=Unbuffered(sys.stdout) if not os.path.isfile(infile):
data_file = file(infile, 'rb').read() raise DrmException('Input File Not Found')
strippedFile = DrmStripper(data_file, pid) book = MobiBook(infile)
return strippedFile.getResult() return book.processBook([pid])
def getUnencryptedBookWithList(infile,pidlist):
if not os.path.isfile(infile):
raise DrmException('Input File Not Found')
book = MobiBook(infile)
return book.processBook(pidlist)
def main(argv=sys.argv): def main(argv=sys.argv):
sys.stdout=Unbuffered(sys.stdout)
print ('MobiDeDrm v%(__version__)s. ' print ('MobiDeDrm v%(__version__)s. '
'Copyright 2008-2010 The Dark Reverser.' % globals()) 'Copyright 2008-2010 The Dark Reverser.' % globals())
if len(argv)<4: if len(argv)<3 or len(argv)>4:
print "Removes protection from Mobipocket books" print "Removes protection from Mobipocket books"
print "Usage:" print "Usage:"
print " %s <infile> <outfile> <PID>" % sys.argv[0] print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
return 1 return 1
else: else:
infile = argv[1] infile = argv[1]
outfile = argv[2] outfile = argv[2]
pid = argv[3] if len(argv) is 4:
pidlist = argv[3].split(',')
else:
pidlist = {}
try: try:
stripped_file = getUnencryptedBook(infile, pid) stripped_file = getUnencryptedBookWithList(infile, pidlist)
file(outfile, 'wb').write(stripped_file) file(outfile, 'wb').write(stripped_file)
except DrmException, e: except DrmException, e:
print "Error: %s" % e print "Error: %s" % e

View File

@@ -47,8 +47,12 @@
# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption # 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100% # 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!) # 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
# 0.28 - slight additional changes to metadata token generation (None -> '')
# 0.29 - It seems that the ideas about when multibyte trailing characters were
# included in the encryption were wrong. They aren't for DOC compressed
# files, but they are for HUFF/CDIC compress files!
__version__ = '0.27' __version__ = '0.29'
import sys import sys
@@ -176,6 +180,7 @@ class MobiBook:
# parse information from section 0 # parse information from section 0
self.sect = self.loadSection(0) self.sect = self.loadSection(0)
self.records, = struct.unpack('>H', self.sect[0x8:0x8+2]) self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd': if self.magic == 'TEXtREAd':
print "Book has format: ", self.magic print "Book has format: ", self.magic
@@ -191,7 +196,7 @@ class MobiBook:
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5): if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4]) self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
print "Extra Data Flags = %d" % self.extra_data_flags print "Extra Data Flags = %d" % self.extra_data_flags
if self.mobi_version < 7: if (self.mobi_version < 7) and (self.compression != 17480):
# multibyte utf8 data is included in the encryption for mobi_version 6 and below # multibyte utf8 data is included in the encryption for mobi_version 6 and below
# so clear that byte so that we leave it to be decrypted. # so clear that byte so that we leave it to be decrypted.
self.extra_data_flags &= 0xFFFE self.extra_data_flags &= 0xFFFE
@@ -237,12 +242,11 @@ class MobiBook:
return title return title
def getPIDMetaInfo(self): def getPIDMetaInfo(self):
rec209 = None rec209 = ''
token = None token = ''
if 209 in self.meta_array: if 209 in self.meta_array:
rec209 = self.meta_array[209] rec209 = self.meta_array[209]
data = rec209 data = rec209
token = ''
# The 209 data comes in five byte groups. Interpret the last four bytes # The 209 data comes in five byte groups. Interpret the last four bytes
# of each group as a big endian unsigned integer to get a key value # of each group as a big endian unsigned integer to get a key value
# if that key exists in the meta_array, append its contents to the token # if that key exists in the meta_array, append its contents to the token