summaryrefslogtreecommitdiffstats
path: root/DeDRM_Windows_Application
diff options
context:
space:
mode:
authorApprentice Alf <[email protected]>2012-12-19 13:48:11 +0000
committerApprentice Alf <[email protected]>2015-03-07 13:48:25 +0000
commit9fda1943914f79671c3d2a7ecbc35cc3da66160c (patch)
treefb5f8d0b8d39991e82562552e55abe47d9a689b1 /DeDRM_Windows_Application
parentb661a6cdc540fee2ad31571692f31ee5abbea20e (diff)
tools v5.5
Plugins now include unaltered stand-alone scripts, so no longer need to keep separate copies.
Diffstat (limited to 'DeDRM_Windows_Application')
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw38
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto.py23
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py3
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/convert2xml.py3
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptpdb.py2
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/encodebase64.py45
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/epubtest.py169
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/erdr2pml.py262
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignobleepub.py401
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeygen.py157
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptepub.py377
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptkey.py93
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptpdf.py337
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mobidedrm.py334
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mutils.py85
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4pcutils.py2
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kgenpids.py90
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kindlepid.py142
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/mobidedrm.py273
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/topazextract.py312
-rw-r--r--DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/zipfix.py5
-rw-r--r--DeDRM_Windows_Application/DeDRM_ReadMe.txt14
22 files changed, 2089 insertions, 1078 deletions
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw
index a0ef90d..d0a2bea 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/DeDRM_app.pyw
@@ -1,9 +1,12 @@
#!/usr/bin/env python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
+# -*- coding: utf-8 -*-
+
+# DeDRM.pyw, version 5.5
+# By some_updates and Apprentice Alf
import sys
import os, os.path
-sys.path.append(sys.path[0]+os.sep+'lib')
+sys.path.append(os.path.join(sys.path[0],"lib"))
os.environ['PYTHONIOENCODING'] = "utf-8"
import shutil
@@ -21,7 +24,7 @@ import re
import simpleprefs
-__version__ = '5.4.1'
+__version__ = '5.5'
class DrmException(Exception):
pass
@@ -327,7 +330,7 @@ class ConvDialog(Toplevel):
self.running = 'inactive'
self.numgood = 0
self.numbad = 0
- self.log = ''
+ self.log = u""
self.status = Tkinter.Label(self, text='DeDRM processing...')
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
@@ -375,18 +378,16 @@ class ConvDialog(Toplevel):
if len(self.filenames) > 0:
filename = self.filenames.pop(0)
if filename == None:
- msg = '\nComplete: '
- msg += 'Successes: %d, ' % self.numgood
- msg += 'Failures: %d\n' % self.numbad
+ msg = u"\nComplete: Successes: {0}, Failures: {1}\n".format(self.numgood,self.numbad)
self.showCmdOutput(msg)
if self.numbad == 0:
self.after(2000,self.conversion_done())
logfile = os.path.join(rscpath,'dedrm.log')
- file(logfile,'w').write(self.log)
+ file(logfile,'w').write(self.log.encode('utf8'))
return
infile = filename
bname = os.path.basename(infile)
- msg = 'Processing: ' + bname + ' ... '
+ msg = u"Processing: {0} ... ".format(bname)
self.log += msg
self.showCmdOutput(msg)
outdir = os.path.dirname(filename)
@@ -400,7 +401,7 @@ class ConvDialog(Toplevel):
self.running = 'active'
self.processPipe()
else:
- msg = 'Unknown File: ' + bname + '\n'
+ msg = u"Unknown File: {0}\n".format(bname)
self.log += msg
self.showCmdOutput(msg)
self.numbad += 1
@@ -433,18 +434,17 @@ class ConvDialog(Toplevel):
if poll != None:
self.bar.stop()
if poll == 0:
- msg = 'Success\n'
+ msg = u"\nSuccess\n"
self.numgood += 1
- text = self.p2.read()
- text += self.p2.readerr()
+ text = self.p2.read().decode('utf8')
+ text += self.p2.readerr().decode('utf8')
self.log += text
self.log += msg
- if poll != 0:
- msg = 'Failed\n'
- text = self.p2.read()
- text += self.p2.readerr()
+ else:
+ text = self.p2.read().decode('utf8')
+ text += self.p2.readerr().decode('utf8')
msg += text
- msg += '\n'
+ msg += u"\nFailed\n"
self.numbad += 1
self.log += msg
self.showCmdOutput(msg)
@@ -491,7 +491,7 @@ def runit(apphome, ncmd, nparms):
# cmdline = pengine + ' "' + os.path.join(apphome, ncmd) + '" '
cmdline += nparms
cmdline = cmdline.encode(sys.getfilesystemencoding())
- p2 = subasyncio.Process(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
+ p2 = subasyncio.Process(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False, env = os.environ)
return p2
def processK4MOBI(apphome, infile, outdir, rscpath):
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto.py
index e25a0c8..b1b0606 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/alfcrypto.py
@@ -1,11 +1,18 @@
-#! /usr/bin/env python
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# crypto library mainly by some_updates
+
+# pbkdf2.py pbkdf2 code taken from pbkdf2.py
+# pbkdf2.py Copyright © 2004 Matt Johnston <matt @ ucc asn au>
+# pbkdf2.py Copyright © 2009 Daniel Holth <[email protected]>
+# pbkdf2.py This code may be freely used and modified for any purpose.
import sys, os
import hmac
from struct import pack
import hashlib
-
# interface to needed routines libalfcrypto
def _load_libalfcrypto():
import ctypes
@@ -26,8 +33,8 @@ def _load_libalfcrypto():
name_of_lib = 'libalfcrypto32.so'
else:
name_of_lib = 'libalfcrypto64.so'
-
- libalfcrypto = sys.path[0] + os.sep + name_of_lib
+
+ libalfcrypto = os.path.join(sys.path[0],name_of_lib)
if not os.path.isfile(libalfcrypto):
raise Exception('libalfcrypto not found')
@@ -55,7 +62,7 @@ def _load_libalfcrypto():
#
# int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
#
- #
+ #
# void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
# const unsigned long length, const AES_KEY *key,
# unsigned char *ivec, const int enc);
@@ -147,7 +154,7 @@ def _load_libalfcrypto():
topazCryptoDecrypt(ctx, data, out, len(data))
return out.raw
- print "Using Library AlfCrypto DLL/DYLIB/SO"
+ print u"Using Library AlfCrypto DLL/DYLIB/SO"
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
@@ -164,8 +171,7 @@ def _load_python_alfcrypto():
sum2 = 0;
keyXorVal = 0;
if len(key)!=16:
- print "Bad key length!"
- return None
+ raise Exception('Pukall_Cipher: Bad key length.')
wkey = []
for i in xrange(8):
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
@@ -234,6 +240,7 @@ def _load_python_alfcrypto():
cleartext = self.aes.decrypt(iv + data)
return cleartext
+ print u"Using Library AlfCrypto Python"
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py
index 9825878..9521540 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/config.py
@@ -1,3 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
from PyQt4.Qt import QWidget, QVBoxLayout, QLabel, QLineEdit
from calibre.utils.config import JSONConfig
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/convert2xml.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/convert2xml.py
index c412d7b..0f64a1b 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/convert2xml.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/convert2xml.py
@@ -230,6 +230,7 @@ class PageParser(object):
'empty' : (1, 'snippets', 1, 0),
'page' : (1, 'snippets', 1, 0),
+ 'page.class' : (1, 'scalar_text', 0, 0),
'page.pageid' : (1, 'scalar_text', 0, 0),
'page.pagelabel' : (1, 'scalar_text', 0, 0),
'page.type' : (1, 'scalar_text', 0, 0),
@@ -238,11 +239,13 @@ class PageParser(object):
'page.startID' : (1, 'scalar_number', 0, 0),
'group' : (1, 'snippets', 1, 0),
+ 'group.class' : (1, 'scalar_text', 0, 0),
'group.type' : (1, 'scalar_text', 0, 0),
'group._tag' : (1, 'scalar_text', 0, 0),
'group.orientation': (1, 'scalar_text', 0, 0),
'region' : (1, 'snippets', 1, 0),
+ 'region.class' : (1, 'scalar_text', 0, 0),
'region.type' : (1, 'scalar_text', 0, 0),
'region.x' : (1, 'scalar_number', 0, 0),
'region.y' : (1, 'scalar_number', 0, 0),
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptpdb.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptpdb.py
index 12b8c10..f0775c1 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptpdb.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/decryptpdb.py
@@ -35,7 +35,7 @@ def main(argv=sys.argv):
except ValueError:
print ' Error parsing user supplied social drm data.'
return 1
- rv = erdr2pml.decryptBook(infile, outdir, name, cc8, True)
+ rv = erdr2pml.decryptBook(infile, outdir, True, erdr2pml.getuser_key(name, cc8) )
if rv == 0:
break
return rv
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/encodebase64.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/encodebase64.py
new file mode 100644
index 0000000..6bb8c37
--- /dev/null
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/encodebase64.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# base64.py, version 1.0
+# Copyright © 2010 Apprentice Alf
+
+# Released under the terms of the GNU General Public Licence, version 3 or
+# later. <http://www.gnu.org/licenses/>
+
+# Revision history:
+# 1 - Initial release. To allow Applescript to do base64 encoding
+
+"""
+Provide base64 encoding.
+"""
+
+from __future__ import with_statement
+
+__license__ = 'GPL v3'
+
+import sys
+import os
+import base64
+
+def usage(progname):
+ print "Applies base64 encoding to the supplied file, sending to standard output"
+ print "Usage:"
+ print " %s <infile>" % progname
+
+def cli_main(argv=sys.argv):
+ progname = os.path.basename(argv[0])
+
+ if len(argv)<2:
+ usage(progname)
+ sys.exit(2)
+
+ keypath = argv[1]
+ with open(keypath, 'rb') as f:
+ keyder = f.read()
+ print keyder.encode('base64')
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(cli_main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/epubtest.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/epubtest.py
new file mode 100644
index 0000000..a44308e
--- /dev/null
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/epubtest.py
@@ -0,0 +1,169 @@
+#!/usr/bin/python
+#
+# This is a python script. You need a Python interpreter to run it.
+# For example, ActiveState Python, which exists for windows.
+#
+# Changelog drmcheck
+# 1.00 - Initial version, with code from various other scripts
+# 1.01 - Moved authorship announcement to usage section.
+#
+# Changelog drmcheck
+# 1.00 - Cut to drmtest.py, testing ePub files only by Apprentice Alf
+#
+# Written in 2011 by Paul Durrant
+# Released with unlicense. See http://unlicense.org/
+#
+#############################################################################
+#
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or
+# distribute this software, either in source code form or as a compiled
+# binary, for any purpose, commercial or non-commercial, and by any
+# means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors
+# of this software dedicate any and all copyright interest in the
+# software to the public domain. We make this dedication for the benefit
+# of the public at large and to the detriment of our heirs and
+# successors. We intend this dedication to be an overt act of
+# relinquishment in perpetuity of all present and future rights to this
+# software under copyright law.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+#############################################################################
+#
+# It's still polite to give attribution if you do reuse this code.
+#
+
+from __future__ import with_statement
+
+__version__ = '1.00'
+
+import sys, struct, os
+import zlib
+import zipfile
+import xml.etree.ElementTree as etree
+
+NSMAP = {'adept': 'http://ns.adobe.com/adept',
+ 'enc': 'http://www.w3.org/2001/04/xmlenc#'}
+
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
+ def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+def unicode_argv():
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = "utf-8"
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
+
+_FILENAME_LEN_OFFSET = 26
+_EXTRA_LEN_OFFSET = 28
+_FILENAME_OFFSET = 30
+_MAX_SIZE = 64 * 1024
+
+
+def uncompress(cmpdata):
+ dc = zlib.decompressobj(-15)
+ data = ''
+ while len(cmpdata) > 0:
+ if len(cmpdata) > _MAX_SIZE :
+ newdata = cmpdata[0:_MAX_SIZE]
+ cmpdata = cmpdata[_MAX_SIZE:]
+ else:
+ newdata = cmpdata
+ cmpdata = ''
+ newdata = dc.decompress(newdata)
+ unprocessed = dc.unconsumed_tail
+ if len(unprocessed) == 0:
+ newdata += dc.flush()
+ data += newdata
+ cmpdata += unprocessed
+ unprocessed = ''
+ return data
+
+def getfiledata(file, zi):
+ # get file name length and exta data length to find start of file data
+ local_header_offset = zi.header_offset
+
+ file.seek(local_header_offset + _FILENAME_LEN_OFFSET)
+ leninfo = file.read(2)
+ local_name_length, = struct.unpack('<H', leninfo)
+
+ file.seek(local_header_offset + _EXTRA_LEN_OFFSET)
+ exinfo = file.read(2)
+ extra_field_length, = struct.unpack('<H', exinfo)
+
+ file.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
+ data = None
+
+ # if not compressed we are good to go
+ if zi.compress_type == zipfile.ZIP_STORED:
+ data = file.read(zi.file_size)
+
+ # if compressed we must decompress it using zlib
+ if zi.compress_type == zipfile.ZIP_DEFLATED:
+ cmpdata = file.read(zi.compress_size)
+ data = uncompress(cmpdata)
+
+ return data
+
+def main(argv=unicode_argv()):
+ infile = argv[1]
+ kind = "Unknown"
+ encryption = "Unknown"
+ with file(infile,'rb') as infileobject:
+ bookdata = infileobject.read(58)
+ # Check for Mobipocket/Kindle
+ if bookdata[0:0+2] == "PK":
+ if bookdata[30:30+28] == 'mimetypeapplication/epub+zip':
+ kind = "ePub"
+ else:
+ kind = "ZIP"
+ encryption = "Unencrypted"
+ foundrights = False
+ foundencryption = False
+ inzip = zipfile.ZipFile(infile,'r')
+ namelist = set(inzip.namelist())
+ if 'META-INF/rights.xml' not in namelist or 'META-INF/encryption.xml' not in namelist:
+ encryption = "Unencrypted"
+ else:
+ rights = etree.fromstring(inzip.read('META-INF/rights.xml'))
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = './/%s' % (adept('encryptedKey'),)
+ bookkey = ''.join(rights.findtext(expr))
+ if len(bookkey) == 172:
+ encryption = "Adobe"
+ elif len(bookkey) == 64:
+ encryption = "B&N"
+ else:
+ encryption = "Unknown"
+
+ print u"{0} {1}".format(encryption, kind)
+ return 0
+
+if __name__ == "__main__":
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
+ sys.exit(main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/erdr2pml.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/erdr2pml.py
index 8f958cd..239c5ac 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/erdr2pml.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/erdr2pml.py
@@ -1,8 +1,11 @@
#!/usr/bin/env python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-#
+# -*- coding: utf-8 -*-
+
# erdr2pml.py
+# Copyright © 2008 The Dark Reverser
#
+# Modified 2008–2012 by some_updates, DiapDealer and Apprentice Alf
+
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
# Changelog
@@ -62,28 +65,78 @@
# 0.21 - Support eReader (drm) version 11.
# - Don't reject dictionary format.
# - Ignore sidebars for dictionaries (different format?)
+# 0.22 - Unicode and plugin support, different image folders for PMLZ and source
+
+__version__='0.22'
-__version__='0.21'
+import sys, re
+import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
+
+if 'calibre' in sys.modules:
+ inCalibre = True
+else:
+ inCalibre = False
-class Unbuffered:
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
-import sys
-import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
-
-if 'calibre' in sys.modules:
- inCalibre = True
-else:
- inCalibre = False
+iswindows = sys.platform.startswith('win')
+isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'.
+
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(start, argc.value)]
+ # if we don't have any arguments at all, just pass back script name
+ # this should never happen
+ return [u"mobidedrm.py"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = "utf-8"
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
Des = None
-if sys.platform.startswith('win'):
+if iswindows:
# first try with pycrypto
if inCalibre:
from calibre_plugins.erdrpdb2pml import pycrypto_des
@@ -168,17 +221,30 @@ class Sectionizer(object):
off = self.sections[section][0]
return self.contents[off:end_off]
-def sanitizeFileName(s):
- r = ''
- for c in s:
- if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-":
- r += c
- return r
+# cleanup unicode filenames
+# borrowed from calibre from calibre/src/calibre/__init__.py
+# added in removal of control (<32) chars
+# and removal of . at start and end
+# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
+def sanitizeFileName(name):
+ # substitute filename unfriendly characters
+ name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
+ # delete control characters
+ name = u"".join(char for char in name if ord(char)>=32)
+ # white space to single space, delete leading and trailing while space
+ name = re.sub(ur"\s", u" ", name).strip()
+ # remove leading dots
+ while len(name)>0 and name[0] == u".":
+ name = name[1:]
+ # remove trailing dots (Windows doesn't like them)
+ if name.endswith(u'.'):
+ name = name[:-1]
+ return name
def fixKey(key):
def fixByte(b):
return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
- return "".join([chr(fixByte(ord(a))) for a in key])
+ return "".join([chr(fixByte(ord(a))) for a in key])
def deXOR(text, sp, table):
r=''
@@ -191,7 +257,7 @@ def deXOR(text, sp, table):
return r
class EreaderProcessor(object):
- def __init__(self, sect, username, creditcard):
+ def __init__(self, sect, user_key):
self.section_reader = sect.loadSection
data = self.section_reader(0)
version, = struct.unpack('>H', data[0:2])
@@ -212,18 +278,10 @@ class EreaderProcessor(object):
for i in xrange(len(data)):
j = (j + shuf) % len(data)
r[j] = data[i]
- assert len("".join(r)) == len(data)
+ assert len("".join(r)) == len(data)
return "".join(r)
r = unshuff(input[0:-8], cookie_shuf)
- def fixUsername(s):
- r = ''
- for c in s.lower():
- if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'):
- r += c
- return r
-
- user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff)
drm_sub_version = struct.unpack('>H', r[0:2])[0]
self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1
self.num_image_pages = struct.unpack('>H', r[26:26+2])[0]
@@ -302,7 +360,7 @@ class EreaderProcessor(object):
sect = self.section_reader(self.first_image_page + i)
name = sect[4:4+32].strip('\0')
data = sect[62:]
- return sanitizeFileName(name), data
+ return sanitizeFileName(unicode(name,'windows-1252')), data
# def getChapterNamePMLOffsetData(self):
@@ -399,60 +457,53 @@ class EreaderProcessor(object):
return r
def cleanPML(pml):
- # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
+ # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
pml2 = pml
for k in xrange(128,256):
badChar = chr(k)
pml2 = pml2.replace(badChar, '\\a%03d' % k)
return pml2
-def convertEreaderToPml(infile, name, cc, outdir):
- if not os.path.exists(outdir):
- os.makedirs(outdir)
+def decryptBook(infile, outpath, make_pmlz, user_key):
bookname = os.path.splitext(os.path.basename(infile))[0]
- print " Decoding File"
- sect = Sectionizer(infile, 'PNRdPPrs')
- er = EreaderProcessor(sect, name, cc)
-
- if er.getNumImages() > 0:
- print " Extracting images"
- imagedir = bookname + '_img/'
- imagedirpath = os.path.join(outdir,imagedir)
- if not os.path.exists(imagedirpath):
- os.makedirs(imagedirpath)
- for i in xrange(er.getNumImages()):
- name, contents = er.getImage(i)
- file(os.path.join(imagedirpath, name), 'wb').write(contents)
-
- print " Extracting pml"
- pml_string = er.getText()
- pmlfilename = bookname + ".pml"
- file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
-
- # bkinfo = er.getBookInfo()
- # if bkinfo != '':
- # print " Extracting book meta information"
- # file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
-
-
-
-def decryptBook(infile, outdir, name, cc, make_pmlz):
- if make_pmlz :
- # ignore specified outdir, use tempdir instead
+ if make_pmlz:
+ # outpath is actually pmlz name
+ pmlzname = outpath
outdir = tempfile.mkdtemp()
+ imagedirpath = os.path.join(outdir,u"images")
+ else:
+ pmlzname = None
+ outdir = outpath
+ imagedirpath = os.path.join(outdir,bookname + u"_img")
+
try:
- print "Processing..."
- convertEreaderToPml(infile, name, cc, outdir)
- if make_pmlz :
+ if not os.path.exists(outdir):
+ os.makedirs(outdir)
+ print u"Decoding File"
+ sect = Sectionizer(infile, 'PNRdPPrs')
+ er = EreaderProcessor(sect, user_key)
+
+ if er.getNumImages() > 0:
+ print u"Extracting images"
+ if not os.path.exists(imagedirpath):
+ os.makedirs(imagedirpath)
+ for i in xrange(er.getNumImages()):
+ name, contents = er.getImage(i)
+ file(os.path.join(imagedirpath, name), 'wb').write(contents)
+
+ print u"Extracting pml"
+ pml_string = er.getText()
+ pmlfilename = bookname + ".pml"
+ file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
+ if pmlzname is not None:
import zipfile
import shutil
- print " Creating PMLZ file"
- zipname = infile[:-4] + '.pmlz'
- myZipFile = zipfile.ZipFile(zipname,'w',zipfile.ZIP_STORED, False)
+ print u"Creating PMLZ file {0}".format(os.path.basename(pmlzname))
+ myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False)
list = os.listdir(outdir)
- for file in list:
- localname = file
- filePath = os.path.join(outdir,file)
+ for filename in list:
+ localname = filename
+ filePath = os.path.join(outdir,filename)
if os.path.isfile(filePath):
myZipFile.write(filePath, localname)
elif os.path.isdir(filePath):
@@ -466,36 +517,46 @@ def decryptBook(infile, outdir, name, cc, make_pmlz):
myZipFile.close()
# remove temporary directory
shutil.rmtree(outdir, True)
- print 'output is %s' % zipname
+ print u"Output is {0}".format(pmlzname)
else :
- print 'output in %s' % outdir
+ print u"Output is in {0}".format(outdir)
print "done"
except ValueError, e:
- print "Error: %s" % e
+ print u"Error: {0}".format(e.args[0])
return 1
return 0
def usage():
- print "Converts DRMed eReader books to PML Source"
- print "Usage:"
- print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number "
- print " "
- print "Options: "
- print " -h prints this message"
- print " --make-pmlz create PMLZ instead of using output directory"
- print " "
- print "Note:"
- print " if ommitted, outdir defaults based on 'infile.pdb'"
- print " It's enough to enter the last 8 digits of the credit card number"
+ print u"Converts DRMed eReader books to PML Source"
+ print u"Usage:"
+ print u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number"
+ print u" "
+ print u"Options: "
+ print u" -h prints this message"
+ print u" -p create PMLZ instead of source folder"
+ print u" --make-pmlz create PMLZ instead of source folder"
+ print u" "
+ print u"Note:"
+ print u" if outpath is ommitted, creates source in 'infile_Source' folder"
+ print u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'"
+ print u" if source folder created, images are in infile_img folder"
+ print u" if pmlz file created, images are in images folder"
+ print u" It's enough to enter the last 8 digits of the credit card number"
return
+def getuser_key(name,cc):
+ newname = "".join(c for c in name.lower() if c >= 'a' and c <= 'z' or c >= '0' and c <= '9')
+ cc = cc.replace(" ","")
+ return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff)
+
+def cli_main(argv=unicode_argv()):
+ print u"eRdr2Pml v{0}. Copyright © 2009–2012 The Dark Reverser et al.".format(__version__)
-def main(argv=None):
try:
- opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"])
+ opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"])
except getopt.GetoptError, err:
- print str(err)
+ print err.args[0]
usage()
return 1
make_pmlz = False
@@ -503,24 +564,31 @@ def main(argv=None):
if o == "-h":
usage()
return 0
+ elif o == "-p":
+ make_pmlz = True
elif o == "--make-pmlz":
make_pmlz = True
- print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
-
if len(args)!=3 and len(args)!=4:
usage()
return 1
if len(args)==3:
- infile, name, cc = args[0], args[1], args[2]
- outdir = infile[:-4] + '_Source'
+ infile, name, cc = args
+ if make_pmlz:
+ outpath = os.path.splitext(infile)[0] + u".pmlz"
+ else:
+ outpath = os.path.splitext(infile)[0] + u"_Source"
elif len(args)==4:
- infile, outdir, name, cc = args[0], args[1], args[2], args[3]
+ infile, outpath, name, cc = args
+
+ print getuser_key(name,cc).encode('hex')
- return decryptBook(infile, outdir, name, cc, make_pmlz)
+ return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc))
if __name__ == "__main__":
- sys.stdout=Unbuffered(sys.stdout)
- sys.exit(main())
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
+ sys.exit(cli_main())
+
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignobleepub.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignobleepub.py
index 03aa91f..2e0bd06 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignobleepub.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignobleepub.py
@@ -1,13 +1,25 @@
-#! /usr/bin/python
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
from __future__ import with_statement
-# ignobleepub.pyw, version 3.5
+# ignobleepub.pyw, version 3.6
+# Copyright © 2009-2010 by i♥cabbages
-# To run this program install Python 2.6 from <http://www.python.org/download/>
-# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
-# (make sure to install the version for Python 2.6). Save this script file as
-# ignobleepub.pyw and double-click on it to run it.
+# Released under the terms of the GNU General Public Licence, version 3
+# <http://www.gnu.org/licenses/>
+
+# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
+
+# Windows users: Before running this program, you must first install Python 2.6
+# from <http://www.python.org/download/> and PyCrypto from
+# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
+# install the version for Python 2.6). Save this script file as
+# ineptepub.pyw and double-click on it to run it.
+#
+# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
+# program from the command line (pythonw ineptepub.pyw) or by double-clicking
+# it when it has been associated with PythonLauncher.
# Revision history:
# 1 - Initial release
@@ -18,21 +30,83 @@ from __future__ import with_statement
# 3.3 - On Windows try PyCrypto first and OpenSSL next
# 3.4 - Modify interace to allow use with import
# 3.5 - Fix for potential problem with PyCrypto
+# 3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code
+"""
+Decrypt Barnes & Noble encrypted ePub books.
+"""
__license__ = 'GPL v3'
+__version__ = "3.6"
import sys
import os
+import traceback
import zlib
import zipfile
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
from contextlib import closing
import xml.etree.ElementTree as etree
-import Tkinter
-import Tkconstants
-import tkFileDialog
-import tkMessageBox
+
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
+ def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+try:
+ from calibre.constants import iswindows, isosx
+except:
+ iswindows = sys.platform.startswith('win')
+ isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'.
+
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(start, argc.value)]
+ return [u"ineptepub.py"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = "utf-8"
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
+
class IGNOBLEError(Exception):
pass
@@ -42,10 +116,11 @@ def _load_crypto_libcrypto():
Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
- if sys.platform.startswith('win'):
+ if iswindows:
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
+
if libcrypto is None:
raise IGNOBLEError('libcrypto not found')
libcrypto = CDLL(libcrypto)
@@ -66,9 +141,6 @@ def _load_crypto_libcrypto():
func.argtypes = argtypes
return func
- AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
- [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
- c_int])
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
[c_char_p, c_int, AES_KEY_p])
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
@@ -123,13 +195,6 @@ def _load_crypto():
AES = _load_crypto()
-
-
-"""
-Decrypt Barnes & Noble ADEPT encrypted EPUB books.
-"""
-
-
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
NSMAP = {'adept': 'http://ns.adobe.com/adept',
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
@@ -144,7 +209,6 @@ class ZipInfo(zipfile.ZipInfo):
class Decryptor(object):
def __init__(self, bookkey, encryption):
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
- # self._aes = AES.new(bookkey, AES.MODE_CBC, '\x00'*16)
self._aes = AES(bookkey)
encryption = etree.fromstring(encryption)
self._encrypted = encrypted = set()
@@ -152,8 +216,8 @@ class Decryptor(object):
enc('CipherReference'))
for elem in encryption.findall(expr):
path = elem.get('URI', None)
- path = path.encode('utf-8')
if path is not None:
+ path = path.encode('utf-8')
encrypted.add(path)
def decompress(self, bytes):
@@ -171,167 +235,186 @@ class Decryptor(object):
data = self.decompress(data)
return data
-
-class DecryptionDialog(Tkinter.Frame):
- def __init__(self, root):
- Tkinter.Frame.__init__(self, root, border=5)
- self.status = Tkinter.Label(self, text='Select files for decryption')
- self.status.pack(fill=Tkconstants.X, expand=1)
- body = Tkinter.Frame(self)
- body.pack(fill=Tkconstants.X, expand=1)
- sticky = Tkconstants.E + Tkconstants.W
- body.grid_columnconfigure(1, weight=2)
- Tkinter.Label(body, text='Key file').grid(row=0)
- self.keypath = Tkinter.Entry(body, width=30)
- self.keypath.grid(row=0, column=1, sticky=sticky)
- if os.path.exists('bnepubkey.b64'):
- self.keypath.insert(0, 'bnepubkey.b64')
- button = Tkinter.Button(body, text="...", command=self.get_keypath)
- button.grid(row=0, column=2)
- Tkinter.Label(body, text='Input file').grid(row=1)
- self.inpath = Tkinter.Entry(body, width=30)
- self.inpath.grid(row=1, column=1, sticky=sticky)
- button = Tkinter.Button(body, text="...", command=self.get_inpath)
- button.grid(row=1, column=2)
- Tkinter.Label(body, text='Output file').grid(row=2)
- self.outpath = Tkinter.Entry(body, width=30)
- self.outpath.grid(row=2, column=1, sticky=sticky)
- button = Tkinter.Button(body, text="...", command=self.get_outpath)
- button.grid(row=2, column=2)
- buttons = Tkinter.Frame(self)
- buttons.pack()
- botton = Tkinter.Button(
- buttons, text="Decrypt", width=10, command=self.decrypt)
- botton.pack(side=Tkconstants.LEFT)
- Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
- button = Tkinter.Button(
- buttons, text="Quit", width=10, command=self.quit)
- button.pack(side=Tkconstants.RIGHT)
-
- def get_keypath(self):
- keypath = tkFileDialog.askopenfilename(
- parent=None, title='Select B&N EPUB key file',
- defaultextension='.b64',
- filetypes=[('base64-encoded files', '.b64'),
- ('All Files', '.*')])
- if keypath:
- keypath = os.path.normpath(keypath)
- self.keypath.delete(0, Tkconstants.END)
- self.keypath.insert(0, keypath)
- return
-
- def get_inpath(self):
- inpath = tkFileDialog.askopenfilename(
- parent=None, title='Select B&N-encrypted EPUB file to decrypt',
- defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
- ('All files', '.*')])
- if inpath:
- inpath = os.path.normpath(inpath)
- self.inpath.delete(0, Tkconstants.END)
- self.inpath.insert(0, inpath)
- return
-
- def get_outpath(self):
- outpath = tkFileDialog.asksaveasfilename(
- parent=None, title='Select unencrypted EPUB file to produce',
- defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
- ('All files', '.*')])
- if outpath:
- outpath = os.path.normpath(outpath)
- self.outpath.delete(0, Tkconstants.END)
- self.outpath.insert(0, outpath)
- return
-
- def decrypt(self):
- keypath = self.keypath.get()
- inpath = self.inpath.get()
- outpath = self.outpath.get()
- if not keypath or not os.path.exists(keypath):
- self.status['text'] = 'Specified key file does not exist'
- return
- if not inpath or not os.path.exists(inpath):
- self.status['text'] = 'Specified input file does not exist'
- return
- if not outpath:
- self.status['text'] = 'Output file not specified'
- return
- if inpath == outpath:
- self.status['text'] = 'Must have different input and output files'
- return
- argv = [sys.argv[0], keypath, inpath, outpath]
- self.status['text'] = 'Decrypting...'
+# check file to make check whether it's probably an Adobe Adept encrypted ePub
+def ignobleBook(inpath):
+ with closing(ZipFile(open(inpath, 'rb'))) as inf:
+ namelist = set(inf.namelist())
+ if 'META-INF/rights.xml' not in namelist or \
+ 'META-INF/encryption.xml' not in namelist:
+ return False
try:
- cli_main(argv)
- except Exception, e:
- self.status['text'] = 'Error: ' + str(e)
- return
- self.status['text'] = 'File successfully decrypted'
-
-
-def decryptBook(keypath, inpath, outpath):
- with open(keypath, 'rb') as f:
- keyb64 = f.read()
+ rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = './/%s' % (adept('encryptedKey'),)
+ bookkey = ''.join(rights.findtext(expr))
+ if len(bookkey) == 64:
+ return True
+ except:
+ # if we couldn't check, assume it is
+ return True
+ return False
+
+# return error code and error message duple
+def decryptBook(keyb64, inpath, outpath):
+ if AES is None:
+ # 1 means don't try again
+ return (1, u"PyCrypto or OpenSSL must be installed.")
key = keyb64.decode('base64')[:16]
- # aes = AES.new(key, AES.MODE_CBC, '\x00'*16)
aes = AES(key)
-
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
- raise IGNOBLEError('%s: not an B&N ADEPT EPUB' % (inpath,))
+ return (1, u"Not a secure Barnes & Noble ePub.")
for name in META_NAMES:
namelist.remove(name)
- rights = etree.fromstring(inf.read('META-INF/rights.xml'))
- adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
- expr = './/%s' % (adept('encryptedKey'),)
- bookkey = ''.join(rights.findtext(expr))
- bookkey = aes.decrypt(bookkey.decode('base64'))
- bookkey = bookkey[:-ord(bookkey[-1])]
- encryption = inf.read('META-INF/encryption.xml')
- decryptor = Decryptor(bookkey[-16:], encryption)
- kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
- with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
- zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
- outf.writestr(zi, inf.read('mimetype'))
- for path in namelist:
- data = inf.read(path)
- outf.writestr(path, decryptor.decrypt(path, data))
- return 0
+ try:
+ rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = './/%s' % (adept('encryptedKey'),)
+ bookkey = ''.join(rights.findtext(expr))
+ if len(bookkey) != 64:
+ return (1, u"Not a secure Barnes & Noble ePub.")
+ bookkey = aes.decrypt(bookkey.decode('base64'))
+ bookkey = bookkey[:-ord(bookkey[-1])]
+ encryption = inf.read('META-INF/encryption.xml')
+ decryptor = Decryptor(bookkey[-16:], encryption)
+ kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
+ with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
+ zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
+ outf.writestr(zi, inf.read('mimetype'))
+ for path in namelist:
+ data = inf.read(path)
+ outf.writestr(path, decryptor.decrypt(path, data))
+ except Exception, e:
+ return (2, u"{0}.".format(e.args[0]))
+ return (0, u"Success")
-def cli_main(argv=sys.argv):
+def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
- if AES is None:
- print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
- "separately. Read the top-of-script comment for details." % \
- (progname,)
- return 1
if len(argv) != 4:
- print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
+ print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname)
return 1
keypath, inpath, outpath = argv[1:]
- return decryptBook(keypath, inpath, outpath)
-
+ userkey = open(keypath,'rb').read()
+ result = decryptBook(userkey, inpath, outpath)
+ print result[1]
+ return result[0]
def gui_main():
+ import Tkinter
+ import Tkconstants
+ import tkFileDialog
+ import traceback
+
+ class DecryptionDialog(Tkinter.Frame):
+ def __init__(self, root):
+ Tkinter.Frame.__init__(self, root, border=5)
+ self.status = Tkinter.Label(self, text=u"Select files for decryption")
+ self.status.pack(fill=Tkconstants.X, expand=1)
+ body = Tkinter.Frame(self)
+ body.pack(fill=Tkconstants.X, expand=1)
+ sticky = Tkconstants.E + Tkconstants.W
+ body.grid_columnconfigure(1, weight=2)
+ Tkinter.Label(body, text=u"Key file").grid(row=0)
+ self.keypath = Tkinter.Entry(body, width=30)
+ self.keypath.grid(row=0, column=1, sticky=sticky)
+ if os.path.exists(u"bnepubkey.b64"):
+ self.keypath.insert(0, u"bnepubkey.b64")
+ button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
+ button.grid(row=0, column=2)
+ Tkinter.Label(body, text=u"Input file").grid(row=1)
+ self.inpath = Tkinter.Entry(body, width=30)
+ self.inpath.grid(row=1, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
+ button.grid(row=1, column=2)
+ Tkinter.Label(body, text=u"Output file").grid(row=2)
+ self.outpath = Tkinter.Entry(body, width=30)
+ self.outpath.grid(row=2, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
+ button.grid(row=2, column=2)
+ buttons = Tkinter.Frame(self)
+ buttons.pack()
+ botton = Tkinter.Button(
+ buttons, text=u"Decrypt", width=10, command=self.decrypt)
+ botton.pack(side=Tkconstants.LEFT)
+ Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
+ button = Tkinter.Button(
+ buttons, text=u"Quit", width=10, command=self.quit)
+ button.pack(side=Tkconstants.RIGHT)
+
+ def get_keypath(self):
+ keypath = tkFileDialog.askopenfilename(
+ parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
+ defaultextension=u".b64",
+ filetypes=[('base64-encoded files', '.b64'),
+ ('All Files', '.*')])
+ if keypath:
+ keypath = os.path.normpath(keypath)
+ self.keypath.delete(0, Tkconstants.END)
+ self.keypath.insert(0, keypath)
+ return
+
+ def get_inpath(self):
+ inpath = tkFileDialog.askopenfilename(
+ parent=None, title=u"Select B&N-encrypted ePub file to decrypt",
+ defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
+ if inpath:
+ inpath = os.path.normpath(inpath)
+ self.inpath.delete(0, Tkconstants.END)
+ self.inpath.insert(0, inpath)
+ return
+
+ def get_outpath(self):
+ outpath = tkFileDialog.asksaveasfilename(
+ parent=None, title=u"Select unencrypted ePub file to produce",
+ defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
+ if outpath:
+ outpath = os.path.normpath(outpath)
+ self.outpath.delete(0, Tkconstants.END)
+ self.outpath.insert(0, outpath)
+ return
+
+ def decrypt(self):
+ keypath = self.keypath.get()
+ inpath = self.inpath.get()
+ outpath = self.outpath.get()
+ if not keypath or not os.path.exists(keypath):
+ self.status['text'] = u"Specified key file does not exist"
+ return
+ if not inpath or not os.path.exists(inpath):
+ self.status['text'] = u"Specified input file does not exist"
+ return
+ if not outpath:
+ self.status['text'] = u"Output file not specified"
+ return
+ if inpath == outpath:
+ self.status['text'] = u"Must have different input and output files"
+ return
+ userkey = open(keypath,'rb').read()
+ self.status['text'] = u"Decrypting..."
+ try:
+ decrypt_status = decryptBook(userkey, inpath, outpath)
+ except Exception, e:
+ self.status['text'] = u"Error: {0}".format(e.args[0])
+ return
+ if decrypt_status[0] == 0:
+ self.status['text'] = u"File successfully decrypted"
+ else:
+ self.status['text'] = decrypt_status[1]
+
root = Tkinter.Tk()
- if AES is None:
- root.withdraw()
- tkMessageBox.showerror(
- "Ignoble EPUB Decrypter",
- "This script requires OpenSSL or PyCrypto, which must be installed "
- "separately. Read the top-of-script comment for details.")
- return 1
- root.title('Ignoble EPUB Decrypter')
+ root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
root.mainloop()
return 0
-
if __name__ == '__main__':
if len(sys.argv) > 1:
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeygen.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeygen.py
index e2c50e2..f25359c 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeygen.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ignoblekeygen.py
@@ -1,13 +1,25 @@
-#! /usr/bin/python
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
from __future__ import with_statement
-# ignoblekeygen.pyw, version 2.4
+# ignoblekeygen.pyw, version 2.5
+# Copyright © 2009-2010 by i♥cabbages
-# To run this program install Python 2.6 from <http://www.python.org/download/>
-# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
-# (make sure to install the version for Python 2.6). Save this script file as
-# ignoblekeygen.pyw and double-click on it to run it.
+# Released under the terms of the GNU General Public Licence, version 3
+# <http://www.gnu.org/licenses/>
+
+# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
+
+# Windows users: Before running this program, you must first install Python 2.6
+# from <http://www.python.org/download/> and PyCrypto from
+# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
+# install the version for Python 2.6). Save this script file as
+# ignoblekeygen.pyw and double-click on it to run it.
+#
+# Mac OS X users: Save this script file as ignoblekeygen.pyw. You can run this
+# program from the command line (pythonw ignoblekeygen.pyw) or by double-clicking
+# it when it has been associated with PythonLauncher.
# Revision history:
# 1 - Initial release
@@ -16,36 +28,92 @@ from __future__ import with_statement
# 2.2 - On Windows try PyCrypto first and then OpenSSL next
# 2.3 - Modify interface to allow use of import
# 2.4 - Improvements to UI and now works in plugins
+# 2.5 - Additional improvement for unicode and plugin support
"""
Generate Barnes & Noble EPUB user key from name and credit card number.
"""
__license__ = 'GPL v3'
+__version__ = "2.5"
import sys
import os
import hashlib
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
+ def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+iswindows = sys.platform.startswith('win')
+isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'.
+
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(start, argc.value)]
+ # if we don't have any arguments at all, just pass back script name
+ # this should never happen
+ return [u"ignoblekeygen.py"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = "utf-8"
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-# use openssl's libcrypt if it exists in place of pycrypto
-# code extracted from the Adobe Adept DRM removal code also by I HeartCabbages
class IGNOBLEError(Exception):
pass
-
def _load_crypto_libcrypto():
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
- if sys.platform.startswith('win'):
+ if iswindows:
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
+
if libcrypto is None:
- print 'libcrypto not found'
raise IGNOBLEError('libcrypto not found')
libcrypto = CDLL(libcrypto)
@@ -70,6 +138,7 @@ def _load_crypto_libcrypto():
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
c_int])
+
class AES(object):
def __init__(self, userkey, iv):
self._blocksize = len(userkey)
@@ -88,7 +157,6 @@ def _load_crypto_libcrypto():
return AES
-
def _load_crypto_pycrypto():
from Crypto.Cipher import AES as _AES
@@ -120,25 +188,28 @@ def normalize_name(name):
return ''.join(x for x in name.lower() if x != ' ')
-def generate_keyfile(name, ccn, outpath):
+def generate_key(name, ccn):
# remove spaces and case from name and CC numbers.
+ if type(name)==unicode:
+ name = name.encode('utf-8')
+ if type(ccn)==unicode:
+ ccn = ccn.encode('utf-8')
+
name = normalize_name(name) + '\x00'
ccn = normalize_name(ccn) + '\x00'
-
+
name_sha = hashlib.sha1(name).digest()[:16]
ccn_sha = hashlib.sha1(ccn).digest()[:16]
both_sha = hashlib.sha1(name + ccn).digest()
aes = AES(ccn_sha, name_sha)
crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
userkey = hashlib.sha1(crypt).digest()
- with open(outpath, 'wb') as f:
- f.write(userkey.encode('base64'))
- return userkey
+ return userkey.encode('base64')
-def cli_main(argv=sys.argv):
+def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
if AES is None:
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
@@ -146,10 +217,11 @@ def cli_main(argv=sys.argv):
(progname,)
return 1
if len(argv) != 4:
- print "usage: %s NAME CC# OUTFILE" % (progname,)
+ print u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname)
return 1
- name, ccn, outpath = argv[1:]
- generate_keyfile(name, ccn, outpath)
+ name, ccn, keypath = argv[1:]
+ userkey = generate_key(name, ccn)
+ open(keypath,'wb').write(userkey)
return 0
@@ -162,38 +234,38 @@ def gui_main():
class DecryptionDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
- self.status = Tkinter.Label(self, text='Enter parameters')
+ self.status = Tkinter.Label(self, text=u"Enter parameters")
self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2)
- Tkinter.Label(body, text='Account Name').grid(row=0)
+ Tkinter.Label(body, text=u"Account Name").grid(row=0)
self.name = Tkinter.Entry(body, width=40)
self.name.grid(row=0, column=1, sticky=sticky)
- Tkinter.Label(body, text='CC#').grid(row=1)
+ Tkinter.Label(body, text=u"CC#").grid(row=1)
self.ccn = Tkinter.Entry(body, width=40)
self.ccn.grid(row=1, column=1, sticky=sticky)
- Tkinter.Label(body, text='Output file').grid(row=2)
+ Tkinter.Label(body, text=u"Output file").grid(row=2)
self.keypath = Tkinter.Entry(body, width=40)
self.keypath.grid(row=2, column=1, sticky=sticky)
- self.keypath.insert(2, 'bnepubkey.b64')
- button = Tkinter.Button(body, text="...", command=self.get_keypath)
+ self.keypath.insert(2, u"bnepubkey.b64")
+ button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
button.grid(row=2, column=2)
buttons = Tkinter.Frame(self)
buttons.pack()
botton = Tkinter.Button(
- buttons, text="Generate", width=10, command=self.generate)
+ buttons, text=u"Generate", width=10, command=self.generate)
botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button(
- buttons, text="Quit", width=10, command=self.quit)
+ buttons, text=u"Quit", width=10, command=self.quit)
button.pack(side=Tkconstants.RIGHT)
-
+
def get_keypath(self):
keypath = tkFileDialog.asksaveasfilename(
- parent=None, title='Select B&N EPUB key file to produce',
- defaultextension='.b64',
+ parent=None, title=u"Select B&N ePub key file to produce",
+ defaultextension=u".b64",
filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')])
if keypath:
@@ -201,27 +273,28 @@ def gui_main():
self.keypath.delete(0, Tkconstants.END)
self.keypath.insert(0, keypath)
return
-
+
def generate(self):
name = self.name.get()
ccn = self.ccn.get()
keypath = self.keypath.get()
if not name:
- self.status['text'] = 'Name not specified'
+ self.status['text'] = u"Name not specified"
return
if not ccn:
- self.status['text'] = 'Credit card number not specified'
+ self.status['text'] = u"Credit card number not specified"
return
if not keypath:
- self.status['text'] = 'Output keyfile path not specified'
+ self.status['text'] = u"Output keyfile path not specified"
return
- self.status['text'] = 'Generating...'
+ self.status['text'] = u"Generating..."
try:
- generate_keyfile(name, ccn, keypath)
+ userkey = generate_key(name, ccn)
except Exception, e:
- self.status['text'] = 'Error: ' + str(e)
+ self.status['text'] = u"Error: (0}".format(e.args[0])
return
- self.status['text'] = 'Keyfile successfully generated'
+ open(keypath,'wb').write(userkey)
+ self.status['text'] = u"Keyfile successfully generated"
root = Tkinter.Tk()
if AES is None:
@@ -231,7 +304,7 @@ def gui_main():
"This script requires OpenSSL or PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.")
return 1
- root.title('Ignoble EPUB Keyfile Generator')
+ root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
@@ -240,5 +313,7 @@ def gui_main():
if __name__ == '__main__':
if len(sys.argv) > 1:
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptepub.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptepub.py
index 2bb32b1..4b5a296 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptepub.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptepub.py
@@ -3,11 +3,13 @@
from __future__ import with_statement
-# ineptepub.pyw, version 5.6
-# Copyright © 2009-2010 i♥cabbages
+# ineptepub.pyw, version 5.8
+# Copyright © 2009-2010 by i♥cabbages
-# Released under the terms of the GNU General Public Licence, version 3 or
-# later. <http://www.gnu.org/licenses/>
+# Released under the terms of the GNU General Public Licence, version 3
+# <http://www.gnu.org/licenses/>
+
+# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
@@ -31,24 +33,83 @@ from __future__ import with_statement
# 5.5 - On Windows try PyCrypto first, OpenSSL next
# 5.6 - Modify interface to allow use with import
# 5.7 - Fix for potential problem with PyCrypto
+# 5.8 - Revised to allow use in calibre plugins to eliminate need for duplicate code
"""
-Decrypt Adobe ADEPT-encrypted EPUB books.
+Decrypt Adobe Digital Editions encrypted ePub books.
"""
__license__ = 'GPL v3'
+__version__ = "5.8"
import sys
import os
+import traceback
import zlib
import zipfile
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
from contextlib import closing
import xml.etree.ElementTree as etree
-import Tkinter
-import Tkconstants
-import tkFileDialog
-import tkMessageBox
+
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
+ def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+try:
+ from calibre.constants import iswindows, isosx
+except:
+ iswindows = sys.platform.startswith('win')
+ isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'.
+
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(start, argc.value)]
+ return [u"ineptepub.py"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = "utf-8"
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
+
class ADEPTError(Exception):
pass
@@ -58,7 +119,7 @@ def _load_crypto_libcrypto():
Structure, c_ulong, create_string_buffer, cast
from ctypes.util import find_library
- if sys.platform.startswith('win'):
+ if iswindows:
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
@@ -272,6 +333,7 @@ def _load_crypto():
except (ImportError, ADEPTError):
pass
return (AES, RSA)
+
AES, RSA = _load_crypto()
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
@@ -314,158 +376,181 @@ class Decryptor(object):
data = self.decompress(data)
return data
-
-class DecryptionDialog(Tkinter.Frame):
- def __init__(self, root):
- Tkinter.Frame.__init__(self, root, border=5)
- self.status = Tkinter.Label(self, text='Select files for decryption')
- self.status.pack(fill=Tkconstants.X, expand=1)
- body = Tkinter.Frame(self)
- body.pack(fill=Tkconstants.X, expand=1)
- sticky = Tkconstants.E + Tkconstants.W
- body.grid_columnconfigure(1, weight=2)
- Tkinter.Label(body, text='Key file').grid(row=0)
- self.keypath = Tkinter.Entry(body, width=30)
- self.keypath.grid(row=0, column=1, sticky=sticky)
- if os.path.exists('adeptkey.der'):
- self.keypath.insert(0, 'adeptkey.der')
- button = Tkinter.Button(body, text="...", command=self.get_keypath)
- button.grid(row=0, column=2)
- Tkinter.Label(body, text='Input file').grid(row=1)
- self.inpath = Tkinter.Entry(body, width=30)
- self.inpath.grid(row=1, column=1, sticky=sticky)
- button = Tkinter.Button(body, text="...", command=self.get_inpath)
- button.grid(row=1, column=2)
- Tkinter.Label(body, text='Output file').grid(row=2)
- self.outpath = Tkinter.Entry(body, width=30)
- self.outpath.grid(row=2, column=1, sticky=sticky)
- button = Tkinter.Button(body, text="...", command=self.get_outpath)
- button.grid(row=2, column=2)
- buttons = Tkinter.Frame(self)
- buttons.pack()
- botton = Tkinter.Button(
- buttons, text="Decrypt", width=10, command=self.decrypt)
- botton.pack(side=Tkconstants.LEFT)
- Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
- button = Tkinter.Button(
- buttons, text="Quit", width=10, command=self.quit)
- button.pack(side=Tkconstants.RIGHT)
-
- def get_keypath(self):
- keypath = tkFileDialog.askopenfilename(
- parent=None, title='Select ADEPT key file',
- defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
- ('All Files', '.*')])
- if keypath:
- keypath = os.path.normpath(keypath)
- self.keypath.delete(0, Tkconstants.END)
- self.keypath.insert(0, keypath)
- return
-
- def get_inpath(self):
- inpath = tkFileDialog.askopenfilename(
- parent=None, title='Select ADEPT-encrypted EPUB file to decrypt',
- defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
- ('All files', '.*')])
- if inpath:
- inpath = os.path.normpath(inpath)
- self.inpath.delete(0, Tkconstants.END)
- self.inpath.insert(0, inpath)
- return
-
- def get_outpath(self):
- outpath = tkFileDialog.asksaveasfilename(
- parent=None, title='Select unencrypted EPUB file to produce',
- defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
- ('All files', '.*')])
- if outpath:
- outpath = os.path.normpath(outpath)
- self.outpath.delete(0, Tkconstants.END)
- self.outpath.insert(0, outpath)
- return
-
- def decrypt(self):
- keypath = self.keypath.get()
- inpath = self.inpath.get()
- outpath = self.outpath.get()
- if not keypath or not os.path.exists(keypath):
- self.status['text'] = 'Specified key file does not exist'
- return
- if not inpath or not os.path.exists(inpath):
- self.status['text'] = 'Specified input file does not exist'
- return
- if not outpath:
- self.status['text'] = 'Output file not specified'
- return
- if inpath == outpath:
- self.status['text'] = 'Must have different input and output files'
- return
- argv = [sys.argv[0], keypath, inpath, outpath]
- self.status['text'] = 'Decrypting...'
+# check file to make check whether it's probably an Adobe Adept encrypted ePub
+def adeptBook(inpath):
+ with closing(ZipFile(open(inpath, 'rb'))) as inf:
+ namelist = set(inf.namelist())
+ if 'META-INF/rights.xml' not in namelist or \
+ 'META-INF/encryption.xml' not in namelist:
+ return False
try:
- cli_main(argv)
- except Exception, e:
- self.status['text'] = 'Error: ' + str(e)
- return
- self.status['text'] = 'File successfully decrypted'
-
-
-def decryptBook(keypath, inpath, outpath):
- with open(keypath, 'rb') as f:
- keyder = f.read()
- rsa = RSA(keyder)
+ rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = './/%s' % (adept('encryptedKey'),)
+ bookkey = ''.join(rights.findtext(expr))
+ if len(bookkey) == 172:
+ return True
+ except:
+ # if we couldn't check, assume it is
+ return True
+ return False
+
+def decryptBook(userkey, inpath, outpath):
+ if AES is None:
+ raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
+ rsa = RSA(userkey)
with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist:
- raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,))
+ print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
+ return 1
for name in META_NAMES:
namelist.remove(name)
- rights = etree.fromstring(inf.read('META-INF/rights.xml'))
- adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
- expr = './/%s' % (adept('encryptedKey'),)
- bookkey = ''.join(rights.findtext(expr))
- bookkey = rsa.decrypt(bookkey.decode('base64'))
- # Padded as per RSAES-PKCS1-v1_5
- if bookkey[-17] != '\x00':
- raise ADEPTError('problem decrypting session key')
- encryption = inf.read('META-INF/encryption.xml')
- decryptor = Decryptor(bookkey[-16:], encryption)
- kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
- with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
- zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
- outf.writestr(zi, inf.read('mimetype'))
- for path in namelist:
- data = inf.read(path)
- outf.writestr(path, decryptor.decrypt(path, data))
+ try:
+ rights = etree.fromstring(inf.read('META-INF/rights.xml'))
+ adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
+ expr = './/%s' % (adept('encryptedKey'),)
+ bookkey = ''.join(rights.findtext(expr))
+ if len(bookkey) != 172:
+ print u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath))
+ return 1
+ bookkey = rsa.decrypt(bookkey.decode('base64'))
+ # Padded as per RSAES-PKCS1-v1_5
+ if bookkey[-17] != '\x00':
+ print u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath))
+ return 2
+ encryption = inf.read('META-INF/encryption.xml')
+ decryptor = Decryptor(bookkey[-16:], encryption)
+ kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
+ with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
+ zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
+ outf.writestr(zi, inf.read('mimetype'))
+ for path in namelist:
+ data = inf.read(path)
+ outf.writestr(path, decryptor.decrypt(path, data))
+ except:
+ print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
+ return 2
return 0
-def cli_main(argv=sys.argv):
+def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
- if AES is None:
- print "%s: This script requires OpenSSL or PyCrypto, which must be" \
- " installed separately. Read the top-of-script comment for" \
- " details." % (progname,)
- return 1
if len(argv) != 4:
- print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
+ print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname)
return 1
keypath, inpath, outpath = argv[1:]
- return decryptBook(keypath, inpath, outpath)
-
+ userkey = open(keypath,'rb').read()
+ result = decryptBook(userkey, inpath, outpath)
+ if result == 0:
+ print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
+ return result
def gui_main():
+ import Tkinter
+ import Tkconstants
+ import tkFileDialog
+ import traceback
+
+ class DecryptionDialog(Tkinter.Frame):
+ def __init__(self, root):
+ Tkinter.Frame.__init__(self, root, border=5)
+ self.status = Tkinter.Label(self, text=u"Select files for decryption")
+ self.status.pack(fill=Tkconstants.X, expand=1)
+ body = Tkinter.Frame(self)
+ body.pack(fill=Tkconstants.X, expand=1)
+ sticky = Tkconstants.E + Tkconstants.W
+ body.grid_columnconfigure(1, weight=2)
+ Tkinter.Label(body, text=u"Key file").grid(row=0)
+ self.keypath = Tkinter.Entry(body, width=30)
+ self.keypath.grid(row=0, column=1, sticky=sticky)
+ if os.path.exists(u"adeptkey.der"):
+ self.keypath.insert(0, u"adeptkey.der")
+ button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
+ button.grid(row=0, column=2)
+ Tkinter.Label(body, text=u"Input file").grid(row=1)
+ self.inpath = Tkinter.Entry(body, width=30)
+ self.inpath.grid(row=1, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
+ button.grid(row=1, column=2)
+ Tkinter.Label(body, text=u"Output file").grid(row=2)
+ self.outpath = Tkinter.Entry(body, width=30)
+ self.outpath.grid(row=2, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
+ button.grid(row=2, column=2)
+ buttons = Tkinter.Frame(self)
+ buttons.pack()
+ botton = Tkinter.Button(
+ buttons, text=u"Decrypt", width=10, command=self.decrypt)
+ botton.pack(side=Tkconstants.LEFT)
+ Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
+ button = Tkinter.Button(
+ buttons, text=u"Quit", width=10, command=self.quit)
+ button.pack(side=Tkconstants.RIGHT)
+
+ def get_keypath(self):
+ keypath = tkFileDialog.askopenfilename(
+ parent=None, title=u"Select Adobe Adept \'.der\' key file",
+ defaultextension=u".der",
+ filetypes=[('Adobe Adept DER-encoded files', '.der'),
+ ('All Files', '.*')])
+ if keypath:
+ keypath = os.path.normpath(keypath)
+ self.keypath.delete(0, Tkconstants.END)
+ self.keypath.insert(0, keypath)
+ return
+
+ def get_inpath(self):
+ inpath = tkFileDialog.askopenfilename(
+ parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt",
+ defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
+ if inpath:
+ inpath = os.path.normpath(inpath)
+ self.inpath.delete(0, Tkconstants.END)
+ self.inpath.insert(0, inpath)
+ return
+
+ def get_outpath(self):
+ outpath = tkFileDialog.asksaveasfilename(
+ parent=None, title=u"Select unencrypted ePub file to produce",
+ defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
+ if outpath:
+ outpath = os.path.normpath(outpath)
+ self.outpath.delete(0, Tkconstants.END)
+ self.outpath.insert(0, outpath)
+ return
+
+ def decrypt(self):
+ keypath = self.keypath.get()
+ inpath = self.inpath.get()
+ outpath = self.outpath.get()
+ if not keypath or not os.path.exists(keypath):
+ self.status['text'] = u"Specified key file does not exist"
+ return
+ if not inpath or not os.path.exists(inpath):
+ self.status['text'] = u"Specified input file does not exist"
+ return
+ if not outpath:
+ self.status['text'] = u"Output file not specified"
+ return
+ if inpath == outpath:
+ self.status['text'] = u"Must have different input and output files"
+ return
+ userkey = open(keypath,'rb').read()
+ self.status['text'] = u"Decrypting..."
+ try:
+ decrypt_status = decryptBook(userkey, inpath, outpath)
+ except Exception, e:
+ self.status['text'] = u"Error; {0}".format(e)
+ return
+ if decrypt_status == 0:
+ self.status['text'] = u"File successfully decrypted"
+ else:
+ self.status['text'] = u"The was an error decrypting the file."
+
root = Tkinter.Tk()
- if AES is None:
- root.withdraw()
- tkMessageBox.showerror(
- "INEPT EPUB Decrypter",
- "This script requires OpenSSL or PyCrypto, which must be"
- " installed separately. Read the top-of-script comment for"
- " details.")
- return 1
- root.title('INEPT EPUB Decrypter')
+ root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
@@ -474,5 +559,7 @@ def gui_main():
if __name__ == '__main__':
if len(sys.argv) > 1:
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptkey.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptkey.py
index 723b7c6..a9bc62d 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptkey.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptkey.py
@@ -6,8 +6,8 @@ from __future__ import with_statement
# ineptkey.pyw, version 5.6
# Copyright © 2009-2010 i♥cabbages
-# Released under the terms of the GNU General Public Licence, version 3 or
-# later. <http://www.gnu.org/licenses/>
+# Released under the terms of the GNU General Public Licence, version 3
+# <http://www.gnu.org/licenses/>
# Windows users: Before running this program, you must first install Python 2.6
# from <http://www.python.org/download/> and PyCrypto from
@@ -37,7 +37,7 @@ from __future__ import with_statement
# 5.3 - On Windows try PyCrypto first, OpenSSL next
# 5.4 - Modify interface to allow use of import
# 5.5 - Fix for potential problem with PyCrypto
-# 5.6 - Revise to allow use in Plugins to eliminate need for duplicate code
+# 5.6 - Revised to allow use in Plugins to eliminate need for duplicate code
"""
Retrieve Adobe ADEPT user key.
@@ -49,12 +49,65 @@ import sys
import os
import struct
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
+ def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
try:
from calibre.constants import iswindows, isosx
except:
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'.
+
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(start, argc.value)]
+ return [u"ineptkey.py"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = "utf-8"
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
+
class ADEPTError(Exception):
pass
@@ -80,13 +133,13 @@ if iswindows:
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
-
+
def F(restype, name, argtypes):
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
-
+
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
[c_char_p, c_int, AES_KEY_p])
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
@@ -308,9 +361,9 @@ if iswindows:
cuser = winreg.HKEY_CURRENT_USER
try:
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
+ device = winreg.QueryValueEx(regkey, 'key')[0]
except WindowsError:
raise ADEPTError("Adobe Digital Editions not activated")
- device = winreg.QueryValueEx(regkey, 'key')[0]
keykey = CryptUnprotectData(device, entropy)
userkey = None
keys = []
@@ -343,7 +396,7 @@ if iswindows:
if len(keys) == 0:
raise ADEPTError('Could not locate privateLicenseKey')
return keys
-
+
elif isosx:
import xml.etree.ElementTree as etree
@@ -386,7 +439,7 @@ else:
def retrieve_keys(keypath):
raise ADEPTError("This script only supports Windows and Mac OS X.")
return []
-
+
def retrieve_key(keypath):
keys = retrieve_keys()
with open(keypath, 'wb') as f:
@@ -397,22 +450,22 @@ def extractKeyfile(keypath):
try:
success = retrieve_key(keypath)
except ADEPTError, e:
- print "Key generation Error: " + str(e)
+ print u"Key generation Error: {0}".format(e.args[0])
return 1
except Exception, e:
- print "General Error: " + str(e)
+ print "General Error: {0}".format(e.args[0])
return 1
if not success:
return 1
return 0
-def cli_main(argv=sys.argv):
+def cli_main(argv=unicode_argv()):
keypath = argv[1]
return extractKeyfile(keypath)
-def main(argv=sys.argv):
+def gui_main(argv=unicode_argv()):
import Tkinter
import Tkconstants
import tkMessageBox
@@ -421,24 +474,24 @@ def main(argv=sys.argv):
class ExceptionDialog(Tkinter.Frame):
def __init__(self, root, text):
Tkinter.Frame.__init__(self, root, border=5)
- label = Tkinter.Label(self, text="Unexpected error:",
+ label = Tkinter.Label(self, text=u"Unexpected error:",
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
label.pack(fill=Tkconstants.X, expand=0)
self.text = Tkinter.Text(self)
self.text.pack(fill=Tkconstants.BOTH, expand=1)
-
+
self.text.insert(Tkconstants.END, text)
root = Tkinter.Tk()
root.withdraw()
- progname = os.path.basename(argv[0])
- keypath = os.path.abspath("adeptkey.der")
+ keypath, progname = os.path.split(argv[0])
+ keypath = os.path.join(keypath, u"adeptkey.der")
success = False
try:
success = retrieve_key(keypath)
except ADEPTError, e:
- tkMessageBox.showerror("ADEPT Key", "Error: " + str(e))
+ tkMessageBox.showerror(u"ADEPT Key", "Error: {0}".format(e.args[0]))
except Exception:
root.wm_state('normal')
root.title('ADEPT Key')
@@ -448,10 +501,12 @@ def main(argv=sys.argv):
if not success:
return 1
tkMessageBox.showinfo(
- "ADEPT Key", "Key successfully retrieved to %s" % (keypath))
+ u"ADEPT Key", u"Key successfully retrieved to {0}".format(keypath))
return 0
if __name__ == '__main__':
if len(sys.argv) > 1:
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
- sys.exit(main())
+ sys.exit(gui_main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptpdf.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptpdf.py
index 20721d1..9f4883e 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptpdf.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/ineptpdf.py
@@ -1,13 +1,25 @@
-#! /usr/bin/env python
-# ineptpdf.pyw, version 7.11
+#! /usr/bin/python
+# -*- coding: utf-8 -*-
from __future__ import with_statement
-# To run this program install Python 2.6 from http://www.python.org/download/
-# and OpenSSL (already installed on Mac OS X and Linux) OR
-# PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
-# (make sure to install the version for Python 2.6). Save this script file as
-# ineptpdf.pyw and double-click on it to run it.
+# ineptpdf.pyw, version 7.11
+# Copyright © 2009-2010 by i♥cabbages
+
+# Released under the terms of the GNU General Public Licence, version 3
+# <http://www.gnu.org/licenses/>
+
+# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
+
+# Windows users: Before running this program, you must first install Python 2.6
+# from <http://www.python.org/download/> and PyCrypto from
+# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
+# install the version for Python 2.6). Save this script file as
+# ineptepub.pyw and double-click on it to run it.
+#
+# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
+# program from the command line (pythonw ineptepub.pyw) or by double-clicking
+# it when it has been associated with PythonLauncher.
# Revision history:
# 1 - Initial release
@@ -36,12 +48,14 @@ from __future__ import with_statement
# 7.9 - Bug fix for some session key errors when len(bookkey) > length required
# 7.10 - Various tweaks to fix minor problems.
# 7.11 - More tweaks to fix minor problems.
+# 7.12 - Revised to allow use in calibre plugins to eliminate need for duplicate code
"""
Decrypts Adobe ADEPT-encrypted PDF files.
"""
__license__ = 'GPL v3'
+__version__ = "7.12"
import sys
import os
@@ -51,10 +65,63 @@ import struct
import hashlib
from itertools import chain, islice
import xml.etree.ElementTree as etree
-import Tkinter
-import Tkconstants
-import tkFileDialog
-import tkMessageBox
+
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
+ def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+iswindows = sys.platform.startswith('win')
+isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'.
+
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(start, argc.value)]
+ return [u"ineptepub.py"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = "utf-8"
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
+
class ADEPTError(Exception):
pass
@@ -1520,9 +1587,7 @@ class PDFDocument(object):
def initialize_ebx(self, password, docid, param):
self.is_printable = self.is_modifiable = self.is_extractable = True
- with open(password, 'rb') as f:
- keyder = f.read()
- rsa = RSA(keyder)
+ rsa = RSA(password)
length = int_value(param.get('Length', 0)) / 8
rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
rights = zlib.decompress(rights, -15)
@@ -1907,14 +1972,14 @@ class PDFObjStrmParser(PDFParser):
### My own code, for which there is none else to blame
class PDFSerializer(object):
- def __init__(self, inf, keypath):
+ def __init__(self, inf, userkey):
global GEN_XREF_STM, gen_xref_stm
gen_xref_stm = GEN_XREF_STM > 1
self.version = inf.read(8)
inf.seek(0)
self.doc = doc = PDFDocument()
parser = PDFParser(doc, inf)
- doc.initialize(keypath)
+ doc.initialize(userkey)
self.objids = objids = set()
for xref in reversed(doc.xrefs):
trailer = xref.trailer
@@ -2097,142 +2162,144 @@ class PDFSerializer(object):
self.write('endobj\n')
-class DecryptionDialog(Tkinter.Frame):
- def __init__(self, root):
- Tkinter.Frame.__init__(self, root, border=5)
- ltext='Select file for decryption\n'
- self.status = Tkinter.Label(self, text=ltext)
- self.status.pack(fill=Tkconstants.X, expand=1)
- body = Tkinter.Frame(self)
- body.pack(fill=Tkconstants.X, expand=1)
- sticky = Tkconstants.E + Tkconstants.W
- body.grid_columnconfigure(1, weight=2)
- Tkinter.Label(body, text='Key file').grid(row=0)
- self.keypath = Tkinter.Entry(body, width=30)
- self.keypath.grid(row=0, column=1, sticky=sticky)
- if os.path.exists('adeptkey.der'):
- self.keypath.insert(0, 'adeptkey.der')
- button = Tkinter.Button(body, text="...", command=self.get_keypath)
- button.grid(row=0, column=2)
- Tkinter.Label(body, text='Input file').grid(row=1)
- self.inpath = Tkinter.Entry(body, width=30)
- self.inpath.grid(row=1, column=1, sticky=sticky)
- button = Tkinter.Button(body, text="...", command=self.get_inpath)
- button.grid(row=1, column=2)
- Tkinter.Label(body, text='Output file').grid(row=2)
- self.outpath = Tkinter.Entry(body, width=30)
- self.outpath.grid(row=2, column=1, sticky=sticky)
- button = Tkinter.Button(body, text="...", command=self.get_outpath)
- button.grid(row=2, column=2)
- buttons = Tkinter.Frame(self)
- buttons.pack()
-
-
- botton = Tkinter.Button(
- buttons, text="Decrypt", width=10, command=self.decrypt)
- botton.pack(side=Tkconstants.LEFT)
- Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
- button = Tkinter.Button(
- buttons, text="Quit", width=10, command=self.quit)
- button.pack(side=Tkconstants.RIGHT)
-
-
- def get_keypath(self):
- keypath = tkFileDialog.askopenfilename(
- parent=None, title='Select ADEPT key file',
- defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
- ('All Files', '.*')])
- if keypath:
- keypath = os.path.normpath(os.path.realpath(keypath))
- self.keypath.delete(0, Tkconstants.END)
- self.keypath.insert(0, keypath)
- return
-
- def get_inpath(self):
- inpath = tkFileDialog.askopenfilename(
- parent=None, title='Select ADEPT encrypted PDF file to decrypt',
- defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
- ('All files', '.*')])
- if inpath:
- inpath = os.path.normpath(os.path.realpath(inpath))
- self.inpath.delete(0, Tkconstants.END)
- self.inpath.insert(0, inpath)
- return
-
- def get_outpath(self):
- outpath = tkFileDialog.asksaveasfilename(
- parent=None, title='Select unencrypted PDF file to produce',
- defaultextension='.pdf', filetypes=[('PDF files', '.pdf'),
- ('All files', '.*')])
- if outpath:
- outpath = os.path.normpath(os.path.realpath(outpath))
- self.outpath.delete(0, Tkconstants.END)
- self.outpath.insert(0, outpath)
- return
-
- def decrypt(self):
- keypath = self.keypath.get()
- inpath = self.inpath.get()
- outpath = self.outpath.get()
- if not keypath or not os.path.exists(keypath):
- # keyfile doesn't exist
- self.status['text'] = 'Specified Adept key file does not exist'
- return
- if not inpath or not os.path.exists(inpath):
- self.status['text'] = 'Specified input file does not exist'
- return
- if not outpath:
- self.status['text'] = 'Output file not specified'
- return
- if inpath == outpath:
- self.status['text'] = 'Must have different input and output files'
- return
- # patch for non-ascii characters
- argv = [sys.argv[0], keypath, inpath, outpath]
- self.status['text'] = 'Processing ...'
- try:
- cli_main(argv)
- except Exception, a:
- self.status['text'] = 'Error: ' + str(a)
- return
- self.status['text'] = 'File successfully decrypted.\n'+\
- 'Close this window or decrypt another pdf file.'
- return
-def decryptBook(keypath, inpath, outpath):
+def decryptBook(userkey, inpath, outpath):
+ if RSA is None:
+ raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
with open(inpath, 'rb') as inf:
try:
- serializer = PDFSerializer(inf, keypath)
+ serializer = PDFSerializer(inf, userkey)
except:
- print "Error serializing pdf. Probably wrong key."
- return 1
+ print u"Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath))
+ return 2
# hope this will fix the 'bad file descriptor' problem
with open(outpath, 'wb') as outf:
- # help construct to make sure the method runs to the end
+ # help construct to make sure the method runs to the end
try:
serializer.dump(outf)
- except:
- print "error writing pdf."
- return 1
+ except Exception, e:
+ print u"error writing pdf: {0}".format(e.args[0])
+ return 2
return 0
-def cli_main(argv=sys.argv):
+def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
- if RSA is None:
- print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
- "separately. Read the top-of-script comment for details." % \
- (progname,)
- return 1
if len(argv) != 4:
- print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
+ print u"usage: {0} <keyfile.der> <inbook.pdf> <outbook.pdf>".format(progname)
return 1
keypath, inpath, outpath = argv[1:]
- return decryptBook(keypath, inpath, outpath)
+ userkey = open(keypath,'rb').read()
+ result = decryptBook(userkey, inpath, outpath)
+ if result == 0:
+ print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
+ return result
def gui_main():
+ import Tkinter
+ import Tkconstants
+ import tkFileDialog
+ import tkMessageBox
+
+ class DecryptionDialog(Tkinter.Frame):
+ def __init__(self, root):
+ Tkinter.Frame.__init__(self, root, border=5)
+ self.status = Tkinter.Label(self, text=u"Select files for decryption")
+ self.status.pack(fill=Tkconstants.X, expand=1)
+ body = Tkinter.Frame(self)
+ body.pack(fill=Tkconstants.X, expand=1)
+ sticky = Tkconstants.E + Tkconstants.W
+ body.grid_columnconfigure(1, weight=2)
+ Tkinter.Label(body, text=u"Key file").grid(row=0)
+ self.keypath = Tkinter.Entry(body, width=30)
+ self.keypath.grid(row=0, column=1, sticky=sticky)
+ if os.path.exists(u"adeptkey.der"):
+ self.keypath.insert(0, u"adeptkey.der")
+ button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
+ button.grid(row=0, column=2)
+ Tkinter.Label(body, text=u"Input file").grid(row=1)
+ self.inpath = Tkinter.Entry(body, width=30)
+ self.inpath.grid(row=1, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
+ button.grid(row=1, column=2)
+ Tkinter.Label(body, text=u"Output file").grid(row=2)
+ self.outpath = Tkinter.Entry(body, width=30)
+ self.outpath.grid(row=2, column=1, sticky=sticky)
+ button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
+ button.grid(row=2, column=2)
+ buttons = Tkinter.Frame(self)
+ buttons.pack()
+ botton = Tkinter.Button(
+ buttons, text=u"Decrypt", width=10, command=self.decrypt)
+ botton.pack(side=Tkconstants.LEFT)
+ Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
+ button = Tkinter.Button(
+ buttons, text=u"Quit", width=10, command=self.quit)
+ button.pack(side=Tkconstants.RIGHT)
+
+ def get_keypath(self):
+ keypath = tkFileDialog.askopenfilename(
+ parent=None, title=u"Select Adobe Adept \'.der\' key file",
+ defaultextension=u".der",
+ filetypes=[('Adobe Adept DER-encoded files', '.der'),
+ ('All Files', '.*')])
+ if keypath:
+ keypath = os.path.normpath(keypath)
+ self.keypath.delete(0, Tkconstants.END)
+ self.keypath.insert(0, keypath)
+ return
+
+ def get_inpath(self):
+ inpath = tkFileDialog.askopenfilename(
+ parent=None, title=u"Select ADEPT-encrypted PDF file to decrypt",
+ defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
+ if inpath:
+ inpath = os.path.normpath(inpath)
+ self.inpath.delete(0, Tkconstants.END)
+ self.inpath.insert(0, inpath)
+ return
+
+ def get_outpath(self):
+ outpath = tkFileDialog.asksaveasfilename(
+ parent=None, title=u"Select unencrypted PDF file to produce",
+ defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
+ if outpath:
+ outpath = os.path.normpath(outpath)
+ self.outpath.delete(0, Tkconstants.END)
+ self.outpath.insert(0, outpath)
+ return
+
+ def decrypt(self):
+ keypath = self.keypath.get()
+ inpath = self.inpath.get()
+ outpath = self.outpath.get()
+ if not keypath or not os.path.exists(keypath):
+ self.status['text'] = u"Specified key file does not exist"
+ return
+ if not inpath or not os.path.exists(inpath):
+ self.status['text'] = u"Specified input file does not exist"
+ return
+ if not outpath:
+ self.status['text'] = u"Output file not specified"
+ return
+ if inpath == outpath:
+ self.status['text'] = u"Must have different input and output files"
+ return
+ userkey = open(keypath,'rb').read()
+ self.status['text'] = u"Decrypting..."
+ try:
+ decrypt_status = decryptBook(userkey, inpath, outpath)
+ except Exception, e:
+ self.status['text'] = u"Error; {0}".format(e.args[0])
+ return
+ if decrypt_status == 0:
+ self.status['text'] = u"File successfully decrypted"
+ else:
+ self.status['text'] = u"The was an error decrypting the file."
+
+
root = Tkinter.Tk()
if RSA is None:
root.withdraw()
@@ -2241,7 +2308,7 @@ def gui_main():
"This script requires OpenSSL or PyCrypto, which must be installed "
"separately. Read the top-of-script comment for details.")
return 1
- root.title('INEPT PDF Decrypter')
+ root.title(u"Adobe Adept PDF Decrypter v.{0}".format(__version__))
root.resizable(True, False)
root.minsize(370, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
@@ -2251,5 +2318,7 @@ def gui_main():
if __name__ == '__main__':
if len(sys.argv) > 1:
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
sys.exit(cli_main())
sys.exit(gui_main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mobidedrm.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mobidedrm.py
index 717b0d0..8adb107 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mobidedrm.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mobidedrm.py
@@ -1,7 +1,11 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
from __future__ import with_statement
+# ignobleepub.pyw, version 3.6
+# Copyright © 2009-2012 by DiapDealer et al.
+
# engine to remove drm from Kindle for Mac and Kindle for PC books
# for personal use for archiving and converting your ebooks
@@ -12,30 +16,51 @@ from __future__ import with_statement
# be able to read OUR books on whatever device we want and to keep
# readable for a long, long time
-# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
+# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
# and many many others
-
-
-__version__ = '4.4'
-
-class Unbuffered:
- def __init__(self, stream):
- self.stream = stream
- def write(self, data):
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-import sys
-import os, csv, getopt
-import string
+# Special thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump
+# from which this script borrows most unashamedly.
+
+
+# Changelog
+# 1.0 - Name change to k4mobidedrm. Adds Mac support, Adds plugin code
+# 1.1 - Adds support for additional kindle.info files
+# 1.2 - Better error handling for older Mobipocket
+# 1.3 - Don't try to decrypt Topaz books
+# 1.7 - Add support for Topaz books and Kindle serial numbers. Split code.
+# 1.9 - Tidy up after Topaz, minor exception changes
+# 2.1 - Topaz fix and filename sanitizing
+# 2.2 - Topaz Fix and minor Mac code fix
+# 2.3 - More Topaz fixes
+# 2.4 - K4PC/Mac key generation fix
+# 2.6 - Better handling of non-K4PC/Mac ebooks
+# 2.7 - Better trailing bytes handling in mobidedrm
+# 2.8 - Moved parsing of kindle.info files to mac & pc util files.
+# 3.1 - Updated for new calibre interface. Now __init__ in plugin.
+# 3.5 - Now support Kindle for PC/Mac 1.6
+# 3.6 - Even better trailing bytes handling in mobidedrm
+# 3.7 - Add support for Amazon Print Replica ebooks.
+# 3.8 - Improved Topaz support
+# 4.1 - Improved Topaz support and faster decryption with alfcrypto
+# 4.2 - Added support for Amazon's KF8 format ebooks
+# 4.4 - Linux calls to Wine added, and improved configuration dialog
+# 4.5 - Linux works again without Wine. Some Mac key file search changes
+# 4.6 - First attempt to handle unicode properly
+# 4.7 - Added timing reports, and changed search for Mac key files
+# 4.8 - Much better unicode handling, matching the updated inept and ignoble scripts
+# - Moved back into plugin, __init__ in plugin now only contains plugin code.
+
+__version__ = '4.8'
+
+
+import sys, os, re
+import csv
+import getopt
import re
import traceback
import time
-
-buildXML = False
+import htmlentitydefs
class DrmException(Exception):
pass
@@ -54,161 +79,203 @@ else:
import topazextract
import kgenpids
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
+ def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+iswindows = sys.platform.startswith('win')
+isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'.
+
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(start, argc.value)]
+ # if we don't have any arguments at all, just pass back script name
+ # this should never happen
+ return [u"mobidedrm.py"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = "utf-8"
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-# cleanup bytestring filenames
+# cleanup unicode filenames
# borrowed from calibre from calibre/src/calibre/__init__.py
-# added in removal of non-printing chars
-# and removal of . at start
-# convert underscores to spaces (we're OK with spaces in file names)
+# added in removal of control (<32) chars
+# and removal of . at start and end
+# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
def cleanup_name(name):
- _filename_sanitize = re.compile(r'[\xae\0\\|\?\*<":>\+/]')
- substitute='_'
- one = ''.join(char for char in name if char in string.printable)
- one = _filename_sanitize.sub(substitute, one)
- one = re.sub(r'\s', ' ', one).strip()
- one = re.sub(r'^\.+$', '_', one)
- one = one.replace('..', substitute)
- # Windows doesn't like path components that end with a period
- if one.endswith('.'):
- one = one[:-1]+substitute
- # Mac and Unix don't like file names that begin with a full stop
- if len(one) > 0 and one[0] == '.':
- one = substitute+one[1:]
- one = one.replace('_',' ')
- return one
-
-def decryptBook(infile, outdir, k4, kInfoFiles, serials, pids):
- global buildXML
-
-
+ # substitute filename unfriendly characters
+ name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
+ # delete control characters
+ name = u"".join(char for char in name if ord(char)>=32)
+ # white space to single space, delete leading and trailing while space
+ name = re.sub(ur"\s", u" ", name).strip()
+ # remove leading dots
+ while len(name)>0 and name[0] == u".":
+ name = name[1:]
+ # remove trailing dots (Windows doesn't like them)
+ if name.endswith(u'.'):
+ name = name[:-1]
+ return name
+
+# must be passed unicode
+def unescape(text):
+ def fixup(m):
+ text = m.group(0)
+ if text[:2] == u"&#":
+ # character reference
+ try:
+ if text[:3] == u"&#x":
+ return unichr(int(text[3:-1], 16))
+ else:
+ return unichr(int(text[2:-1]))
+ except ValueError:
+ pass
+ else:
+ # named entity
+ try:
+ text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
+ except KeyError:
+ pass
+ return text # leave as is
+ return re.sub(u"&#?\w+;", fixup, text)
+
+def GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime = time.time()):
# handle the obvious cases at the beginning
if not os.path.isfile(infile):
- print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: Input file does not exist"
- return 1
-
- starttime = time.time()
- print "Starting decryptBook routine."
-
+ raise DRMException (u"Input file does not exist.")
mobi = True
magic3 = file(infile,'rb').read(3)
if magic3 == 'TPZ':
mobi = False
- bookname = os.path.splitext(os.path.basename(infile))[0]
-
if mobi:
mb = mobidedrm.MobiBook(infile)
else:
mb = topazextract.TopazBook(infile)
- title = mb.getBookTitle()
- print "Processing Book: ", title
- filenametitle = cleanup_name(title)
- outfilename = cleanup_name(bookname)
-
- # generate 'sensible' filename, that will sort with the original name,
- # but is close to the name from the file.
- outlength = len(outfilename)
- comparelength = min(8,min(outlength,len(filenametitle)))
- copylength = min(max(outfilename.find(' '),8),len(outfilename))
- if outlength==0:
- outfilename = filenametitle
- elif comparelength > 0:
- if outfilename[:comparelength] == filenametitle[:comparelength]:
- outfilename = filenametitle
- else:
- outfilename = outfilename[:copylength] + " " + filenametitle
+ bookname = unescape(mb.getBookTitle())
+ print u"Decrypting {1} ebook: {0}".format(bookname, mb.getBookType())
- # avoid excessively long file names
- if len(outfilename)>150:
- outfilename = outfilename[:150]
-
- # build pid list
+ # extend PID list with book-specific PIDs
md1, md2 = mb.getPIDMetaInfo()
- pids.extend(kgenpids.getPidList(md1, md2, k4, serials, kInfoFiles))
-
- print "Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(pids))
-
+ pids.extend(kgenpids.getPidList(md1, md2, serials, kInfoFiles))
+ print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(pids))
try:
mb.processBook(pids)
+ except:
+ mb.cleanup
+ raise
- except mobidedrm.DrmException, e:
- print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
- print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime)
- return 1
- except topazextract.TpzDRMError, e:
- print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
- print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime)
- return 1
+ print u"Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime)
+ return mb
+
+
+# infile, outdir and kInfoFiles should be unicode strings
+def decryptBook(infile, outdir, kInfoFiles, serials, pids):
+ starttime = time.time()
+ print "Starting decryptBook routine."
+ try:
+ book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime)
except Exception, e:
- print >>sys.stderr, ('K4MobiDeDrm v%(__version__)s\n' % globals()) + "Error: " + str(e) + "\nDRM Removal Failed.\n"
- print "Failed to decrypted book after {0:.1f} seconds".format(time.time()-starttime)
+ print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
return 1
- print "Successfully decrypted book after {0:.1f} seconds".format(time.time()-starttime)
+ # if we're saving to the same folder as the original, use file name_
+ # if to a different folder, use book name
+ if os.path.normcase(os.path.normpath(outdir)) == os.path.normcase(os.path.normpath(os.path.dirname(infile))):
+ outfilename = os.path.splitext(os.path.basename(infile))[0]
+ else:
+ outfilename = cleanup_name(book.getBookTitle())
- if mobi:
- if mb.getPrintReplica():
- outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw4')
- elif mb.getMobiVersion() >= 8:
- outfile = os.path.join(outdir, outfilename + '_nodrm' + '.azw3')
- else:
- outfile = os.path.join(outdir, outfilename + '_nodrm' + '.mobi')
- mb.getMobiFile(outfile)
- print "Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename + '_nodrm')
- return 0
+ # avoid excessively long file names
+ if len(outfilename)>150:
+ outfilename = outfilename[:150]
- # topaz:
- print " Creating NoDRM HTMLZ Archive"
- zipname = os.path.join(outdir, outfilename + '_nodrm' + '.htmlz')
- mb.getHTMLZip(zipname)
+ outfilename = outfilename+u"_nodrm"
+ outfile = os.path.join(outdir, outfilename + book.getBookExtension())
- print " Creating SVG ZIP Archive"
- zipname = os.path.join(outdir, outfilename + '_SVG' + '.zip')
- mb.getSVGZip(zipname)
+ book.getFile(outfile)
+ print u"Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
- if buildXML:
- print " Creating XML ZIP Archive"
- zipname = os.path.join(outdir, outfilename + '_XML' + '.zip')
- mb.getXMLZip(zipname)
+ if book.getBookType()==u"Topaz":
+ zipname = os.path.join(outdir, outfilename + u"_SVG.zip")
+ book.getSVGZip(zipname)
+ print u"Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
# remove internal temporary directory of Topaz pieces
- mb.cleanup()
- print "Saved decrypted Topaz book parts after {0:.1f} seconds".format(time.time()-starttime)
- return 0
+ book.cleanup()
def usage(progname):
- print "Removes DRM protection from K4PC/M, Kindle, Mobi and Topaz ebooks"
- print "Usage:"
- print " %s [-k <kindle.info>] [-p <pidnums>] [-s <kindleSerialNumbers>] <infile> <outdir> " % progname
+ print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks"
+ print u"Usage:"
+ print u" {0} [-k <kindle.info>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)
#
# Main
#
-def main(argv=sys.argv):
+def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
-
- k4 = False
- kInfoFiles = []
- serials = []
- pids = []
-
- print ('K4MobiDeDrm v%(__version__)s '
- 'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
+ print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
try:
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
except getopt.GetoptError, err:
- print str(err)
+ print u"Error in options or arguments: {0}".format(err.args[0])
usage(progname)
sys.exit(2)
if len(args)<2:
usage(progname)
sys.exit(2)
+ infile = args[0]
+ outdir = args[1]
+ kInfoFiles = []
+ serials = []
+ pids = []
+
for o, a in opts:
if o == "-k":
if a == None :
@@ -223,16 +290,13 @@ def main(argv=sys.argv):
raise DrmException("Invalid parameter for -s")
serials = a.split(',')
- # try with built in Kindle Info files
- k4 = True
- if sys.platform.startswith('linux'):
- k4 = False
- kInfoFiles = None
- infile = args[0]
- outdir = args[1]
- return decryptBook(infile, outdir, k4, kInfoFiles, serials, pids)
+ # try with built in Kindle Info files if not on Linux
+ k4 = not sys.platform.startswith('linux')
+
+ return decryptBook(infile, outdir, kInfoFiles, serials, pids)
if __name__ == '__main__':
- sys.stdout=Unbuffered(sys.stdout)
- sys.exit(main())
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
+ sys.exit(cli_main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mutils.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mutils.py
index 1fc08cb..bceb3a3 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mutils.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4mutils.py
@@ -1,3 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
# standlone set of Mac OSX specific routines needed for KindleBooks
from __future__ import with_statement
@@ -22,7 +25,7 @@ def _load_crypto_libcrypto():
libcrypto = find_library('crypto')
if libcrypto is None:
- raise DrmException('libcrypto not found')
+ raise DrmException(u"libcrypto not found")
libcrypto = CDLL(libcrypto)
# From OpenSSL's crypto aes header
@@ -80,14 +83,14 @@ def _load_crypto_libcrypto():
def set_decrypt_key(self, userkey, iv):
self._blocksize = len(userkey)
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
- raise DrmException('AES improper key used')
+ raise DrmException(u"AES improper key used")
return
keyctx = self._keyctx = AES_KEY()
self._iv = iv
self._userkey = userkey
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
if rv < 0:
- raise DrmException('Failed to initialize AES key')
+ raise DrmException(u"Failed to initialize AES key")
def decrypt(self, data):
out = create_string_buffer(len(data))
@@ -95,7 +98,7 @@ def _load_crypto_libcrypto():
keyctx = self._keyctx
rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
if rv == 0:
- raise DrmException('AES decryption failed')
+ raise DrmException(u"AES decryption failed")
return out.raw
def keyivgen(self, passwd, salt, iter, keylen):
@@ -139,20 +142,20 @@ def SHA256(message):
return ctx.digest()
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
-charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
-charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
+charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
+charMap2 = 'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
# For kinf approach of K4Mac 1.6.X or later
-# On K4PC charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
+# On K4PC charMap5 = 'AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE'
# For Mac they seem to re-use charMap2 here
charMap5 = charMap2
# new in K4M 1.9.X
-testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
+testMap8 = 'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD'
def encode(data, map):
- result = ""
+ result = ''
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
@@ -167,14 +170,14 @@ def encodeHash(data,map):
# Decode the string in data with the characters in map. Returns the decoded bytes
def decode(data,map):
- result = ""
+ result = ''
for i in range (0,len(data)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
if (high == -1) or (low == -1) :
break
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
- result += pack("B",value)
+ result += pack('B',value)
return result
# For K4M 1.6.X and later
@@ -200,7 +203,7 @@ def primes(n):
# uses a sub process to get the Hard Drive Serial Number using ioreg
-# returns with the serial number of drive whose BSD Name is "disk0"
+# returns with the serial number of drive whose BSD Name is 'disk0'
def GetVolumeSerialNumber():
sernum = os.getenv('MYSERIALNUMBER')
if sernum != None:
@@ -216,11 +219,11 @@ def GetVolumeSerialNumber():
foundIt = False
for j in xrange(cnt):
resline = reslst[j]
- pp = resline.find('"Serial Number" = "')
+ pp = resline.find('\"Serial Number\" = \"')
if pp >= 0:
sernum = resline[pp+19:-1]
sernum = sernum.strip()
- bb = resline.find('"BSD Name" = "')
+ bb = resline.find('\"BSD Name\" = \"')
if bb >= 0:
bsdname = resline[bb+14:-1]
bsdname = bsdname.strip()
@@ -277,7 +280,7 @@ def GetDiskPartitionUUID(diskpart):
nest += 1
if resline.find('}') >= 0:
nest -= 1
- pp = resline.find('"UUID" = "')
+ pp = resline.find('\"UUID\" = \"')
if pp >= 0:
uuidnum = resline[pp+10:-1]
uuidnum = uuidnum.strip()
@@ -285,7 +288,7 @@ def GetDiskPartitionUUID(diskpart):
if partnest == uuidnest and uuidnest > 0:
foundIt = True
break
- bb = resline.find('"BSD Name" = "')
+ bb = resline.find('\"BSD Name\" = \"')
if bb >= 0:
bsdname = resline[bb+14:-1]
bsdname = bsdname.strip()
@@ -323,7 +326,7 @@ def GetMACAddressMunged():
if pp >= 0:
macnum = resline[pp+6:-1]
macnum = macnum.strip()
- # print "original mac", macnum
+ # print 'original mac', macnum
# now munge it up the way Kindle app does
# by xoring it with 0xa5 and swapping elements 3 and 4
maclst = macnum.split(':')
@@ -340,7 +343,7 @@ def GetMACAddressMunged():
mlst[2] = maclst[2] ^ 0xa5
mlst[1] = maclst[1] ^ 0xa5
mlst[0] = maclst[0] ^ 0xa5
- macnum = "%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x" % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
+ macnum = '%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
foundIt = True
break
if not foundIt:
@@ -367,6 +370,19 @@ def isNewInstall():
return False
+class Memoize:
+ """Memoize(fn) - an instance which acts like fn but memoizes its arguments
+ Will only work on functions with non-mutable arguments
+ """
+ def __init__(self, fn):
+ self.fn = fn
+ self.memo = {}
+ def __call__(self, *args):
+ if not self.memo.has_key(args):
+ self.memo[args] = self.fn(*args)
+ return self.memo[args]
+
+@Memoize
def GetIDString():
# K4Mac now has an extensive set of ids strings it uses
# in encoding pids and in creating unique passwords
@@ -530,7 +546,8 @@ def getKindleInfoFiles():
# determine type of kindle info provided and return a
# database of keynames and values
def getDBfromFile(kInfoFile):
- names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber", "max_date", "SIGVERIF"]
+
+ names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF']
DB = {}
cnt = 0
infoReader = open(kInfoFile, 'r')
@@ -545,12 +562,12 @@ def getDBfromFile(kInfoFile):
for item in items:
if item != '':
keyhash, rawdata = item.split(':')
- keyname = "unknown"
+ keyname = 'unknown'
for name in names:
if encodeHash(name,charMap2) == keyhash:
keyname = name
break
- if keyname == "unknown":
+ if keyname == 'unknown':
keyname = keyhash
encryptedValue = decode(rawdata,charMap2)
cleartext = cud.decrypt(encryptedValue)
@@ -563,8 +580,8 @@ def getDBfromFile(kInfoFile):
if hdr == '/':
# else newer style .kinf file used by K4Mac >= 1.6.0
- # the .kinf file uses "/" to separate it into records
- # so remove the trailing "/" to make it easy to use split
+ # the .kinf file uses '/' to separate it into records
+ # so remove the trailing '/' to make it easy to use split
data = data[:-1]
items = data.split('/')
cud = CryptUnprotectDataV2()
@@ -578,11 +595,11 @@ def getDBfromFile(kInfoFile):
# the first 32 chars of the first record of a group
# is the MD5 hash of the key name encoded by charMap5
keyhash = item[0:32]
- keyname = "unknown"
+ keyname = 'unknown'
# the raw keyhash string is also used to create entropy for the actual
# CryptProtectData Blob that represents that keys contents
- # "entropy" not used for K4Mac only K4PC
+ # 'entropy' not used for K4Mac only K4PC
# entropy = SHA1(keyhash)
# the remainder of the first record when decoded with charMap5
@@ -599,12 +616,12 @@ def getDBfromFile(kInfoFile):
item = items.pop(0)
edlst.append(item)
- keyname = "unknown"
+ keyname = 'unknown'
for name in names:
if encodeHash(name,charMap5) == keyhash:
keyname = name
break
- if keyname == "unknown":
+ if keyname == 'unknown':
keyname = keyhash
# the charMap5 encoded contents data has had a length
@@ -615,10 +632,10 @@ def getDBfromFile(kInfoFile):
# The offset into the charMap5 encoded contents seems to be:
# len(contents) - largest prime number less than or equal to int(len(content)/3)
- # (in other words split "about" 2/3rds of the way through)
+ # (in other words split 'about' 2/3rds of the way through)
# move first offsets chars to end to align for decode by charMap5
- encdata = "".join(edlst)
+ encdata = ''.join(edlst)
contlen = len(encdata)
# now properly split and recombine
@@ -667,7 +684,7 @@ def getDBfromFile(kInfoFile):
# the first 32 chars of the first record of a group
# is the MD5 hash of the key name encoded by charMap5
keyhash = item[0:32]
- keyname = "unknown"
+ keyname = 'unknown'
# unlike K4PC the keyhash is not used in generating entropy
# entropy = SHA1(keyhash) + added_entropy
@@ -687,12 +704,12 @@ def getDBfromFile(kInfoFile):
item = items.pop(0)
edlst.append(item)
- keyname = "unknown"
+ keyname = 'unknown'
for name in names:
if encodeHash(name,testMap8) == keyhash:
keyname = name
break
- if keyname == "unknown":
+ if keyname == 'unknown':
keyname = keyhash
# the testMap8 encoded contents data has had a length
@@ -703,10 +720,10 @@ def getDBfromFile(kInfoFile):
# The offset into the testMap8 encoded contents seems to be:
# len(contents) - largest prime number less than or equal to int(len(content)/3)
- # (in other words split "about" 2/3rds of the way through)
+ # (in other words split 'about' 2/3rds of the way through)
# move first offsets chars to end to align for decode by testMap8
- encdata = "".join(edlst)
+ encdata = ''.join(edlst)
contlen = len(encdata)
# now properly split and recombine
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4pcutils.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4pcutils.py
index 9f9ca07..476844c 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4pcutils.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/k4pcutils.py
@@ -1,4 +1,6 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
# K4PC Windows specific routines
from __future__ import with_statement
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kgenpids.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kgenpids.py
index b0fbaa4..c5de9b9 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kgenpids.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kgenpids.py
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
from __future__ import with_statement
import sys
@@ -17,26 +18,24 @@ global charMap4
if 'calibre' in sys.modules:
inCalibre = True
-else:
- inCalibre = False
-
-if inCalibre:
- if sys.platform.startswith('win'):
+ from calibre.constants import iswindows, isosx
+ if iswindows:
from calibre_plugins.k4mobidedrm.k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
-
- if sys.platform.startswith('darwin'):
+ if isosx:
from calibre_plugins.k4mobidedrm.k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
else:
- if sys.platform.startswith('win'):
+ inCalibre = False
+ iswindows = sys.platform.startswith('win')
+ isosx = sys.platform.startswith('darwin')
+ if iswindows:
from k4pcutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
-
- if sys.platform.startswith('darwin'):
+ if isosx:
from k4mutils import getKindleInfoFiles, getDBfromFile, GetUserName, GetIDString
-charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
-charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
-charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
+charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
+charMap3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+charMap4 = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
# crypto digestroutines
import hashlib
@@ -54,7 +53,7 @@ def SHA1(message):
# Encode the bytes in data with the characters in map
def encode(data, map):
- result = ""
+ result = ''
for char in data:
value = ord(char)
Q = (value ^ 0x80) // len(map)
@@ -69,14 +68,14 @@ def encodeHash(data,map):
# Decode the string in data with the characters in map. Returns the decoded bytes
def decode(data,map):
- result = ""
+ result = ''
for i in range (0,len(data)-1,2):
high = map.find(data[i])
low = map.find(data[i+1])
if (high == -1) or (low == -1) :
break
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
- result += pack("B",value)
+ result += pack('B',value)
return result
#
@@ -98,7 +97,7 @@ def getSixBitsFromBitField(bitField,offset):
# 8 bits to six bits encoding from hash to generate PID string
def encodePID(hash):
global charMap3
- PID = ""
+ PID = ''
for position in range (0,8):
PID += charMap3[getSixBitsFromBitField(hash,position)]
return PID
@@ -129,7 +128,7 @@ def generatePidSeed(table,dsn) :
def generateDevicePID(table,dsn,nbRoll):
global charMap4
seed = generatePidSeed(table,dsn)
- pidAscii = ""
+ pidAscii = ''
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
index = 0
for counter in range (0,nbRoll):
@@ -176,28 +175,31 @@ 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):
+def getKindlePids(rec209, token, serialnum):
+ pids=[]
+
# Compute book PID
pidHash = SHA1(serialnum+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
- pidlst.append(bookPID)
+ pids.append(bookPID)
# compute fixed pid for old pre 2.5 firmware update pid as well
- bookPID = pidFromSerial(serialnum, 7) + "*"
- bookPID = checksumPid(bookPID)
- pidlst.append(bookPID)
+ kindlePID = pidFromSerial(serialnum, 7) + "*"
+ kindlePID = checksumPid(kindlePID)
+ pids.append(kindlePID)
- return pidlst
+ return pids
# parse the Kindleinfo file to calculate the book pid.
-keynames = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
+keynames = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber']
-def getK4Pids(pidlst, rec209, token, kInfoFile):
+def getK4Pids(rec209, token, kInfoFile):
global charMap1
kindleDatabase = None
+ pids = []
try:
kindleDatabase = getDBfromFile(kInfoFile)
except Exception, message:
@@ -206,17 +208,17 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
pass
if kindleDatabase == None :
- return pidlst
+ return pids
try:
# Get the Mazama Random number
- MazamaRandomNumber = kindleDatabase["MazamaRandomNumber"]
+ MazamaRandomNumber = kindleDatabase['MazamaRandomNumber']
# Get the kindle account token
- kindleAccountToken = kindleDatabase["kindle.account.tokens"]
+ kindleAccountToken = kindleDatabase['kindle.account.tokens']
except KeyError:
- print "Keys not found in " + kInfoFile
- return pidlst
+ print u"Keys not found in {0}".format(os.path.basename(kInfoFile))
+ return pids
# Get the ID string used
encodedIDString = encodeHash(GetIDString(),charMap1)
@@ -231,7 +233,7 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
table = generatePidEncryptionTable()
devicePID = generateDevicePID(table,DSN,4)
devicePID = checksumPid(devicePID)
- pidlst.append(devicePID)
+ pids.append(devicePID)
# Compute book PIDs
@@ -239,36 +241,38 @@ def getK4Pids(pidlst, rec209, token, kInfoFile):
pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
- pidlst.append(bookPID)
+ pids.append(bookPID)
# variant 1
pidHash = SHA1(kindleAccountToken+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
- pidlst.append(bookPID)
+ pids.append(bookPID)
# variant 2
pidHash = SHA1(DSN+rec209+token)
bookPID = encodePID(pidHash)
bookPID = checksumPid(bookPID)
- pidlst.append(bookPID)
+ pids.append(bookPID)
- return pidlst
+ return pids
-def getPidList(md1, md2, k4 = True, serials=[], kInfoFiles=[]):
+def getPidList(md1, md2, serials=[], kInfoFiles=[]):
pidlst = []
if kInfoFiles is None:
kInfoFiles = []
- if k4:
+ if serials is None:
+ serials = []
+ if iswindows or isosx:
kInfoFiles.extend(getKindleInfoFiles())
for infoFile in kInfoFiles:
try:
- pidlst = getK4Pids(pidlst, md1, md2, infoFile)
- except Exception, message:
- print("Error getting PIDs from " + infoFile + ": " + message)
+ pidlst.extend(getK4Pids(md1, md2, infoFile))
+ except Exception, e:
+ print u"Error getting PIDs from {0}: {1}".format(os.path.basename(infoFile),e.args[0])
for serialnum in serials:
try:
- pidlst = getKindlePid(pidlst, md1, md2, serialnum)
+ pidlst.extend(getKindlePids(md1, md2, serialnum))
except Exception, message:
- print("Error getting PIDs from " + serialnum + ": " + message)
+ print u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0])
return pidlst
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kindlepid.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kindlepid.py
new file mode 100644
index 0000000..38c5e4e
--- /dev/null
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/kindlepid.py
@@ -0,0 +1,142 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Mobipocket PID calculator v0.4 for Amazon Kindle.
+# Copyright (c) 2007, 2009 Igor Skochinsky <[email protected]>
+# History:
+# 0.1 Initial release
+# 0.2 Added support for generating PID for iPhone (thanks to mbp)
+# 0.3 changed to autoflush stdout, fixed return code usage
+# 0.3 updated for unicode
+
+import sys
+import binascii
+
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
+ def __init__(self, stream):
+ self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
+ def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
+ self.stream.write(data)
+ self.stream.flush()
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+iswindows = sys.platform.startswith('win')
+isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'.
+
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(start, argc.value)]
+ # if we don't have any arguments at all, just pass back script name
+ # this should never happen
+ return [u"mobidedrm.py"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = "utf-8"
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
+
+if sys.hexversion >= 0x3000000:
+ print 'This script is incompatible with Python 3.x. Please install Python 2.7.x.'
+ sys.exit(2)
+
+letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
+
+def crc32(s):
+ return (~binascii.crc32(s,-1))&0xFFFFFFFF
+
+def checksumPid(s):
+ crc = crc32(s)
+ crc = crc ^ (crc >> 16)
+ res = s
+ l = len(letters)
+ for i in (0,1):
+ b = crc & 0xff
+ pos = (b // l) ^ (b % l)
+ res += letters[pos%l]
+ crc >>= 8
+
+ return res
+
+
+def pidFromSerial(s, l):
+ crc = crc32(s)
+
+ arr1 = [0]*l
+ for i in xrange(len(s)):
+ arr1[i%l] ^= ord(s[i])
+
+ crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
+ for i in xrange(l):
+ arr1[i] ^= crc_bytes[i&3]
+
+ pid = ''
+ for i in xrange(l):
+ b = arr1[i] & 0xff
+ pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
+
+ return pid
+
+def cli_main(argv=unicode_argv()):
+ print u"Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky"
+ if len(sys.argv)==2:
+ serial = sys.argv[1]
+ else:
+ print u"Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>"
+ return 1
+ if len(serial)==16:
+ if serial.startswith("B"):
+ print u"Kindle serial number detected"
+ else:
+ print u"Warning: unrecognized serial number. Please recheck input."
+ return 1
+ pid = pidFromSerial(serial.encode("utf-8"),7)+'*'
+ print u"Mobipocket PID for Kindle serial#{0} is {1} ".format(serial,checksumPid(pid))
+ return 0
+ elif len(serial)==40:
+ print u"iPhone serial number (UDID) detected"
+ pid = pidFromSerial(serial.encode("utf-8"),8)
+ print u"Mobipocket PID for iPhone serial#{0} is {1} ".format(serial,checksumPid(pid))
+ return 0
+ print u"Warning: unrecognized serial number. Please recheck input."
+ return 1
+
+
+if __name__ == "__main__":
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
+ sys.exit(cli_main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/mobidedrm.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/mobidedrm.py
index cd993e1..113f57a 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/mobidedrm.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/mobidedrm.py
@@ -1,5 +1,11 @@
-#!/usr/bin/python
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# mobidedrm.py, version 0.38
+# Copyright © 2008 The Dark Reverser
#
+# Modified 2008–2012 by some_updates, DiapDealer and Apprentice Alf
+
# This is a python script. You need a Python interpreter to run it.
# For example, ActiveState Python, which exists for windows.
#
@@ -59,26 +65,78 @@
# 0.35 - add interface to get mobi_version
# 0.36 - fixed problem with TEXtREAd and getBookTitle interface
# 0.37 - Fixed double announcement for stand-alone operation
+# 0.38 - Unicode used wherever possible, cope with absent alfcrypto
-__version__ = '0.37'
+__version__ = u"0.38"
import sys
-
-class Unbuffered:
+import os
+import struct
+import binascii
+try:
+ from alfcrypto import Pukall_Cipher
+except:
+ print u"AlfCrypto not found. Using python PC1 implementation."
+
+# Wrap a stream so that output gets flushed immediately
+# and also make sure that any unicode strings get
+# encoded using "replace" before writing them.
+class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
-sys.stdout=Unbuffered(sys.stdout)
-import os
-import struct
-import binascii
-from alfcrypto import Pukall_Cipher
+iswindows = sys.platform.startswith('win')
+isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'.
+
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(start, argc.value)]
+ # if we don't have any arguments at all, just pass back script name
+ # this should never happen
+ return [u"mobidedrm.py"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = 'utf-8'
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
+
class DrmException(Exception):
pass
@@ -90,40 +148,45 @@ class DrmException(Exception):
# Implementation of Pukall Cipher 1
def PC1(key, src, decryption=True):
- return Pukall_Cipher().PC1(key,src,decryption)
-# sum1 = 0;
-# sum2 = 0;
-# keyXorVal = 0;
-# if len(key)!=16:
-# print "Bad key length!"
-# return None
-# 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;
-# byteXorVal = 0;
-# for j in xrange(8):
-# temp1 ^= wkey[j]
-# sum2 = (sum2+j)*20021 + sum1
-# sum1 = (temp1*346)&0xFFFF
-# sum2 = (sum2+sum1)&0xFFFF
-# temp1 = (temp1*20021+1)&0xFFFF
-# byteXorVal ^= temp1 ^ sum2
-# curByte = ord(src[i])
-# if not decryption:
-# keyXorVal = curByte * 257;
-# curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
-# if decryption:
-# keyXorVal = curByte * 257;
-# for j in xrange(8):
-# wkey[j] ^= keyXorVal;
-# dst+=chr(curByte)
-# return dst
+ # if we can get it from alfcrypto, use that
+ try:
+ return Pukall_Cipher().PC1(key,src,decryption)
+ except NameError:
+ pass
+
+ # use slow python version, since Pukall_Cipher didn't load
+ sum1 = 0;
+ sum2 = 0;
+ keyXorVal = 0;
+ if len(key)!=16:
+ DrmException (u"PC1: Bad key length")
+ 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;
+ byteXorVal = 0;
+ for j in xrange(8):
+ temp1 ^= wkey[j]
+ sum2 = (sum2+j)*20021 + sum1
+ sum1 = (temp1*346)&0xFFFF
+ sum2 = (sum2+sum1)&0xFFFF
+ temp1 = (temp1*20021+1)&0xFFFF
+ byteXorVal ^= temp1 ^ sum2
+ curByte = ord(src[i])
+ if not decryption:
+ keyXorVal = curByte * 257;
+ curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
+ if decryption:
+ keyXorVal = curByte * 257;
+ for j in xrange(8):
+ wkey[j] ^= keyXorVal;
+ dst+=chr(curByte)
+ return dst
def checksumPid(s):
- letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
+ letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
crc = crc ^ (crc >> 16)
res = s
@@ -171,17 +234,24 @@ class MobiBook:
off = self.sections[section][0]
return self.data_file[off:endoff]
- def __init__(self, infile, announce = True):
- if announce:
- print ('MobiDeDrm v%(__version__)s. '
- 'Copyright 2008-2012 The Dark Reverser et al.' % globals())
+ def cleanup(self):
+ # to match function in Topaz book
+ pass
+
+ def __init__(self, infile):
+ print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
+
+ try:
+ from alfcrypto import Pukall_Cipher
+ except:
+ print u"AlfCrypto not found. Using python PC1 implementation."
# initial sanity check on file
self.data_file = file(infile, 'rb').read()
self.mobi_data = ''
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")
+ raise DrmException(u"Invalid file format")
self.magic = self.header[0x3C:0x3C+8]
self.crypto_type = -1
@@ -199,7 +269,7 @@ class MobiBook:
self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
if self.magic == 'TEXtREAd':
- print "Book has format: ", self.magic
+ print u"PalmDoc format book detected."
self.extra_data_flags = 0
self.mobi_length = 0
self.mobi_codepage = 1252
@@ -209,11 +279,11 @@ class MobiBook:
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
- print "MOBI header version = %d, length = %d" %(self.mobi_version, self.mobi_length)
+ print u"MOBI header version {0:d}, header length {1:d}".format(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
+ print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
if (self.compression != 17480):
# multibyte utf8 data is included in the encryption for PalmDoc compression
# so clear that byte so that we leave it to be decrypted.
@@ -223,10 +293,10 @@ class MobiBook:
self.meta_array = {}
try:
exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
- exth = 'NONE'
+ exth = ''
if exth_flag & 0x40:
exth = self.sect[16 + self.mobi_length:]
- if (len(exth) >= 4) and (exth[:4] == 'EXTH'):
+ if (len(exth) >= 12) and (exth[:4] == 'EXTH'):
nitems, = struct.unpack('>I', exth[8:12])
pos = 12
for i in xrange(nitems):
@@ -236,10 +306,10 @@ class MobiBook:
# 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)
+ 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)
+ self.patchSection(0, '\0', 16 + self.mobi_length + pos + 8)
# print type, size, content, content.encode('hex')
pos += size
except:
@@ -265,8 +335,8 @@ class MobiBook:
codec = codec_map[self.mobi_codepage]
if title == '':
title = self.header[:32]
- title = title.split("\0")[0]
- return unicode(title, codec).encode('utf-8')
+ title = title.split('\0')[0]
+ return unicode(title, codec)
def getPIDMetaInfo(self):
rec209 = ''
@@ -297,7 +367,7 @@ class MobiBook:
def parseDRM(self, data, count, pidlist):
found_key = None
- keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
+ 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)
@@ -315,7 +385,7 @@ class MobiBook:
break
if not found_key:
# Then try the default encoding that doesn't require a PID
- pid = "00000000"
+ pid = '00000000'
temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count):
@@ -328,82 +398,90 @@ class MobiBook:
break
return [found_key,pid]
- def getMobiFile(self, outpath):
+ def getFile(self, outpath):
file(outpath,'wb').write(self.mobi_data)
- def getMobiVersion(self):
- return self.mobi_version
+ def getBookType(self):
+ if self.print_replica:
+ return u"Print Replica"
+ if self.mobi_version >= 8:
+ return u"Kindle Format 8"
+ return u"Mobipocket"
- def getPrintReplica(self):
- return self.print_replica
+ def getBookExtension(self):
+ if self.print_replica:
+ return u".azw4"
+ if self.mobi_version >= 8:
+ return u".azw3"
+ return u".mobi"
def processBook(self, pidlist):
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
- print 'Crypto Type is: ', crypto_type
+ print u"Crypto Type is: {0:d}".format(crypto_type)
self.crypto_type = crypto_type
if crypto_type == 0:
- print "This book is not encrypted."
+ print u"This book is not encrypted."
# we must still check for Print Replica
self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
self.mobi_data = self.data_file
return
if crypto_type != 2 and crypto_type != 1:
- raise DrmException("Cannot decode unknown Mobipocket encryption type %d" % crypto_type)
+ raise DrmException(u"Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type))
if 406 in self.meta_array:
data406 = self.meta_array[406]
val406, = struct.unpack('>Q',data406)
if val406 != 0:
- raise DrmException("Cannot decode library or rented ebooks.")
+ raise DrmException(u"Cannot decode library or rented ebooks.")
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])
+ print u"Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2]))
goodpids.append(pid[0:-2])
elif len(pid)==8:
goodpids.append(pid)
if self.crypto_type == 1:
- t1_keyvec = "QDCVEPMU675RUBSZ"
+ 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"
+ pid = '00000000'
found_key = PC1(t1_keyvec, bookkey_data)
else :
# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
if drm_count == 0:
- raise DrmException("Not yet initialised with PID. Must be opened with Mobipocket Reader first.")
+ raise DrmException(u"Encryption not initialised. 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 in " + str(len(goodpids)) + " keys tried. Read the FAQs at Alf's blog. Only if none apply, report this failure for help.")
+ raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(len(goodpids)))
# kill the drm keys
- self.patchSection(0, "\0" * drm_size, drm_ptr)
+ self.patchSection(0, '\0' * drm_size, drm_ptr)
# kill the drm pointers
- self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
+ self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8)
- if pid=="00000000":
- print "File has default encryption, no specific PID."
+ if pid=='00000000':
+ print u"File has default encryption, no specific key needed."
else:
- print "File is encoded with PID "+checksumPid(pid)+"."
+ print u"File is encoded with PID {0}.".format(checksumPid(pid))
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC)
# decrypt sections
- print "Decrypting. Please wait . . .",
+ print u"Decrypting. Please wait . . .",
mobidataList = []
mobidataList.append(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 u".",
# print "record %d, extra_size %d" %(i,extra_size)
decoded_data = PC1(found_key, data[0:len(data) - extra_size])
if i==1:
@@ -414,31 +492,24 @@ class MobiBook:
if self.num_sections > self.records+1:
mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
self.mobi_data = "".join(mobidataList)
- print "done"
+ print u"done"
return
-def getUnencryptedBook(infile,pid,announce=True):
- if not os.path.isfile(infile):
- raise DrmException('Input File Not Found')
- book = MobiBook(infile,announce)
- book.processBook([pid])
- return book.mobi_data
-
-def getUnencryptedBookWithList(infile,pidlist,announce=True):
+def getUnencryptedBook(infile,pidlist):
if not os.path.isfile(infile):
- raise DrmException('Input File Not Found')
- book = MobiBook(infile, announce)
+ raise DrmException(u"Input File Not Found.")
+ book = MobiBook(infile)
book.processBook(pidlist)
return book.mobi_data
-def main(argv=sys.argv):
- print ('MobiDeDrm v%(__version__)s. '
- 'Copyright 2008-2012 The Dark Reverser et al.' % globals())
+def cli_main(argv=unicode_argv()):
+ progname = os.path.basename(argv[0])
if len(argv)<3 or len(argv)>4:
- print "Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
- print "Usage:"
- print " %s <infile> <outfile> [<Comma separated list of PIDs to try>]" % sys.argv[0]
+ print u"MobiDeDrm v{0}.\nCopyright © 2008-2012 The Dark Reverser et al.".format(__version__)
+ print u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
+ print u"Usage:"
+ print u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(os.path.basename(sys.argv[0]))
return 1
else:
infile = argv[1]
@@ -446,15 +517,17 @@ def main(argv=sys.argv):
if len(argv) is 4:
pidlist = argv[3].split(',')
else:
- pidlist = {}
+ pidlist = []
try:
- stripped_file = getUnencryptedBookWithList(infile, pidlist, False)
+ stripped_file = getUnencryptedBook(infile, pidlist)
file(outfile, 'wb').write(stripped_file)
except DrmException, e:
- print "Error: %s" % e
+ print u"MobiDeDRM v{0} Error: {0:s}".format(__version__,e.args[0])
return 1
return 0
-if __name__ == "__main__":
- sys.exit(main())
+if __name__ == '__main__':
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
+ sys.exit(cli_main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/topazextract.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/topazextract.py
index bf2ad47..a343922 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/topazextract.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/topazextract.py
@@ -1,43 +1,90 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
-class Unbuffered:
+# topazextract.py, version ?
+# Mostly written by some_updates based on code from many others
+
+__version__ = '4.8'
+
+import sys
+import os, csv, getopt
+import zlib, zipfile, tempfile, shutil
+import traceback
+from struct import pack
+from struct import unpack
+from alfcrypto import Topaz_Cipher
+
+class SafeUnbuffered:
def __init__(self, stream):
self.stream = stream
+ self.encoding = stream.encoding
+ if self.encoding == None:
+ self.encoding = "utf-8"
def write(self, data):
+ if isinstance(data,unicode):
+ data = data.encode(self.encoding,"replace")
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
-import sys
+iswindows = sys.platform.startswith('win')
+isosx = sys.platform.startswith('darwin')
+
+def unicode_argv():
+ if iswindows:
+ # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
+ # strings.
+
+ # Versions 2.x of Python don't support Unicode in sys.argv on
+ # Windows, with the underlying Windows API instead replacing multi-byte
+ # characters with '?'.
+
+
+ from ctypes import POINTER, byref, cdll, c_int, windll
+ from ctypes.wintypes import LPCWSTR, LPWSTR
+
+ GetCommandLineW = cdll.kernel32.GetCommandLineW
+ GetCommandLineW.argtypes = []
+ GetCommandLineW.restype = LPCWSTR
+
+ CommandLineToArgvW = windll.shell32.CommandLineToArgvW
+ CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
+ CommandLineToArgvW.restype = POINTER(LPWSTR)
+
+ cmd = GetCommandLineW()
+ argc = c_int(0)
+ argv = CommandLineToArgvW(cmd, byref(argc))
+ if argc.value > 0:
+ # Remove Python executable and commands if present
+ start = argc.value - len(sys.argv)
+ return [argv[i] for i in
+ xrange(start, argc.value)]
+ # if we don't have any arguments at all, just pass back script name
+ # this should never happen
+ return [u"mobidedrm.py"]
+ else:
+ argvencoding = sys.stdin.encoding
+ if argvencoding == None:
+ argvencoding = 'utf-8'
+ return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
if 'calibre' in sys.modules:
inCalibre = True
+ from calibre_plugins.k4mobidedrm import kgenpids
else:
inCalibre = False
+ import kgenpids
-buildXML = False
-import os, csv, getopt
-import zlib, zipfile, tempfile, shutil
-from struct import pack
-from struct import unpack
-from alfcrypto import Topaz_Cipher
-
-class TpzDRMError(Exception):
+class DrmException(Exception):
pass
-# local support routines
-if inCalibre:
- from calibre_plugins.k4mobidedrm import kgenpids
-else:
- import kgenpids
-
# recursive zip creation support routine
def zipUpDir(myzip, tdir, localname):
currentdir = tdir
- if localname != "":
+ if localname != u"":
currentdir = os.path.join(currentdir,localname)
list = os.listdir(currentdir)
for file in list:
@@ -73,7 +120,7 @@ def bookReadEncodedNumber(fo):
# Get a length prefixed string from file
def bookReadString(fo):
stringLength = bookReadEncodedNumber(fo)
- return unpack(str(stringLength)+"s",fo.read(stringLength))[0]
+ return unpack(str(stringLength)+'s',fo.read(stringLength))[0]
#
# crypto routines
@@ -112,13 +159,13 @@ def decryptRecord(data,PID):
# Try to decrypt a dkey record (contains the bookPID)
def decryptDkeyRecord(data,PID):
record = decryptRecord(data,PID)
- fields = unpack("3sB8sB8s3s",record)
- if fields[0] != "PID" or fields[5] != "pid" :
- raise TpzDRMError("Didn't find PID magic numbers in record")
+ fields = unpack('3sB8sB8s3s',record)
+ if fields[0] != 'PID' or fields[5] != 'pid' :
+ raise DrmException(u"Didn't find PID magic numbers in record")
elif fields[1] != 8 or fields[3] != 8 :
- raise TpzDRMError("Record didn't contain correct length fields")
+ raise DrmException(u"Record didn't contain correct length fields")
elif fields[2] != PID :
- raise TpzDRMError("Record didn't contain PID")
+ raise DrmException(u"Record didn't contain PID")
return fields[4]
# Decrypt all dkey records (contain the book PID)
@@ -131,11 +178,11 @@ def decryptDkeyRecords(data,PID):
try:
key = decryptDkeyRecord(data[1:length+1],PID)
records.append(key)
- except TpzDRMError:
+ except DrmException:
pass
data = data[1+length:]
if len(records) == 0:
- raise TpzDRMError("BookKey Not Found")
+ raise DrmException(u"BookKey Not Found")
return records
@@ -148,9 +195,9 @@ class TopazBook:
self.bookHeaderRecords = {}
self.bookMetadata = {}
self.bookKey = None
- magic = unpack("4s",self.fo.read(4))[0]
+ magic = unpack('4s',self.fo.read(4))[0]
if magic != 'TPZ0':
- raise TpzDRMError("Parse Error : Invalid Header, not a Topaz file")
+ raise DrmException(u"Parse Error : Invalid Header, not a Topaz file")
self.parseTopazHeaders()
self.parseMetadata()
@@ -167,7 +214,7 @@ class TopazBook:
# Read and parse one header record at the current book file position and return the associated data
# [[offset,decompressedLength,compressedLength],...]
if ord(self.fo.read(1)) != 0x63:
- raise TpzDRMError("Parse Error : Invalid Header")
+ raise DrmException(u"Parse Error : Invalid Header")
tag = bookReadString(self.fo)
record = bookReadHeaderRecordData()
return [tag,record]
@@ -177,15 +224,15 @@ class TopazBook:
# print result[0], result[1]
self.bookHeaderRecords[result[0]] = result[1]
if ord(self.fo.read(1)) != 0x64 :
- raise TpzDRMError("Parse Error : Invalid Header")
+ raise DrmException(u"Parse Error : Invalid Header")
self.bookPayloadOffset = self.fo.tell()
def parseMetadata(self):
# Parse the metadata record from the book payload and return a list of [key,values]
- self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords["metadata"][0][0])
+ self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords['metadata'][0][0])
tag = bookReadString(self.fo)
- if tag != "metadata" :
- raise TpzDRMError("Parse Error : Record Names Don't Match")
+ if tag != 'metadata' :
+ raise DrmException(u"Parse Error : Record Names Don't Match")
flags = ord(self.fo.read(1))
nbRecords = ord(self.fo.read(1))
# print nbRecords
@@ -210,7 +257,7 @@ class TopazBook:
title = ''
if 'Title' in self.bookMetadata:
title = self.bookMetadata['Title']
- return title
+ return title.decode('utf-8')
def setBookKey(self, key):
self.bookKey = key
@@ -223,13 +270,13 @@ class TopazBook:
try:
recordOffset = self.bookHeaderRecords[name][index][0]
except:
- raise TpzDRMError("Parse Error : Invalid Record, record not found")
+ raise DrmException("Parse Error : Invalid Record, record not found")
self.fo.seek(self.bookPayloadOffset + recordOffset)
tag = bookReadString(self.fo)
if tag != name :
- raise TpzDRMError("Parse Error : Invalid Record, record name doesn't match")
+ raise DrmException("Parse Error : Invalid Record, record name doesn't match")
recordIndex = bookReadEncodedNumber(self.fo)
if recordIndex < 0 :
@@ -237,7 +284,7 @@ class TopazBook:
recordIndex = -recordIndex -1
if recordIndex != index :
- raise TpzDRMError("Parse Error : Invalid Record, index doesn't match")
+ raise DrmException("Parse Error : Invalid Record, index doesn't match")
if (self.bookHeaderRecords[name][index][2] > 0):
compressed = True
@@ -250,7 +297,7 @@ class TopazBook:
ctx = topazCryptoInit(self.bookKey)
record = topazCryptoDecrypt(record,ctx)
else :
- raise TpzDRMError("Error: Attempt to decrypt without bookKey")
+ raise DrmException("Error: Attempt to decrypt without bookKey")
if compressed:
record = zlib.decompress(record)
@@ -262,12 +309,12 @@ class TopazBook:
fixedimage=True
try:
keydata = self.getBookPayloadRecord('dkey', 0)
- except TpzDRMError, e:
- print "no dkey record found, book may not be encrypted"
- print "attempting to extrct files without a book key"
+ except DrmException, e:
+ print u"no dkey record found, book may not be encrypted"
+ print u"attempting to extrct files without a book key"
self.createBookDirectory()
self.extractFiles()
- print "Successfully Extracted Topaz contents"
+ print u"Successfully Extracted Topaz contents"
if inCalibre:
from calibre_plugins.k4mobidedrm import genbook
else:
@@ -275,7 +322,7 @@ class TopazBook:
rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0:
- print "\nBook Successfully generated"
+ print u"Book Successfully generated."
return rv
# try each pid to decode the file
@@ -283,25 +330,25 @@ class TopazBook:
for pid in pidlst:
# use 8 digit pids here
pid = pid[0:8]
- print "\nTrying: ", pid
+ print u"Trying: {0}".format(pid)
bookKeys = []
data = keydata
try:
bookKeys+=decryptDkeyRecords(data,pid)
- except TpzDRMError, e:
+ except DrmException, e:
pass
else:
bookKey = bookKeys[0]
- print "Book Key Found!"
+ print u"Book Key Found! ({0})".format(bookKey.encode('hex'))
break
if not bookKey:
- raise TpzDRMError("Topaz Book. No key found in " + str(len(pidlst)) + " keys tried. Read the FAQs at Alf's blog. Only if none apply, report this failure for help.")
+ raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(len(pidlst)))
self.setBookKey(bookKey)
self.createBookDirectory()
self.extractFiles()
- print "Successfully Extracted Topaz contents"
+ print u"Successfully Extracted Topaz contents"
if inCalibre:
from calibre_plugins.k4mobidedrm import genbook
else:
@@ -309,7 +356,7 @@ class TopazBook:
rv = genbook.generateBook(self.outdir, raw, fixedimage)
if rv == 0:
- print "\nBook Successfully generated"
+ print u"Book Successfully generated"
return rv
def createBookDirectory(self):
@@ -317,16 +364,16 @@ class TopazBook:
# create output directory structure
if not os.path.exists(outdir):
os.makedirs(outdir)
- destdir = os.path.join(outdir,'img')
+ destdir = os.path.join(outdir,u"img")
if not os.path.exists(destdir):
os.makedirs(destdir)
- destdir = os.path.join(outdir,'color_img')
+ destdir = os.path.join(outdir,u"color_img")
if not os.path.exists(destdir):
os.makedirs(destdir)
- destdir = os.path.join(outdir,'page')
+ destdir = os.path.join(outdir,u"page")
if not os.path.exists(destdir):
os.makedirs(destdir)
- destdir = os.path.join(outdir,'glyphs')
+ destdir = os.path.join(outdir,u"glyphs")
if not os.path.exists(destdir):
os.makedirs(destdir)
@@ -334,149 +381,148 @@ class TopazBook:
outdir = self.outdir
for headerRecord in self.bookHeaderRecords:
name = headerRecord
- if name != "dkey" :
- ext = '.dat'
- if name == 'img' : ext = '.jpg'
- if name == 'color' : ext = '.jpg'
- print "\nProcessing Section: %s " % name
+ if name != 'dkey':
+ ext = u".dat"
+ if name == 'img': ext = u".jpg"
+ if name == 'color' : ext = u".jpg"
+ print u"Processing Section: {0}\n. . .".format(name),
for index in range (0,len(self.bookHeaderRecords[name])) :
- fnum = "%04d" % index
- fname = name + fnum + ext
+ fname = u"{0}{1:04d}{2}".format(name,index,ext)
destdir = outdir
if name == 'img':
- destdir = os.path.join(outdir,'img')
+ destdir = os.path.join(outdir,u"img")
if name == 'color':
- destdir = os.path.join(outdir,'color_img')
+ destdir = os.path.join(outdir,u"color_img")
if name == 'page':
- destdir = os.path.join(outdir,'page')
+ destdir = os.path.join(outdir,u"page")
if name == 'glyphs':
- destdir = os.path.join(outdir,'glyphs')
+ destdir = os.path.join(outdir,u"glyphs")
outputFile = os.path.join(destdir,fname)
- print ".",
+ print u".",
record = self.getBookPayloadRecord(name,index)
if record != '':
file(outputFile, 'wb').write(record)
- print " "
+ print u" "
- def getHTMLZip(self, zipname):
+ def getFile(self, zipname):
htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
- htmlzip.write(os.path.join(self.outdir,'book.html'),'book.html')
- htmlzip.write(os.path.join(self.outdir,'book.opf'),'book.opf')
- if os.path.isfile(os.path.join(self.outdir,'cover.jpg')):
- htmlzip.write(os.path.join(self.outdir,'cover.jpg'),'cover.jpg')
- htmlzip.write(os.path.join(self.outdir,'style.css'),'style.css')
- zipUpDir(htmlzip, self.outdir, 'img')
+ htmlzip.write(os.path.join(self.outdir,u"book.html"),u"book.html")
+ htmlzip.write(os.path.join(self.outdir,u"book.opf"),u"book.opf")
+ if os.path.isfile(os.path.join(self.outdir,u"cover.jpg")):
+ htmlzip.write(os.path.join(self.outdir,u"cover.jpg"),u"cover.jpg")
+ htmlzip.write(os.path.join(self.outdir,u"style.css"),u"style.css")
+ zipUpDir(htmlzip, self.outdir, u"img")
htmlzip.close()
+ def getBookType(self):
+ return u"Topaz"
+
+ def getBookExtension(self):
+ return u".htmlz"
+
def getSVGZip(self, zipname):
svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
- svgzip.write(os.path.join(self.outdir,'index_svg.xhtml'),'index_svg.xhtml')
- zipUpDir(svgzip, self.outdir, 'svg')
- zipUpDir(svgzip, self.outdir, 'img')
+ svgzip.write(os.path.join(self.outdir,u"index_svg.xhtml"),u"index_svg.xhtml")
+ zipUpDir(svgzip, self.outdir, u"svg")
+ zipUpDir(svgzip, self.outdir, u"img")
svgzip.close()
- def getXMLZip(self, zipname):
- xmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
- targetdir = os.path.join(self.outdir,'xml')
- zipUpDir(xmlzip, targetdir, '')
- zipUpDir(xmlzip, self.outdir, 'img')
- xmlzip.close()
-
def cleanup(self):
if os.path.isdir(self.outdir):
shutil.rmtree(self.outdir, True)
def usage(progname):
- print "Removes DRM protection from Topaz ebooks and extract the contents"
- print "Usage:"
- print " %s [-k <kindle.info>] [-p <pidnums>] [-s <kindleSerialNumbers>] <infile> <outdir> " % progname
-
+ print u"Removes DRM protection from Topaz ebooks and extracts the contents"
+ print u"Usage:"
+ print u" {0} [-k <kindle.info>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)
# Main
-def main(argv=sys.argv):
- global buildXML
+def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0])
- k4 = False
- pids = []
- serials = []
- kInfoFiles = []
+ print u"TopazExtract v{0}.".format(__version__)
try:
- opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
+ opts, args = getopt.getopt(sys.argv[1:], "k:p:s:x")
except getopt.GetoptError, err:
- print str(err)
+ print u"Error in options or arguments: {0}".format(err.args[0])
usage(progname)
return 1
if len(args)<2:
usage(progname)
return 1
+ infile = args[0]
+ outdir = args[1]
+ if not os.path.isfile(infile):
+ print u"Input File {0} Does Not Exist.".format(infile)
+ return 1
+
+ if not os.path.exists(outdir):
+ print u"Output Directory {0} Does Not Exist.".format(outdir)
+ return 1
+
+ kInfoFiles = []
+ serials = []
+ pids = []
+
for o, a in opts:
- if o == "-k":
+ if o == '-k':
if a == None :
- print "Invalid parameter for -k"
- return 1
+ raise DrmException("Invalid parameter for -k")
kInfoFiles.append(a)
- if o == "-p":
+ if o == '-p':
if a == None :
- print "Invalid parameter for -p"
- return 1
+ raise DrmException("Invalid parameter for -p")
pids = a.split(',')
- if o == "-s":
+ if o == '-s':
if a == None :
- print "Invalid parameter for -s"
- return 1
- serials = a.split(',')
- k4 = True
-
- infile = args[0]
- outdir = args[1]
-
- if not os.path.isfile(infile):
- print "Input File Does Not Exist"
- return 1
+ raise DrmException("Invalid parameter for -s")
+ serials = [serial.replace(" ","") for serial in a.split(',')]
bookname = os.path.splitext(os.path.basename(infile))[0]
tb = TopazBook(infile)
title = tb.getBookTitle()
- print "Processing Book: ", title
- keysRecord, keysRecordRecord = tb.getPIDMetaInfo()
- pids.extend(kgenpids.getPidList(keysRecord, keysRecordRecord, k4, serials, kInfoFiles))
+ print u"Processing Book: {0}".format(title)
+ md1, md2 = tb.getPIDMetaInfo()
+ pids.extend(kgenpids.getPidList(md1, md2, serials, kInfoFiles))
try:
- print "Decrypting Book"
+ print u"Decrypting Book"
tb.processBook(pids)
- print " Creating HTML ZIP Archive"
- zipname = os.path.join(outdir, bookname + '_nodrm' + '.htmlz')
- tb.getHTMLZip(zipname)
+ print u" Creating HTML ZIP Archive"
+ zipname = os.path.join(outdir, bookname + u"_nodrm.htmlz")
+ tb.getFile(zipname)
- print " Creating SVG ZIP Archive"
- zipname = os.path.join(outdir, bookname + '_SVG' + '.zip')
+ print u" Creating SVG ZIP Archive"
+ zipname = os.path.join(outdir, bookname + u"_SVG.zip")
tb.getSVGZip(zipname)
- if buildXML:
- print " Creating XML ZIP Archive"
- zipname = os.path.join(outdir, bookname + '_XML' + '.zip')
- tb.getXMLZip(zipname)
-
# removing internal temporary directory of pieces
tb.cleanup()
- except TpzDRMError, e:
- print str(e)
- # tb.cleanup()
+ except DrmException, e:
+ print u"Decryption failed\n{0}".format(traceback.format_exc())
+
+ try:
+ tb.cleanup()
+ except:
+ pass
return 1
except Exception, e:
- print str(e)
- # tb.cleanup
+ print u"Decryption failed\m{0}".format(traceback.format_exc())
+ try:
+ tb.cleanup()
+ except:
+ pass
return 1
return 0
if __name__ == '__main__':
- sys.stdout=Unbuffered(sys.stdout)
- sys.exit(main())
+ sys.stdout=SafeUnbuffered(sys.stdout)
+ sys.stderr=SafeUnbuffered(sys.stderr)
+ sys.exit(cli_main())
diff --git a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/zipfix.py b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/zipfix.py
index c7921f2..eaee20d 100644
--- a/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/zipfix.py
+++ b/DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/zipfix.py
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
import sys
import zlib
@@ -27,14 +28,10 @@ class fixZip:
self.ztype = 'zip'
if zinput.lower().find('.epub') >= 0 :
self.ztype = 'epub'
- print "opening input"
self.inzip = zipfilerugged.ZipFile(zinput,'r')
- print "opening outout"
self.outzip = zipfilerugged.ZipFile(zoutput,'w')
- print "opening input as raw file"
# open the input zip for reading only as a raw file
self.bzf = file(zinput,'rb')
- print "finished initialising"
def getlocalname(self, zi):
local_header_offset = zi.header_offset
diff --git a/DeDRM_Windows_Application/DeDRM_ReadMe.txt b/DeDRM_Windows_Application/DeDRM_ReadMe.txt
index 2c73c84..df13eb5 100644
--- a/DeDRM_Windows_Application/DeDRM_ReadMe.txt
+++ b/DeDRM_Windows_Application/DeDRM_ReadMe.txt
@@ -1,9 +1,9 @@
-ReadMe_DeDRM_v5.4.1_WinApp
------------------------
+ReadMe_DeDRM_v5.5_WinApp
+========================
-DeDRM_v5.4.1_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto the DeDRM_Drop_Target to have the DRM removed. It repackages the"tools" python software in one easy to use program that remembers preferences and settings.
+DeDRM_v5.5_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto the DeDRM_Drop_Target to have the DRM removed. It repackages all the "tools" python software in one easy to use program that remembers preferences and settings.
-It should work out of the box with Kindle for PC ebooks and Adobe Adept epub and pdf ebooks.
+It will work without manual configuration for Kindle for PC ebooks and Adobe Adept epub and pdf ebooks.
To remove the DRM from standalone Kindle ebooks, eReader pdb ebooks, Barnes and Noble epubs, and Mobipocket ebooks requires the user to double-click the DeDRM_Drop_Target and set some additional Preferences including:
@@ -16,14 +16,16 @@ Once these preferences have been set, the user can simply drag and drop ebooks o
This program requires that a 32 bit version of Python 2.X (tested with Python 2.5 through Python 2.7) and PyCrypto be installed on your computer before it will work. See below for where to get theese programs for Windows.
+NB Although the individual scripts have been updated to work with unicode file names, the Windows DeDRM script has not yet been updated for technical reasons. Therefore, if you try to use it with paths or file names that contain non-ASCII characters, it might not work.
+
Installation
------------
0. If you don't already have a correct version of Python and PyCrypto installed, follow the "Installing Python on Windows" and "Installing PyCrypto on Windows" sections below before continuing.
-1. Drag the DeDRM_5.4.1 folder from tools_v5.4.1/DeDRM_Applications/Windows to your "My Documents" folder.
+1. Drag the DeDRM_5.5 folder from tools_v5.5/DeDRM_Applications/Windows to your "My Documents" folder.
-2. Open the DeDRM_5.4.1 folder you've just dragged, and make a short-cut of the DeDRM_Drop_Target.bat file (right-click/Create Shortcut). Drag the shortcut file onto your Desktop.
+2. Open the DeDRM_5.5 folder you've just dragged, and make a short-cut of the DeDRM_Drop_Target.bat file (right-click/Create Shortcut). Drag the shortcut file onto your Desktop.
3. To set the preferences simply double-click on your just created short-cut.