]> xmof Git - DeDRM.git/commitdiff
tools v3.6
authorApprentice Alf <apprenticealf@gmail.com>
Fri, 18 Feb 2011 11:37:27 +0000 (11:37 +0000)
committerApprentice Alf <apprenticealf@gmail.com>
Thu, 5 Mar 2015 17:37:44 +0000 (17:37 +0000)
17 files changed:
DeDRM_Macintosh_Application/DeDRM.app/Contents/Info.plist
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py
DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/topazextract.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/k4mobidedrm.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/kgenpids.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/mobidedrm.py
DeDRM_Windows_Application/DeDRM_WinApp/DeDRM_lib/lib/topazextract.py
DeDRM_Windows_Application/ReadMe_DeDRM_WinApp.txt
KindleBooks_Tools/KindleBooks/lib/k4mobidedrm.py
KindleBooks_Tools/KindleBooks/lib/kgenpids.py
KindleBooks_Tools/KindleBooks/lib/mobidedrm.py
KindleBooks_Tools/KindleBooks/lib/topazextract.py
KindleBooks_Tools/Kindle_4_Mac_Unswindle/lib/mobidedrm.py
KindleBooks_Tools/Kindle_4_PC_Unswindle/mobidedrm.py
Mobi_Additional_Tools/lib/mobidedrm.py

index d1feae25d6d885a404ad5bebc229f11801e4e1ce..4f3beb797205f9fd254ad5e3b687ba4ea4e3534a 100644 (file)
@@ -24,7 +24,7 @@
        <key>CFBundleExecutable</key>
        <string>droplet</string>
        <key>CFBundleGetInfoString</key>
-       <string>DeDRM 2.3, Copyright © 2010–2011 by Apprentice Alf and others.</string>
+       <string>DeDRM 2.4, Copyright © 2010–2011 by Apprentice Alf and others.</string>
        <key>CFBundleIconFile</key>
        <string>droplet</string>
        <key>CFBundleInfoDictionaryVersion</key>
@@ -34,7 +34,7 @@
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersionString</key>
-       <string>2.3</string>
+       <string>2.4</string>
        <key>CFBundleSignature</key>
        <string>dplt</string>
        <key>LSMinimumSystemVersion</key>
index 0255a3c84a02c0763d3af41b87191c35228bffb5..3a0000e97f955a212ebaac18c8de596cd6d3c86a 100644 (file)
@@ -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
index 6dcbf73b184ffa486e6b24701a9b8f7305fe401a..039daf9a4c3c24cddc38a0a223956329fbf29f6f 100644 (file)
@@ -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")
index ec756b91828fe1db33f9d95bc98ffceb12694b28..e660a1a2499aec73fcb5503d9386bfc085b9b574 100644 (file)
@@ -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
index 732bbaeb5e1c2dccf5a1667cdec1cc78af7decaf..59bc5faaec6e97e19bafef99bc15b0bae07232bb 100644 (file)
@@ -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):
index 0255a3c84a02c0763d3af41b87191c35228bffb5..3a0000e97f955a212ebaac18c8de596cd6d3c86a 100644 (file)
@@ -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
index 6dcbf73b184ffa486e6b24701a9b8f7305fe401a..039daf9a4c3c24cddc38a0a223956329fbf29f6f 100644 (file)
@@ -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")
index ec756b91828fe1db33f9d95bc98ffceb12694b28..e660a1a2499aec73fcb5503d9386bfc085b9b574 100644 (file)
@@ -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
index 732bbaeb5e1c2dccf5a1667cdec1cc78af7decaf..59bc5faaec6e97e19bafef99bc15b0bae07232bb 100644 (file)
@@ -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):
index 111d2b955a304e9dd39b92345b065b4b1f6e8cd2..b260625efe2589d4f9dfc5eb2670451ebcd3ec84 100644 (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.
index 0255a3c84a02c0763d3af41b87191c35228bffb5..3a0000e97f955a212ebaac18c8de596cd6d3c86a 100644 (file)
@@ -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
index 6dcbf73b184ffa486e6b24701a9b8f7305fe401a..039daf9a4c3c24cddc38a0a223956329fbf29f6f 100644 (file)
@@ -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")
index ec756b91828fe1db33f9d95bc98ffceb12694b28..e660a1a2499aec73fcb5503d9386bfc085b9b574 100644 (file)
@@ -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
index 732bbaeb5e1c2dccf5a1667cdec1cc78af7decaf..59bc5faaec6e97e19bafef99bc15b0bae07232bb 100644 (file)
@@ -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):
index ec756b91828fe1db33f9d95bc98ffceb12694b28..e660a1a2499aec73fcb5503d9386bfc085b9b574 100644 (file)
@@ -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
index 183432cc82b3b540d56227f4b707155cd648e95a..e660a1a2499aec73fcb5503d9386bfc085b9b574 100644 (file)
@@ -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 
 #         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
-
-    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")
+                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]
 
-        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)
-
-            # 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"
-
-    def getResult(self):
+            
+        if pid=="00000000":
+            print "File has default encryption, no specific PID."
+        else:
+            print "File is encoded with PID "+checksumPid(pid)+"."
+
+        # clear the crypto type
+        self.patchSection(0, "\0" * 2, 0xC)
+
+        # 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 <infile> <outfile> <PID>" % sys.argv[0]
+        print "    %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % 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
index ec756b91828fe1db33f9d95bc98ffceb12694b28..e660a1a2499aec73fcb5503d9386bfc085b9b574 100644 (file)
@@ -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