diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist
index d1feae2..4f3beb7 100644
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist
+++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist
@@ -24,7 +24,7 @@
CFBundleExecutable
droplet
CFBundleGetInfoString
- DeDRM 2.3, Copyright © 2010–2011 by Apprentice Alf and others.
+ DeDRM 2.4, Copyright © 2010–2011 by Apprentice Alf and others.
CFBundleIconFile
droplet
CFBundleInfoDictionaryVersion
@@ -34,7 +34,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 2.3
+ 2.4
CFBundleSignature
dplt
LSMinimumSystemVersion
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py
index 0255a3c..3a0000e 100644
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py
+++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py
@@ -29,7 +29,7 @@ from __future__ import with_statement
# and import that ZIP into Calibre using its plugin configuration GUI.
-__version__ = '2.4'
+__version__ = '2.6'
class Unbuffered:
def __init__(self, stream):
@@ -250,7 +250,7 @@ if not __name__ == "__main__" and inCalibre:
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
author = 'DiapDealer, SomeUpdates' # The author of this plugin
- version = (0, 2, 4) # The version number of this plugin
+ version = (0, 2, 6) # The version number of this plugin
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
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py
index 6dcbf73..039daf9 100644
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py
+++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py
@@ -224,13 +224,11 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum):
-
- if rec209 != None and token != None:
- # Compute book PID
- pidHash = SHA1(serialnum+rec209+token)
- bookPID = encodePID(pidHash)
- bookPID = checksumPid(bookPID)
- pidlst.append(bookPID)
+ # Compute book PID
+ pidHash = SHA1(serialnum+rec209+token)
+ bookPID = encodePID(pidHash)
+ bookPID = checksumPid(bookPID)
+ pidlst.append(bookPID)
# compute fixed pid for old pre 2.5 firmware update pid as well
bookPID = pidFromSerial(serialnum, 7) + "*"
@@ -276,9 +274,6 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
pidlst.append(devicePID)
# Compute book PID
- 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")
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py
index ec756b9..e660a1a 100644
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py
+++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py
@@ -47,8 +47,9 @@
# 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 -> '')
-__version__ = '0.27'
+__version__ = '0.28'
import sys
@@ -237,12 +238,11 @@ class MobiBook:
return title
def getPIDMetaInfo(self):
- rec209 = None
- token = None
+ rec209 = ''
+ token = ''
if 209 in self.meta_array:
rec209 = self.meta_array[209]
data = rec209
- token = ''
# 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
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py
index 732bbae..59bc5fa 100644
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py
+++ b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py
@@ -157,18 +157,22 @@ class TopazBook:
raise TpzDRMError("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1))
+ # print nbRecords
for i in range (0,nbRecords) :
- record = [bookReadString(self.fo), bookReadString(self.fo)]
- self.bookMetadata[record[0]] = record[1]
+ keyval = bookReadString(self.fo)
+ content = bookReadString(self.fo)
+ # print keyval
+ # print content
+ self.bookMetadata[keyval] = content
return self.bookMetadata
def getPIDMetaInfo(self):
- keysRecord = None
- keysRecordRecord = None
- if 'keys' in self.bookMetadata:
- keysRecord = self.bookMetadata['keys']
- if keysRecord in self.bookMetadata:
- keysRecordRecord = self.bookMetadata[keysRecord]
+ keysRecord = self.bookMetadata.get('keys','')
+ keysRecordRecord = ''
+ if keysRecord != '':
+ keylst = keysRecord.split(',')
+ for keyval in keylst:
+ keysRecordRecord += self.bookMetadata.get(keyval,'')
return keysRecord, keysRecordRecord
def getBookTitle(self):
diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py
index 0255a3c..3a0000e 100644
--- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py
+++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py
@@ -29,7 +29,7 @@ from __future__ import with_statement
# and import that ZIP into Calibre using its plugin configuration GUI.
-__version__ = '2.4'
+__version__ = '2.6'
class Unbuffered:
def __init__(self, stream):
@@ -250,7 +250,7 @@ if not __name__ == "__main__" and inCalibre:
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
author = 'DiapDealer, SomeUpdates' # The author of this plugin
- version = (0, 2, 4) # The version number of this plugin
+ version = (0, 2, 6) # The version number of this plugin
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
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py
index 6dcbf73..039daf9 100644
--- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py
+++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py
@@ -224,13 +224,11 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum):
-
- if rec209 != None and token != None:
- # Compute book PID
- pidHash = SHA1(serialnum+rec209+token)
- bookPID = encodePID(pidHash)
- bookPID = checksumPid(bookPID)
- pidlst.append(bookPID)
+ # Compute book PID
+ pidHash = SHA1(serialnum+rec209+token)
+ bookPID = encodePID(pidHash)
+ bookPID = checksumPid(bookPID)
+ pidlst.append(bookPID)
# compute fixed pid for old pre 2.5 firmware update pid as well
bookPID = pidFromSerial(serialnum, 7) + "*"
@@ -276,9 +274,6 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
pidlst.append(devicePID)
# Compute book PID
- 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")
diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py
index ec756b9..e660a1a 100644
--- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py
+++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py
@@ -47,8 +47,9 @@
# 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 -> '')
-__version__ = '0.27'
+__version__ = '0.28'
import sys
@@ -237,12 +238,11 @@ class MobiBook:
return title
def getPIDMetaInfo(self):
- rec209 = None
- token = None
+ rec209 = ''
+ token = ''
if 209 in self.meta_array:
rec209 = self.meta_array[209]
data = rec209
- token = ''
# 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
diff --git a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py
index 732bbae..59bc5fa 100644
--- a/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py
+++ b/DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py
@@ -157,18 +157,22 @@ class TopazBook:
raise TpzDRMError("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1))
+ # print nbRecords
for i in range (0,nbRecords) :
- record = [bookReadString(self.fo), bookReadString(self.fo)]
- self.bookMetadata[record[0]] = record[1]
+ keyval = bookReadString(self.fo)
+ content = bookReadString(self.fo)
+ # print keyval
+ # print content
+ self.bookMetadata[keyval] = content
return self.bookMetadata
def getPIDMetaInfo(self):
- keysRecord = None
- keysRecordRecord = None
- if 'keys' in self.bookMetadata:
- keysRecord = self.bookMetadata['keys']
- if keysRecord in self.bookMetadata:
- keysRecordRecord = self.bookMetadata[keysRecord]
+ keysRecord = self.bookMetadata.get('keys','')
+ keysRecordRecord = ''
+ if keysRecord != '':
+ keylst = keysRecord.split(',')
+ for keyval in keylst:
+ keysRecordRecord += self.bookMetadata.get(keyval,'')
return keysRecord, keysRecordRecord
def getBookTitle(self):
diff --git a/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt b/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt
index 111d2b9..b260625 100644
--- a/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt
+++ b/DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt
@@ -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.
diff --git a/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py b/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py
index 0255a3c..3a0000e 100644
--- a/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py
+++ b/KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py
@@ -29,7 +29,7 @@ from __future__ import with_statement
# and import that ZIP into Calibre using its plugin configuration GUI.
-__version__ = '2.4'
+__version__ = '2.6'
class Unbuffered:
def __init__(self, stream):
@@ -250,7 +250,7 @@ if not __name__ == "__main__" and inCalibre:
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
author = 'DiapDealer, SomeUpdates' # The author of this plugin
- version = (0, 2, 4) # The version number of this plugin
+ version = (0, 2, 6) # The version number of this plugin
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
priority = 210 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
diff --git a/KindleBooks_Tools/KindleBooks/lib/kgenpids.py b/KindleBooks_Tools/KindleBooks/lib/kgenpids.py
index 6dcbf73..039daf9 100644
--- a/KindleBooks_Tools/KindleBooks/lib/kgenpids.py
+++ b/KindleBooks_Tools/KindleBooks/lib/kgenpids.py
@@ -224,13 +224,11 @@ def pidFromSerial(s, l):
# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
def getKindlePid(pidlst, rec209, token, serialnum):
-
- if rec209 != None and token != None:
- # Compute book PID
- pidHash = SHA1(serialnum+rec209+token)
- bookPID = encodePID(pidHash)
- bookPID = checksumPid(bookPID)
- pidlst.append(bookPID)
+ # Compute book PID
+ pidHash = SHA1(serialnum+rec209+token)
+ bookPID = encodePID(pidHash)
+ bookPID = checksumPid(bookPID)
+ pidlst.append(bookPID)
# compute fixed pid for old pre 2.5 firmware update pid as well
bookPID = pidFromSerial(serialnum, 7) + "*"
@@ -276,9 +274,6 @@ def getK4Pids(pidlst, rec209, token, kInfoFile=None):
pidlst.append(devicePID)
# Compute book PID
- 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")
diff --git a/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py b/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py
index ec756b9..e660a1a 100644
--- a/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py
+++ b/KindleBooks_Tools/KindleBooks/lib/mobidedrm.py
@@ -47,8 +47,9 @@
# 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 -> '')
-__version__ = '0.27'
+__version__ = '0.28'
import sys
@@ -237,12 +238,11 @@ class MobiBook:
return title
def getPIDMetaInfo(self):
- rec209 = None
- token = None
+ rec209 = ''
+ token = ''
if 209 in self.meta_array:
rec209 = self.meta_array[209]
data = rec209
- token = ''
# 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
diff --git a/KindleBooks_Tools/KindleBooks/lib/topazextract.py b/KindleBooks_Tools/KindleBooks/lib/topazextract.py
index 732bbae..59bc5fa 100644
--- a/KindleBooks_Tools/KindleBooks/lib/topazextract.py
+++ b/KindleBooks_Tools/KindleBooks/lib/topazextract.py
@@ -157,18 +157,22 @@ class TopazBook:
raise TpzDRMError("Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1))
+ # print nbRecords
for i in range (0,nbRecords) :
- record = [bookReadString(self.fo), bookReadString(self.fo)]
- self.bookMetadata[record[0]] = record[1]
+ keyval = bookReadString(self.fo)
+ content = bookReadString(self.fo)
+ # print keyval
+ # print content
+ self.bookMetadata[keyval] = content
return self.bookMetadata
def getPIDMetaInfo(self):
- keysRecord = None
- keysRecordRecord = None
- if 'keys' in self.bookMetadata:
- keysRecord = self.bookMetadata['keys']
- if keysRecord in self.bookMetadata:
- keysRecordRecord = self.bookMetadata[keysRecord]
+ keysRecord = self.bookMetadata.get('keys','')
+ keysRecordRecord = ''
+ if keysRecord != '':
+ keylst = keysRecord.split(',')
+ for keyval in keylst:
+ keysRecordRecord += self.bookMetadata.get(keyval,'')
return keysRecord, keysRecordRecord
def getBookTitle(self):
diff --git a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py
index ec756b9..e660a1a 100644
--- a/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py
+++ b/KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py
@@ -47,8 +47,9 @@
# 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 -> '')
-__version__ = '0.27'
+__version__ = '0.28'
import sys
@@ -237,12 +238,11 @@ class MobiBook:
return title
def getPIDMetaInfo(self):
- rec209 = None
- token = None
+ rec209 = ''
+ token = ''
if 209 in self.meta_array:
rec209 = self.meta_array[209]
data = rec209
- token = ''
# 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
diff --git a/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py b/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py
index 183432c..e660a1a 100644
--- a/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py
+++ b/KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py
@@ -24,7 +24,7 @@
# 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
# 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
# in utf8 file are encrypted. (Although neither kind gets compressed.)
# This knowledge leads to a simplification of the test for the
@@ -39,13 +39,19 @@
# Removed the disabled Calibre plug-in code
# Permit use of 8-digit PIDs
# 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 -> '')
-__version__ = '0.20'
+__version__ = '0.28'
import sys
-import struct
-import binascii
class Unbuffered:
def __init__(self, stream):
@@ -55,10 +61,20 @@ class Unbuffered:
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
+sys.stdout=Unbuffered(sys.stdout)
+
+import os
+import struct
+import binascii
class DrmException(Exception):
pass
+
+#
+# MobiBook Utility Routines
+#
+
# Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True):
sum1 = 0;
@@ -70,7 +86,6 @@ def PC1(key, src, decryption=True):
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
-
dst = ""
for i in xrange(len(src)):
temp1 = 0;
@@ -131,7 +146,9 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
num += (ord(ptr[size - num - 1]) & 0x3) + 1
return num
-class DrmStripper:
+
+
+class MobiBook:
def loadSection(self, section):
if (section + 1 == self.num_sections):
endoff = len(self.data_file)
@@ -140,6 +157,101 @@ class DrmStripper:
off = self.sections[section][0]
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])
+
+ 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:
+ # 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):
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
@@ -152,134 +264,136 @@ class DrmStripper:
assert off + in_off + len(new) <= endoff
self.patch(off + in_off, new)
- def parseDRM(self, data, count, pid):
- 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
+ def parseDRM(self, data, count, pidlist):
found_key = None
- for i in xrange(count):
- verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
- cookie = PC1(temp_key, cookie)
- ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
- if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
- found_key = finalkey
+ keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
+ for pid in pidlist:
+ bigpid = pid.ljust(16,'\0')
+ temp_key = PC1(keyvec1, bigpid, False)
+ temp_key_sum = sum(map(ord,temp_key)) & 0xff
+ 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
if not found_key:
# Then try the default encoding that doesn't require a PID
+ pid = "00000000"
temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
- cookie = PC1(temp_key, cookie)
- ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
- if verification == ver and cksum == temp_key_sum:
- found_key = finalkey
- break
- return found_key
+ if cksum == temp_key_sum:
+ cookie = PC1(temp_key, cookie)
+ ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
+ if verification == ver:
+ found_key = finalkey
+ break
+ return [found_key,pid]
- def __init__(self, data_file, pid):
- if len(pid)==10:
- if checksumPid(pid[0:-2]) != pid:
- raise DrmException("invalid PID checksum")
- 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])
+ def processBook(self, pidlist):
+ crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
+ print 'Crypto Type is: ', crypto_type
+ self.crypto_type = crypto_type
if crypto_type == 0:
print "This book is not encrypted."
- else:
- if crypto_type == 1:
- raise DrmException("cannot decode Mobipocket encryption type 1")
- if crypto_type != 2:
- raise DrmException("unknown encryption type: %d" % crypto_type)
+ return self.data_file
+ if crypto_type != 2 and crypto_type != 1:
+ raise DrmException("Cannot decode unknown Mobipocket 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
- 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:
- raise DrmException("no PIDs found in this file")
- found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
+ raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
+ found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
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
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
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
- print "Decrypting. Please wait . . .",
- 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"
+ # clear the crypto type
+ self.patchSection(0, "\0" * 2, 0xC)
- 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
def getUnencryptedBook(infile,pid):
- sys.stdout=Unbuffered(sys.stdout)
- data_file = file(infile, 'rb').read()
- strippedFile = DrmStripper(data_file, pid)
- return strippedFile.getResult()
+ if not os.path.isfile(infile):
+ raise DrmException('Input File Not Found')
+ book = MobiBook(infile)
+ 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):
- sys.stdout=Unbuffered(sys.stdout)
print ('MobiDeDrm v%(__version__)s. '
'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 "Usage:"
- print " %s " % sys.argv[0]
+ print " %s []" % sys.argv[0]
return 1
else:
infile = argv[1]
outfile = argv[2]
- pid = argv[3]
+ if len(argv) is 4:
+ pidlist = argv[3].split(',')
+ else:
+ pidlist = {}
try:
- stripped_file = getUnencryptedBook(infile, pid)
+ stripped_file = getUnencryptedBookWithList(infile, pidlist)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
print "Error: %s" % e
diff --git a/Mobi_Additional_Tools/lib/mobidedrm.py b/Mobi_Additional_Tools/lib/mobidedrm.py
index ec756b9..e660a1a 100644
--- a/Mobi_Additional_Tools/lib/mobidedrm.py
+++ b/Mobi_Additional_Tools/lib/mobidedrm.py
@@ -47,8 +47,9 @@
# 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 -> '')
-__version__ = '0.27'
+__version__ = '0.28'
import sys
@@ -237,12 +238,11 @@ class MobiBook:
return title
def getPIDMetaInfo(self):
- rec209 = None
- token = None
+ rec209 = ''
+ token = ''
if 209 in self.meta_array:
rec209 = self.meta_array[209]
data = rec209
- token = ''
# 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