mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-21 05:18:56 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4fc10395b | ||
|
|
a30aace99c | ||
|
|
b1feca321d | ||
|
|
74a4c894cb | ||
|
|
5a502dbce3 | ||
|
|
a399d3b7bd | ||
|
|
cd2d74601a | ||
|
|
51919284ca | ||
|
|
d586f74faa |
Binary file not shown.
@@ -24,9 +24,11 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>droplet</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>DeDRM AppleScript 6.0.1. Written 2010–2013 by Apprentice Alf and others.</string>
|
||||
<string>DeDRM AppleScript 6.1.0. Written 2010–2014 by Apprentice Alf and others.</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>DeDRM</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.apple.ScriptEditor.id.707CCCD5-0C6C-4BEB-B67C-B6E866ADE85A</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
@@ -34,7 +36,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>6.0.1</string>
|
||||
<string>6.1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>dplt</string>
|
||||
<key>LSRequiresCarbon</key>
|
||||
@@ -48,9 +50,9 @@
|
||||
<key>name</key>
|
||||
<string>ScriptWindowState</string>
|
||||
<key>positionOfDivider</key>
|
||||
<real>0</real>
|
||||
<real>0.0</real>
|
||||
<key>savedFrame</key>
|
||||
<string>1616 -35 765 818 1440 -180 1920 1080 </string>
|
||||
<string>85 100 1002 753 0 0 1440 878 </string>
|
||||
<key>selectedTabView</key>
|
||||
<string>event log</string>
|
||||
</dict>
|
||||
|
||||
@@ -44,11 +44,16 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.der’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.</p>
|
||||
|
||||
<h3>Linux Users: WINEPREFIX</h3>
|
||||
|
||||
<p>Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are use Adobe Digital Editions under Wine, and your wine installation containing Adobe Digital Editions isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.</p>
|
||||
|
||||
<h3>Importing Existing Keyfiles:</h3>
|
||||
|
||||
<p>At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.der’ key files. Key files might come from being exported from this or older plugins, or may have been generated using the adobekey.pyw script running under Wine on Linux systems.</p>
|
||||
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes will only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ p {margin-top: 0}
|
||||
|
||||
<body>
|
||||
|
||||
<h1>DeDRM Plugin <span class="version">(v6.0.0)</span></h1>
|
||||
<h1>DeDRM Plugin <span class="version">(v6.0.9)</span></h1>
|
||||
|
||||
<p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
|
||||
|
||||
|
||||
@@ -44,6 +44,10 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.der’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.</p>
|
||||
|
||||
<h3>Linux Users: WINEPREFIX</h3>
|
||||
|
||||
<p>Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are use Kindle for PC under Wine, and your wine installation containing Kindle for PC isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.</p>
|
||||
|
||||
<h3>Importing Existing Keyfiles:</h3>
|
||||
|
||||
<p>At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.k4i’ key files. Key files might come from being exported from this plugin, or may have been generated using the kindlekey.pyw script running under Wine on Linux systems.</p>
|
||||
|
||||
Binary file not shown.
@@ -26,14 +26,23 @@ __docformat__ = 'restructuredtext en'
|
||||
#
|
||||
# Revision history:
|
||||
# 6.0.0 - Initial release
|
||||
# 6.0.1 - Bug Fixes for Windows App
|
||||
# 6.0.1 - Bug Fixes for Windows App, Kindle for Mac and Windows Adobe Digital Editions
|
||||
# 6.0.2 - Restored call to Wine to get Kindle for PC keys, added for ADE
|
||||
# 6.0.3 - Fixes for Kindle for Mac and Windows non-ascii user names
|
||||
# 6.0.4 - Fixes for stand-alone scripts and applications
|
||||
# and pdb files in plugin and initial conversion of prefs.
|
||||
# 6.0.5 - Fix a key issue
|
||||
# 6.0.6 - Fix up an incorrect function call
|
||||
# 6.0.7 - Error handling for incomplete PDF metadata
|
||||
# 6.0.8 - Fixes a Wine key issue and topaz support
|
||||
# 6.0.9 - Ported to work with newer versions of Calibre (moved to Qt5). Still supports older Qt4 versions.
|
||||
|
||||
"""
|
||||
Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 0, 1)
|
||||
PLUGIN_VERSION_TUPLE = (6, 0, 9)
|
||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||
@@ -83,9 +92,11 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
def initialize(self):
|
||||
# convert old preferences, if necessary.
|
||||
import calibre_plugins.dedrm.config
|
||||
|
||||
config.convertprefs()
|
||||
try:
|
||||
from calibre_plugins.dedrm.prefs import convertprefs
|
||||
convertprefs()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
"""
|
||||
Dynamic modules can't be imported/loaded from a zipfile... so this routine
|
||||
@@ -101,7 +112,7 @@ class DeDRM(FileTypePlugin):
|
||||
elif isosx:
|
||||
names = [u"libalfcrypto.dylib"]
|
||||
else:
|
||||
names = [u"libalfcrypto32.so",u"libalfcrypto64.so"]
|
||||
names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"]
|
||||
lib_dict = self.load_resources(names)
|
||||
self.pluginsdir = os.path.join(config_dir,u"plugins")
|
||||
if not os.path.exists(self.pluginsdir):
|
||||
@@ -112,7 +123,7 @@ class DeDRM(FileTypePlugin):
|
||||
self.helpdir = os.path.join(self.maindir,u"help")
|
||||
if not os.path.exists(self.helpdir):
|
||||
os.mkdir(self.helpdir)
|
||||
self.alfdir = os.path.join(self.maindir,u"alfcrypto")
|
||||
self.alfdir = os.path.join(self.maindir,u"libraryfiles")
|
||||
if not os.path.exists(self.alfdir):
|
||||
os.mkdir(self.alfdir)
|
||||
for entry, data in lib_dict.items():
|
||||
@@ -129,25 +140,27 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
inf = self.temporary_file(u".epub")
|
||||
try:
|
||||
print u"{0} v{1}: Verifying zip archive integrity.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print u"{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
fr = zipfix.fixZip(path_to_ebook, inf.name)
|
||||
fr.fix()
|
||||
except Exception, e:
|
||||
print u"{0} v{1}: Error \'{2}\' when checking zip archive.".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||
print u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||
raise Exception(e)
|
||||
|
||||
# import the decryption keys
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
|
||||
# import the Barnes & Noble ePub handler
|
||||
import calibre_plugins.dedrm.ignobleepub as ignobleepub
|
||||
|
||||
|
||||
#check the book
|
||||
if ignobleepub.ignobleBook(inf.name):
|
||||
print u"{0} v{1}: “{2}” is a secure Barnes & Noble ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: “{2}” is a secure Barnes & Noble ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
for keyname, userkey in config.dedrmprefs['bandnkeys'].items():
|
||||
for keyname, userkey in dedrmprefs['bandnkeys'].items():
|
||||
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)
|
||||
of = self.temporary_file(u".epub")
|
||||
@@ -162,7 +175,7 @@ class DeDRM(FileTypePlugin):
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
|
||||
@@ -171,10 +184,10 @@ class DeDRM(FileTypePlugin):
|
||||
import calibre_plugins.dedrm.ineptepub as ineptepub
|
||||
|
||||
if ineptepub.adeptBook(inf.name):
|
||||
print u"{0} v{1}: {2} is a secure Adobe Adept ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: {2} is a secure Adobe Adept ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
for keyname, userkeyhex in config.dedrmprefs['adeptkeys'].items():
|
||||
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
||||
userkey = userkeyhex.decode('hex')
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
|
||||
of = self.temporary_file(u".epub")
|
||||
@@ -192,54 +205,64 @@ class DeDRM(FileTypePlugin):
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||
|
||||
# perhaps we need to get a new default ADE key
|
||||
if iswindows or isosx:
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
# get the default Adobe keys
|
||||
import calibre_plugins.dedrm.adobekey as adobe
|
||||
# get the default Adobe keys
|
||||
defaultkeys = []
|
||||
|
||||
try:
|
||||
if iswindows or isosx:
|
||||
from calibre_plugins.dedrm.adobekey import adeptkeys
|
||||
|
||||
defaultkeys = adeptkeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
||||
|
||||
self.default_key = defaultkeys[0]
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.default_key = u""
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
defaultkeys = adobe.adeptkeys()
|
||||
except:
|
||||
defaultkeys = []
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".epub")
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in config.dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
try:
|
||||
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
||||
except:
|
||||
result = 1
|
||||
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".epub")
|
||||
of.close()
|
||||
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
dedrmprefs.writeprefs()
|
||||
except:
|
||||
result = 1
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
of.close()
|
||||
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
config.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
config.writeprefs()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
@@ -247,16 +270,17 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
# Not a Barnes & Noble nor an Adobe Adept
|
||||
# Import the fixed epub.
|
||||
print u"{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
return inf.name
|
||||
|
||||
def PDFDecrypt(self,path_to_ebook):
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
import calibre_plugins.dedrm.ineptpdf
|
||||
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
print u"{0} v{1}: {2} is a PDF ebook.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
for keyname, userkeyhex in config.dedrmprefs['adeptkeys'].items():
|
||||
print u"{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
||||
userkey = userkeyhex.decode('hex')
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
|
||||
of = self.temporary_file(u".pdf")
|
||||
@@ -275,56 +299,68 @@ class DeDRM(FileTypePlugin):
|
||||
return of.name
|
||||
|
||||
# perhaps we need to get a new default ADE key
|
||||
if iswindows or isosx:
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
# get the default Adobe keys
|
||||
# get the default Adobe keys
|
||||
defaultkeys = []
|
||||
|
||||
if iswindows or isosx:
|
||||
import calibre_plugins.dedrm.adobekey as adobe
|
||||
|
||||
try:
|
||||
defaultkeys = adobe.adeptkeys()
|
||||
except:
|
||||
defaultkeys = []
|
||||
pass
|
||||
else:
|
||||
# linux
|
||||
try:
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in config.dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
||||
except:
|
||||
pass
|
||||
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".pdf")
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".pdf")
|
||||
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
try:
|
||||
result = ineptepdf.decryptBook(userkey, inf.name, of.name)
|
||||
except:
|
||||
result = 1
|
||||
|
||||
of.close()
|
||||
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
result = ineptepdf.decryptBook(userkey, inf.name, of.name)
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
dedrmprefs.writeprefs()
|
||||
except:
|
||||
result = 1
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
of.close()
|
||||
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
config.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
config.writeprefs()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
|
||||
|
||||
|
||||
def KindleMobiDecrypt(self,path_to_ebook):
|
||||
|
||||
# add the alfcrypto directory to sys.path so alfcrypto.py
|
||||
@@ -333,42 +369,53 @@ class DeDRM(FileTypePlugin):
|
||||
# Had to move this import here so the custom libs can be
|
||||
# extracted to the appropriate places beforehand these routines
|
||||
# look for them.
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
import calibre_plugins.dedrm.k4mobidedrm
|
||||
|
||||
pids = config.dedrmprefs['pids']
|
||||
serials = config.dedrmprefs['serials']
|
||||
kindleDatabases = config.dedrmprefs['kindlekeys'].items()
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
pids = dedrmprefs['pids']
|
||||
serials = dedrmprefs['serials']
|
||||
kindleDatabases = dedrmprefs['kindlekeys'].items()
|
||||
|
||||
try:
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,serials,pids,self.starttime)
|
||||
except Exception, e:
|
||||
decoded = False
|
||||
# perhaps we need to get a new default Kindle for Mac/PC key
|
||||
if iswindows or isosx:
|
||||
print u"{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0])
|
||||
print u"{0} v{1}: Looking for new default Kindle Key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
import calibre_plugins.dedrm.kindlekey as amazon
|
||||
defaultkeys = []
|
||||
print u"{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0])
|
||||
print u"{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
try:
|
||||
if iswindows or isosx:
|
||||
from calibre_plugins.dedrm.kindlekey import kindlekeys
|
||||
|
||||
defaultkeys = kindlekeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,u"kindlekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix'])
|
||||
except:
|
||||
pass
|
||||
|
||||
newkeys = {}
|
||||
for i,keyvalue in enumerate(defaultkeys):
|
||||
keyname = u"default_key_{0:d}".format(i+1)
|
||||
if keyvalue not in dedrmprefs['kindlekeys'].values():
|
||||
newkeys[keyname] = keyvalue
|
||||
if len(newkeys) > 0:
|
||||
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||
try:
|
||||
defaultkeys = amazon.kindlekeys()
|
||||
except:
|
||||
defaultkeys = []
|
||||
newkeys = {}
|
||||
for i,keyvalue in enumerate(defaultkeys):
|
||||
keyname = u"default_key_{0:d}".format(i+1)
|
||||
if keyvalue not in config.dedrmprefs['kindlekeys'].values():
|
||||
newkeys[keyname] = keyvalue
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
|
||||
decoded = True
|
||||
# store the new successful keys in the defaults
|
||||
for keyvalue in newkeys.values():
|
||||
config.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
|
||||
config.writeprefs()
|
||||
except Exception, e:
|
||||
pass
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
|
||||
decoded = True
|
||||
# store the new successful keys in the defaults
|
||||
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||
for keyvalue in newkeys.values():
|
||||
dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
|
||||
dedrmprefs.writeprefs()
|
||||
except Exception, e:
|
||||
pass
|
||||
if not decoded:
|
||||
#if you reached here then no luck raise and exception
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
@@ -384,11 +431,12 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
def eReaderDecrypt(self,path_to_ebook):
|
||||
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
import calibre_plugins.dedrm.erdr2pml
|
||||
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
for keyname, userkey in config.dedrmprefs['ereaderkeys'].items():
|
||||
for keyname, userkey in dedrmprefs['ereaderkeys'].items():
|
||||
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)
|
||||
of = self.temporary_file(u".pmlz")
|
||||
@@ -403,7 +451,7 @@ class DeDRM(FileTypePlugin):
|
||||
if result == 0:
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
|
||||
@@ -415,7 +463,7 @@ class DeDRM(FileTypePlugin):
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
|
||||
print u"{0} v{1}: Trying to decrypt {2}.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: Trying to decrypt {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
self.starttime = time.time()
|
||||
|
||||
booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
|
||||
@@ -434,7 +482,7 @@ class DeDRM(FileTypePlugin):
|
||||
# Adobe Adept or B&N ePub
|
||||
decrypted_ebook = self.ePubDecrypt(path_to_ebook)
|
||||
else:
|
||||
print u"Unknown booktype {0}. Passing back to calibre unchanged.".format(booktype)
|
||||
print u"Unknown booktype {0}. Passing back to calibre unchanged".format(booktype)
|
||||
return path_to_ebook
|
||||
print u"{0} v{1}: Successfully decrypted book after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
return decrypted_ebook
|
||||
@@ -445,7 +493,7 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
def config_widget(self):
|
||||
import calibre_plugins.dedrm.config as config
|
||||
return config.ConfigWidget(self.plugin_path)
|
||||
return config.ConfigWidget(self.plugin_path, self.alfdir)
|
||||
|
||||
def save_settings(self, config_widget):
|
||||
config_widget.save_settings()
|
||||
|
||||
@@ -47,13 +47,14 @@ from __future__ import with_statement
|
||||
# 5.7 - Unicode support added, renamed adobekey from ineptkey
|
||||
# 5.8 - Added getkey interface for Windows DeDRM application
|
||||
# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.0 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Retrieve Adobe ADEPT user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '5.9'
|
||||
__version__ = '6.0'
|
||||
|
||||
import sys, os, struct, getopt
|
||||
|
||||
@@ -402,9 +403,11 @@ if iswindows:
|
||||
aes = AES(keykey)
|
||||
userkey = aes.decrypt(userkey)
|
||||
userkey = userkey[26:-ord(userkey[-1])]
|
||||
#print "found key:",userkey.encode('hex')
|
||||
keys.append(userkey)
|
||||
if len(keys) == 0:
|
||||
raise ADEPTError('Could not locate privateLicenseKey')
|
||||
print u"Found {0:d} keys".format(len(keys))
|
||||
return keys
|
||||
|
||||
|
||||
@@ -485,6 +488,8 @@ def usage(progname):
|
||||
print u" {0:s} [-h] [<outpath>]".format(progname)
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__)
|
||||
@@ -541,10 +546,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
@@ -577,7 +585,7 @@ def gui_main():
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except DrmException, e:
|
||||
except ADEPTError, e:
|
||||
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||
except Exception:
|
||||
root.wm_state('normal')
|
||||
@@ -591,7 +599,5 @@ 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())
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python
|
||||
#fileencoding: utf-8
|
||||
|
||||
import os
|
||||
import sys
|
||||
import zlib
|
||||
import tarfile
|
||||
from hashlib import md5
|
||||
from cStringIO import StringIO
|
||||
from binascii import a2b_hex, b2a_hex
|
||||
|
||||
STORAGE = 'AmazonSecureStorage.xml'
|
||||
|
||||
class AndroidObfuscation(object):
|
||||
'''AndroidObfuscation
|
||||
For the key, it's written in java, and run in android dalvikvm
|
||||
'''
|
||||
|
||||
key = a2b_hex('0176e04c9408b1702d90be333fd53523')
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
cipher = self._get_cipher()
|
||||
padding = len(self.key) - len(plaintext) % len(self.key)
|
||||
plaintext += chr(padding) * padding
|
||||
return b2a_hex(cipher.encrypt(plaintext))
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
cipher = self._get_cipher()
|
||||
plaintext = cipher.decrypt(a2b_hex(ciphertext))
|
||||
return plaintext[:-ord(plaintext[-1])]
|
||||
|
||||
def _get_cipher(self):
|
||||
try:
|
||||
from Crypto.Cipher import AES
|
||||
return AES.new(self.key)
|
||||
except ImportError:
|
||||
from aescbc import AES, noPadding
|
||||
return AES(self.key, padding=noPadding())
|
||||
|
||||
class AndroidObfuscationV2(AndroidObfuscation):
|
||||
'''AndroidObfuscationV2
|
||||
'''
|
||||
|
||||
count = 503
|
||||
password = 'Thomsun was here!'
|
||||
|
||||
def __init__(self, salt):
|
||||
key = self.password + salt
|
||||
for _ in range(self.count):
|
||||
key = md5(key).digest()
|
||||
self.key = key[:8]
|
||||
self.iv = key[8:16]
|
||||
|
||||
def _get_cipher(self):
|
||||
try :
|
||||
from Crypto.Cipher import DES
|
||||
return DES.new(self.key, DES.MODE_CBC, self.iv)
|
||||
except ImportError:
|
||||
from python_des import Des, CBC
|
||||
return Des(self.key, CBC, self.iv)
|
||||
|
||||
def parse_preference(path):
|
||||
''' parse android's shared preference xml '''
|
||||
storage = {}
|
||||
read = open(path)
|
||||
for line in read:
|
||||
line = line.strip()
|
||||
# <string name="key">value</string>
|
||||
if line.startswith('<string name="'):
|
||||
index = line.find('"', 14)
|
||||
key = line[14:index]
|
||||
value = line[index+2:-9]
|
||||
storage[key] = value
|
||||
read.close()
|
||||
return storage
|
||||
|
||||
def get_serials(path=None):
|
||||
''' get serials from android's shared preference xml '''
|
||||
if path is None:
|
||||
if not os.path.isfile(STORAGE):
|
||||
if os.path.isfile("backup.ab"):
|
||||
get_storage()
|
||||
else:
|
||||
return []
|
||||
path = STORAGE
|
||||
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
storage = parse_preference(path)
|
||||
salt = storage.get('AmazonSaltKey')
|
||||
if salt and len(salt) == 16:
|
||||
sys.stdout.write('Using AndroidObfuscationV2\n')
|
||||
obfuscation = AndroidObfuscationV2(a2b_hex(salt))
|
||||
else:
|
||||
sys.stdout.write('Using AndroidObfuscation\n')
|
||||
obfuscation = AndroidObfuscation()
|
||||
|
||||
def get_value(key):
|
||||
encrypted_key = obfuscation.encrypt(key)
|
||||
encrypted_value = storage.get(encrypted_key)
|
||||
if encrypted_value:
|
||||
return obfuscation.decrypt(encrypted_value)
|
||||
return ''
|
||||
|
||||
# also see getK4Pids in kgenpids.py
|
||||
try:
|
||||
dsnid = get_value('DsnId')
|
||||
except:
|
||||
sys.stderr.write('cannot get DsnId\n')
|
||||
return []
|
||||
|
||||
try:
|
||||
tokens = set(get_value('kindle.account.tokens').split(','))
|
||||
except:
|
||||
return [dsnid]
|
||||
|
||||
serials = []
|
||||
for token in tokens:
|
||||
if token:
|
||||
serials.append('%s%s' % (dsnid, token))
|
||||
serials.append(dsnid)
|
||||
for token in tokens:
|
||||
if token:
|
||||
serials.append(token)
|
||||
return serials
|
||||
|
||||
def get_storage(path='backup.ab'):
|
||||
'''get AmazonSecureStorage.xml from android backup.ab
|
||||
backup.ab can be get using adb command:
|
||||
shell> adb backup com.amazon.kindle
|
||||
'''
|
||||
output = None
|
||||
read = open(path, 'rb')
|
||||
head = read.read(24)
|
||||
if head == 'ANDROID BACKUP\n1\n1\nnone\n':
|
||||
output = StringIO(zlib.decompress(read.read()))
|
||||
read.close()
|
||||
|
||||
if not output:
|
||||
return False
|
||||
|
||||
tar = tarfile.open(fileobj=output)
|
||||
for member in tar.getmembers():
|
||||
if member.name.strip().endswith(STORAGE):
|
||||
write = open(STORAGE, 'w')
|
||||
write.write(tar.extractfile(member).read())
|
||||
write.close()
|
||||
break
|
||||
|
||||
return True
|
||||
|
||||
__all__ = [ 'get_storage', 'get_serials', 'parse_preference',
|
||||
'AndroidObfuscation', 'AndroidObfuscationV2', 'STORAGE']
|
||||
|
||||
if __name__ == '__main__':
|
||||
print get_serials()
|
||||
@@ -0,0 +1,6 @@
|
||||
1.1 get AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml
|
||||
|
||||
1.2 on android 4.0+, run `adb backup com.amazon.kindle` from PC will get backup.ab
|
||||
now android.py can convert backup.ab to AmazonSecureStorage.xml
|
||||
|
||||
2. run `k4mobidedrm.py -a AmazonSecureStorage.xml <infile> <outdir>'
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf370
|
||||
{\fonttbl}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
}
|
||||
@@ -1,719 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
from __future__ import with_statement
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
# Standard Python modules.
|
||||
import os, sys, re, hashlib
|
||||
import json
|
||||
|
||||
from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QListWidget, QListWidgetItem, QAbstractItemView, QLineEdit, QPushButton, QIcon, QGroupBox, QDialog, QDialogButtonBox, QUrl, QString)
|
||||
from PyQt4 import QtGui
|
||||
|
||||
# calibre modules and constants.
|
||||
from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url,
|
||||
choose_dir, choose_files)
|
||||
from calibre.utils.config import dynamic, config_dir, JSONConfig
|
||||
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
from calibre_plugins.dedrm.utilities import (uStrCmp, DETAILED_MESSAGE, parseCustString)
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key
|
||||
from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key
|
||||
from calibre_plugins.dedrm.adobekey import adeptkeys as retrieve_adept_keys
|
||||
from calibre_plugins.dedrm.kindlekey import kindlekeys as retrieve_kindle_keys
|
||||
|
||||
class ManageKeysDialog(QDialog):
|
||||
def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = u""):
|
||||
QDialog.__init__(self,parent)
|
||||
self.parent = parent
|
||||
self.key_type_name = key_type_name
|
||||
self.plugin_keys = plugin_keys
|
||||
self.create_key = create_key
|
||||
self.keyfile_ext = keyfile_ext
|
||||
self.import_key = (keyfile_ext != u"")
|
||||
self.binary_file = (key_type_name == u"Adobe Digital Editions Key")
|
||||
self.json_file = (key_type_name == u"Kindle for Mac and PC Key")
|
||||
|
||||
self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
|
||||
|
||||
# Start Qt Gui dialog layout
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
help_layout = QHBoxLayout()
|
||||
layout.addLayout(help_layout)
|
||||
# Add hyperlink to a help file at the right. We will replace the correct name when it is clicked.
|
||||
help_label = QLabel('<a href="http://www.foo.com/">Help</a>', self)
|
||||
help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
|
||||
help_label.setAlignment(Qt.AlignRight)
|
||||
help_label.linkActivated.connect(self.help_link_activated)
|
||||
help_layout.addWidget(help_label)
|
||||
|
||||
keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self)
|
||||
layout.addWidget(keys_group_box)
|
||||
keys_group_box_layout = QHBoxLayout()
|
||||
keys_group_box.setLayout(keys_group_box_layout)
|
||||
|
||||
self.listy = QListWidget(self)
|
||||
self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name))
|
||||
self.listy.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.populate_list()
|
||||
keys_group_box_layout.addWidget(self.listy)
|
||||
|
||||
button_layout = QVBoxLayout()
|
||||
keys_group_box_layout.addLayout(button_layout)
|
||||
self._add_key_button = QtGui.QToolButton(self)
|
||||
self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name))
|
||||
self._add_key_button.setIcon(QIcon(I('plus.png')))
|
||||
self._add_key_button.clicked.connect(self.add_key)
|
||||
button_layout.addWidget(self._add_key_button)
|
||||
|
||||
self._delete_key_button = QtGui.QToolButton(self)
|
||||
self._delete_key_button.setToolTip(_(u"Delete highlighted key"))
|
||||
self._delete_key_button.setIcon(QIcon(I('list_remove.png')))
|
||||
self._delete_key_button.clicked.connect(self.delete_key)
|
||||
button_layout.addWidget(self._delete_key_button)
|
||||
|
||||
if type(self.plugin_keys) == dict:
|
||||
self._rename_key_button = QtGui.QToolButton(self)
|
||||
self._rename_key_button.setToolTip(_(u"Rename highlighted key"))
|
||||
self._rename_key_button.setIcon(QIcon(I('edit-select-all.png')))
|
||||
self._rename_key_button.clicked.connect(self.rename_key)
|
||||
button_layout.addWidget(self._rename_key_button)
|
||||
|
||||
self.export_key_button = QtGui.QToolButton(self)
|
||||
self.export_key_button.setToolTip(u"Save highlighted key to a .{0} file".format(self.keyfile_ext))
|
||||
self.export_key_button.setIcon(QIcon(I('save.png')))
|
||||
self.export_key_button.clicked.connect(self.export_key)
|
||||
button_layout.addWidget(self.export_key_button)
|
||||
spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
button_layout.addItem(spacerItem)
|
||||
|
||||
layout.addSpacing(5)
|
||||
migrate_layout = QHBoxLayout()
|
||||
layout.addLayout(migrate_layout)
|
||||
if self.import_key:
|
||||
migrate_layout.setAlignment(Qt.AlignJustify)
|
||||
self.migrate_btn = QPushButton(u"Import Existing Keyfiles", self)
|
||||
self.migrate_btn.setToolTip(u"Import *.{0} files (created using other tools).".format(self.keyfile_ext))
|
||||
self.migrate_btn.clicked.connect(self.migrate_wrapper)
|
||||
migrate_layout.addWidget(self.migrate_btn)
|
||||
migrate_layout.addStretch()
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
self.button_box.rejected.connect(self.close)
|
||||
migrate_layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
def populate_list(self):
|
||||
if type(self.plugin_keys) == dict:
|
||||
for key in self.plugin_keys.keys():
|
||||
self.listy.addItem(QListWidgetItem(key))
|
||||
else:
|
||||
for key in self.plugin_keys:
|
||||
self.listy.addItem(QListWidgetItem(key))
|
||||
|
||||
def add_key(self):
|
||||
d = self.create_key(self)
|
||||
d.exec_()
|
||||
|
||||
if d.result() != d.Accepted:
|
||||
# New key generation cancelled.
|
||||
return
|
||||
new_key_value = d.key_value
|
||||
if type(self.plugin_keys) == dict:
|
||||
if new_key_value in self.plugin_keys.values():
|
||||
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
|
||||
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
||||
u"The new {1} is the same as the existing {1} named <strong>{0}</strong> and has not been added.".format(old_key_name,self.key_type_name), show=True)
|
||||
return
|
||||
self.plugin_keys[d.key_name] = new_key_value
|
||||
else:
|
||||
if new_key_value in self.plugin_keys:
|
||||
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
||||
u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
|
||||
return
|
||||
|
||||
self.plugin_keys.append(d.key_value)
|
||||
self.listy.clear()
|
||||
self.populate_list()
|
||||
|
||||
def rename_key(self):
|
||||
if not self.listy.currentItem():
|
||||
errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name)
|
||||
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
return
|
||||
|
||||
d = RenameKeyDialog(self)
|
||||
d.exec_()
|
||||
|
||||
if d.result() != d.Accepted:
|
||||
# rename cancelled or moot.
|
||||
return
|
||||
keyname = unicode(self.listy.currentItem().text().toUtf8(),'utf8')
|
||||
if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False):
|
||||
return
|
||||
self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
|
||||
del self.plugin_keys[keyname]
|
||||
|
||||
self.listy.clear()
|
||||
self.populate_list()
|
||||
|
||||
def delete_key(self):
|
||||
if not self.listy.currentItem():
|
||||
return
|
||||
keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
|
||||
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
|
||||
return
|
||||
if type(self.plugin_keys) == dict:
|
||||
del self.plugin_keys[keyname]
|
||||
else:
|
||||
self.plugin_keys.remove(keyname)
|
||||
|
||||
self.listy.clear()
|
||||
self.populate_list()
|
||||
|
||||
def help_link_activated(self, url):
|
||||
def get_help_file_resource():
|
||||
# Copy the HTML helpfile to the plugin directory each time the
|
||||
# link is clicked in case the helpfile is updated in newer plugins.
|
||||
help_file_name = u"{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name)
|
||||
file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name)
|
||||
with open(file_path,'w') as f:
|
||||
f.write(self.parent.load_resource(help_file_name))
|
||||
return file_path
|
||||
url = 'file:///' + get_help_file_resource()
|
||||
open_url(QUrl(url))
|
||||
|
||||
def migrate_files(self):
|
||||
dynamic[PLUGIN_NAME + u"config_dir"] = config_dir
|
||||
files = choose_files(self, PLUGIN_NAME + u"config_dir",
|
||||
u"Select {0} files to import".format(self.key_type_name), [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])], False)
|
||||
counter = 0
|
||||
skipped = 0
|
||||
if files:
|
||||
for filename in files:
|
||||
fpath = os.path.join(config_dir, filename)
|
||||
filename = os.path.basename(filename)
|
||||
new_key_name = os.path.splitext(os.path.basename(filename))[0]
|
||||
with open(fpath,'rb') as keyfile:
|
||||
new_key_value = keyfile.read()
|
||||
if self.binary_file:
|
||||
new_key_value = new_key_value.encode('hex')
|
||||
elif self.json_file:
|
||||
new_key_value = json.loads(new_key_value)
|
||||
match = False
|
||||
for key in self.plugin_keys.keys():
|
||||
if uStrCmp(new_key_name, key, True):
|
||||
skipped += 1
|
||||
msg = u"A key with the name <strong>{0}</strong> already exists!\nSkipping key file <strong>{1}</strong>.\nRename the existing key and import again".format(new_key_name,filename)
|
||||
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(msg), show_copy_button=False, show=True)
|
||||
match = True
|
||||
break
|
||||
if not match:
|
||||
if new_key_value in self.plugin_keys.values():
|
||||
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
|
||||
skipped += 1
|
||||
info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
u"The key in file {0} is the same as the existing key <strong>{1}</strong> and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True)
|
||||
else:
|
||||
counter += 1
|
||||
self.plugin_keys[new_key_name] = new_key_value
|
||||
|
||||
msg = u""
|
||||
if counter+skipped > 1:
|
||||
if counter > 0:
|
||||
msg += u"Imported <strong>{0:d}</strong> key {1}. ".format(counter, u"file" if counter == 1 else u"files")
|
||||
if skipped > 0:
|
||||
msg += u"Skipped <strong>{0:d}</strong> key {1}.".format(skipped, u"file" if counter == 1 else u"files")
|
||||
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(msg), show_copy_button=False, show=True)
|
||||
return counter > 0
|
||||
|
||||
def migrate_wrapper(self):
|
||||
if self.migrate_files():
|
||||
self.listy.clear()
|
||||
self.populate_list()
|
||||
|
||||
def export_key(self):
|
||||
if not self.listy.currentItem():
|
||||
errmsg = u"No keyfile selected to export. Highlight a keyfile first."
|
||||
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
return
|
||||
filter = QString(u"{0} Files (*.{1})".format(self.key_type_name, self.keyfile_ext))
|
||||
keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
|
||||
if dynamic.get(PLUGIN_NAME + 'save_dir'):
|
||||
defaultname = os.path.join(dynamic.get(PLUGIN_NAME + 'save_dir'), u"{0}.{1}".format(keyname , self.keyfile_ext))
|
||||
else:
|
||||
defaultname = os.path.join(os.path.expanduser('~'), u"{0}.{1}".format(keyname , self.keyfile_ext))
|
||||
filename = unicode(QtGui.QFileDialog.getSaveFileName(self, u"Save {0} File as...".format(self.key_type_name), defaultname,
|
||||
u"{0} Files (*.{1})".format(self.key_type_name,self.keyfile_ext), filter))
|
||||
if filename:
|
||||
dynamic[PLUGIN_NAME + 'save_dir'] = os.path.split(filename)[0]
|
||||
with file(filename, 'w') as fname:
|
||||
if self.binary_file:
|
||||
fname.write(self.plugin_keys[keyname].decode('hex'))
|
||||
elif self.json_file:
|
||||
fname.write(json.dumps(self.plugin_keys[keyname]))
|
||||
else:
|
||||
fname.write(self.plugin_keys[keyname])
|
||||
|
||||
|
||||
|
||||
|
||||
class RenameKeyDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
print repr(self), repr(parent)
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle("{0} {1}: Rename {0}".format(PLUGIN_NAME, PLUGIN_VERSION, parent.key_type_name))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox('', self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
data_group_box_layout.addWidget(QLabel('New Key Name:', self))
|
||||
self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self)
|
||||
self.key_ledit.setToolTip(u"Enter a new name for this existing {0}.".format(parent.key_type_name))
|
||||
data_group_box_layout.addWidget(self.key_ledit)
|
||||
|
||||
layout.addSpacing(20)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
def accept(self):
|
||||
if self.key_ledit.text().isEmpty() or unicode(self.key_ledit.text()).isspace():
|
||||
errmsg = u"Key name field cannot be empty!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
if len(self.key_ledit.text()) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()):
|
||||
# Same exact name ... do nothing.
|
||||
return QDialog.reject(self)
|
||||
for k in self.parent.plugin_keys.keys():
|
||||
if (uStrCmp(self.key_ledit.text(), k, True) and
|
||||
not uStrCmp(k, self.parent.listy.currentItem().text(), True)):
|
||||
errmsg = u"The key name <strong>{0}</strong> is already being used.".format(self.key_ledit.text())
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class AddBandNKeyDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(_(u"<p>Enter an identifying name for this new key.</p>" +
|
||||
u"<p>It should be something that will help you remember " +
|
||||
u"what personal information was used to create it."))
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
|
||||
name_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(name_group)
|
||||
name_group.addWidget(QLabel(u"Your Name:", self))
|
||||
self.name_ledit = QLineEdit(u"", self)
|
||||
self.name_ledit.setToolTip(_(u"<p>Enter your name as it appears in your B&N " +
|
||||
u"account or on your credit card.</p>" +
|
||||
u"<p>It will only be used to generate this " +
|
||||
u"one-time key and won\'t be stored anywhere " +
|
||||
u"in calibre or on your computer.</p>" +
|
||||
u"<p>(ex: Jonathan Smith)"))
|
||||
name_group.addWidget(self.name_ledit)
|
||||
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
|
||||
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(name_disclaimer_label)
|
||||
|
||||
ccn_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(ccn_group)
|
||||
ccn_group.addWidget(QLabel(u"Credit Card#:", self))
|
||||
self.cc_ledit = QLineEdit(u"", self)
|
||||
self.cc_ledit.setToolTip(_(u"<p>Enter the full credit card number on record " +
|
||||
u"in your B&N account.</p>" +
|
||||
u"<p>No spaces or dashes... just the numbers. " +
|
||||
u"This number will only be used to generate this " +
|
||||
u"one-time key and won\'t be stored anywhere in " +
|
||||
u"calibre or on your computer."))
|
||||
ccn_group.addWidget(self.cc_ledit)
|
||||
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
||||
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(ccn_disclaimer_label)
|
||||
layout.addSpacing(10)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return generate_bandn_key(self.user_name,self.cc_number)
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
|
||||
|
||||
@property
|
||||
def cc_number(self):
|
||||
return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if not self.cc_number.isdigit():
|
||||
errmsg = u"Numbers only in the credit card number field!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
class AddEReaderDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"<p>Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
|
||||
name_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(name_group)
|
||||
name_group.addWidget(QLabel(u"Your Name:", self))
|
||||
self.name_ledit = QLineEdit(u"", self)
|
||||
self.name_ledit.setToolTip(u"Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)")
|
||||
name_group.addWidget(self.name_ledit)
|
||||
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
|
||||
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(name_disclaimer_label)
|
||||
|
||||
ccn_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(ccn_group)
|
||||
ccn_group.addWidget(QLabel(u"Credit Card#:", self))
|
||||
self.cc_ledit = QLineEdit(u"", self)
|
||||
self.cc_ledit.setToolTip(u"<p>Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.")
|
||||
ccn_group.addWidget(self.cc_ledit)
|
||||
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
||||
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(ccn_disclaimer_label)
|
||||
layout.addSpacing(10)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return generate_ereader_key(self.user_name,self.cc_number).encode('hex')
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
|
||||
|
||||
@property
|
||||
def cc_number(self):
|
||||
return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if not self.cc_number.isdigit():
|
||||
errmsg = u"Numbers only in the credit card number field!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
|
||||
class AddAdeptDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
try:
|
||||
self.default_key = retrieve_adept_keys()[0]
|
||||
except:
|
||||
self.default_key = u""
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
|
||||
if len(self.default_key)>0:
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Adobe Digital Editions key.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
else:
|
||||
default_key_error = QLabel(u"The default encryption key for Adobe Digital Editions could not be found.", self)
|
||||
default_key_error.setAlignment(Qt.AlignHCenter)
|
||||
layout.addWidget(default_key_error)
|
||||
# if no default, bot buttons do the same
|
||||
self.button_box.accepted.connect(self.reject)
|
||||
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return self.default_key.encode('hex')
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
|
||||
class AddKindleDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
try:
|
||||
self.default_key = retrieve_kindle_keys()[0]
|
||||
except:
|
||||
self.default_key = u""
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
|
||||
if len(self.default_key)>0:
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
else:
|
||||
default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self)
|
||||
default_key_error.setAlignment(Qt.AlignHCenter)
|
||||
layout.addWidget(default_key_error)
|
||||
# if no default, bot buttons do the same
|
||||
self.button_box.accepted.connect(self.reject)
|
||||
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return self.default_key
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
|
||||
class AddSerialDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"EInk Kindle Serial Number:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog."
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) != 16:
|
||||
errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
|
||||
class AddPIDDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"PID:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog."
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) != 8 and len(self.key_name) != 10:
|
||||
errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
|
||||
@@ -458,7 +458,11 @@ class DocParser(object):
|
||||
(wtype, num) = pdesc[j]
|
||||
|
||||
if wtype == 'ocr' :
|
||||
word = self.ocrtext[num]
|
||||
try:
|
||||
word = self.ocrtext[num]
|
||||
except:
|
||||
word = ""
|
||||
|
||||
sep = ' '
|
||||
|
||||
if handle_links:
|
||||
|
||||
@@ -34,13 +34,14 @@ from __future__ import with_statement
|
||||
# 3.7 - Tweaked to match ineptepub more closely
|
||||
# 3.8 - Fixed to retain zip file metadata (e.g. file modification date)
|
||||
# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 4.0 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Decrypt Barnes & Noble encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "3.9"
|
||||
__version__ = "4.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -318,6 +319,8 @@ def decryptBook(keyb64, inpath, outpath):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
@@ -331,10 +334,13 @@ def cli_main():
|
||||
return result
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -442,7 +448,5 @@ 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())
|
||||
|
||||
@@ -32,13 +32,14 @@ from __future__ import with_statement
|
||||
# 2.4 - Improvements to UI and now works in plugins
|
||||
# 2.5 - Additional improvement for unicode and plugin support
|
||||
# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 2.7 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "2.6"
|
||||
__version__ = "2.7"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -216,6 +217,8 @@ def generate_key(name, ccn):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if AES is None:
|
||||
@@ -233,10 +236,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -320,7 +326,5 @@ 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())
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 5.9
|
||||
# ineptepub.pyw, version 6.1
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
@@ -35,14 +35,15 @@ from __future__ import with_statement
|
||||
# 5.7 - Fix for potential problem with PyCrypto
|
||||
# 5.8 - Revised to allow use in calibre plugins to eliminate need for duplicate code
|
||||
# 5.9 - Fixed to retain zip file metadata (e.g. file modification date)
|
||||
# 5.10 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.1 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "5.10"
|
||||
__version__ = "6.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -460,6 +461,8 @@ def decryptBook(userkey, inpath, outpath):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
@@ -473,10 +476,13 @@ def cli_main():
|
||||
return result
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -584,7 +590,5 @@ 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())
|
||||
|
||||
@@ -51,13 +51,15 @@ from __future__ import with_statement
|
||||
# 7.12 - Revised to allow use in calibre plugins to eliminate need for duplicate code
|
||||
# 7.13 - Fixed erroneous mentions of ineptepub
|
||||
# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 8.0 - Work if TkInter is missing
|
||||
# 8.0.1 - Broken Metadata fix.
|
||||
|
||||
"""
|
||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "7.14"
|
||||
__version__ = "8.0.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -948,8 +950,11 @@ class PSStackParser(PSBaseParser):
|
||||
try:
|
||||
(pos, objs) = self.end_type('d')
|
||||
if len(objs) % 2 != 0:
|
||||
raise PSSyntaxError(
|
||||
'Invalid dictionary construct: %r' % objs)
|
||||
print "Incomplete dictionary construct"
|
||||
objs.append("") # this isn't necessary.
|
||||
# temporary fix. is this due to rental books?
|
||||
# raise PSSyntaxError(
|
||||
# 'Invalid dictionary construct: %r' % objs)
|
||||
d = dict((literal_name(k), v) \
|
||||
for (k,v) in choplist(2, objs))
|
||||
self.push((pos, d))
|
||||
@@ -2187,6 +2192,8 @@ def decryptBook(userkey, inpath, outpath):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
@@ -2201,10 +2208,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -2321,7 +2331,5 @@ 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())
|
||||
|
||||
@@ -54,8 +54,9 @@ from __future__ import with_statement
|
||||
# 5.0 - Extraction of info from Kindle for PC/Mac moved into kindlekey.py
|
||||
# - tweaked GetDecryptedBook interface to leave passed parameters unchanged
|
||||
# 5.1 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 5.2 - Fixed error in command line processing of unicode arguments
|
||||
|
||||
__version__ = '5.1'
|
||||
__version__ = '5.2'
|
||||
|
||||
|
||||
import sys, os, re
|
||||
@@ -79,10 +80,12 @@ if inCalibre:
|
||||
from calibre_plugins.dedrm import mobidedrm
|
||||
from calibre_plugins.dedrm import topazextract
|
||||
from calibre_plugins.dedrm import kgenpids
|
||||
from calibre_plugins.dedrm import android
|
||||
else:
|
||||
import mobidedrm
|
||||
import topazextract
|
||||
import kgenpids
|
||||
import android
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
@@ -187,7 +190,7 @@ def unescape(text):
|
||||
def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time()):
|
||||
# handle the obvious cases at the beginning
|
||||
if not os.path.isfile(infile):
|
||||
raise DRMException (u"Input file does not exist.")
|
||||
raise DrmException(u"Input file does not exist.")
|
||||
|
||||
mobi = True
|
||||
magic3 = open(infile,'rb').read(3)
|
||||
@@ -272,7 +275,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
|
||||
def usage(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.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)
|
||||
print u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml> ] <infile> <outdir>".format(progname)
|
||||
|
||||
#
|
||||
# Main
|
||||
@@ -283,7 +286,7 @@ def cli_main():
|
||||
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2013 The Dark Reverser et al.".format(__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
usage(progname)
|
||||
@@ -311,6 +314,11 @@ def cli_main():
|
||||
if a == None :
|
||||
raise DrmException("Invalid parameter for -s")
|
||||
serials = a.split(',')
|
||||
if o == '-a':
|
||||
if a == None:
|
||||
continue
|
||||
serials.extend(android.get_serials(a))
|
||||
serials.extend(android.get_serials())
|
||||
|
||||
# try with built in Kindle Info files if not on Linux
|
||||
k4 = not sys.platform.startswith('linux')
|
||||
|
||||
@@ -17,6 +17,9 @@ from __future__ import with_statement
|
||||
# 1.4 - Remove dependency on alfcrypto
|
||||
# 1.5 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 1.6 - Fixed a problem getting the disk serial numbers
|
||||
# 1.7 - Work if TkInter is missing
|
||||
# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names
|
||||
# 1.9 - Fixes for Unicode in Windows user names
|
||||
|
||||
|
||||
"""
|
||||
@@ -24,7 +27,7 @@ Retrieve Kindle for PC/Mac user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.6'
|
||||
__version__ = '1.9'
|
||||
|
||||
import sys, os, re
|
||||
from struct import pack, unpack, unpack_from
|
||||
@@ -905,18 +908,34 @@ if iswindows:
|
||||
return CryptUnprotectData
|
||||
CryptUnprotectData = CryptUnprotectData()
|
||||
|
||||
# Returns Environmental Variables that contain unicode
|
||||
def getEnvironmentVariable(name):
|
||||
import ctypes
|
||||
name = unicode(name) # make sure string argument is unicode
|
||||
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
|
||||
if n == 0:
|
||||
return None
|
||||
buf = ctypes.create_unicode_buffer(u'\0'*n)
|
||||
ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n)
|
||||
return buf.value
|
||||
|
||||
# Locate all of the kindle-info style files and return as list
|
||||
def getKindleInfoFiles():
|
||||
kInfoFiles = []
|
||||
# some 64 bit machines do not have the proper registry key for some reason
|
||||
# or the pythonn interface to the 32 vs 64 bit registry is broken
|
||||
# or the python interface to the 32 vs 64 bit registry is broken
|
||||
path = ""
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
path = os.environ['LOCALAPPDATA']
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
# this is just another alternative.
|
||||
# path = getEnvironmentVariable('LOCALAPPDATA')
|
||||
if not os.path.isdir(path):
|
||||
path = ""
|
||||
else:
|
||||
# User Shell Folders show take precedent over Shell Folders if present
|
||||
try:
|
||||
# this will still break
|
||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
|
||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||
if not os.path.isdir(path):
|
||||
@@ -935,13 +954,14 @@ if iswindows:
|
||||
if path == "":
|
||||
print ('Could not find the folder in which to look for kinfoFiles.')
|
||||
else:
|
||||
print('searching for kinfoFiles in ' + path)
|
||||
# Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
|
||||
print(u'searching for kinfoFiles in ' + path.encode('ascii', 'ignore'))
|
||||
|
||||
# look for (K4PC 1.9.0 and later) .kinf2011 file
|
||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
||||
if os.path.isfile(kinfopath):
|
||||
found = True
|
||||
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath)
|
||||
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath.encode('ascii','ignore'))
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
||||
@@ -1140,8 +1160,8 @@ if iswindows:
|
||||
cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName())
|
||||
if 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode("latin-1"))
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
@@ -1520,20 +1540,32 @@ elif isosx:
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kinf2011 file: ' + testpath)
|
||||
found = True
|
||||
# check for .kinf2011 files
|
||||
# check for .kinf2011 files from 1.10
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kinf2011 file: ' + testpath)
|
||||
found = True
|
||||
# check for .rainier-2.1.1-kinf files
|
||||
# check for .rainier-2.1.1-kinf files from 1.6
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac rainier file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info files
|
||||
# check for .kindle-info files from 1.4
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info file from 1.2.2
|
||||
testpath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info file from 1.0 beta 1 (27214)
|
||||
testpath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
@@ -1744,7 +1776,7 @@ elif isosx:
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
if 'kindle.account.tokens' in DB:
|
||||
# store values used in decryption
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())
|
||||
DB['IDString'] = IDString
|
||||
@@ -1804,6 +1836,8 @@ def usage(progname):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2010-2013 some_updates and Apprentice Alf".format(progname,__version__)
|
||||
@@ -1845,10 +1879,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
@@ -1895,7 +1932,5 @@ 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())
|
||||
|
||||
@@ -68,9 +68,10 @@
|
||||
# 0.38 - Unicode used wherever possible, cope with absent alfcrypto
|
||||
# 0.39 - Fixed problem with TEXtREAd and getBookType interface
|
||||
# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 0.41 - Fixed potential unicode problem in command line calls
|
||||
|
||||
|
||||
__version__ = u"0.40"
|
||||
__version__ = u"0.41"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -155,6 +156,8 @@ def PC1(key, src, decryption=True):
|
||||
return Pukall_Cipher().PC1(key,src,decryption)
|
||||
except NameError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# use slow python version, since Pukall_Cipher didn't load
|
||||
sum1 = 0;
|
||||
@@ -514,7 +517,7 @@ def cli_main():
|
||||
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]))
|
||||
print u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname)
|
||||
return 1
|
||||
else:
|
||||
infile = argv[1]
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
from __future__ import with_statement
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
# Standard Python modules.
|
||||
import os, sys, re, hashlib
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from calibre.utils.config import dynamic, config_dir, JSONConfig
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
from calibre.constants import iswindows, isosx
|
||||
|
||||
class DeDRM_Prefs():
|
||||
def __init__(self):
|
||||
JSON_PATH = os.path.join(u"plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
|
||||
self.dedrmprefs = JSONConfig(JSON_PATH)
|
||||
|
||||
self.dedrmprefs.defaults['configured'] = False
|
||||
self.dedrmprefs.defaults['bandnkeys'] = {}
|
||||
self.dedrmprefs.defaults['adeptkeys'] = {}
|
||||
self.dedrmprefs.defaults['ereaderkeys'] = {}
|
||||
self.dedrmprefs.defaults['kindlekeys'] = {}
|
||||
self.dedrmprefs.defaults['pids'] = []
|
||||
self.dedrmprefs.defaults['serials'] = []
|
||||
self.dedrmprefs.defaults['adobewineprefix'] = ""
|
||||
self.dedrmprefs.defaults['kindlewineprefix'] = ""
|
||||
|
||||
# initialise
|
||||
# we must actually set the prefs that are dictionaries and lists
|
||||
# to empty dictionaries and lists, otherwise we are unable to add to them
|
||||
# as then it just adds to the (memory only) dedrmprefs.defaults versions!
|
||||
if self.dedrmprefs['bandnkeys'] == {}:
|
||||
self.dedrmprefs['bandnkeys'] = {}
|
||||
if self.dedrmprefs['adeptkeys'] == {}:
|
||||
self.dedrmprefs['adeptkeys'] = {}
|
||||
if self.dedrmprefs['ereaderkeys'] == {}:
|
||||
self.dedrmprefs['ereaderkeys'] = {}
|
||||
if self.dedrmprefs['kindlekeys'] == {}:
|
||||
self.dedrmprefs['kindlekeys'] = {}
|
||||
if self.dedrmprefs['pids'] == []:
|
||||
self.dedrmprefs['pids'] = []
|
||||
if self.dedrmprefs['serials'] == []:
|
||||
self.dedrmprefs['serials'] = []
|
||||
|
||||
def __getitem__(self,kind = None):
|
||||
if kind is not None:
|
||||
return self.dedrmprefs[kind]
|
||||
return self.dedrmprefs
|
||||
|
||||
def set(self, kind, value):
|
||||
self.dedrmprefs[kind] = value
|
||||
|
||||
def writeprefs(self,value = True):
|
||||
self.dedrmprefs['configured'] = value
|
||||
|
||||
def addnamedvaluetoprefs(self, prefkind, keyname, keyvalue):
|
||||
try:
|
||||
if keyvalue not in self.dedrmprefs[prefkind].values():
|
||||
# ensure that the keyname is unique
|
||||
# by adding a number (starting with 2) to the name if it is not
|
||||
namecount = 1
|
||||
newname = keyname
|
||||
while newname in self.dedrmprefs[prefkind]:
|
||||
namecount += 1
|
||||
newname = "{0:s}_{1:d}".format(keyname,namecount)
|
||||
# add to the preferences
|
||||
self.dedrmprefs[prefkind][newname] = keyvalue
|
||||
return (True, newname)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
return (False, keyname)
|
||||
|
||||
def addvaluetoprefs(self, prefkind, prefsvalue):
|
||||
# ensure the keyvalue isn't already in the preferences
|
||||
try:
|
||||
if prefsvalue not in self.dedrmprefs[prefkind]:
|
||||
self.dedrmprefs[prefkind].append(prefsvalue)
|
||||
return True
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def convertprefs(always = False):
|
||||
|
||||
def parseIgnobleString(keystuff):
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key
|
||||
userkeys = []
|
||||
ar = keystuff.split(':')
|
||||
for keystring in ar:
|
||||
try:
|
||||
name, ccn = keystring.split(',')
|
||||
# Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||
keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:])
|
||||
keyvalue = generate_key(name, ccn)
|
||||
userkeys.append([keyname,keyvalue])
|
||||
except Exception, e:
|
||||
traceback.print_exc()
|
||||
print e.args[0]
|
||||
pass
|
||||
return userkeys
|
||||
|
||||
def parseeReaderString(keystuff):
|
||||
from calibre_plugins.dedrm.erdr2pml import getuser_key
|
||||
userkeys = []
|
||||
ar = keystuff.split(':')
|
||||
for keystring in ar:
|
||||
try:
|
||||
name, cc = keystring.split(',')
|
||||
# Generate eReader user key from name and credit card number.
|
||||
keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:])
|
||||
keyvalue = getuser_key(name,cc).encode('hex')
|
||||
userkeys.append([keyname,keyvalue])
|
||||
except Exception, e:
|
||||
traceback.print_exc()
|
||||
print e.args[0]
|
||||
pass
|
||||
return userkeys
|
||||
|
||||
def parseKindleString(keystuff):
|
||||
pids = []
|
||||
serials = []
|
||||
ar = keystuff.split(',')
|
||||
for keystring in ar:
|
||||
keystring = str(keystring).strip().replace(" ","")
|
||||
if len(keystring) == 10 or len(keystring) == 8 and keystring not in pids:
|
||||
pids.append(keystring)
|
||||
elif len(keystring) == 16 and keystring[0] == 'B' and keystring not in serials:
|
||||
serials.append(keystring)
|
||||
return (pids,serials)
|
||||
|
||||
def getConfigFiles(extension, encoding = None):
|
||||
# get any files with extension 'extension' in the config dir
|
||||
userkeys = []
|
||||
files = [f for f in os.listdir(config_dir) if f.endswith(extension)]
|
||||
for filename in files:
|
||||
try:
|
||||
fpath = os.path.join(config_dir, filename)
|
||||
key = os.path.splitext(filename)[0]
|
||||
value = open(fpath, 'rb').read()
|
||||
if encoding is not None:
|
||||
value = value.encode(encoding)
|
||||
userkeys.append([key,value])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
return userkeys
|
||||
|
||||
dedrmprefs = DeDRM_Prefs()
|
||||
|
||||
if (not always) and dedrmprefs['configured']:
|
||||
# We've already converted old preferences,
|
||||
# and we're not being forced to do it again, so just return
|
||||
return
|
||||
|
||||
|
||||
print u"{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
|
||||
IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM"
|
||||
EREADERPLUGINNAME = "eReader PDB 2 PML"
|
||||
OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM"
|
||||
|
||||
# get prefs from older tools
|
||||
kindleprefs = JSONConfig(os.path.join(u"plugins", u"K4MobiDeDRM"))
|
||||
ignobleprefs = JSONConfig(os.path.join(u"plugins", u"ignoble_epub_dedrm"))
|
||||
|
||||
# Handle the old ignoble plugin's customization string by converting the
|
||||
# old string to stored keys... get that personal data out of plain sight.
|
||||
from calibre.customize.ui import config
|
||||
sc = config['plugin_customization']
|
||||
val = sc.pop(IGNOBLEPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
priorkeycount = len(dedrmprefs['bandnkeys'])
|
||||
userkeys = parseIgnobleString(str(val))
|
||||
for keypair in userkeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# Handle the old eReader plugin's customization string by converting the
|
||||
# old string to stored keys... get that personal data out of plain sight.
|
||||
val = sc.pop(EREADERPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
priorkeycount = len(dedrmprefs['ereaderkeys'])
|
||||
userkeys = parseeReaderString(str(val))
|
||||
for keypair in userkeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount
|
||||
print u"{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get old Kindle plugin configuration string
|
||||
val = sc.pop(OLDKINDLEPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
priorpidcount = len(dedrmprefs['pids'])
|
||||
priorserialcount = len(dedrmprefs['serials'])
|
||||
pids, serials = parseKindleString(val)
|
||||
for pid in pids:
|
||||
dedrmprefs.addvaluetoprefs('pids',pid)
|
||||
for serial in serials:
|
||||
dedrmprefs.addvaluetoprefs('serials',serial)
|
||||
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
||||
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
||||
print u"{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs", addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# copy the customisations back into calibre preferences, as we've now removed the nasty plaintext
|
||||
config['plugin_customization'] = sc
|
||||
|
||||
# get any .b64 files in the config dir
|
||||
priorkeycount = len(dedrmprefs['bandnkeys'])
|
||||
bandnfilekeys = getConfigFiles('.b64')
|
||||
for keypair in bandnfilekeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key file" if addedkeycount==1 else u"key files")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get any .der files in the config dir
|
||||
priorkeycount = len(dedrmprefs['adeptkeys'])
|
||||
adeptfilekeys = getConfigFiles('.der','hex')
|
||||
for keypair in adeptfilekeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"keyfile" if addedkeycount==1 else u"keyfiles")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get ignoble json prefs
|
||||
if 'keys' in ignobleprefs:
|
||||
priorkeycount = len(dedrmprefs['bandnkeys'])
|
||||
for name in ignobleprefs['keys']:
|
||||
value = ignobleprefs['keys'][name]
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount
|
||||
# no need to delete old prefs, since they contain no recoverable private data
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get kindle json prefs
|
||||
priorpidcount = len(dedrmprefs['pids'])
|
||||
priorserialcount = len(dedrmprefs['serials'])
|
||||
if 'pids' in kindleprefs:
|
||||
pids, serials = parseKindleString(kindleprefs['pids'])
|
||||
for pid in pids:
|
||||
dedrmprefs.addvaluetoprefs('pids',pid)
|
||||
if 'serials' in kindleprefs:
|
||||
pids, serials = parseKindleString(kindleprefs['serials'])
|
||||
for serial in serials:
|
||||
dedrmprefs.addvaluetoprefs('serials',serial)
|
||||
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
||||
if addedpidcount > 0:
|
||||
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs")
|
||||
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
||||
if addedserialcount > 0:
|
||||
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
|
||||
try:
|
||||
if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "":
|
||||
dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix'])
|
||||
dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix'])
|
||||
print u"{0} v{1}: WINEPREFIX ‘(2)’ imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix'])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs()
|
||||
print u"{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
@@ -178,7 +178,12 @@ class DocParser(object):
|
||||
if val == "":
|
||||
val = 0
|
||||
|
||||
if not ((attr == 'hang') and (int(val) == 0)) :
|
||||
if not ((attr == 'hang') and (int(val) == 0)):
|
||||
try:
|
||||
f = float(val)
|
||||
except:
|
||||
print "Warning: unrecognised val, ignoring"
|
||||
val = 0
|
||||
pv = float(val)/scale
|
||||
cssargs[attr] = (self.attr_val_map[attr], pv)
|
||||
keep = True
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
|
||||
# Changelog
|
||||
# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 5.0 - Fixed potential unicode problem with command line interface
|
||||
|
||||
__version__ = '4.9'
|
||||
__version__ = '5.0'
|
||||
|
||||
import sys
|
||||
import os, csv, getopt
|
||||
@@ -355,7 +356,7 @@ class TopazBook:
|
||||
|
||||
self.setBookKey(bookKey)
|
||||
self.createBookDirectory()
|
||||
self.extractFiles()
|
||||
self.extractFiles()
|
||||
print u"Successfully Extracted Topaz contents"
|
||||
if inCalibre:
|
||||
from calibre_plugins.dedrm import genbook
|
||||
@@ -451,7 +452,7 @@ def cli_main():
|
||||
print u"TopazExtract v{0}.".format(__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:x")
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:x")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
usage(progname)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
# Standard Python modules.
|
||||
import os, sys, re, hashlib
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
|
||||
def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||
import subprocess
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
import subasyncio
|
||||
from subasyncio import Process
|
||||
|
||||
if extension == u".k4i":
|
||||
import json
|
||||
|
||||
basepath, script = os.path.split(scriptpath)
|
||||
print u"{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script)
|
||||
|
||||
outdirpath = os.path.join(basepath, u"winekeysdir")
|
||||
if not os.path.exists(outdirpath):
|
||||
os.makedirs(outdirpath)
|
||||
|
||||
if wineprefix != "" and os.path.exists(wineprefix):
|
||||
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||
else:
|
||||
cmdline = u"wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
||||
print u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)
|
||||
|
||||
try:
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
||||
result = p2.wait("wait")
|
||||
except Exception, e:
|
||||
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||
return []
|
||||
|
||||
winekeys = []
|
||||
# get any files with extension in the output dir
|
||||
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
||||
for filename in files:
|
||||
try:
|
||||
fpath = os.path.join(outdirpath, filename)
|
||||
with open(fpath, 'rb') as keyfile:
|
||||
if extension == u".k4i":
|
||||
new_key_value = json.loads(keyfile.read())
|
||||
else:
|
||||
new_key_value = keyfile.read()
|
||||
winekeys.append(new_key_value)
|
||||
except:
|
||||
print u"{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename)
|
||||
traceback.print_exc()
|
||||
os.remove(fpath)
|
||||
print u"{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), u"key file" if len(winekeys) == 1 else u"key files")
|
||||
return winekeys
|
||||
@@ -1 +1 @@
|
||||
chcp 65001 > nul && set PWD=%~dp0 && cd /d %~dp0DeDRM_lib && start /min python DeDRM_App.pyw %*
|
||||
chcp 65001 > nul && set PWD="%~dp0" && cd /d "%~dp0DeDRM_lib" && start /min python DeDRM_App.pyw %*
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
# Revision history:
|
||||
# 6.0.0 - Release along with unified plugin
|
||||
# 6.0.1 - Bug Fixes for Windows App
|
||||
# 6.0.2 - Fixed problem with spaces in paths and the bat file
|
||||
# 6.0.3 - Fix for Windows non-ascii user names
|
||||
# 6.0.4 - Fix for other potential unicode problems
|
||||
# 6.0.5 - Fix typo
|
||||
|
||||
__version__ = '6.0.1'
|
||||
__version__ = '6.0.8'
|
||||
|
||||
import sys
|
||||
import os, os.path
|
||||
|
||||
@@ -44,11 +44,16 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.der’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.</p>
|
||||
|
||||
<h3>Linux Users: WINEPREFIX</h3>
|
||||
|
||||
<p>Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are use Adobe Digital Editions under Wine, and your wine installation containing Adobe Digital Editions isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.</p>
|
||||
|
||||
<h3>Importing Existing Keyfiles:</h3>
|
||||
|
||||
<p>At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.der’ key files. Key files might come from being exported from this or older plugins, or may have been generated using the adobekey.pyw script running under Wine on Linux systems.</p>
|
||||
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes will only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ p {margin-top: 0}
|
||||
|
||||
<body>
|
||||
|
||||
<h1>DeDRM Plugin <span class="version">(v6.0.0)</span></h1>
|
||||
<h1>DeDRM Plugin <span class="version">(v6.0.9)</span></h1>
|
||||
|
||||
<p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
|
||||
|
||||
|
||||
@@ -44,6 +44,10 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.der’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.</p>
|
||||
|
||||
<h3>Linux Users: WINEPREFIX</h3>
|
||||
|
||||
<p>Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are use Kindle for PC under Wine, and your wine installation containing Kindle for PC isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.</p>
|
||||
|
||||
<h3>Importing Existing Keyfiles:</h3>
|
||||
|
||||
<p>At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.k4i’ key files. Key files might come from being exported from this plugin, or may have been generated using the kindlekey.pyw script running under Wine on Linux systems.</p>
|
||||
|
||||
@@ -26,14 +26,23 @@ __docformat__ = 'restructuredtext en'
|
||||
#
|
||||
# Revision history:
|
||||
# 6.0.0 - Initial release
|
||||
# 6.0.1 - Bug Fixes for Windows App
|
||||
# 6.0.1 - Bug Fixes for Windows App, Kindle for Mac and Windows Adobe Digital Editions
|
||||
# 6.0.2 - Restored call to Wine to get Kindle for PC keys, added for ADE
|
||||
# 6.0.3 - Fixes for Kindle for Mac and Windows non-ascii user names
|
||||
# 6.0.4 - Fixes for stand-alone scripts and applications
|
||||
# and pdb files in plugin and initial conversion of prefs.
|
||||
# 6.0.5 - Fix a key issue
|
||||
# 6.0.6 - Fix up an incorrect function call
|
||||
# 6.0.7 - Error handling for incomplete PDF metadata
|
||||
# 6.0.8 - Fixes a Wine key issue and topaz support
|
||||
# 6.0.9 - Ported to work with newer versions of Calibre (moved to Qt5). Still supports older Qt4 versions.
|
||||
|
||||
"""
|
||||
Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 0, 1)
|
||||
PLUGIN_VERSION_TUPLE = (6, 0, 9)
|
||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||
@@ -83,9 +92,11 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
def initialize(self):
|
||||
# convert old preferences, if necessary.
|
||||
import calibre_plugins.dedrm.config
|
||||
|
||||
config.convertprefs()
|
||||
try:
|
||||
from calibre_plugins.dedrm.prefs import convertprefs
|
||||
convertprefs()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
"""
|
||||
Dynamic modules can't be imported/loaded from a zipfile... so this routine
|
||||
@@ -101,7 +112,7 @@ class DeDRM(FileTypePlugin):
|
||||
elif isosx:
|
||||
names = [u"libalfcrypto.dylib"]
|
||||
else:
|
||||
names = [u"libalfcrypto32.so",u"libalfcrypto64.so"]
|
||||
names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"]
|
||||
lib_dict = self.load_resources(names)
|
||||
self.pluginsdir = os.path.join(config_dir,u"plugins")
|
||||
if not os.path.exists(self.pluginsdir):
|
||||
@@ -112,7 +123,7 @@ class DeDRM(FileTypePlugin):
|
||||
self.helpdir = os.path.join(self.maindir,u"help")
|
||||
if not os.path.exists(self.helpdir):
|
||||
os.mkdir(self.helpdir)
|
||||
self.alfdir = os.path.join(self.maindir,u"alfcrypto")
|
||||
self.alfdir = os.path.join(self.maindir,u"libraryfiles")
|
||||
if not os.path.exists(self.alfdir):
|
||||
os.mkdir(self.alfdir)
|
||||
for entry, data in lib_dict.items():
|
||||
@@ -129,25 +140,27 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
inf = self.temporary_file(u".epub")
|
||||
try:
|
||||
print u"{0} v{1}: Verifying zip archive integrity.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print u"{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
fr = zipfix.fixZip(path_to_ebook, inf.name)
|
||||
fr.fix()
|
||||
except Exception, e:
|
||||
print u"{0} v{1}: Error \'{2}\' when checking zip archive.".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||
print u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||
raise Exception(e)
|
||||
|
||||
# import the decryption keys
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
|
||||
# import the Barnes & Noble ePub handler
|
||||
import calibre_plugins.dedrm.ignobleepub as ignobleepub
|
||||
|
||||
|
||||
#check the book
|
||||
if ignobleepub.ignobleBook(inf.name):
|
||||
print u"{0} v{1}: “{2}” is a secure Barnes & Noble ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: “{2}” is a secure Barnes & Noble ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
for keyname, userkey in config.dedrmprefs['bandnkeys'].items():
|
||||
for keyname, userkey in dedrmprefs['bandnkeys'].items():
|
||||
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)
|
||||
of = self.temporary_file(u".epub")
|
||||
@@ -162,7 +175,7 @@ class DeDRM(FileTypePlugin):
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
|
||||
@@ -171,10 +184,10 @@ class DeDRM(FileTypePlugin):
|
||||
import calibre_plugins.dedrm.ineptepub as ineptepub
|
||||
|
||||
if ineptepub.adeptBook(inf.name):
|
||||
print u"{0} v{1}: {2} is a secure Adobe Adept ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: {2} is a secure Adobe Adept ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
for keyname, userkeyhex in config.dedrmprefs['adeptkeys'].items():
|
||||
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
||||
userkey = userkeyhex.decode('hex')
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
|
||||
of = self.temporary_file(u".epub")
|
||||
@@ -192,54 +205,64 @@ class DeDRM(FileTypePlugin):
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||
|
||||
# perhaps we need to get a new default ADE key
|
||||
if iswindows or isosx:
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
# get the default Adobe keys
|
||||
import calibre_plugins.dedrm.adobekey as adobe
|
||||
# get the default Adobe keys
|
||||
defaultkeys = []
|
||||
|
||||
try:
|
||||
if iswindows or isosx:
|
||||
from calibre_plugins.dedrm.adobekey import adeptkeys
|
||||
|
||||
defaultkeys = adeptkeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
||||
|
||||
self.default_key = defaultkeys[0]
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.default_key = u""
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
defaultkeys = adobe.adeptkeys()
|
||||
except:
|
||||
defaultkeys = []
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".epub")
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in config.dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
try:
|
||||
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
||||
except:
|
||||
result = 1
|
||||
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".epub")
|
||||
of.close()
|
||||
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
dedrmprefs.writeprefs()
|
||||
except:
|
||||
result = 1
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
of.close()
|
||||
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
config.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
config.writeprefs()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
@@ -247,16 +270,17 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
# Not a Barnes & Noble nor an Adobe Adept
|
||||
# Import the fixed epub.
|
||||
print u"{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
return inf.name
|
||||
|
||||
def PDFDecrypt(self,path_to_ebook):
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
import calibre_plugins.dedrm.ineptpdf
|
||||
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
print u"{0} v{1}: {2} is a PDF ebook.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
for keyname, userkeyhex in config.dedrmprefs['adeptkeys'].items():
|
||||
print u"{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
||||
userkey = userkeyhex.decode('hex')
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
|
||||
of = self.temporary_file(u".pdf")
|
||||
@@ -275,56 +299,68 @@ class DeDRM(FileTypePlugin):
|
||||
return of.name
|
||||
|
||||
# perhaps we need to get a new default ADE key
|
||||
if iswindows or isosx:
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
# get the default Adobe keys
|
||||
# get the default Adobe keys
|
||||
defaultkeys = []
|
||||
|
||||
if iswindows or isosx:
|
||||
import calibre_plugins.dedrm.adobekey as adobe
|
||||
|
||||
try:
|
||||
defaultkeys = adobe.adeptkeys()
|
||||
except:
|
||||
defaultkeys = []
|
||||
pass
|
||||
else:
|
||||
# linux
|
||||
try:
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in config.dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
||||
except:
|
||||
pass
|
||||
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".pdf")
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".pdf")
|
||||
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
try:
|
||||
result = ineptepdf.decryptBook(userkey, inf.name, of.name)
|
||||
except:
|
||||
result = 1
|
||||
|
||||
of.close()
|
||||
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
result = ineptepdf.decryptBook(userkey, inf.name, of.name)
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
dedrmprefs.writeprefs()
|
||||
except:
|
||||
result = 1
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
of.close()
|
||||
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
config.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
config.writeprefs()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
|
||||
|
||||
|
||||
def KindleMobiDecrypt(self,path_to_ebook):
|
||||
|
||||
# add the alfcrypto directory to sys.path so alfcrypto.py
|
||||
@@ -333,42 +369,53 @@ class DeDRM(FileTypePlugin):
|
||||
# Had to move this import here so the custom libs can be
|
||||
# extracted to the appropriate places beforehand these routines
|
||||
# look for them.
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
import calibre_plugins.dedrm.k4mobidedrm
|
||||
|
||||
pids = config.dedrmprefs['pids']
|
||||
serials = config.dedrmprefs['serials']
|
||||
kindleDatabases = config.dedrmprefs['kindlekeys'].items()
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
pids = dedrmprefs['pids']
|
||||
serials = dedrmprefs['serials']
|
||||
kindleDatabases = dedrmprefs['kindlekeys'].items()
|
||||
|
||||
try:
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,serials,pids,self.starttime)
|
||||
except Exception, e:
|
||||
decoded = False
|
||||
# perhaps we need to get a new default Kindle for Mac/PC key
|
||||
if iswindows or isosx:
|
||||
print u"{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0])
|
||||
print u"{0} v{1}: Looking for new default Kindle Key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
import calibre_plugins.dedrm.kindlekey as amazon
|
||||
defaultkeys = []
|
||||
print u"{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0])
|
||||
print u"{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
try:
|
||||
if iswindows or isosx:
|
||||
from calibre_plugins.dedrm.kindlekey import kindlekeys
|
||||
|
||||
defaultkeys = kindlekeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,u"kindlekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix'])
|
||||
except:
|
||||
pass
|
||||
|
||||
newkeys = {}
|
||||
for i,keyvalue in enumerate(defaultkeys):
|
||||
keyname = u"default_key_{0:d}".format(i+1)
|
||||
if keyvalue not in dedrmprefs['kindlekeys'].values():
|
||||
newkeys[keyname] = keyvalue
|
||||
if len(newkeys) > 0:
|
||||
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||
try:
|
||||
defaultkeys = amazon.kindlekeys()
|
||||
except:
|
||||
defaultkeys = []
|
||||
newkeys = {}
|
||||
for i,keyvalue in enumerate(defaultkeys):
|
||||
keyname = u"default_key_{0:d}".format(i+1)
|
||||
if keyvalue not in config.dedrmprefs['kindlekeys'].values():
|
||||
newkeys[keyname] = keyvalue
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
|
||||
decoded = True
|
||||
# store the new successful keys in the defaults
|
||||
for keyvalue in newkeys.values():
|
||||
config.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
|
||||
config.writeprefs()
|
||||
except Exception, e:
|
||||
pass
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
|
||||
decoded = True
|
||||
# store the new successful keys in the defaults
|
||||
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||
for keyvalue in newkeys.values():
|
||||
dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
|
||||
dedrmprefs.writeprefs()
|
||||
except Exception, e:
|
||||
pass
|
||||
if not decoded:
|
||||
#if you reached here then no luck raise and exception
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
@@ -384,11 +431,12 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
def eReaderDecrypt(self,path_to_ebook):
|
||||
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
import calibre_plugins.dedrm.erdr2pml
|
||||
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
for keyname, userkey in config.dedrmprefs['ereaderkeys'].items():
|
||||
for keyname, userkey in dedrmprefs['ereaderkeys'].items():
|
||||
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)
|
||||
of = self.temporary_file(u".pmlz")
|
||||
@@ -403,7 +451,7 @@ class DeDRM(FileTypePlugin):
|
||||
if result == 0:
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
|
||||
@@ -415,7 +463,7 @@ class DeDRM(FileTypePlugin):
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
|
||||
print u"{0} v{1}: Trying to decrypt {2}.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: Trying to decrypt {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
self.starttime = time.time()
|
||||
|
||||
booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
|
||||
@@ -434,7 +482,7 @@ class DeDRM(FileTypePlugin):
|
||||
# Adobe Adept or B&N ePub
|
||||
decrypted_ebook = self.ePubDecrypt(path_to_ebook)
|
||||
else:
|
||||
print u"Unknown booktype {0}. Passing back to calibre unchanged.".format(booktype)
|
||||
print u"Unknown booktype {0}. Passing back to calibre unchanged".format(booktype)
|
||||
return path_to_ebook
|
||||
print u"{0} v{1}: Successfully decrypted book after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
return decrypted_ebook
|
||||
@@ -445,7 +493,7 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
def config_widget(self):
|
||||
import calibre_plugins.dedrm.config as config
|
||||
return config.ConfigWidget(self.plugin_path)
|
||||
return config.ConfigWidget(self.plugin_path, self.alfdir)
|
||||
|
||||
def save_settings(self, config_widget):
|
||||
config_widget.save_settings()
|
||||
|
||||
@@ -47,13 +47,14 @@ from __future__ import with_statement
|
||||
# 5.7 - Unicode support added, renamed adobekey from ineptkey
|
||||
# 5.8 - Added getkey interface for Windows DeDRM application
|
||||
# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.0 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Retrieve Adobe ADEPT user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '5.9'
|
||||
__version__ = '6.0'
|
||||
|
||||
import sys, os, struct, getopt
|
||||
|
||||
@@ -402,9 +403,11 @@ if iswindows:
|
||||
aes = AES(keykey)
|
||||
userkey = aes.decrypt(userkey)
|
||||
userkey = userkey[26:-ord(userkey[-1])]
|
||||
#print "found key:",userkey.encode('hex')
|
||||
keys.append(userkey)
|
||||
if len(keys) == 0:
|
||||
raise ADEPTError('Could not locate privateLicenseKey')
|
||||
print u"Found {0:d} keys".format(len(keys))
|
||||
return keys
|
||||
|
||||
|
||||
@@ -485,6 +488,8 @@ def usage(progname):
|
||||
print u" {0:s} [-h] [<outpath>]".format(progname)
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__)
|
||||
@@ -541,10 +546,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
@@ -577,7 +585,7 @@ def gui_main():
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except DrmException, e:
|
||||
except ADEPTError, e:
|
||||
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||
except Exception:
|
||||
root.wm_state('normal')
|
||||
@@ -591,7 +599,5 @@ 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())
|
||||
|
||||
157
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/android.py
Normal file
157
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/android.py
Normal file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python
|
||||
#fileencoding: utf-8
|
||||
|
||||
import os
|
||||
import sys
|
||||
import zlib
|
||||
import tarfile
|
||||
from hashlib import md5
|
||||
from cStringIO import StringIO
|
||||
from binascii import a2b_hex, b2a_hex
|
||||
|
||||
STORAGE = 'AmazonSecureStorage.xml'
|
||||
|
||||
class AndroidObfuscation(object):
|
||||
'''AndroidObfuscation
|
||||
For the key, it's written in java, and run in android dalvikvm
|
||||
'''
|
||||
|
||||
key = a2b_hex('0176e04c9408b1702d90be333fd53523')
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
cipher = self._get_cipher()
|
||||
padding = len(self.key) - len(plaintext) % len(self.key)
|
||||
plaintext += chr(padding) * padding
|
||||
return b2a_hex(cipher.encrypt(plaintext))
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
cipher = self._get_cipher()
|
||||
plaintext = cipher.decrypt(a2b_hex(ciphertext))
|
||||
return plaintext[:-ord(plaintext[-1])]
|
||||
|
||||
def _get_cipher(self):
|
||||
try:
|
||||
from Crypto.Cipher import AES
|
||||
return AES.new(self.key)
|
||||
except ImportError:
|
||||
from aescbc import AES, noPadding
|
||||
return AES(self.key, padding=noPadding())
|
||||
|
||||
class AndroidObfuscationV2(AndroidObfuscation):
|
||||
'''AndroidObfuscationV2
|
||||
'''
|
||||
|
||||
count = 503
|
||||
password = 'Thomsun was here!'
|
||||
|
||||
def __init__(self, salt):
|
||||
key = self.password + salt
|
||||
for _ in range(self.count):
|
||||
key = md5(key).digest()
|
||||
self.key = key[:8]
|
||||
self.iv = key[8:16]
|
||||
|
||||
def _get_cipher(self):
|
||||
try :
|
||||
from Crypto.Cipher import DES
|
||||
return DES.new(self.key, DES.MODE_CBC, self.iv)
|
||||
except ImportError:
|
||||
from python_des import Des, CBC
|
||||
return Des(self.key, CBC, self.iv)
|
||||
|
||||
def parse_preference(path):
|
||||
''' parse android's shared preference xml '''
|
||||
storage = {}
|
||||
read = open(path)
|
||||
for line in read:
|
||||
line = line.strip()
|
||||
# <string name="key">value</string>
|
||||
if line.startswith('<string name="'):
|
||||
index = line.find('"', 14)
|
||||
key = line[14:index]
|
||||
value = line[index+2:-9]
|
||||
storage[key] = value
|
||||
read.close()
|
||||
return storage
|
||||
|
||||
def get_serials(path=None):
|
||||
''' get serials from android's shared preference xml '''
|
||||
if path is None:
|
||||
if not os.path.isfile(STORAGE):
|
||||
if os.path.isfile("backup.ab"):
|
||||
get_storage()
|
||||
else:
|
||||
return []
|
||||
path = STORAGE
|
||||
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
storage = parse_preference(path)
|
||||
salt = storage.get('AmazonSaltKey')
|
||||
if salt and len(salt) == 16:
|
||||
sys.stdout.write('Using AndroidObfuscationV2\n')
|
||||
obfuscation = AndroidObfuscationV2(a2b_hex(salt))
|
||||
else:
|
||||
sys.stdout.write('Using AndroidObfuscation\n')
|
||||
obfuscation = AndroidObfuscation()
|
||||
|
||||
def get_value(key):
|
||||
encrypted_key = obfuscation.encrypt(key)
|
||||
encrypted_value = storage.get(encrypted_key)
|
||||
if encrypted_value:
|
||||
return obfuscation.decrypt(encrypted_value)
|
||||
return ''
|
||||
|
||||
# also see getK4Pids in kgenpids.py
|
||||
try:
|
||||
dsnid = get_value('DsnId')
|
||||
except:
|
||||
sys.stderr.write('cannot get DsnId\n')
|
||||
return []
|
||||
|
||||
try:
|
||||
tokens = set(get_value('kindle.account.tokens').split(','))
|
||||
except:
|
||||
return [dsnid]
|
||||
|
||||
serials = []
|
||||
for token in tokens:
|
||||
if token:
|
||||
serials.append('%s%s' % (dsnid, token))
|
||||
serials.append(dsnid)
|
||||
for token in tokens:
|
||||
if token:
|
||||
serials.append(token)
|
||||
return serials
|
||||
|
||||
def get_storage(path='backup.ab'):
|
||||
'''get AmazonSecureStorage.xml from android backup.ab
|
||||
backup.ab can be get using adb command:
|
||||
shell> adb backup com.amazon.kindle
|
||||
'''
|
||||
output = None
|
||||
read = open(path, 'rb')
|
||||
head = read.read(24)
|
||||
if head == 'ANDROID BACKUP\n1\n1\nnone\n':
|
||||
output = StringIO(zlib.decompress(read.read()))
|
||||
read.close()
|
||||
|
||||
if not output:
|
||||
return False
|
||||
|
||||
tar = tarfile.open(fileobj=output)
|
||||
for member in tar.getmembers():
|
||||
if member.name.strip().endswith(STORAGE):
|
||||
write = open(STORAGE, 'w')
|
||||
write.write(tar.extractfile(member).read())
|
||||
write.close()
|
||||
break
|
||||
|
||||
return True
|
||||
|
||||
__all__ = [ 'get_storage', 'get_serials', 'parse_preference',
|
||||
'AndroidObfuscation', 'AndroidObfuscationV2', 'STORAGE']
|
||||
|
||||
if __name__ == '__main__':
|
||||
print get_serials()
|
||||
@@ -0,0 +1,6 @@
|
||||
1.1 get AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml
|
||||
|
||||
1.2 on android 4.0+, run `adb backup com.amazon.kindle` from PC will get backup.ab
|
||||
now android.py can convert backup.ab to AmazonSecureStorage.xml
|
||||
|
||||
2. run `k4mobidedrm.py -a AmazonSecureStorage.xml <infile> <outdir>'
|
||||
File diff suppressed because it is too large
Load Diff
@@ -458,7 +458,11 @@ class DocParser(object):
|
||||
(wtype, num) = pdesc[j]
|
||||
|
||||
if wtype == 'ocr' :
|
||||
word = self.ocrtext[num]
|
||||
try:
|
||||
word = self.ocrtext[num]
|
||||
except:
|
||||
word = ""
|
||||
|
||||
sep = ' '
|
||||
|
||||
if handle_links:
|
||||
|
||||
@@ -34,13 +34,14 @@ from __future__ import with_statement
|
||||
# 3.7 - Tweaked to match ineptepub more closely
|
||||
# 3.8 - Fixed to retain zip file metadata (e.g. file modification date)
|
||||
# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 4.0 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Decrypt Barnes & Noble encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "3.9"
|
||||
__version__ = "4.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -318,6 +319,8 @@ def decryptBook(keyb64, inpath, outpath):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
@@ -331,10 +334,13 @@ def cli_main():
|
||||
return result
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -442,7 +448,5 @@ 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())
|
||||
|
||||
@@ -32,13 +32,14 @@ from __future__ import with_statement
|
||||
# 2.4 - Improvements to UI and now works in plugins
|
||||
# 2.5 - Additional improvement for unicode and plugin support
|
||||
# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 2.7 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "2.6"
|
||||
__version__ = "2.7"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -216,6 +217,8 @@ def generate_key(name, ccn):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if AES is None:
|
||||
@@ -233,10 +236,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -320,7 +326,5 @@ 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())
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 5.9
|
||||
# ineptepub.pyw, version 6.1
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
@@ -35,14 +35,15 @@ from __future__ import with_statement
|
||||
# 5.7 - Fix for potential problem with PyCrypto
|
||||
# 5.8 - Revised to allow use in calibre plugins to eliminate need for duplicate code
|
||||
# 5.9 - Fixed to retain zip file metadata (e.g. file modification date)
|
||||
# 5.10 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.1 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "5.10"
|
||||
__version__ = "6.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -460,6 +461,8 @@ def decryptBook(userkey, inpath, outpath):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
@@ -473,10 +476,13 @@ def cli_main():
|
||||
return result
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -584,7 +590,5 @@ 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())
|
||||
|
||||
@@ -51,13 +51,15 @@ from __future__ import with_statement
|
||||
# 7.12 - Revised to allow use in calibre plugins to eliminate need for duplicate code
|
||||
# 7.13 - Fixed erroneous mentions of ineptepub
|
||||
# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 8.0 - Work if TkInter is missing
|
||||
# 8.0.1 - Broken Metadata fix.
|
||||
|
||||
"""
|
||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "7.14"
|
||||
__version__ = "8.0.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -948,8 +950,11 @@ class PSStackParser(PSBaseParser):
|
||||
try:
|
||||
(pos, objs) = self.end_type('d')
|
||||
if len(objs) % 2 != 0:
|
||||
raise PSSyntaxError(
|
||||
'Invalid dictionary construct: %r' % objs)
|
||||
print "Incomplete dictionary construct"
|
||||
objs.append("") # this isn't necessary.
|
||||
# temporary fix. is this due to rental books?
|
||||
# raise PSSyntaxError(
|
||||
# 'Invalid dictionary construct: %r' % objs)
|
||||
d = dict((literal_name(k), v) \
|
||||
for (k,v) in choplist(2, objs))
|
||||
self.push((pos, d))
|
||||
@@ -2187,6 +2192,8 @@ def decryptBook(userkey, inpath, outpath):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
@@ -2201,10 +2208,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -2321,7 +2331,5 @@ 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())
|
||||
|
||||
@@ -54,8 +54,9 @@ from __future__ import with_statement
|
||||
# 5.0 - Extraction of info from Kindle for PC/Mac moved into kindlekey.py
|
||||
# - tweaked GetDecryptedBook interface to leave passed parameters unchanged
|
||||
# 5.1 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 5.2 - Fixed error in command line processing of unicode arguments
|
||||
|
||||
__version__ = '5.1'
|
||||
__version__ = '5.2'
|
||||
|
||||
|
||||
import sys, os, re
|
||||
@@ -79,10 +80,12 @@ if inCalibre:
|
||||
from calibre_plugins.dedrm import mobidedrm
|
||||
from calibre_plugins.dedrm import topazextract
|
||||
from calibre_plugins.dedrm import kgenpids
|
||||
from calibre_plugins.dedrm import android
|
||||
else:
|
||||
import mobidedrm
|
||||
import topazextract
|
||||
import kgenpids
|
||||
import android
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
@@ -187,7 +190,7 @@ def unescape(text):
|
||||
def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time()):
|
||||
# handle the obvious cases at the beginning
|
||||
if not os.path.isfile(infile):
|
||||
raise DRMException (u"Input file does not exist.")
|
||||
raise DrmException(u"Input file does not exist.")
|
||||
|
||||
mobi = True
|
||||
magic3 = open(infile,'rb').read(3)
|
||||
@@ -272,7 +275,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
|
||||
def usage(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.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)
|
||||
print u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml> ] <infile> <outdir>".format(progname)
|
||||
|
||||
#
|
||||
# Main
|
||||
@@ -283,7 +286,7 @@ def cli_main():
|
||||
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2013 The Dark Reverser et al.".format(__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
usage(progname)
|
||||
@@ -311,6 +314,11 @@ def cli_main():
|
||||
if a == None :
|
||||
raise DrmException("Invalid parameter for -s")
|
||||
serials = a.split(',')
|
||||
if o == '-a':
|
||||
if a == None:
|
||||
continue
|
||||
serials.extend(android.get_serials(a))
|
||||
serials.extend(android.get_serials())
|
||||
|
||||
# try with built in Kindle Info files if not on Linux
|
||||
k4 = not sys.platform.startswith('linux')
|
||||
|
||||
@@ -17,6 +17,9 @@ from __future__ import with_statement
|
||||
# 1.4 - Remove dependency on alfcrypto
|
||||
# 1.5 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 1.6 - Fixed a problem getting the disk serial numbers
|
||||
# 1.7 - Work if TkInter is missing
|
||||
# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names
|
||||
# 1.9 - Fixes for Unicode in Windows user names
|
||||
|
||||
|
||||
"""
|
||||
@@ -24,7 +27,7 @@ Retrieve Kindle for PC/Mac user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.6'
|
||||
__version__ = '1.9'
|
||||
|
||||
import sys, os, re
|
||||
from struct import pack, unpack, unpack_from
|
||||
@@ -905,18 +908,34 @@ if iswindows:
|
||||
return CryptUnprotectData
|
||||
CryptUnprotectData = CryptUnprotectData()
|
||||
|
||||
# Returns Environmental Variables that contain unicode
|
||||
def getEnvironmentVariable(name):
|
||||
import ctypes
|
||||
name = unicode(name) # make sure string argument is unicode
|
||||
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
|
||||
if n == 0:
|
||||
return None
|
||||
buf = ctypes.create_unicode_buffer(u'\0'*n)
|
||||
ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n)
|
||||
return buf.value
|
||||
|
||||
# Locate all of the kindle-info style files and return as list
|
||||
def getKindleInfoFiles():
|
||||
kInfoFiles = []
|
||||
# some 64 bit machines do not have the proper registry key for some reason
|
||||
# or the pythonn interface to the 32 vs 64 bit registry is broken
|
||||
# or the python interface to the 32 vs 64 bit registry is broken
|
||||
path = ""
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
path = os.environ['LOCALAPPDATA']
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
# this is just another alternative.
|
||||
# path = getEnvironmentVariable('LOCALAPPDATA')
|
||||
if not os.path.isdir(path):
|
||||
path = ""
|
||||
else:
|
||||
# User Shell Folders show take precedent over Shell Folders if present
|
||||
try:
|
||||
# this will still break
|
||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
|
||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||
if not os.path.isdir(path):
|
||||
@@ -935,13 +954,14 @@ if iswindows:
|
||||
if path == "":
|
||||
print ('Could not find the folder in which to look for kinfoFiles.')
|
||||
else:
|
||||
print('searching for kinfoFiles in ' + path)
|
||||
# Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
|
||||
print(u'searching for kinfoFiles in ' + path.encode('ascii', 'ignore'))
|
||||
|
||||
# look for (K4PC 1.9.0 and later) .kinf2011 file
|
||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
||||
if os.path.isfile(kinfopath):
|
||||
found = True
|
||||
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath)
|
||||
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath.encode('ascii','ignore'))
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
||||
@@ -1140,8 +1160,8 @@ if iswindows:
|
||||
cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName())
|
||||
if 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode("latin-1"))
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
@@ -1520,20 +1540,32 @@ elif isosx:
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kinf2011 file: ' + testpath)
|
||||
found = True
|
||||
# check for .kinf2011 files
|
||||
# check for .kinf2011 files from 1.10
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kinf2011 file: ' + testpath)
|
||||
found = True
|
||||
# check for .rainier-2.1.1-kinf files
|
||||
# check for .rainier-2.1.1-kinf files from 1.6
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac rainier file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info files
|
||||
# check for .kindle-info files from 1.4
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info file from 1.2.2
|
||||
testpath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info file from 1.0 beta 1 (27214)
|
||||
testpath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
@@ -1744,7 +1776,7 @@ elif isosx:
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
if 'kindle.account.tokens' in DB:
|
||||
# store values used in decryption
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())
|
||||
DB['IDString'] = IDString
|
||||
@@ -1804,6 +1836,8 @@ def usage(progname):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2010-2013 some_updates and Apprentice Alf".format(progname,__version__)
|
||||
@@ -1845,10 +1879,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
@@ -1895,7 +1932,5 @@ 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())
|
||||
|
||||
@@ -68,9 +68,10 @@
|
||||
# 0.38 - Unicode used wherever possible, cope with absent alfcrypto
|
||||
# 0.39 - Fixed problem with TEXtREAd and getBookType interface
|
||||
# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 0.41 - Fixed potential unicode problem in command line calls
|
||||
|
||||
|
||||
__version__ = u"0.40"
|
||||
__version__ = u"0.41"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -155,6 +156,8 @@ def PC1(key, src, decryption=True):
|
||||
return Pukall_Cipher().PC1(key,src,decryption)
|
||||
except NameError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# use slow python version, since Pukall_Cipher didn't load
|
||||
sum1 = 0;
|
||||
@@ -514,7 +517,7 @@ def cli_main():
|
||||
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]))
|
||||
print u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname)
|
||||
return 1
|
||||
else:
|
||||
infile = argv[1]
|
||||
|
||||
292
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/prefs.py
Normal file
292
DeDRM_Windows_Application/DeDRM_App/DeDRM_lib/lib/prefs.py
Normal file
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
from __future__ import with_statement
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
# Standard Python modules.
|
||||
import os, sys, re, hashlib
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from calibre.utils.config import dynamic, config_dir, JSONConfig
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
from calibre.constants import iswindows, isosx
|
||||
|
||||
class DeDRM_Prefs():
|
||||
def __init__(self):
|
||||
JSON_PATH = os.path.join(u"plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
|
||||
self.dedrmprefs = JSONConfig(JSON_PATH)
|
||||
|
||||
self.dedrmprefs.defaults['configured'] = False
|
||||
self.dedrmprefs.defaults['bandnkeys'] = {}
|
||||
self.dedrmprefs.defaults['adeptkeys'] = {}
|
||||
self.dedrmprefs.defaults['ereaderkeys'] = {}
|
||||
self.dedrmprefs.defaults['kindlekeys'] = {}
|
||||
self.dedrmprefs.defaults['pids'] = []
|
||||
self.dedrmprefs.defaults['serials'] = []
|
||||
self.dedrmprefs.defaults['adobewineprefix'] = ""
|
||||
self.dedrmprefs.defaults['kindlewineprefix'] = ""
|
||||
|
||||
# initialise
|
||||
# we must actually set the prefs that are dictionaries and lists
|
||||
# to empty dictionaries and lists, otherwise we are unable to add to them
|
||||
# as then it just adds to the (memory only) dedrmprefs.defaults versions!
|
||||
if self.dedrmprefs['bandnkeys'] == {}:
|
||||
self.dedrmprefs['bandnkeys'] = {}
|
||||
if self.dedrmprefs['adeptkeys'] == {}:
|
||||
self.dedrmprefs['adeptkeys'] = {}
|
||||
if self.dedrmprefs['ereaderkeys'] == {}:
|
||||
self.dedrmprefs['ereaderkeys'] = {}
|
||||
if self.dedrmprefs['kindlekeys'] == {}:
|
||||
self.dedrmprefs['kindlekeys'] = {}
|
||||
if self.dedrmprefs['pids'] == []:
|
||||
self.dedrmprefs['pids'] = []
|
||||
if self.dedrmprefs['serials'] == []:
|
||||
self.dedrmprefs['serials'] = []
|
||||
|
||||
def __getitem__(self,kind = None):
|
||||
if kind is not None:
|
||||
return self.dedrmprefs[kind]
|
||||
return self.dedrmprefs
|
||||
|
||||
def set(self, kind, value):
|
||||
self.dedrmprefs[kind] = value
|
||||
|
||||
def writeprefs(self,value = True):
|
||||
self.dedrmprefs['configured'] = value
|
||||
|
||||
def addnamedvaluetoprefs(self, prefkind, keyname, keyvalue):
|
||||
try:
|
||||
if keyvalue not in self.dedrmprefs[prefkind].values():
|
||||
# ensure that the keyname is unique
|
||||
# by adding a number (starting with 2) to the name if it is not
|
||||
namecount = 1
|
||||
newname = keyname
|
||||
while newname in self.dedrmprefs[prefkind]:
|
||||
namecount += 1
|
||||
newname = "{0:s}_{1:d}".format(keyname,namecount)
|
||||
# add to the preferences
|
||||
self.dedrmprefs[prefkind][newname] = keyvalue
|
||||
return (True, newname)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
return (False, keyname)
|
||||
|
||||
def addvaluetoprefs(self, prefkind, prefsvalue):
|
||||
# ensure the keyvalue isn't already in the preferences
|
||||
try:
|
||||
if prefsvalue not in self.dedrmprefs[prefkind]:
|
||||
self.dedrmprefs[prefkind].append(prefsvalue)
|
||||
return True
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def convertprefs(always = False):
|
||||
|
||||
def parseIgnobleString(keystuff):
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key
|
||||
userkeys = []
|
||||
ar = keystuff.split(':')
|
||||
for keystring in ar:
|
||||
try:
|
||||
name, ccn = keystring.split(',')
|
||||
# Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||
keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:])
|
||||
keyvalue = generate_key(name, ccn)
|
||||
userkeys.append([keyname,keyvalue])
|
||||
except Exception, e:
|
||||
traceback.print_exc()
|
||||
print e.args[0]
|
||||
pass
|
||||
return userkeys
|
||||
|
||||
def parseeReaderString(keystuff):
|
||||
from calibre_plugins.dedrm.erdr2pml import getuser_key
|
||||
userkeys = []
|
||||
ar = keystuff.split(':')
|
||||
for keystring in ar:
|
||||
try:
|
||||
name, cc = keystring.split(',')
|
||||
# Generate eReader user key from name and credit card number.
|
||||
keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:])
|
||||
keyvalue = getuser_key(name,cc).encode('hex')
|
||||
userkeys.append([keyname,keyvalue])
|
||||
except Exception, e:
|
||||
traceback.print_exc()
|
||||
print e.args[0]
|
||||
pass
|
||||
return userkeys
|
||||
|
||||
def parseKindleString(keystuff):
|
||||
pids = []
|
||||
serials = []
|
||||
ar = keystuff.split(',')
|
||||
for keystring in ar:
|
||||
keystring = str(keystring).strip().replace(" ","")
|
||||
if len(keystring) == 10 or len(keystring) == 8 and keystring not in pids:
|
||||
pids.append(keystring)
|
||||
elif len(keystring) == 16 and keystring[0] == 'B' and keystring not in serials:
|
||||
serials.append(keystring)
|
||||
return (pids,serials)
|
||||
|
||||
def getConfigFiles(extension, encoding = None):
|
||||
# get any files with extension 'extension' in the config dir
|
||||
userkeys = []
|
||||
files = [f for f in os.listdir(config_dir) if f.endswith(extension)]
|
||||
for filename in files:
|
||||
try:
|
||||
fpath = os.path.join(config_dir, filename)
|
||||
key = os.path.splitext(filename)[0]
|
||||
value = open(fpath, 'rb').read()
|
||||
if encoding is not None:
|
||||
value = value.encode(encoding)
|
||||
userkeys.append([key,value])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
return userkeys
|
||||
|
||||
dedrmprefs = DeDRM_Prefs()
|
||||
|
||||
if (not always) and dedrmprefs['configured']:
|
||||
# We've already converted old preferences,
|
||||
# and we're not being forced to do it again, so just return
|
||||
return
|
||||
|
||||
|
||||
print u"{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
|
||||
IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM"
|
||||
EREADERPLUGINNAME = "eReader PDB 2 PML"
|
||||
OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM"
|
||||
|
||||
# get prefs from older tools
|
||||
kindleprefs = JSONConfig(os.path.join(u"plugins", u"K4MobiDeDRM"))
|
||||
ignobleprefs = JSONConfig(os.path.join(u"plugins", u"ignoble_epub_dedrm"))
|
||||
|
||||
# Handle the old ignoble plugin's customization string by converting the
|
||||
# old string to stored keys... get that personal data out of plain sight.
|
||||
from calibre.customize.ui import config
|
||||
sc = config['plugin_customization']
|
||||
val = sc.pop(IGNOBLEPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
priorkeycount = len(dedrmprefs['bandnkeys'])
|
||||
userkeys = parseIgnobleString(str(val))
|
||||
for keypair in userkeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# Handle the old eReader plugin's customization string by converting the
|
||||
# old string to stored keys... get that personal data out of plain sight.
|
||||
val = sc.pop(EREADERPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
priorkeycount = len(dedrmprefs['ereaderkeys'])
|
||||
userkeys = parseeReaderString(str(val))
|
||||
for keypair in userkeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount
|
||||
print u"{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get old Kindle plugin configuration string
|
||||
val = sc.pop(OLDKINDLEPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
priorpidcount = len(dedrmprefs['pids'])
|
||||
priorserialcount = len(dedrmprefs['serials'])
|
||||
pids, serials = parseKindleString(val)
|
||||
for pid in pids:
|
||||
dedrmprefs.addvaluetoprefs('pids',pid)
|
||||
for serial in serials:
|
||||
dedrmprefs.addvaluetoprefs('serials',serial)
|
||||
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
||||
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
||||
print u"{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs", addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# copy the customisations back into calibre preferences, as we've now removed the nasty plaintext
|
||||
config['plugin_customization'] = sc
|
||||
|
||||
# get any .b64 files in the config dir
|
||||
priorkeycount = len(dedrmprefs['bandnkeys'])
|
||||
bandnfilekeys = getConfigFiles('.b64')
|
||||
for keypair in bandnfilekeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key file" if addedkeycount==1 else u"key files")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get any .der files in the config dir
|
||||
priorkeycount = len(dedrmprefs['adeptkeys'])
|
||||
adeptfilekeys = getConfigFiles('.der','hex')
|
||||
for keypair in adeptfilekeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"keyfile" if addedkeycount==1 else u"keyfiles")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get ignoble json prefs
|
||||
if 'keys' in ignobleprefs:
|
||||
priorkeycount = len(dedrmprefs['bandnkeys'])
|
||||
for name in ignobleprefs['keys']:
|
||||
value = ignobleprefs['keys'][name]
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount
|
||||
# no need to delete old prefs, since they contain no recoverable private data
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get kindle json prefs
|
||||
priorpidcount = len(dedrmprefs['pids'])
|
||||
priorserialcount = len(dedrmprefs['serials'])
|
||||
if 'pids' in kindleprefs:
|
||||
pids, serials = parseKindleString(kindleprefs['pids'])
|
||||
for pid in pids:
|
||||
dedrmprefs.addvaluetoprefs('pids',pid)
|
||||
if 'serials' in kindleprefs:
|
||||
pids, serials = parseKindleString(kindleprefs['serials'])
|
||||
for serial in serials:
|
||||
dedrmprefs.addvaluetoprefs('serials',serial)
|
||||
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
||||
if addedpidcount > 0:
|
||||
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs")
|
||||
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
||||
if addedserialcount > 0:
|
||||
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
|
||||
try:
|
||||
if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "":
|
||||
dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix'])
|
||||
dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix'])
|
||||
print u"{0} v{1}: WINEPREFIX ‘(2)’ imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix'])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs()
|
||||
print u"{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
@@ -178,7 +178,12 @@ class DocParser(object):
|
||||
if val == "":
|
||||
val = 0
|
||||
|
||||
if not ((attr == 'hang') and (int(val) == 0)) :
|
||||
if not ((attr == 'hang') and (int(val) == 0)):
|
||||
try:
|
||||
f = float(val)
|
||||
except:
|
||||
print "Warning: unrecognised val, ignoring"
|
||||
val = 0
|
||||
pv = float(val)/scale
|
||||
cssargs[attr] = (self.attr_val_map[attr], pv)
|
||||
keep = True
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
|
||||
# Changelog
|
||||
# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 5.0 - Fixed potential unicode problem with command line interface
|
||||
|
||||
__version__ = '4.9'
|
||||
__version__ = '5.0'
|
||||
|
||||
import sys
|
||||
import os, csv, getopt
|
||||
@@ -355,7 +356,7 @@ class TopazBook:
|
||||
|
||||
self.setBookKey(bookKey)
|
||||
self.createBookDirectory()
|
||||
self.extractFiles()
|
||||
self.extractFiles()
|
||||
print u"Successfully Extracted Topaz contents"
|
||||
if inCalibre:
|
||||
from calibre_plugins.dedrm import genbook
|
||||
@@ -451,7 +452,7 @@ def cli_main():
|
||||
print u"TopazExtract v{0}.".format(__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:x")
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:x")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
usage(progname)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
# Standard Python modules.
|
||||
import os, sys, re, hashlib
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
|
||||
def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||
import subprocess
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
import subasyncio
|
||||
from subasyncio import Process
|
||||
|
||||
if extension == u".k4i":
|
||||
import json
|
||||
|
||||
basepath, script = os.path.split(scriptpath)
|
||||
print u"{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script)
|
||||
|
||||
outdirpath = os.path.join(basepath, u"winekeysdir")
|
||||
if not os.path.exists(outdirpath):
|
||||
os.makedirs(outdirpath)
|
||||
|
||||
if wineprefix != "" and os.path.exists(wineprefix):
|
||||
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||
else:
|
||||
cmdline = u"wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
||||
print u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)
|
||||
|
||||
try:
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
||||
result = p2.wait("wait")
|
||||
except Exception, e:
|
||||
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||
return []
|
||||
|
||||
winekeys = []
|
||||
# get any files with extension in the output dir
|
||||
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
||||
for filename in files:
|
||||
try:
|
||||
fpath = os.path.join(outdirpath, filename)
|
||||
with open(fpath, 'rb') as keyfile:
|
||||
if extension == u".k4i":
|
||||
new_key_value = json.loads(keyfile.read())
|
||||
else:
|
||||
new_key_value = keyfile.read()
|
||||
winekeys.append(new_key_value)
|
||||
except:
|
||||
print u"{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename)
|
||||
traceback.print_exc()
|
||||
os.remove(fpath)
|
||||
print u"{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), u"key file" if len(winekeys) == 1 else u"key files")
|
||||
return winekeys
|
||||
@@ -21,7 +21,7 @@ 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_App folder from tools_v6.0.0/DeDRM_Application_Windows to your "My Documents" folder.
|
||||
1. Drag the DeDRM_App folder from tools_v6.1.0/DeDRM_Application_Windows to your "My Documents" folder.
|
||||
|
||||
2. Open the DeDRM_App 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.
|
||||
|
||||
|
||||
Binary file not shown.
@@ -44,11 +44,16 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.der’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.</p>
|
||||
|
||||
<h3>Linux Users: WINEPREFIX</h3>
|
||||
|
||||
<p>Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are use Adobe Digital Editions under Wine, and your wine installation containing Adobe Digital Editions isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.</p>
|
||||
|
||||
<h3>Importing Existing Keyfiles:</h3>
|
||||
|
||||
<p>At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.der’ key files. Key files might come from being exported from this or older plugins, or may have been generated using the adobekey.pyw script running under Wine on Linux systems.</p>
|
||||
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes will only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
@@ -22,7 +22,18 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>If you have upgraded from an earlier version of the plugin, any existing Barnes and Noble keys will have been automatically imported, so you might not need to do any more configuration. Continue reading for key generation and management instructions.</p>
|
||||
|
||||
<h3>Creating New Keys:</h3>
|
||||
<h3>Changes at Barnes & Noble</h3>
|
||||
|
||||
<p>In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the B&N servers. This means that some users will find that no combination of their name and CC# will work in decrypting their ebooks.</p>
|
||||
|
||||
<p>There is a work-around. B&N's desktop apps for Mac and Windows (nook for Mac/PC, nookStudy) generate a log file that contains the encryption key. For Mac the log file can be found somewhere in [user folder]/Library/Application Support/Barnes & Noble/ and for Windows the log file can be found somewhere in C:\Users\admin\AppData\Roaming\Barnes & Noble\</p>
|
||||
<p>In both cases, the log file will be called BNClientLog.txt</p>
|
||||
<p>You will need to open the application, sign in to your account, and download your books, checking that you can read them in the application, and then quit the application.</p>
|
||||
<p>Then you must open the log file in a text editor and search for CCHashResponseV1. Immediately after that text should be some more text, similar to ccHash: "rLYiGD+vcPoXvsj/87kDAb1AkBy="<p>
|
||||
<p>Copy the text after ccHash, include the " marks. Save it in a new text file, with file name extension .b64</p>
|
||||
<p>Follow the instructions below "Importing Existing Keyfiles:" to import that newly saved file into the preferences.</p>
|
||||
|
||||
<h3>Old instructions: Creating New Keys:</h3>
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.</p>
|
||||
<ul>
|
||||
@@ -48,7 +59,7 @@ li {margin-top: 0.5em}
|
||||
|
||||
<h3>Importing Existing Keyfiles:</h3>
|
||||
|
||||
<p>At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.b64’ key files. Key files might come from being exported from this or older plugins, or may have been generated using the original i♥cabbages script.</p>
|
||||
<p>At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.b64’ key files. Key files might come from being exported from this or older plugins, or may have been generated using the original i♥cabbages script, or you may have made it by following the instructions above.</p>
|
||||
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ p {margin-top: 0}
|
||||
|
||||
<body>
|
||||
|
||||
<h1>DeDRM Plugin <span class="version">(v6.0.0)</span></h1>
|
||||
<h1>DeDRM Plugin <span class="version">(v6.1.0)</span></h1>
|
||||
|
||||
<p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
|
||||
|
||||
|
||||
@@ -44,6 +44,10 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a computer’s hard-drive. Use this button to export the highlighted key to a file (with a ‘.der’ file name extension). Used for backup purposes or to migrate key data to other computers/calibre installations. The dialog will prompt you for a place to save the file.</p>
|
||||
|
||||
<h3>Linux Users: WINEPREFIX</h3>
|
||||
|
||||
<p>Under the list of keys, Linux users will see a text field labeled "WINEPREFIX". If you are use Kindle for PC under Wine, and your wine installation containing Kindle for PC isn't the default Wine installation, you may enter the full path to the correct Wine installation here. Leave blank if you are unsure.</p>
|
||||
|
||||
<h3>Importing Existing Keyfiles:</h3>
|
||||
|
||||
<p>At the bottom-left of the plugin’s customization dialog, you will see a button labeled "Import Existing Keyfiles". Use this button to import existing ‘.k4i’ key files. Key files might come from being exported from this plugin, or may have been generated using the kindlekey.pyw script running under Wine on Linux systems.</p>
|
||||
|
||||
@@ -27,13 +27,23 @@ __docformat__ = 'restructuredtext en'
|
||||
# Revision history:
|
||||
# 6.0.0 - Initial release
|
||||
# 6.0.1 - Bug Fixes for Windows App, Kindle for Mac and Windows Adobe Digital Editions
|
||||
# 6.0.2 - Restored call to Wine to get Kindle for PC keys, added for ADE
|
||||
# 6.0.3 - Fixes for Kindle for Mac and Windows non-ascii user names
|
||||
# 6.0.4 - Fixes for stand-alone scripts and applications
|
||||
# and pdb files in plugin and initial conversion of prefs.
|
||||
# 6.0.5 - Fix a key issue
|
||||
# 6.0.6 - Fix up an incorrect function call
|
||||
# 6.0.7 - Error handling for incomplete PDF metadata
|
||||
# 6.0.8 - Fixes a Wine key issue and topaz support
|
||||
# 6.0.9 - Ported to work with newer versions of Calibre (moved to Qt5). Still supports older Qt4 versions.
|
||||
# 6.1.0 - Fixed multiple books import problem and PDF import with no key problem
|
||||
|
||||
"""
|
||||
Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 0, 1)
|
||||
PLUGIN_VERSION_TUPLE = (6, 1, 0)
|
||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||
@@ -81,19 +91,22 @@ class DeDRM(FileTypePlugin):
|
||||
on_import = True
|
||||
priority = 600
|
||||
|
||||
def initialize(self):
|
||||
# convert old preferences, if necessary.
|
||||
import calibre_plugins.dedrm.config
|
||||
|
||||
config.convertprefs()
|
||||
def load_resources(self, names):
|
||||
print u"{0} v{1}: In load_resources".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
return {}
|
||||
|
||||
def __init__(self, plugin_path):
|
||||
print u"{0} v{1}: In __init__".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
super(DeDRM, self).__init__(plugin_path)
|
||||
"""
|
||||
Dynamic modules can't be imported/loaded from a zipfile... so this routine
|
||||
runs whenever the plugin gets initialized. This will extract the appropriate
|
||||
Dynamic modules can't be imported/loaded from a zipfile.
|
||||
So this routine will extract the appropriate
|
||||
library for the target OS and copy it to the 'alfcrypto' subdirectory of
|
||||
calibre's configuration directory. That 'alfcrypto' directory is then
|
||||
inserted into the syspath (as the very first entry) in the run function
|
||||
so the CDLL stuff will work in the alfcrypto.py script.
|
||||
|
||||
The extraction only happens once per version of the plugin
|
||||
"""
|
||||
try:
|
||||
if iswindows:
|
||||
@@ -101,7 +114,7 @@ class DeDRM(FileTypePlugin):
|
||||
elif isosx:
|
||||
names = [u"libalfcrypto.dylib"]
|
||||
else:
|
||||
names = [u"libalfcrypto32.so",u"libalfcrypto64.so"]
|
||||
names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"]
|
||||
lib_dict = self.load_resources(names)
|
||||
self.pluginsdir = os.path.join(config_dir,u"plugins")
|
||||
if not os.path.exists(self.pluginsdir):
|
||||
@@ -112,16 +125,34 @@ class DeDRM(FileTypePlugin):
|
||||
self.helpdir = os.path.join(self.maindir,u"help")
|
||||
if not os.path.exists(self.helpdir):
|
||||
os.mkdir(self.helpdir)
|
||||
self.alfdir = os.path.join(self.maindir,u"alfcrypto")
|
||||
self.alfdir = os.path.join(self.maindir,u"libraryfiles")
|
||||
if not os.path.exists(self.alfdir):
|
||||
os.mkdir(self.alfdir)
|
||||
for entry, data in lib_dict.items():
|
||||
file_path = os.path.join(self.alfdir, entry)
|
||||
open(file_path,'wb').write(data)
|
||||
# only continue if we've never run this version of the plugin before
|
||||
self.verdir = os.path.join(self.maindir,PLUGIN_VERSION)
|
||||
print u"{0} v{1}: verdir {2}".format(PLUGIN_NAME, PLUGIN_VERSION, self.verdir)
|
||||
if not os.path.exists(self.verdir):
|
||||
print u"{0} v{1}: Copying needed libraries from zip".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
os.mkdir(self.verdir)
|
||||
for entry, data in lib_dict.items():
|
||||
file_path = os.path.join(self.alfdir, entry)
|
||||
os.remove(file_path)
|
||||
open(file_path,'wb').write(data)
|
||||
except Exception, e:
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
|
||||
|
||||
def initialize(self):
|
||||
print u"{0} v{1}: In initialize".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
# convert old preferences, if necessary.
|
||||
try:
|
||||
from calibre_plugins.dedrm.prefs import convertprefs
|
||||
convertprefs()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
def ePubDecrypt(self,path_to_ebook):
|
||||
# Create a TemporaryPersistent file to work with.
|
||||
# Check original epub archive for zip errors.
|
||||
@@ -129,25 +160,27 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
inf = self.temporary_file(u".epub")
|
||||
try:
|
||||
print u"{0} v{1}: Verifying zip archive integrity.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print u"{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
fr = zipfix.fixZip(path_to_ebook, inf.name)
|
||||
fr.fix()
|
||||
except Exception, e:
|
||||
print u"{0} v{1}: Error \'{2}\' when checking zip archive.".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||
print u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||
raise Exception(e)
|
||||
|
||||
# import the decryption keys
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
|
||||
# import the Barnes & Noble ePub handler
|
||||
import calibre_plugins.dedrm.ignobleepub as ignobleepub
|
||||
|
||||
|
||||
#check the book
|
||||
if ignobleepub.ignobleBook(inf.name):
|
||||
print u"{0} v{1}: “{2}” is a secure Barnes & Noble ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: “{2}” is a secure Barnes & Noble ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
for keyname, userkey in config.dedrmprefs['bandnkeys'].items():
|
||||
for keyname, userkey in dedrmprefs['bandnkeys'].items():
|
||||
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)
|
||||
of = self.temporary_file(u".epub")
|
||||
@@ -162,7 +195,7 @@ class DeDRM(FileTypePlugin):
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
|
||||
@@ -171,10 +204,10 @@ class DeDRM(FileTypePlugin):
|
||||
import calibre_plugins.dedrm.ineptepub as ineptepub
|
||||
|
||||
if ineptepub.adeptBook(inf.name):
|
||||
print u"{0} v{1}: {2} is a secure Adobe Adept ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: {2} is a secure Adobe Adept ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
for keyname, userkeyhex in config.dedrmprefs['adeptkeys'].items():
|
||||
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
||||
userkey = userkeyhex.decode('hex')
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
|
||||
of = self.temporary_file(u".epub")
|
||||
@@ -192,54 +225,64 @@ class DeDRM(FileTypePlugin):
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||
|
||||
# perhaps we need to get a new default ADE key
|
||||
if iswindows or isosx:
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
# get the default Adobe keys
|
||||
import calibre_plugins.dedrm.adobekey as adobe
|
||||
# get the default Adobe keys
|
||||
defaultkeys = []
|
||||
|
||||
try:
|
||||
if iswindows or isosx:
|
||||
from calibre_plugins.dedrm.adobekey import adeptkeys
|
||||
|
||||
defaultkeys = adeptkeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
||||
|
||||
self.default_key = defaultkeys[0]
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.default_key = u""
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
defaultkeys = adobe.adeptkeys()
|
||||
except:
|
||||
defaultkeys = []
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".epub")
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in config.dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
try:
|
||||
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
||||
except:
|
||||
result = 1
|
||||
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".epub")
|
||||
of.close()
|
||||
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
dedrmprefs.writeprefs()
|
||||
except:
|
||||
result = 1
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
of.close()
|
||||
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
config.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
config.writeprefs()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
@@ -247,16 +290,17 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
# Not a Barnes & Noble nor an Adobe Adept
|
||||
# Import the fixed epub.
|
||||
print u"{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
return inf.name
|
||||
|
||||
def PDFDecrypt(self,path_to_ebook):
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
import calibre_plugins.dedrm.ineptpdf
|
||||
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
print u"{0} v{1}: {2} is a PDF ebook.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
for keyname, userkeyhex in config.dedrmprefs['adeptkeys'].items():
|
||||
print u"{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
||||
userkey = userkeyhex.decode('hex')
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
|
||||
of = self.temporary_file(u".pdf")
|
||||
@@ -274,57 +318,69 @@ class DeDRM(FileTypePlugin):
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
# perhaps we need to get a new default ADE key
|
||||
if iswindows or isosx:
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
# perhaps we need to get a new default ADE key
|
||||
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
# get the default Adobe keys
|
||||
import calibre_plugins.dedrm.adobekey as adobe
|
||||
# get the default Adobe keys
|
||||
defaultkeys = []
|
||||
|
||||
try:
|
||||
defaultkeys = adobe.adeptkeys()
|
||||
except:
|
||||
defaultkeys = []
|
||||
if iswindows or isosx:
|
||||
import calibre_plugins.dedrm.adobekey as adobe
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in config.dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
try:
|
||||
defaultkeys = adobe.adeptkeys()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# linux
|
||||
try:
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
if len(newkeys) > 0:
|
||||
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
||||
except:
|
||||
pass
|
||||
|
||||
newkeys = []
|
||||
for keyvalue in defaultkeys:
|
||||
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
||||
newkeys.append(keyvalue)
|
||||
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".pdf")
|
||||
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
try:
|
||||
for i,userkey in enumerate(newkeys):
|
||||
print u"{0} v{1}: Trying a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
of = self.temporary_file(u".pdf")
|
||||
result = ineptepdf.decryptBook(userkey, inf.name, of.name)
|
||||
except:
|
||||
result = 1
|
||||
|
||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||
try:
|
||||
result = ineptepdf.decryptBook(userkey, inf.name, of.name)
|
||||
except:
|
||||
result = 1
|
||||
of.close()
|
||||
|
||||
of.close()
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
dedrmprefs.writeprefs()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
if result == 0:
|
||||
# Decryption was a success
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
config.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||
config.writeprefs()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
# Something went wrong with decryption.
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
|
||||
|
||||
|
||||
def KindleMobiDecrypt(self,path_to_ebook):
|
||||
|
||||
# add the alfcrypto directory to sys.path so alfcrypto.py
|
||||
@@ -333,42 +389,53 @@ class DeDRM(FileTypePlugin):
|
||||
# Had to move this import here so the custom libs can be
|
||||
# extracted to the appropriate places beforehand these routines
|
||||
# look for them.
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
import calibre_plugins.dedrm.k4mobidedrm
|
||||
|
||||
pids = config.dedrmprefs['pids']
|
||||
serials = config.dedrmprefs['serials']
|
||||
kindleDatabases = config.dedrmprefs['kindlekeys'].items()
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
pids = dedrmprefs['pids']
|
||||
serials = dedrmprefs['serials']
|
||||
kindleDatabases = dedrmprefs['kindlekeys'].items()
|
||||
|
||||
try:
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,serials,pids,self.starttime)
|
||||
except Exception, e:
|
||||
decoded = False
|
||||
# perhaps we need to get a new default Kindle for Mac/PC key
|
||||
if iswindows or isosx:
|
||||
print u"{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0])
|
||||
print u"{0} v{1}: Looking for new default Kindle Key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
import calibre_plugins.dedrm.kindlekey as amazon
|
||||
defaultkeys = []
|
||||
print u"{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0])
|
||||
print u"{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
try:
|
||||
if iswindows or isosx:
|
||||
from calibre_plugins.dedrm.kindlekey import kindlekeys
|
||||
|
||||
defaultkeys = kindlekeys()
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,u"kindlekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix'])
|
||||
except:
|
||||
pass
|
||||
|
||||
newkeys = {}
|
||||
for i,keyvalue in enumerate(defaultkeys):
|
||||
keyname = u"default_key_{0:d}".format(i+1)
|
||||
if keyvalue not in dedrmprefs['kindlekeys'].values():
|
||||
newkeys[keyname] = keyvalue
|
||||
if len(newkeys) > 0:
|
||||
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||
try:
|
||||
defaultkeys = amazon.kindlekeys()
|
||||
except:
|
||||
defaultkeys = []
|
||||
newkeys = {}
|
||||
for i,keyvalue in enumerate(defaultkeys):
|
||||
keyname = u"default_key_{0:d}".format(i+1)
|
||||
if keyvalue not in config.dedrmprefs['kindlekeys'].values():
|
||||
newkeys[keyname] = keyvalue
|
||||
if len(newkeys) > 0:
|
||||
try:
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
|
||||
decoded = True
|
||||
# store the new successful keys in the defaults
|
||||
for keyvalue in newkeys.values():
|
||||
config.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
|
||||
config.writeprefs()
|
||||
except Exception, e:
|
||||
pass
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],self.starttime)
|
||||
decoded = True
|
||||
# store the new successful keys in the defaults
|
||||
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||
for keyvalue in newkeys.values():
|
||||
dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
|
||||
dedrmprefs.writeprefs()
|
||||
except Exception, e:
|
||||
pass
|
||||
if not decoded:
|
||||
#if you reached here then no luck raise and exception
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
@@ -384,11 +451,12 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
def eReaderDecrypt(self,path_to_ebook):
|
||||
|
||||
import calibre_plugins.dedrm.config as config
|
||||
import calibre_plugins.dedrm.prefs as prefs
|
||||
import calibre_plugins.dedrm.erdr2pml
|
||||
|
||||
dedrmprefs = prefs.DeDRM_Prefs()
|
||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||
for keyname, userkey in config.dedrmprefs['ereaderkeys'].items():
|
||||
for keyname, userkey in dedrmprefs['ereaderkeys'].items():
|
||||
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
|
||||
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)
|
||||
of = self.temporary_file(u".pmlz")
|
||||
@@ -403,7 +471,7 @@ class DeDRM(FileTypePlugin):
|
||||
if result == 0:
|
||||
return of.name
|
||||
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds.".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||
|
||||
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt “{2}” after {3:.1f} seconds.\nRead the FAQs at Alf's blog: http://apprenticealf.wordpress.com/".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook),time.time()-self.starttime))
|
||||
@@ -415,7 +483,7 @@ class DeDRM(FileTypePlugin):
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
|
||||
print u"{0} v{1}: Trying to decrypt {2}.".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
print u"{0} v{1}: Trying to decrypt {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||
self.starttime = time.time()
|
||||
|
||||
booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
|
||||
@@ -434,7 +502,7 @@ class DeDRM(FileTypePlugin):
|
||||
# Adobe Adept or B&N ePub
|
||||
decrypted_ebook = self.ePubDecrypt(path_to_ebook)
|
||||
else:
|
||||
print u"Unknown booktype {0}. Passing back to calibre unchanged.".format(booktype)
|
||||
print u"Unknown booktype {0}. Passing back to calibre unchanged".format(booktype)
|
||||
return path_to_ebook
|
||||
print u"{0} v{1}: Successfully decrypted book after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
return decrypted_ebook
|
||||
@@ -445,7 +513,7 @@ class DeDRM(FileTypePlugin):
|
||||
|
||||
def config_widget(self):
|
||||
import calibre_plugins.dedrm.config as config
|
||||
return config.ConfigWidget(self.plugin_path)
|
||||
return config.ConfigWidget(self.plugin_path, self.alfdir)
|
||||
|
||||
def save_settings(self, config_widget):
|
||||
config_widget.save_settings()
|
||||
|
||||
@@ -47,13 +47,14 @@ from __future__ import with_statement
|
||||
# 5.7 - Unicode support added, renamed adobekey from ineptkey
|
||||
# 5.8 - Added getkey interface for Windows DeDRM application
|
||||
# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.0 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Retrieve Adobe ADEPT user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '5.9'
|
||||
__version__ = '6.0'
|
||||
|
||||
import sys, os, struct, getopt
|
||||
|
||||
@@ -487,6 +488,8 @@ def usage(progname):
|
||||
print u" {0:s} [-h] [<outpath>]".format(progname)
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__)
|
||||
@@ -543,10 +546,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
@@ -593,7 +599,5 @@ 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())
|
||||
|
||||
157
DeDRM_calibre_plugin/DeDRM_plugin/android.py
Normal file
157
DeDRM_calibre_plugin/DeDRM_plugin/android.py
Normal file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python
|
||||
#fileencoding: utf-8
|
||||
|
||||
import os
|
||||
import sys
|
||||
import zlib
|
||||
import tarfile
|
||||
from hashlib import md5
|
||||
from cStringIO import StringIO
|
||||
from binascii import a2b_hex, b2a_hex
|
||||
|
||||
STORAGE = 'AmazonSecureStorage.xml'
|
||||
|
||||
class AndroidObfuscation(object):
|
||||
'''AndroidObfuscation
|
||||
For the key, it's written in java, and run in android dalvikvm
|
||||
'''
|
||||
|
||||
key = a2b_hex('0176e04c9408b1702d90be333fd53523')
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
cipher = self._get_cipher()
|
||||
padding = len(self.key) - len(plaintext) % len(self.key)
|
||||
plaintext += chr(padding) * padding
|
||||
return b2a_hex(cipher.encrypt(plaintext))
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
cipher = self._get_cipher()
|
||||
plaintext = cipher.decrypt(a2b_hex(ciphertext))
|
||||
return plaintext[:-ord(plaintext[-1])]
|
||||
|
||||
def _get_cipher(self):
|
||||
try:
|
||||
from Crypto.Cipher import AES
|
||||
return AES.new(self.key)
|
||||
except ImportError:
|
||||
from aescbc import AES, noPadding
|
||||
return AES(self.key, padding=noPadding())
|
||||
|
||||
class AndroidObfuscationV2(AndroidObfuscation):
|
||||
'''AndroidObfuscationV2
|
||||
'''
|
||||
|
||||
count = 503
|
||||
password = 'Thomsun was here!'
|
||||
|
||||
def __init__(self, salt):
|
||||
key = self.password + salt
|
||||
for _ in range(self.count):
|
||||
key = md5(key).digest()
|
||||
self.key = key[:8]
|
||||
self.iv = key[8:16]
|
||||
|
||||
def _get_cipher(self):
|
||||
try :
|
||||
from Crypto.Cipher import DES
|
||||
return DES.new(self.key, DES.MODE_CBC, self.iv)
|
||||
except ImportError:
|
||||
from python_des import Des, CBC
|
||||
return Des(self.key, CBC, self.iv)
|
||||
|
||||
def parse_preference(path):
|
||||
''' parse android's shared preference xml '''
|
||||
storage = {}
|
||||
read = open(path)
|
||||
for line in read:
|
||||
line = line.strip()
|
||||
# <string name="key">value</string>
|
||||
if line.startswith('<string name="'):
|
||||
index = line.find('"', 14)
|
||||
key = line[14:index]
|
||||
value = line[index+2:-9]
|
||||
storage[key] = value
|
||||
read.close()
|
||||
return storage
|
||||
|
||||
def get_serials(path=None):
|
||||
''' get serials from android's shared preference xml '''
|
||||
if path is None:
|
||||
if not os.path.isfile(STORAGE):
|
||||
if os.path.isfile("backup.ab"):
|
||||
get_storage()
|
||||
else:
|
||||
return []
|
||||
path = STORAGE
|
||||
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
storage = parse_preference(path)
|
||||
salt = storage.get('AmazonSaltKey')
|
||||
if salt and len(salt) == 16:
|
||||
sys.stdout.write('Using AndroidObfuscationV2\n')
|
||||
obfuscation = AndroidObfuscationV2(a2b_hex(salt))
|
||||
else:
|
||||
sys.stdout.write('Using AndroidObfuscation\n')
|
||||
obfuscation = AndroidObfuscation()
|
||||
|
||||
def get_value(key):
|
||||
encrypted_key = obfuscation.encrypt(key)
|
||||
encrypted_value = storage.get(encrypted_key)
|
||||
if encrypted_value:
|
||||
return obfuscation.decrypt(encrypted_value)
|
||||
return ''
|
||||
|
||||
# also see getK4Pids in kgenpids.py
|
||||
try:
|
||||
dsnid = get_value('DsnId')
|
||||
except:
|
||||
sys.stderr.write('cannot get DsnId\n')
|
||||
return []
|
||||
|
||||
try:
|
||||
tokens = set(get_value('kindle.account.tokens').split(','))
|
||||
except:
|
||||
return [dsnid]
|
||||
|
||||
serials = []
|
||||
for token in tokens:
|
||||
if token:
|
||||
serials.append('%s%s' % (dsnid, token))
|
||||
serials.append(dsnid)
|
||||
for token in tokens:
|
||||
if token:
|
||||
serials.append(token)
|
||||
return serials
|
||||
|
||||
def get_storage(path='backup.ab'):
|
||||
'''get AmazonSecureStorage.xml from android backup.ab
|
||||
backup.ab can be get using adb command:
|
||||
shell> adb backup com.amazon.kindle
|
||||
'''
|
||||
output = None
|
||||
read = open(path, 'rb')
|
||||
head = read.read(24)
|
||||
if head == 'ANDROID BACKUP\n1\n1\nnone\n':
|
||||
output = StringIO(zlib.decompress(read.read()))
|
||||
read.close()
|
||||
|
||||
if not output:
|
||||
return False
|
||||
|
||||
tar = tarfile.open(fileobj=output)
|
||||
for member in tar.getmembers():
|
||||
if member.name.strip().endswith(STORAGE):
|
||||
write = open(STORAGE, 'w')
|
||||
write.write(tar.extractfile(member).read())
|
||||
write.close()
|
||||
break
|
||||
|
||||
return True
|
||||
|
||||
__all__ = [ 'get_storage', 'get_serials', 'parse_preference',
|
||||
'AndroidObfuscation', 'AndroidObfuscationV2', 'STORAGE']
|
||||
|
||||
if __name__ == '__main__':
|
||||
print get_serials()
|
||||
6
DeDRM_calibre_plugin/DeDRM_plugin/android_readme.txt
Normal file
6
DeDRM_calibre_plugin/DeDRM_plugin/android_readme.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
1.1 get AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml
|
||||
|
||||
1.2 on android 4.0+, run `adb backup com.amazon.kindle` from PC will get backup.ab
|
||||
now android.py can convert backup.ab to AmazonSecureStorage.xml
|
||||
|
||||
2. run `k4mobidedrm.py -a AmazonSecureStorage.xml <infile> <outdir>'
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,722 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
from __future__ import with_statement
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
# Standard Python modules.
|
||||
import os, sys, re, hashlib
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QListWidget, QListWidgetItem, QAbstractItemView, QLineEdit, QPushButton, QIcon, QGroupBox, QDialog, QDialogButtonBox, QUrl, QString)
|
||||
from PyQt4 import QtGui
|
||||
|
||||
# calibre modules and constants.
|
||||
from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url,
|
||||
choose_dir, choose_files)
|
||||
from calibre.utils.config import dynamic, config_dir, JSONConfig
|
||||
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
from calibre_plugins.dedrm.utilities import (uStrCmp, DETAILED_MESSAGE, parseCustString)
|
||||
|
||||
class ManageKeysDialog(QDialog):
|
||||
def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = u""):
|
||||
QDialog.__init__(self,parent)
|
||||
self.parent = parent
|
||||
self.key_type_name = key_type_name
|
||||
self.plugin_keys = plugin_keys
|
||||
self.create_key = create_key
|
||||
self.keyfile_ext = keyfile_ext
|
||||
self.import_key = (keyfile_ext != u"")
|
||||
self.binary_file = (key_type_name == u"Adobe Digital Editions Key")
|
||||
self.json_file = (key_type_name == u"Kindle for Mac and PC Key")
|
||||
|
||||
self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
|
||||
|
||||
# Start Qt Gui dialog layout
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
help_layout = QHBoxLayout()
|
||||
layout.addLayout(help_layout)
|
||||
# Add hyperlink to a help file at the right. We will replace the correct name when it is clicked.
|
||||
help_label = QLabel('<a href="http://www.foo.com/">Help</a>', self)
|
||||
help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
|
||||
help_label.setAlignment(Qt.AlignRight)
|
||||
help_label.linkActivated.connect(self.help_link_activated)
|
||||
help_layout.addWidget(help_label)
|
||||
|
||||
keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self)
|
||||
layout.addWidget(keys_group_box)
|
||||
keys_group_box_layout = QHBoxLayout()
|
||||
keys_group_box.setLayout(keys_group_box_layout)
|
||||
|
||||
self.listy = QListWidget(self)
|
||||
self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name))
|
||||
self.listy.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.populate_list()
|
||||
keys_group_box_layout.addWidget(self.listy)
|
||||
|
||||
button_layout = QVBoxLayout()
|
||||
keys_group_box_layout.addLayout(button_layout)
|
||||
self._add_key_button = QtGui.QToolButton(self)
|
||||
self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name))
|
||||
self._add_key_button.setIcon(QIcon(I('plus.png')))
|
||||
self._add_key_button.clicked.connect(self.add_key)
|
||||
button_layout.addWidget(self._add_key_button)
|
||||
|
||||
self._delete_key_button = QtGui.QToolButton(self)
|
||||
self._delete_key_button.setToolTip(_(u"Delete highlighted key"))
|
||||
self._delete_key_button.setIcon(QIcon(I('list_remove.png')))
|
||||
self._delete_key_button.clicked.connect(self.delete_key)
|
||||
button_layout.addWidget(self._delete_key_button)
|
||||
|
||||
if type(self.plugin_keys) == dict:
|
||||
self._rename_key_button = QtGui.QToolButton(self)
|
||||
self._rename_key_button.setToolTip(_(u"Rename highlighted key"))
|
||||
self._rename_key_button.setIcon(QIcon(I('edit-select-all.png')))
|
||||
self._rename_key_button.clicked.connect(self.rename_key)
|
||||
button_layout.addWidget(self._rename_key_button)
|
||||
|
||||
self.export_key_button = QtGui.QToolButton(self)
|
||||
self.export_key_button.setToolTip(u"Save highlighted key to a .{0} file".format(self.keyfile_ext))
|
||||
self.export_key_button.setIcon(QIcon(I('save.png')))
|
||||
self.export_key_button.clicked.connect(self.export_key)
|
||||
button_layout.addWidget(self.export_key_button)
|
||||
spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
button_layout.addItem(spacerItem)
|
||||
|
||||
layout.addSpacing(5)
|
||||
migrate_layout = QHBoxLayout()
|
||||
layout.addLayout(migrate_layout)
|
||||
if self.import_key:
|
||||
migrate_layout.setAlignment(Qt.AlignJustify)
|
||||
self.migrate_btn = QPushButton(u"Import Existing Keyfiles", self)
|
||||
self.migrate_btn.setToolTip(u"Import *.{0} files (created using other tools).".format(self.keyfile_ext))
|
||||
self.migrate_btn.clicked.connect(self.migrate_wrapper)
|
||||
migrate_layout.addWidget(self.migrate_btn)
|
||||
migrate_layout.addStretch()
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
self.button_box.rejected.connect(self.close)
|
||||
migrate_layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
def populate_list(self):
|
||||
if type(self.plugin_keys) == dict:
|
||||
for key in self.plugin_keys.keys():
|
||||
self.listy.addItem(QListWidgetItem(key))
|
||||
else:
|
||||
for key in self.plugin_keys:
|
||||
self.listy.addItem(QListWidgetItem(key))
|
||||
|
||||
def add_key(self):
|
||||
d = self.create_key(self)
|
||||
d.exec_()
|
||||
|
||||
if d.result() != d.Accepted:
|
||||
# New key generation cancelled.
|
||||
return
|
||||
new_key_value = d.key_value
|
||||
if type(self.plugin_keys) == dict:
|
||||
if new_key_value in self.plugin_keys.values():
|
||||
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
|
||||
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
||||
u"The new {1} is the same as the existing {1} named <strong>{0}</strong> and has not been added.".format(old_key_name,self.key_type_name), show=True)
|
||||
return
|
||||
self.plugin_keys[d.key_name] = new_key_value
|
||||
else:
|
||||
if new_key_value in self.plugin_keys:
|
||||
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
||||
u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
|
||||
return
|
||||
|
||||
self.plugin_keys.append(d.key_value)
|
||||
self.listy.clear()
|
||||
self.populate_list()
|
||||
|
||||
def rename_key(self):
|
||||
if not self.listy.currentItem():
|
||||
errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name)
|
||||
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
return
|
||||
|
||||
d = RenameKeyDialog(self)
|
||||
d.exec_()
|
||||
|
||||
if d.result() != d.Accepted:
|
||||
# rename cancelled or moot.
|
||||
return
|
||||
keyname = unicode(self.listy.currentItem().text().toUtf8(),'utf8')
|
||||
if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False):
|
||||
return
|
||||
self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
|
||||
del self.plugin_keys[keyname]
|
||||
|
||||
self.listy.clear()
|
||||
self.populate_list()
|
||||
|
||||
def delete_key(self):
|
||||
if not self.listy.currentItem():
|
||||
return
|
||||
keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
|
||||
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
|
||||
return
|
||||
if type(self.plugin_keys) == dict:
|
||||
del self.plugin_keys[keyname]
|
||||
else:
|
||||
self.plugin_keys.remove(keyname)
|
||||
|
||||
self.listy.clear()
|
||||
self.populate_list()
|
||||
|
||||
def help_link_activated(self, url):
|
||||
def get_help_file_resource():
|
||||
# Copy the HTML helpfile to the plugin directory each time the
|
||||
# link is clicked in case the helpfile is updated in newer plugins.
|
||||
help_file_name = u"{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name)
|
||||
file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name)
|
||||
with open(file_path,'w') as f:
|
||||
f.write(self.parent.load_resource(help_file_name))
|
||||
return file_path
|
||||
url = 'file:///' + get_help_file_resource()
|
||||
open_url(QUrl(url))
|
||||
|
||||
def migrate_files(self):
|
||||
dynamic[PLUGIN_NAME + u"config_dir"] = config_dir
|
||||
files = choose_files(self, PLUGIN_NAME + u"config_dir",
|
||||
u"Select {0} files to import".format(self.key_type_name), [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])], False)
|
||||
counter = 0
|
||||
skipped = 0
|
||||
if files:
|
||||
for filename in files:
|
||||
fpath = os.path.join(config_dir, filename)
|
||||
filename = os.path.basename(filename)
|
||||
new_key_name = os.path.splitext(os.path.basename(filename))[0]
|
||||
with open(fpath,'rb') as keyfile:
|
||||
new_key_value = keyfile.read()
|
||||
if self.binary_file:
|
||||
new_key_value = new_key_value.encode('hex')
|
||||
elif self.json_file:
|
||||
new_key_value = json.loads(new_key_value)
|
||||
match = False
|
||||
for key in self.plugin_keys.keys():
|
||||
if uStrCmp(new_key_name, key, True):
|
||||
skipped += 1
|
||||
msg = u"A key with the name <strong>{0}</strong> already exists!\nSkipping key file <strong>{1}</strong>.\nRename the existing key and import again".format(new_key_name,filename)
|
||||
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(msg), show_copy_button=False, show=True)
|
||||
match = True
|
||||
break
|
||||
if not match:
|
||||
if new_key_value in self.plugin_keys.values():
|
||||
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
|
||||
skipped += 1
|
||||
info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
u"The key in file {0} is the same as the existing key <strong>{1}</strong> and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True)
|
||||
else:
|
||||
counter += 1
|
||||
self.plugin_keys[new_key_name] = new_key_value
|
||||
|
||||
msg = u""
|
||||
if counter+skipped > 1:
|
||||
if counter > 0:
|
||||
msg += u"Imported <strong>{0:d}</strong> key {1}. ".format(counter, u"file" if counter == 1 else u"files")
|
||||
if skipped > 0:
|
||||
msg += u"Skipped <strong>{0:d}</strong> key {1}.".format(skipped, u"file" if counter == 1 else u"files")
|
||||
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(msg), show_copy_button=False, show=True)
|
||||
return counter > 0
|
||||
|
||||
def migrate_wrapper(self):
|
||||
if self.migrate_files():
|
||||
self.listy.clear()
|
||||
self.populate_list()
|
||||
|
||||
def export_key(self):
|
||||
if not self.listy.currentItem():
|
||||
errmsg = u"No keyfile selected to export. Highlight a keyfile first."
|
||||
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
return
|
||||
filter = QString(u"{0} Files (*.{1})".format(self.key_type_name, self.keyfile_ext))
|
||||
keyname = unicode(self.listy.currentItem().text().toUtf8(), 'utf8')
|
||||
if dynamic.get(PLUGIN_NAME + 'save_dir'):
|
||||
defaultname = os.path.join(dynamic.get(PLUGIN_NAME + 'save_dir'), u"{0}.{1}".format(keyname , self.keyfile_ext))
|
||||
else:
|
||||
defaultname = os.path.join(os.path.expanduser('~'), u"{0}.{1}".format(keyname , self.keyfile_ext))
|
||||
filename = unicode(QtGui.QFileDialog.getSaveFileName(self, u"Save {0} File as...".format(self.key_type_name), defaultname,
|
||||
u"{0} Files (*.{1})".format(self.key_type_name,self.keyfile_ext), filter))
|
||||
if filename:
|
||||
dynamic[PLUGIN_NAME + 'save_dir'] = os.path.split(filename)[0]
|
||||
with file(filename, 'w') as fname:
|
||||
if self.binary_file:
|
||||
fname.write(self.plugin_keys[keyname].decode('hex'))
|
||||
elif self.json_file:
|
||||
fname.write(json.dumps(self.plugin_keys[keyname]))
|
||||
else:
|
||||
fname.write(self.plugin_keys[keyname])
|
||||
|
||||
|
||||
|
||||
|
||||
class RenameKeyDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
print repr(self), repr(parent)
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle("{0} {1}: Rename {0}".format(PLUGIN_NAME, PLUGIN_VERSION, parent.key_type_name))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox('', self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
data_group_box_layout.addWidget(QLabel('New Key Name:', self))
|
||||
self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self)
|
||||
self.key_ledit.setToolTip(u"Enter a new name for this existing {0}.".format(parent.key_type_name))
|
||||
data_group_box_layout.addWidget(self.key_ledit)
|
||||
|
||||
layout.addSpacing(20)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
def accept(self):
|
||||
if self.key_ledit.text().isEmpty() or unicode(self.key_ledit.text()).isspace():
|
||||
errmsg = u"Key name field cannot be empty!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
if len(self.key_ledit.text()) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()):
|
||||
# Same exact name ... do nothing.
|
||||
return QDialog.reject(self)
|
||||
for k in self.parent.plugin_keys.keys():
|
||||
if (uStrCmp(self.key_ledit.text(), k, True) and
|
||||
not uStrCmp(k, self.parent.listy.currentItem().text(), True)):
|
||||
errmsg = u"The key name <strong>{0}</strong> is already being used.".format(self.key_ledit.text())
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||
_(errmsg), show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class AddBandNKeyDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(_(u"<p>Enter an identifying name for this new key.</p>" +
|
||||
u"<p>It should be something that will help you remember " +
|
||||
u"what personal information was used to create it."))
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
|
||||
name_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(name_group)
|
||||
name_group.addWidget(QLabel(u"Your Name:", self))
|
||||
self.name_ledit = QLineEdit(u"", self)
|
||||
self.name_ledit.setToolTip(_(u"<p>Enter your name as it appears in your B&N " +
|
||||
u"account or on your credit card.</p>" +
|
||||
u"<p>It will only be used to generate this " +
|
||||
u"one-time key and won\'t be stored anywhere " +
|
||||
u"in calibre or on your computer.</p>" +
|
||||
u"<p>(ex: Jonathan Smith)"))
|
||||
name_group.addWidget(self.name_ledit)
|
||||
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
|
||||
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(name_disclaimer_label)
|
||||
|
||||
ccn_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(ccn_group)
|
||||
ccn_group.addWidget(QLabel(u"Credit Card#:", self))
|
||||
self.cc_ledit = QLineEdit(u"", self)
|
||||
self.cc_ledit.setToolTip(_(u"<p>Enter the full credit card number on record " +
|
||||
u"in your B&N account.</p>" +
|
||||
u"<p>No spaces or dashes... just the numbers. " +
|
||||
u"This number will only be used to generate this " +
|
||||
u"one-time key and won\'t be stored anywhere in " +
|
||||
u"calibre or on your computer."))
|
||||
ccn_group.addWidget(self.cc_ledit)
|
||||
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
||||
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(ccn_disclaimer_label)
|
||||
layout.addSpacing(10)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key
|
||||
return generate_bandn_key(self.user_name,self.cc_number)
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
|
||||
|
||||
@property
|
||||
def cc_number(self):
|
||||
return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if not self.cc_number.isdigit():
|
||||
errmsg = u"Numbers only in the credit card number field!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
class AddEReaderDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"<p>Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
|
||||
name_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(name_group)
|
||||
name_group.addWidget(QLabel(u"Your Name:", self))
|
||||
self.name_ledit = QLineEdit(u"", self)
|
||||
self.name_ledit.setToolTip(u"Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)")
|
||||
name_group.addWidget(self.name_ledit)
|
||||
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
|
||||
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(name_disclaimer_label)
|
||||
|
||||
ccn_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(ccn_group)
|
||||
ccn_group.addWidget(QLabel(u"Credit Card#:", self))
|
||||
self.cc_ledit = QLineEdit(u"", self)
|
||||
self.cc_ledit.setToolTip(u"<p>Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.")
|
||||
ccn_group.addWidget(self.cc_ledit)
|
||||
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
||||
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(ccn_disclaimer_label)
|
||||
layout.addSpacing(10)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key
|
||||
return generate_ereader_key(self.user_name,self.cc_number).encode('hex')
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
return unicode(self.name_ledit.text().toUtf8(), 'utf8').strip().lower().replace(' ','')
|
||||
|
||||
@property
|
||||
def cc_number(self):
|
||||
return unicode(self.cc_ledit.text().toUtf8(), 'utf8').strip().replace(' ', '').replace('-','')
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if not self.cc_number.isdigit():
|
||||
errmsg = u"Numbers only in the credit card number field!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
|
||||
class AddAdeptDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
from calibre_plugins.dedrm.adobekey import adeptkeys as retrieve_adept_keys
|
||||
try:
|
||||
default_keys = retrieve_adept_keys()
|
||||
self.default_key = default_keys[0]
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.default_key = u""
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
|
||||
if len(self.default_key)>0:
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Adobe Digital Editions key.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
else:
|
||||
default_key_error = QLabel(u"The default encryption key for Adobe Digital Editions could not be found.", self)
|
||||
default_key_error.setAlignment(Qt.AlignHCenter)
|
||||
layout.addWidget(default_key_error)
|
||||
# if no default, bot buttons do the same
|
||||
self.button_box.accepted.connect(self.reject)
|
||||
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return self.default_key.encode('hex')
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
|
||||
class AddKindleDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
from calibre_plugins.dedrm.kindlekey import kindlekeys as retrieve_kindle_keys
|
||||
try:
|
||||
self.default_key = retrieve_kindle_keys()[0]
|
||||
except:
|
||||
self.default_key = u""
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
|
||||
if len(self.default_key)>0:
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
else:
|
||||
default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self)
|
||||
default_key_error.setAlignment(Qt.AlignHCenter)
|
||||
layout.addWidget(default_key_error)
|
||||
# if no default, bot buttons do the same
|
||||
self.button_box.accepted.connect(self.reject)
|
||||
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return self.default_key
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
|
||||
class AddSerialDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"EInk Kindle Serial Number:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog."
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) != 16:
|
||||
errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
|
||||
class AddPIDDialog(QDialog):
|
||||
def __init__(self, parent=None,):
|
||||
QDialog.__init__(self, parent)
|
||||
self.parent = parent
|
||||
self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||
layout = QVBoxLayout(self)
|
||||
self.setLayout(layout)
|
||||
|
||||
data_group_box = QGroupBox(u"", self)
|
||||
layout.addWidget(data_group_box)
|
||||
data_group_box_layout = QVBoxLayout()
|
||||
data_group_box.setLayout(data_group_box_layout)
|
||||
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel(u"PID:", self))
|
||||
self.key_ledit = QLineEdit("", self)
|
||||
self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
key_label = QLabel(_(''), self)
|
||||
key_label.setAlignment(Qt.AlignHCenter)
|
||||
data_group_box_layout.addWidget(key_label)
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self.button_box)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
@property
|
||||
def key_name(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
return unicode(self.key_ledit.text().toUtf8(), 'utf8').strip()
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||
errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog."
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) != 8 and len(self.key_name) != 10:
|
||||
errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
QDialog.accept(self)
|
||||
|
||||
|
||||
@@ -458,7 +458,11 @@ class DocParser(object):
|
||||
(wtype, num) = pdesc[j]
|
||||
|
||||
if wtype == 'ocr' :
|
||||
word = self.ocrtext[num]
|
||||
try:
|
||||
word = self.ocrtext[num]
|
||||
except:
|
||||
word = ""
|
||||
|
||||
sep = ' '
|
||||
|
||||
if handle_links:
|
||||
|
||||
@@ -34,13 +34,14 @@ from __future__ import with_statement
|
||||
# 3.7 - Tweaked to match ineptepub more closely
|
||||
# 3.8 - Fixed to retain zip file metadata (e.g. file modification date)
|
||||
# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 4.0 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Decrypt Barnes & Noble encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "3.9"
|
||||
__version__ = "4.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -318,6 +319,8 @@ def decryptBook(keyb64, inpath, outpath):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
@@ -331,10 +334,13 @@ def cli_main():
|
||||
return result
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -442,7 +448,5 @@ 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())
|
||||
|
||||
@@ -32,13 +32,14 @@ from __future__ import with_statement
|
||||
# 2.4 - Improvements to UI and now works in plugins
|
||||
# 2.5 - Additional improvement for unicode and plugin support
|
||||
# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 2.7 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "2.6"
|
||||
__version__ = "2.7"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -216,6 +217,8 @@ def generate_key(name, ccn):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if AES is None:
|
||||
@@ -233,10 +236,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -320,7 +326,5 @@ 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())
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 5.9
|
||||
# ineptepub.pyw, version 6.1
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
@@ -35,14 +35,15 @@ from __future__ import with_statement
|
||||
# 5.7 - Fix for potential problem with PyCrypto
|
||||
# 5.8 - Revised to allow use in calibre plugins to eliminate need for duplicate code
|
||||
# 5.9 - Fixed to retain zip file metadata (e.g. file modification date)
|
||||
# 5.10 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.1 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "5.10"
|
||||
__version__ = "6.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -460,6 +461,8 @@ def decryptBook(userkey, inpath, outpath):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
@@ -473,10 +476,13 @@ def cli_main():
|
||||
return result
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -584,7 +590,5 @@ 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())
|
||||
|
||||
@@ -51,13 +51,15 @@ from __future__ import with_statement
|
||||
# 7.12 - Revised to allow use in calibre plugins to eliminate need for duplicate code
|
||||
# 7.13 - Fixed erroneous mentions of ineptepub
|
||||
# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 8.0 - Work if TkInter is missing
|
||||
# 8.0.1 - Broken Metadata fix.
|
||||
|
||||
"""
|
||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "7.14"
|
||||
__version__ = "8.0.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -948,8 +950,11 @@ class PSStackParser(PSBaseParser):
|
||||
try:
|
||||
(pos, objs) = self.end_type('d')
|
||||
if len(objs) % 2 != 0:
|
||||
raise PSSyntaxError(
|
||||
'Invalid dictionary construct: %r' % objs)
|
||||
print "Incomplete dictionary construct"
|
||||
objs.append("") # this isn't necessary.
|
||||
# temporary fix. is this due to rental books?
|
||||
# raise PSSyntaxError(
|
||||
# 'Invalid dictionary construct: %r' % objs)
|
||||
d = dict((literal_name(k), v) \
|
||||
for (k,v) in choplist(2, objs))
|
||||
self.push((pos, d))
|
||||
@@ -2187,6 +2192,8 @@ def decryptBook(userkey, inpath, outpath):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
@@ -2201,10 +2208,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -2321,7 +2331,5 @@ 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())
|
||||
|
||||
@@ -54,8 +54,9 @@ from __future__ import with_statement
|
||||
# 5.0 - Extraction of info from Kindle for PC/Mac moved into kindlekey.py
|
||||
# - tweaked GetDecryptedBook interface to leave passed parameters unchanged
|
||||
# 5.1 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 5.2 - Fixed error in command line processing of unicode arguments
|
||||
|
||||
__version__ = '5.1'
|
||||
__version__ = '5.2'
|
||||
|
||||
|
||||
import sys, os, re
|
||||
@@ -79,10 +80,12 @@ if inCalibre:
|
||||
from calibre_plugins.dedrm import mobidedrm
|
||||
from calibre_plugins.dedrm import topazextract
|
||||
from calibre_plugins.dedrm import kgenpids
|
||||
from calibre_plugins.dedrm import android
|
||||
else:
|
||||
import mobidedrm
|
||||
import topazextract
|
||||
import kgenpids
|
||||
import android
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
@@ -187,7 +190,7 @@ def unescape(text):
|
||||
def GetDecryptedBook(infile, kDatabases, serials, pids, starttime = time.time()):
|
||||
# handle the obvious cases at the beginning
|
||||
if not os.path.isfile(infile):
|
||||
raise DRMException (u"Input file does not exist.")
|
||||
raise DrmException(u"Input file does not exist.")
|
||||
|
||||
mobi = True
|
||||
magic3 = open(infile,'rb').read(3)
|
||||
@@ -272,7 +275,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, serials, pids):
|
||||
def usage(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.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname)
|
||||
print u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml> ] <infile> <outdir>".format(progname)
|
||||
|
||||
#
|
||||
# Main
|
||||
@@ -283,7 +286,7 @@ def cli_main():
|
||||
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2013 The Dark Reverser et al.".format(__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:")
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
usage(progname)
|
||||
@@ -311,6 +314,11 @@ def cli_main():
|
||||
if a == None :
|
||||
raise DrmException("Invalid parameter for -s")
|
||||
serials = a.split(',')
|
||||
if o == '-a':
|
||||
if a == None:
|
||||
continue
|
||||
serials.extend(android.get_serials(a))
|
||||
serials.extend(android.get_serials())
|
||||
|
||||
# try with built in Kindle Info files if not on Linux
|
||||
k4 = not sys.platform.startswith('linux')
|
||||
|
||||
@@ -17,6 +17,9 @@ from __future__ import with_statement
|
||||
# 1.4 - Remove dependency on alfcrypto
|
||||
# 1.5 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 1.6 - Fixed a problem getting the disk serial numbers
|
||||
# 1.7 - Work if TkInter is missing
|
||||
# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names
|
||||
# 1.9 - Fixes for Unicode in Windows user names
|
||||
|
||||
|
||||
"""
|
||||
@@ -24,7 +27,7 @@ Retrieve Kindle for PC/Mac user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.6'
|
||||
__version__ = '1.9'
|
||||
|
||||
import sys, os, re
|
||||
from struct import pack, unpack, unpack_from
|
||||
@@ -905,18 +908,34 @@ if iswindows:
|
||||
return CryptUnprotectData
|
||||
CryptUnprotectData = CryptUnprotectData()
|
||||
|
||||
# Returns Environmental Variables that contain unicode
|
||||
def getEnvironmentVariable(name):
|
||||
import ctypes
|
||||
name = unicode(name) # make sure string argument is unicode
|
||||
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
|
||||
if n == 0:
|
||||
return None
|
||||
buf = ctypes.create_unicode_buffer(u'\0'*n)
|
||||
ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n)
|
||||
return buf.value
|
||||
|
||||
# Locate all of the kindle-info style files and return as list
|
||||
def getKindleInfoFiles():
|
||||
kInfoFiles = []
|
||||
# some 64 bit machines do not have the proper registry key for some reason
|
||||
# or the pythonn interface to the 32 vs 64 bit registry is broken
|
||||
# or the python interface to the 32 vs 64 bit registry is broken
|
||||
path = ""
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
path = os.environ['LOCALAPPDATA']
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
# this is just another alternative.
|
||||
# path = getEnvironmentVariable('LOCALAPPDATA')
|
||||
if not os.path.isdir(path):
|
||||
path = ""
|
||||
else:
|
||||
# User Shell Folders show take precedent over Shell Folders if present
|
||||
try:
|
||||
# this will still break
|
||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
|
||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||
if not os.path.isdir(path):
|
||||
@@ -935,13 +954,14 @@ if iswindows:
|
||||
if path == "":
|
||||
print ('Could not find the folder in which to look for kinfoFiles.')
|
||||
else:
|
||||
print('searching for kinfoFiles in ' + path)
|
||||
# Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
|
||||
print(u'searching for kinfoFiles in ' + path.encode('ascii', 'ignore'))
|
||||
|
||||
# look for (K4PC 1.9.0 and later) .kinf2011 file
|
||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
||||
if os.path.isfile(kinfopath):
|
||||
found = True
|
||||
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath)
|
||||
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath.encode('ascii','ignore'))
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
||||
@@ -1140,8 +1160,8 @@ if iswindows:
|
||||
cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName())
|
||||
if 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode("latin-1"))
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
@@ -1520,20 +1540,32 @@ elif isosx:
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kinf2011 file: ' + testpath)
|
||||
found = True
|
||||
# check for .kinf2011 files
|
||||
# check for .kinf2011 files from 1.10
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kinf2011 file: ' + testpath)
|
||||
found = True
|
||||
# check for .rainier-2.1.1-kinf files
|
||||
# check for .rainier-2.1.1-kinf files from 1.6
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac rainier file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info files
|
||||
# check for .kindle-info files from 1.4
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info file from 1.2.2
|
||||
testpath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info file from 1.0 beta 1 (27214)
|
||||
testpath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
@@ -1744,7 +1776,7 @@ elif isosx:
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
if 'kindle.account.tokens' in DB:
|
||||
# store values used in decryption
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())
|
||||
DB['IDString'] = IDString
|
||||
@@ -1804,6 +1836,8 @@ def usage(progname):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2010-2013 some_updates and Apprentice Alf".format(progname,__version__)
|
||||
@@ -1845,10 +1879,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
@@ -1895,7 +1932,5 @@ 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())
|
||||
|
||||
@@ -68,9 +68,10 @@
|
||||
# 0.38 - Unicode used wherever possible, cope with absent alfcrypto
|
||||
# 0.39 - Fixed problem with TEXtREAd and getBookType interface
|
||||
# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 0.41 - Fixed potential unicode problem in command line calls
|
||||
|
||||
|
||||
__version__ = u"0.40"
|
||||
__version__ = u"0.41"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -155,6 +156,8 @@ def PC1(key, src, decryption=True):
|
||||
return Pukall_Cipher().PC1(key,src,decryption)
|
||||
except NameError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# use slow python version, since Pukall_Cipher didn't load
|
||||
sum1 = 0;
|
||||
@@ -514,7 +517,7 @@ def cli_main():
|
||||
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]))
|
||||
print u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname)
|
||||
return 1
|
||||
else:
|
||||
infile = argv[1]
|
||||
|
||||
292
DeDRM_calibre_plugin/DeDRM_plugin/prefs.py
Normal file
292
DeDRM_calibre_plugin/DeDRM_plugin/prefs.py
Normal file
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
from __future__ import with_statement
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
# Standard Python modules.
|
||||
import os, sys, re, hashlib
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from calibre.utils.config import dynamic, config_dir, JSONConfig
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
from calibre.constants import iswindows, isosx
|
||||
|
||||
class DeDRM_Prefs():
|
||||
def __init__(self):
|
||||
JSON_PATH = os.path.join(u"plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
|
||||
self.dedrmprefs = JSONConfig(JSON_PATH)
|
||||
|
||||
self.dedrmprefs.defaults['configured'] = False
|
||||
self.dedrmprefs.defaults['bandnkeys'] = {}
|
||||
self.dedrmprefs.defaults['adeptkeys'] = {}
|
||||
self.dedrmprefs.defaults['ereaderkeys'] = {}
|
||||
self.dedrmprefs.defaults['kindlekeys'] = {}
|
||||
self.dedrmprefs.defaults['pids'] = []
|
||||
self.dedrmprefs.defaults['serials'] = []
|
||||
self.dedrmprefs.defaults['adobewineprefix'] = ""
|
||||
self.dedrmprefs.defaults['kindlewineprefix'] = ""
|
||||
|
||||
# initialise
|
||||
# we must actually set the prefs that are dictionaries and lists
|
||||
# to empty dictionaries and lists, otherwise we are unable to add to them
|
||||
# as then it just adds to the (memory only) dedrmprefs.defaults versions!
|
||||
if self.dedrmprefs['bandnkeys'] == {}:
|
||||
self.dedrmprefs['bandnkeys'] = {}
|
||||
if self.dedrmprefs['adeptkeys'] == {}:
|
||||
self.dedrmprefs['adeptkeys'] = {}
|
||||
if self.dedrmprefs['ereaderkeys'] == {}:
|
||||
self.dedrmprefs['ereaderkeys'] = {}
|
||||
if self.dedrmprefs['kindlekeys'] == {}:
|
||||
self.dedrmprefs['kindlekeys'] = {}
|
||||
if self.dedrmprefs['pids'] == []:
|
||||
self.dedrmprefs['pids'] = []
|
||||
if self.dedrmprefs['serials'] == []:
|
||||
self.dedrmprefs['serials'] = []
|
||||
|
||||
def __getitem__(self,kind = None):
|
||||
if kind is not None:
|
||||
return self.dedrmprefs[kind]
|
||||
return self.dedrmprefs
|
||||
|
||||
def set(self, kind, value):
|
||||
self.dedrmprefs[kind] = value
|
||||
|
||||
def writeprefs(self,value = True):
|
||||
self.dedrmprefs['configured'] = value
|
||||
|
||||
def addnamedvaluetoprefs(self, prefkind, keyname, keyvalue):
|
||||
try:
|
||||
if keyvalue not in self.dedrmprefs[prefkind].values():
|
||||
# ensure that the keyname is unique
|
||||
# by adding a number (starting with 2) to the name if it is not
|
||||
namecount = 1
|
||||
newname = keyname
|
||||
while newname in self.dedrmprefs[prefkind]:
|
||||
namecount += 1
|
||||
newname = "{0:s}_{1:d}".format(keyname,namecount)
|
||||
# add to the preferences
|
||||
self.dedrmprefs[prefkind][newname] = keyvalue
|
||||
return (True, newname)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
return (False, keyname)
|
||||
|
||||
def addvaluetoprefs(self, prefkind, prefsvalue):
|
||||
# ensure the keyvalue isn't already in the preferences
|
||||
try:
|
||||
if prefsvalue not in self.dedrmprefs[prefkind]:
|
||||
self.dedrmprefs[prefkind].append(prefsvalue)
|
||||
return True
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def convertprefs(always = False):
|
||||
|
||||
def parseIgnobleString(keystuff):
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key
|
||||
userkeys = []
|
||||
ar = keystuff.split(':')
|
||||
for keystring in ar:
|
||||
try:
|
||||
name, ccn = keystring.split(',')
|
||||
# Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||
keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:])
|
||||
keyvalue = generate_key(name, ccn)
|
||||
userkeys.append([keyname,keyvalue])
|
||||
except Exception, e:
|
||||
traceback.print_exc()
|
||||
print e.args[0]
|
||||
pass
|
||||
return userkeys
|
||||
|
||||
def parseeReaderString(keystuff):
|
||||
from calibre_plugins.dedrm.erdr2pml import getuser_key
|
||||
userkeys = []
|
||||
ar = keystuff.split(':')
|
||||
for keystring in ar:
|
||||
try:
|
||||
name, cc = keystring.split(',')
|
||||
# Generate eReader user key from name and credit card number.
|
||||
keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:])
|
||||
keyvalue = getuser_key(name,cc).encode('hex')
|
||||
userkeys.append([keyname,keyvalue])
|
||||
except Exception, e:
|
||||
traceback.print_exc()
|
||||
print e.args[0]
|
||||
pass
|
||||
return userkeys
|
||||
|
||||
def parseKindleString(keystuff):
|
||||
pids = []
|
||||
serials = []
|
||||
ar = keystuff.split(',')
|
||||
for keystring in ar:
|
||||
keystring = str(keystring).strip().replace(" ","")
|
||||
if len(keystring) == 10 or len(keystring) == 8 and keystring not in pids:
|
||||
pids.append(keystring)
|
||||
elif len(keystring) == 16 and keystring[0] == 'B' and keystring not in serials:
|
||||
serials.append(keystring)
|
||||
return (pids,serials)
|
||||
|
||||
def getConfigFiles(extension, encoding = None):
|
||||
# get any files with extension 'extension' in the config dir
|
||||
userkeys = []
|
||||
files = [f for f in os.listdir(config_dir) if f.endswith(extension)]
|
||||
for filename in files:
|
||||
try:
|
||||
fpath = os.path.join(config_dir, filename)
|
||||
key = os.path.splitext(filename)[0]
|
||||
value = open(fpath, 'rb').read()
|
||||
if encoding is not None:
|
||||
value = value.encode(encoding)
|
||||
userkeys.append([key,value])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
return userkeys
|
||||
|
||||
dedrmprefs = DeDRM_Prefs()
|
||||
|
||||
if (not always) and dedrmprefs['configured']:
|
||||
# We've already converted old preferences,
|
||||
# and we're not being forced to do it again, so just return
|
||||
return
|
||||
|
||||
|
||||
print u"{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
|
||||
IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM"
|
||||
EREADERPLUGINNAME = "eReader PDB 2 PML"
|
||||
OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM"
|
||||
|
||||
# get prefs from older tools
|
||||
kindleprefs = JSONConfig(os.path.join(u"plugins", u"K4MobiDeDRM"))
|
||||
ignobleprefs = JSONConfig(os.path.join(u"plugins", u"ignoble_epub_dedrm"))
|
||||
|
||||
# Handle the old ignoble plugin's customization string by converting the
|
||||
# old string to stored keys... get that personal data out of plain sight.
|
||||
from calibre.customize.ui import config
|
||||
sc = config['plugin_customization']
|
||||
val = sc.pop(IGNOBLEPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
priorkeycount = len(dedrmprefs['bandnkeys'])
|
||||
userkeys = parseIgnobleString(str(val))
|
||||
for keypair in userkeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# Handle the old eReader plugin's customization string by converting the
|
||||
# old string to stored keys... get that personal data out of plain sight.
|
||||
val = sc.pop(EREADERPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
priorkeycount = len(dedrmprefs['ereaderkeys'])
|
||||
userkeys = parseeReaderString(str(val))
|
||||
for keypair in userkeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount
|
||||
print u"{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get old Kindle plugin configuration string
|
||||
val = sc.pop(OLDKINDLEPLUGINNAME, None)
|
||||
if val is not None:
|
||||
print u"{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
priorpidcount = len(dedrmprefs['pids'])
|
||||
priorserialcount = len(dedrmprefs['serials'])
|
||||
pids, serials = parseKindleString(val)
|
||||
for pid in pids:
|
||||
dedrmprefs.addvaluetoprefs('pids',pid)
|
||||
for serial in serials:
|
||||
dedrmprefs.addvaluetoprefs('serials',serial)
|
||||
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
||||
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
||||
print u"{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs", addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# copy the customisations back into calibre preferences, as we've now removed the nasty plaintext
|
||||
config['plugin_customization'] = sc
|
||||
|
||||
# get any .b64 files in the config dir
|
||||
priorkeycount = len(dedrmprefs['bandnkeys'])
|
||||
bandnfilekeys = getConfigFiles('.b64')
|
||||
for keypair in bandnfilekeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key file" if addedkeycount==1 else u"key files")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get any .der files in the config dir
|
||||
priorkeycount = len(dedrmprefs['adeptkeys'])
|
||||
adeptfilekeys = getConfigFiles('.der','hex')
|
||||
for keypair in adeptfilekeys:
|
||||
name = keypair[0]
|
||||
value = keypair[1]
|
||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"keyfile" if addedkeycount==1 else u"keyfiles")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get ignoble json prefs
|
||||
if 'keys' in ignobleprefs:
|
||||
priorkeycount = len(dedrmprefs['bandnkeys'])
|
||||
for name in ignobleprefs['keys']:
|
||||
value = ignobleprefs['keys'][name]
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||
addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount
|
||||
# no need to delete old prefs, since they contain no recoverable private data
|
||||
if addedkeycount > 0:
|
||||
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs(False)
|
||||
|
||||
# get kindle json prefs
|
||||
priorpidcount = len(dedrmprefs['pids'])
|
||||
priorserialcount = len(dedrmprefs['serials'])
|
||||
if 'pids' in kindleprefs:
|
||||
pids, serials = parseKindleString(kindleprefs['pids'])
|
||||
for pid in pids:
|
||||
dedrmprefs.addvaluetoprefs('pids',pid)
|
||||
if 'serials' in kindleprefs:
|
||||
pids, serials = parseKindleString(kindleprefs['serials'])
|
||||
for serial in serials:
|
||||
dedrmprefs.addvaluetoprefs('serials',serial)
|
||||
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
||||
if addedpidcount > 0:
|
||||
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs")
|
||||
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
||||
if addedserialcount > 0:
|
||||
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
|
||||
try:
|
||||
if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "":
|
||||
dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix'])
|
||||
dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix'])
|
||||
print u"{0} v{1}: WINEPREFIX ‘(2)’ imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix'])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
# Make the json write all the prefs to disk
|
||||
dedrmprefs.writeprefs()
|
||||
print u"{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
@@ -178,7 +178,12 @@ class DocParser(object):
|
||||
if val == "":
|
||||
val = 0
|
||||
|
||||
if not ((attr == 'hang') and (int(val) == 0)) :
|
||||
if not ((attr == 'hang') and (int(val) == 0)):
|
||||
try:
|
||||
f = float(val)
|
||||
except:
|
||||
print "Warning: unrecognised val, ignoring"
|
||||
val = 0
|
||||
pv = float(val)/scale
|
||||
cssargs[attr] = (self.attr_val_map[attr], pv)
|
||||
keep = True
|
||||
|
||||
148
DeDRM_calibre_plugin/DeDRM_plugin/subasyncio.py
Normal file
148
DeDRM_calibre_plugin/DeDRM_plugin/subasyncio.py
Normal file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||
|
||||
import os, sys
|
||||
import signal
|
||||
import threading
|
||||
import subprocess
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
# **heavily** chopped up and modfied version of asyncproc.py
|
||||
# to make it actually work on Windows as well as Mac/Linux
|
||||
# For the original see:
|
||||
# "http://www.lysator.liu.se/~bellman/download/"
|
||||
# author is "Thomas Bellman <bellman@lysator.liu.se>"
|
||||
# available under GPL version 3 or Later
|
||||
|
||||
# create an asynchronous subprocess whose output can be collected in
|
||||
# a non-blocking manner
|
||||
|
||||
# What a mess! Have to use threads just to get non-blocking io
|
||||
# in a cross-platform manner
|
||||
|
||||
# luckily all thread use is hidden within this class
|
||||
|
||||
class Process(object):
|
||||
def __init__(self, *params, **kwparams):
|
||||
if len(params) <= 3:
|
||||
kwparams.setdefault('stdin', subprocess.PIPE)
|
||||
if len(params) <= 4:
|
||||
kwparams.setdefault('stdout', subprocess.PIPE)
|
||||
if len(params) <= 5:
|
||||
kwparams.setdefault('stderr', subprocess.PIPE)
|
||||
self.__pending_input = []
|
||||
self.__collected_outdata = []
|
||||
self.__collected_errdata = []
|
||||
self.__exitstatus = None
|
||||
self.__lock = threading.Lock()
|
||||
self.__inputsem = threading.Semaphore(0)
|
||||
self.__quit = False
|
||||
|
||||
self.__process = subprocess.Popen(*params, **kwparams)
|
||||
|
||||
if self.__process.stdin:
|
||||
self.__stdin_thread = threading.Thread(
|
||||
name="stdin-thread",
|
||||
target=self.__feeder, args=(self.__pending_input,
|
||||
self.__process.stdin))
|
||||
self.__stdin_thread.setDaemon(True)
|
||||
self.__stdin_thread.start()
|
||||
|
||||
if self.__process.stdout:
|
||||
self.__stdout_thread = threading.Thread(
|
||||
name="stdout-thread",
|
||||
target=self.__reader, args=(self.__collected_outdata,
|
||||
self.__process.stdout))
|
||||
self.__stdout_thread.setDaemon(True)
|
||||
self.__stdout_thread.start()
|
||||
|
||||
if self.__process.stderr:
|
||||
self.__stderr_thread = threading.Thread(
|
||||
name="stderr-thread",
|
||||
target=self.__reader, args=(self.__collected_errdata,
|
||||
self.__process.stderr))
|
||||
self.__stderr_thread.setDaemon(True)
|
||||
self.__stderr_thread.start()
|
||||
|
||||
def pid(self):
|
||||
return self.__process.pid
|
||||
|
||||
def kill(self, signal):
|
||||
self.__process.send_signal(signal)
|
||||
|
||||
# check on subprocess (pass in 'nowait') to act like poll
|
||||
def wait(self, flag):
|
||||
if flag.lower() == 'nowait':
|
||||
rc = self.__process.poll()
|
||||
else:
|
||||
rc = self.__process.wait()
|
||||
if rc != None:
|
||||
if self.__process.stdin:
|
||||
self.closeinput()
|
||||
if self.__process.stdout:
|
||||
self.__stdout_thread.join()
|
||||
if self.__process.stderr:
|
||||
self.__stderr_thread.join()
|
||||
return self.__process.returncode
|
||||
|
||||
def terminate(self):
|
||||
if self.__process.stdin:
|
||||
self.closeinput()
|
||||
self.__process.terminate()
|
||||
|
||||
# thread gets data from subprocess stdout
|
||||
def __reader(self, collector, source):
|
||||
while True:
|
||||
data = os.read(source.fileno(), 65536)
|
||||
self.__lock.acquire()
|
||||
collector.append(data)
|
||||
self.__lock.release()
|
||||
if data == "":
|
||||
source.close()
|
||||
break
|
||||
return
|
||||
|
||||
# thread feeds data to subprocess stdin
|
||||
def __feeder(self, pending, drain):
|
||||
while True:
|
||||
self.__inputsem.acquire()
|
||||
self.__lock.acquire()
|
||||
if not pending and self.__quit:
|
||||
drain.close()
|
||||
self.__lock.release()
|
||||
break
|
||||
data = pending.pop(0)
|
||||
self.__lock.release()
|
||||
drain.write(data)
|
||||
|
||||
# non-blocking read of data from subprocess stdout
|
||||
def read(self):
|
||||
self.__lock.acquire()
|
||||
outdata = "".join(self.__collected_outdata)
|
||||
del self.__collected_outdata[:]
|
||||
self.__lock.release()
|
||||
return outdata
|
||||
|
||||
# non-blocking read of data from subprocess stderr
|
||||
def readerr(self):
|
||||
self.__lock.acquire()
|
||||
errdata = "".join(self.__collected_errdata)
|
||||
del self.__collected_errdata[:]
|
||||
self.__lock.release()
|
||||
return errdata
|
||||
|
||||
# non-blocking write to stdin of subprocess
|
||||
def write(self, data):
|
||||
if self.__process.stdin is None:
|
||||
raise ValueError("Writing to process with stdin not a pipe")
|
||||
self.__lock.acquire()
|
||||
self.__pending_input.append(data)
|
||||
self.__inputsem.release()
|
||||
self.__lock.release()
|
||||
|
||||
# close stdinput of subprocess
|
||||
def closeinput(self):
|
||||
self.__lock.acquire()
|
||||
self.__quit = True
|
||||
self.__inputsem.release()
|
||||
self.__lock.release()
|
||||
@@ -6,8 +6,9 @@
|
||||
|
||||
# Changelog
|
||||
# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 5.0 - Fixed potential unicode problem with command line interface
|
||||
|
||||
__version__ = '4.9'
|
||||
__version__ = '5.0'
|
||||
|
||||
import sys
|
||||
import os, csv, getopt
|
||||
@@ -355,7 +356,7 @@ class TopazBook:
|
||||
|
||||
self.setBookKey(bookKey)
|
||||
self.createBookDirectory()
|
||||
self.extractFiles()
|
||||
self.extractFiles()
|
||||
print u"Successfully Extracted Topaz contents"
|
||||
if inCalibre:
|
||||
from calibre_plugins.dedrm import genbook
|
||||
@@ -451,7 +452,7 @@ def cli_main():
|
||||
print u"TopazExtract v{0}.".format(__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "k:p:s:x")
|
||||
opts, args = getopt.getopt(argv[1:], "k:p:s:x")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
usage(progname)
|
||||
|
||||
60
DeDRM_calibre_plugin/DeDRM_plugin/wineutils.py
Normal file
60
DeDRM_calibre_plugin/DeDRM_plugin/wineutils.py
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
# Standard Python modules.
|
||||
import os, sys, re, hashlib
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
|
||||
def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||
import subprocess
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
import subasyncio
|
||||
from subasyncio import Process
|
||||
|
||||
if extension == u".k4i":
|
||||
import json
|
||||
|
||||
basepath, script = os.path.split(scriptpath)
|
||||
print u"{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script)
|
||||
|
||||
outdirpath = os.path.join(basepath, u"winekeysdir")
|
||||
if not os.path.exists(outdirpath):
|
||||
os.makedirs(outdirpath)
|
||||
|
||||
if wineprefix != "" and os.path.exists(wineprefix):
|
||||
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||
else:
|
||||
cmdline = u"wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
||||
print u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline)
|
||||
|
||||
try:
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
||||
result = p2.wait("wait")
|
||||
except Exception, e:
|
||||
print u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||
return []
|
||||
|
||||
winekeys = []
|
||||
# get any files with extension in the output dir
|
||||
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
||||
for filename in files:
|
||||
try:
|
||||
fpath = os.path.join(outdirpath, filename)
|
||||
with open(fpath, 'rb') as keyfile:
|
||||
if extension == u".k4i":
|
||||
new_key_value = json.loads(keyfile.read())
|
||||
else:
|
||||
new_key_value = keyfile.read()
|
||||
winekeys.append(new_key_value)
|
||||
except:
|
||||
print u"{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename)
|
||||
traceback.print_exc()
|
||||
os.remove(fpath)
|
||||
print u"{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), u"key file" if len(winekeys) == 1 else u"key files")
|
||||
return winekeys
|
||||
@@ -3,19 +3,19 @@ DeDRM_plugin.zip
|
||||
|
||||
This calibre plugin replaces all previous DRM removal plugins. When you install this plugin, the older separate plugins should be removed.
|
||||
|
||||
This plugin will remove the DRM from Amazon Kindle ebooks (Mobi, KF8, Topaz and Print Replica), Mobipocket, Adobe Digital Edition ePubs (including Kobo ePubs), Barnes and Noble ePubs, Adobe Digital Edition PDFs, and Fictionwise eReader ebooks.
|
||||
This plugin will remove the DRM from Amazon Kindle ebooks (Mobi, KF8, Topaz and Print Replica), Mobipocket, Adobe Digital Edition ePubs (including Sony and Kobo ePubs), Barnes and Noble ePubs, Adobe Digital Edition PDFs, and Fictionwise eReader ebooks.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
Do **NOT** select "Get plugins to enhance calibre" as this is reserved for "official" calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page. Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (DeDRM_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
|
||||
Do NOT select "Get plugins to enhance calibre" as this is reserved for 'official' calibre plugins, instead select "Change calibre behavior" to go to Calibre's Preferences page. Under "Advanced" click on the Plugins button. Use the "Load plugin from file" button to select the plugin's zip file (DeDRM_plugin.zip) and click the "Add" button. Click "Yes" in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
|
||||
|
||||
|
||||
Customization
|
||||
-------------
|
||||
On Windows and Mac, the keys for ebooks downloaded for Kindle for Mac/PC and Adobe Digital Editions are automatically generated. If all your DRMed ebooks can be opened and read in Kindle for Mac/PC and/or Adobe Digital Editions on the same computer on which you are running calibre, you do not need to do any configuration of this plugin. On Linux, keys for Kindle for PC and Adobe Digital Editions need to be generated separately (see Linux systems section)
|
||||
The keys for ebooks downloaded using Kindle for Mac/PC and Adobe Digital Editions are automatically generated and saved when needed. If all your DRMed ebooks can be opened and read in Kindle for Mac/PC or Adobe Digital Editions on the same computer on which you are running calibre, you do not need to do any configuration of this plugin. (On Linux, Kindle for PC and Adobe Digital Editions along with Python and PyCrypto need to be installed under Wine for this to work, see the Linux section at the end.)
|
||||
|
||||
Otherwise, highlight the plugin (DeDRM under the "File type plugins" category) and click the "Customize Plugin" button.
|
||||
If you have books from other sources (e.g. from an eInk Kindle), highlight the plugin (DeDRM under the "File type plugins" category) and click the "Customize Plugin" button.
|
||||
|
||||
The buttons in the configuration dialog will open individual configuration dialogs that will allow you to enter the needed information, depending on the type and source of your DRMed eBooks. Additional help on the information required is available in each of the the dialogs.
|
||||
|
||||
@@ -65,77 +65,31 @@ The DeDRM all-in-one python script was created by some_updates and Apprentice Al
|
||||
Linux Systems Only
|
||||
==================
|
||||
|
||||
Generating decryption keys for Adobe Digital Editions and Kindle for PC
|
||||
-----------------------------------------------------------------------
|
||||
If you install Kindle for PC and/or Adobe Digital Editions in Wine, you will be able to download DRMed ebooks to them under Wine. To be able to remove the DRM, you will need to generate key files and add them in the plugin's customisation dialogs.
|
||||
Instructions for installing Wine, Kindle for PC, Adobe Digital Editions, Python and PyCrypto
|
||||
--------------------------------------------------------------------------------------------
|
||||
|
||||
To generate the key files you will need to install Python and PyCrypto under the same Wine setup as your Kindle for PC and/or Adobe Digital Editions installations. (Kindle for PC, Python and Pycrypto installation instructions are below.)
|
||||
These instructions have been tested with Wine 1.4 on Ubuntu.
|
||||
|
||||
Once everything's installed under Wine, you'll need to run the adobekey.pyw script (for Adobe Digital Editions) and kindlekey.pyw (For Kindle for PC) using the python installation in your Wine system. The scripts can be found in Other_Tools/Key_Retrieval_Scripts.
|
||||
|
||||
Each script will create a key file in the same folder as the script. Copy the key files to your Linux system and use the plugin customisation dialog in calibre to load the key files.
|
||||
1. First download the software you're going to to have to install.
|
||||
a. Kindle for PC from http://www.amazon.co.uk/gp/kindle/pc/
|
||||
b. Adobe Digital Editions 1.7.x from http://helpx.adobe.com/digital-editions/kb/cant-install-digital-editions.html
|
||||
(Adobe Digital Editions 2.x doesn't work with Wine.)
|
||||
c. ActivePython 2.7.X for Windows (x86) from http://www.activestate.com/activepython/downloads
|
||||
d. PyCrypto 2.1 for 32bit Windows and Python 2.7 from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||
(PyCrypto downloads as a zip file. You will need to unzip it.)
|
||||
2. Install Wine for 32-bit x86. (e.g. on Ubuntu, Open the Ubuntu Software Center, search for Wine, and install "Wine Windows Program Loader".)
|
||||
3. Run "Configure Wine", which will set up the default 'wineprefix'
|
||||
4. Run winetricks, select the default wineprefix and install component vcrun2008
|
||||
5. Run the mis-named "Uninstall Wine Software", which also allows installation of software.
|
||||
6. Install Kindle for PC. Accept all defaults and register with your Amazon Account.
|
||||
7. Install Adobe Digital Editions. Accept all defaults and register with your Adobe ID.
|
||||
8. Install ActiveState Python 2.7.x. Accept all defaults.
|
||||
9. Install PyCrypto 2.1. Accept all defaults.
|
||||
|
||||
|
||||
Instructions for installing Kindle for PC on Linux under Wine. (Thank you Eyeless and Pete)
|
||||
-------------------------------------------------------------------------------------------
|
||||
1. upgrade to very recent versions of Wine; This has been tested with Wine 1.3.15 – 1.3.2X. It may work with earlier versions but no promises. It does not work with wine 1.2.X versions.
|
||||
Instructions for getting Kindle for PC and Adobe Digital Editions default decryption keys
|
||||
-----------------------------------------------------------------------------------------
|
||||
|
||||
If you have not already installed Kindle for PC under wine, follow steps 2 and 3 otherwise jump to step 4
|
||||
|
||||
2. Some versions of winecfg have a bug in setting the volume serial number, so create a .windows-serial file at root of drive_c to set a proper windows volume serial number (8 digit hex value for unsigned integer).
|
||||
cd ~
|
||||
cd .wine
|
||||
cd drive_c
|
||||
echo deadbeef > .windows-serial
|
||||
|
||||
Replace "deadbeef" with whatever hex value you want but I would stay away from the default setting of "ffffffff" which does not seem to work. BTW: deadbeef is itself a valid possible hex value if you want to use it
|
||||
|
||||
3. Only ***after*** setting the volume serial number properly – download and install under wine K4PC version for Windows. Register it and download from your Archive one of your Kindle ebooks.
|
||||
|
||||
|
||||
More such Instructions
|
||||
----------------------
|
||||
Hi everyone, I struggled to get this working on Ubuntu 12.04. Here are the secrets for everyone:
|
||||
|
||||
1. Make sure your Wine installation is set up to be 32 bit. 64 bit is not going to work! To do this, remove your .wine directory (or use a different wineprefix). Then use WINEARCH=win32 winecfg
|
||||
|
||||
2. But wait, you can’t install Kindle yet. It won’t work. You need to do: winetricks -q vcrun2008 or else you’ll get an error: unimplemented function msvcp90.dll .
|
||||
|
||||
3. Now download and install Kindle for PC and download your content as normal.
|
||||
|
||||
4. Now download and install Python 2.7 32 bit for Windows from python.org, 32 bit, install it the usual way, and you can now run the Kindle DRM tools.
|
||||
|
||||
|
||||
Yet more such Instructions
|
||||
--------------------------
|
||||
It took a while to figure out that I needed wine 32 bit, plus Python 27 32 bit, plus the winetricks, to get all this working together but once it’s done, it’s great and I can read my Kindle content on my Nook Color running Cyanogenmod!!!
|
||||
Linux Systems Only:
|
||||
For all of the following wine installs, use WINEARCH=win32 if you are on x86_64. Also remember that in order to execute a *.msi file, you have to run ‘WINEARCH=win32 wine msiexec /i xxxxx.msi’.
|
||||
1. Install Kindle for PC with wine.
|
||||
2. Install ActivePython 2.7.x (Windows x86) with wine from here: http://www.activestate.com/activepython/downloads
|
||||
3. Install the pycrypto (Windows 32 bit for Python 2.7) module with wine from here: http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||
4. Install the K4MobiDeDRM plugin into your _Linux_ Calibre installation
|
||||
Now all Kindle books downloaded from Kindle for PC in Wine will be automatically de-DRM’d when they are added to your _Linux_ Calibre. As always, you can troubleshoot problems by adding a book from the terminal using ‘calibredb add xxxx’.
|
||||
|
||||
Or something like that! Hope that helps someone out.
|
||||
|
||||
|
||||
Installing Python on Windows
|
||||
----------------------------
|
||||
I strongly recommend fully installing ActiveState’s Active Python, free Community Edition for Windows (x86) 32 bits. This is a free, full version of the Python. It comes with some important additional modules that are not included in the bare-bones version from www.python.org unless you choose to install everything.
|
||||
|
||||
1. Download ActivePython 2.7.X for Windows (x86) (or later 2.7 version for Windows (x86) ) from http://www.activestate.com/activepython/downloads. Do not download the ActivePython 2.7.X for Windows (64-bit, x64) verson, even if you are running 64-bit Windows.
|
||||
|
||||
2. When it has finished downloading, run the installer. Accept the default options.
|
||||
|
||||
|
||||
Installing PyCrypto on Windows
|
||||
------------------------------
|
||||
PyCrypto is a set of encryption/decryption routines that work with Python. The sources are freely available, and compiled versions are available from several sources. You must install a version that is for 32-bit Windows and Python 2.7. I recommend the installer linked from Michael Foord’s blog.
|
||||
|
||||
1. Download PyCrypto 2.1 for 32bit Windows and Python 2.7 from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||
|
||||
2. When it has finished downloading, unzip it. This will produce a file “pycrypto-2.1.0.win32-py2.7.exe”.
|
||||
|
||||
3. Double-click “pycrypto-2.1.0.win32-py2.7.exe” to run it. Accept the default options.
|
||||
If everything has been installed in wine as above, the keys will be retrieve automatically.
|
||||
|
||||
If you have a more complex wine installation, you may enter the appropriate WINEPREFIX in the configuration dialogs for Kindle for PC and Adobe Digital Editions. You can also test that you have entered the WINEPREFIX correctly by trying to add the default keys to the preferences by clicking on the green plus button in the configuration dialogs.
|
||||
|
||||
@@ -9,7 +9,7 @@ If the downloaded file is encrypted, install and configure the ignoble plugin in
|
||||
DOWNLOAD HIDDEN FILES FROM B&N
|
||||
------------------------------
|
||||
|
||||
Some content is not downloadable from the B&N website, notably magazines. A Greasemonkey script (link below) modifies the myNook page of the Barnes and Noble website to show a download button for normally non-downloadable content. This will work until Barnes & Noble changes their website.
|
||||
Some content is not downloadable from the B&N website, notably magazines. A Greasemonkey script (details below) modifies the myNook page of the Barnes and Noble website to show a download button for normally non-downloadable content. This will work until Barnes & Noble changes their website.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
@@ -47,13 +47,14 @@ from __future__ import with_statement
|
||||
# 5.7 - Unicode support added, renamed adobekey from ineptkey
|
||||
# 5.8 - Added getkey interface for Windows DeDRM application
|
||||
# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.0 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Retrieve Adobe ADEPT user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '5.9'
|
||||
__version__ = '6.0'
|
||||
|
||||
import sys, os, struct, getopt
|
||||
|
||||
@@ -402,9 +403,11 @@ if iswindows:
|
||||
aes = AES(keykey)
|
||||
userkey = aes.decrypt(userkey)
|
||||
userkey = userkey[26:-ord(userkey[-1])]
|
||||
#print "found key:",userkey.encode('hex')
|
||||
keys.append(userkey)
|
||||
if len(keys) == 0:
|
||||
raise ADEPTError('Could not locate privateLicenseKey')
|
||||
print u"Found {0:d} keys".format(len(keys))
|
||||
return keys
|
||||
|
||||
|
||||
@@ -485,6 +488,8 @@ def usage(progname):
|
||||
print u" {0:s} [-h] [<outpath>]".format(progname)
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__)
|
||||
@@ -541,10 +546,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
@@ -577,7 +585,7 @@ def gui_main():
|
||||
keyfileout.write(key)
|
||||
success = True
|
||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||
except DrmException, e:
|
||||
except ADEPTError, e:
|
||||
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||
except Exception:
|
||||
root.wm_state('normal')
|
||||
@@ -591,7 +599,5 @@ 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())
|
||||
|
||||
@@ -32,13 +32,14 @@ from __future__ import with_statement
|
||||
# 2.4 - Improvements to UI and now works in plugins
|
||||
# 2.5 - Additional improvement for unicode and plugin support
|
||||
# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 2.7 - Work if TkInter is missing
|
||||
|
||||
"""
|
||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "2.6"
|
||||
__version__ = "2.7"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -216,6 +217,8 @@ def generate_key(name, ccn):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if AES is None:
|
||||
@@ -233,10 +236,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
import tkMessageBox
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
@@ -320,7 +326,5 @@ 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())
|
||||
|
||||
@@ -16,6 +16,10 @@ from __future__ import with_statement
|
||||
# Simplified some of the Kindle for Mac code.
|
||||
# 1.4 - Remove dependency on alfcrypto
|
||||
# 1.5 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 1.6 - Fixed a problem getting the disk serial numbers
|
||||
# 1.7 - Work if TkInter is missing
|
||||
# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names
|
||||
# 1.9 - Fixes for Unicode in Windows user names
|
||||
|
||||
|
||||
"""
|
||||
@@ -23,7 +27,7 @@ Retrieve Kindle for PC/Mac user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.5'
|
||||
__version__ = '1.9'
|
||||
|
||||
import sys, os, re
|
||||
from struct import pack, unpack, unpack_from
|
||||
@@ -904,18 +908,34 @@ if iswindows:
|
||||
return CryptUnprotectData
|
||||
CryptUnprotectData = CryptUnprotectData()
|
||||
|
||||
# Returns Environmental Variables that contain unicode
|
||||
def getEnvironmentVariable(name):
|
||||
import ctypes
|
||||
name = unicode(name) # make sure string argument is unicode
|
||||
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
|
||||
if n == 0:
|
||||
return None
|
||||
buf = ctypes.create_unicode_buffer(u'\0'*n)
|
||||
ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n)
|
||||
return buf.value
|
||||
|
||||
# Locate all of the kindle-info style files and return as list
|
||||
def getKindleInfoFiles():
|
||||
kInfoFiles = []
|
||||
# some 64 bit machines do not have the proper registry key for some reason
|
||||
# or the pythonn interface to the 32 vs 64 bit registry is broken
|
||||
# or the python interface to the 32 vs 64 bit registry is broken
|
||||
path = ""
|
||||
if 'LOCALAPPDATA' in os.environ.keys():
|
||||
path = os.environ['LOCALAPPDATA']
|
||||
# Python 2.x does not return unicode env. Use Python 3.x
|
||||
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||
# this is just another alternative.
|
||||
# path = getEnvironmentVariable('LOCALAPPDATA')
|
||||
if not os.path.isdir(path):
|
||||
path = ""
|
||||
else:
|
||||
# User Shell Folders show take precedent over Shell Folders if present
|
||||
try:
|
||||
# this will still break
|
||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
|
||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||
if not os.path.isdir(path):
|
||||
@@ -934,13 +954,14 @@ if iswindows:
|
||||
if path == "":
|
||||
print ('Could not find the folder in which to look for kinfoFiles.')
|
||||
else:
|
||||
print('searching for kinfoFiles in ' + path)
|
||||
# Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
|
||||
print(u'searching for kinfoFiles in ' + path.encode('ascii', 'ignore'))
|
||||
|
||||
# look for (K4PC 1.9.0 and later) .kinf2011 file
|
||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
||||
if os.path.isfile(kinfopath):
|
||||
found = True
|
||||
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath)
|
||||
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath.encode('ascii','ignore'))
|
||||
kInfoFiles.append(kinfopath)
|
||||
|
||||
# look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
||||
@@ -1139,8 +1160,8 @@ if iswindows:
|
||||
cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName())
|
||||
if 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode("latin-1"))
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
@@ -1268,10 +1289,10 @@ elif isosx:
|
||||
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||
# returns serial numbers of all internal hard drive drives
|
||||
def GetVolumesSerialNumbers():
|
||||
sernums = []
|
||||
sernum = os.getenv('MYSERIALNUMBER')
|
||||
if sernum != None:
|
||||
return [sernum]
|
||||
sernums = []
|
||||
sernums.append(sernum.strip())
|
||||
cmdline = '/usr/sbin/ioreg -w 0 -r -c AppleAHCIDiskDriver'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
@@ -1287,7 +1308,7 @@ elif isosx:
|
||||
if pp >= 0:
|
||||
sernum = resline[pp+19:-1]
|
||||
sernums.append(sernum.strip())
|
||||
return [sernum]
|
||||
return sernums
|
||||
|
||||
def GetUserHomeAppSupKindleDirParitionName():
|
||||
home = os.getenv('HOME')
|
||||
@@ -1313,10 +1334,11 @@ elif isosx:
|
||||
return disk
|
||||
|
||||
# uses a sub process to get the UUID of the specified disk partition using ioreg
|
||||
def GetDiskPartitionUUID(diskpart):
|
||||
def GetDiskPartitionUUIDs(diskpart):
|
||||
uuids = []
|
||||
uuidnum = os.getenv('MYUUIDNUMBER')
|
||||
if uuidnum != None:
|
||||
return uuidnum
|
||||
uuids.append(strip(uuidnum))
|
||||
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
@@ -1359,14 +1381,15 @@ elif isosx:
|
||||
uuidnest = -1
|
||||
uuidnum = None
|
||||
bsdname = None
|
||||
if not foundIt:
|
||||
uuidnum = ''
|
||||
return uuidnum
|
||||
if foundIt:
|
||||
uuids.append(uuidnum)
|
||||
return uuids
|
||||
|
||||
def GetMACAddressMunged():
|
||||
def GetMACAddressesMunged():
|
||||
macnums = []
|
||||
macnum = os.getenv('MYMACNUM')
|
||||
if macnum != None:
|
||||
return macnum
|
||||
macnums.append(macnum)
|
||||
cmdline = '/sbin/ifconfig en0'
|
||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||
@@ -1401,9 +1424,9 @@ elif isosx:
|
||||
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:
|
||||
macnum = ''
|
||||
return macnum
|
||||
if foundIt:
|
||||
macnums.append(macnum)
|
||||
return macnums
|
||||
|
||||
|
||||
# uses unix env to get username instead of using sysctlbyname
|
||||
@@ -1414,11 +1437,12 @@ elif isosx:
|
||||
def GetIDStrings():
|
||||
# Return all possible ID Strings
|
||||
strings = []
|
||||
strings.append(GetMACAddressMunged())
|
||||
strings.extend(GetMACAddressesMunged())
|
||||
strings.extend(GetVolumesSerialNumbers())
|
||||
diskpart = GetUserHomeAppSupKindleDirParitionName()
|
||||
strings.append(GetDiskPartitionUUID(diskpart))
|
||||
strings.extend(GetDiskPartitionUUIDs(diskpart))
|
||||
strings.append('9999999999')
|
||||
#print strings
|
||||
return strings
|
||||
|
||||
|
||||
@@ -1516,20 +1540,32 @@ elif isosx:
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kinf2011 file: ' + testpath)
|
||||
found = True
|
||||
# check for .kinf2011 files
|
||||
# check for .kinf2011 files from 1.10
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kinf2011 file: ' + testpath)
|
||||
found = True
|
||||
# check for .rainier-2.1.1-kinf files
|
||||
# check for .rainier-2.1.1-kinf files from 1.6
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac rainier file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info files
|
||||
# check for .kindle-info files from 1.4
|
||||
testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info file from 1.2.2
|
||||
testpath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
found = True
|
||||
# check for .kindle-info file from 1.0 beta 1 (27214)
|
||||
testpath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info'
|
||||
if os.path.isfile(testpath):
|
||||
kInfoFiles.append(testpath)
|
||||
print('Found k4Mac kindle-info file: ' + testpath)
|
||||
@@ -1740,7 +1776,7 @@ elif isosx:
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
if 'kindle.account.tokens' in DB:
|
||||
# store values used in decryption
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())
|
||||
DB['IDString'] = IDString
|
||||
@@ -1800,6 +1836,8 @@ def usage(progname):
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2010-2013 some_updates and Apprentice Alf".format(progname,__version__)
|
||||
@@ -1841,10 +1879,13 @@ def cli_main():
|
||||
|
||||
|
||||
def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class ExceptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root, text):
|
||||
@@ -1891,7 +1932,5 @@ 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())
|
||||
|
||||
275
Other_Tools/DRM_Key_Scripts/Kindle_for_iOS/kindleiospidgen.py
Normal file
275
Other_Tools/DRM_Key_Scripts/Kindle_for_iOS/kindleiospidgen.py
Normal file
@@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindleforios4key.py
|
||||
# Copyright © 2013 by Apprentice Alf
|
||||
# Portions Copyright © 2007, 2009 Igor Skochinsky <skochinsky@mail.ru>
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Generates fixed PID for Kindle for iOS 3.1.1 running on iOS 4.x
|
||||
|
||||
|
||||
"""
|
||||
Generate fixed PID for Kindle for iOS 3.1.1
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.0'
|
||||
|
||||
import sys, os
|
||||
import getopt
|
||||
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]
|
||||
|
||||
import hashlib
|
||||
|
||||
def SHA256(message):
|
||||
ctx = hashlib.sha256()
|
||||
ctx.update(message)
|
||||
return ctx.digest()
|
||||
|
||||
def crc32(s):
|
||||
return (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||
|
||||
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||
|
||||
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 generatekeys(email, mac):
|
||||
keys = []
|
||||
email = email.encode('utf-8').lower()
|
||||
mac = mac.encode('utf-8').lower()
|
||||
cleanmac = "".join(c if (c in "0123456789abcdef") else "" for c in mac)
|
||||
lowermac = cleanmac.lower()
|
||||
#print lowermac
|
||||
keyseed = lowermac + email.encode('utf-8')
|
||||
#print keyseed
|
||||
keysha256 = SHA256(keyseed)
|
||||
keybase64 = keysha256.encode('base64')
|
||||
#print keybase64
|
||||
cleankeybase64 = "".join(c if (c in "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") else "0" for c in keybase64)
|
||||
#print cleankeybase64
|
||||
pseudoudid = cleankeybase64[:40]
|
||||
#print pseudoudid
|
||||
keys.append(pidFromSerial(pseudoudid.encode("utf-8"),8))
|
||||
return keys
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
def getkey(email, mac, outpath):
|
||||
keys = generatekeys(email,mac)
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"kindleios{0:d}.pid".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
return True
|
||||
return False
|
||||
|
||||
def usage(progname):
|
||||
print u"Generates the key for Kindle for iOS 3.1.1"
|
||||
print u"Requires email address of Amazon acccount"
|
||||
print u"And MAC address for iOS device’s wifi"
|
||||
print u"Outputs to a file or to stdout"
|
||||
print u"Usage:"
|
||||
print u" {0:s} [-h] <email address> <MAC address> [<outfile>]".format(progname)
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2013 Apprentice Alf".format(progname,__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "h")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage(progname)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if len(args) < 2 or len(args) > 3:
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
if len(args) == 3:
|
||||
# save to the specified file or folder
|
||||
getkey(args[0],args[1],args[2])
|
||||
else:
|
||||
keys = generatekeys(args[0],args[1])
|
||||
for key in keys:
|
||||
print key
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
except:
|
||||
print "Tkinter not installed"
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
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=u"Amazon email address").grid(row=0)
|
||||
self.email = Tkinter.Entry(body, width=40)
|
||||
self.email.grid(row=0, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"iOS MAC address").grid(row=1)
|
||||
self.mac = Tkinter.Entry(body, width=40)
|
||||
self.mac.grid(row=1, column=1, sticky=sticky)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
button = Tkinter.Button(
|
||||
buttons, text=u"Generate", width=10, command=self.generate)
|
||||
button.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 generate(self):
|
||||
email = self.email.get()
|
||||
mac = self.mac.get()
|
||||
if not email:
|
||||
self.status['text'] = u"Email not specified"
|
||||
return
|
||||
if not mac:
|
||||
self.status['text'] = u"MAC not specified"
|
||||
return
|
||||
self.status['text'] = u"Generating..."
|
||||
try:
|
||||
keys = generatekeys(email, mac)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: (0}".format(e.args[0])
|
||||
return
|
||||
self.status['text'] = ", ".join(key for key in keys)
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Kindle for iOS PID Generator 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.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
@@ -0,0 +1,238 @@
|
||||
Only in kindle4.0.2.1: build
|
||||
diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali kindle4.0.2.1/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali
|
||||
--- kindle4.0.2.1_orig/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali 2013-05-22 18:39:03.000000000 -0500
|
||||
+++ kindle4.0.2.1/smali/com/amazon/kcp/application/AndroidDeviceInformationProvider.smali 2013-05-23 16:54:53.000000000 -0500
|
||||
@@ -36,20 +36,22 @@
|
||||
.field private maxCpuSpeed:J
|
||||
|
||||
.field private maxMemory:J
|
||||
|
||||
.field private minCpuSpeed:J
|
||||
|
||||
.field private resources:Landroid/content/res/Resources;
|
||||
|
||||
.field private security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
|
||||
|
||||
+.field private pidList:Ljava/lang/String;
|
||||
+
|
||||
.field private totalMemory:J
|
||||
|
||||
|
||||
# direct methods
|
||||
.method static constructor <clinit>()V
|
||||
.locals 1
|
||||
|
||||
.prologue
|
||||
.line 30
|
||||
const-class v0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;
|
||||
@@ -72,20 +74,24 @@
|
||||
.prologue
|
||||
.line 130
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
|
||||
.line 131
|
||||
iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->security:Lcom/mobipocket/android/library/reader/AndroidSecurity;
|
||||
|
||||
.line 132
|
||||
iput-object p2, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->deviceType:Lcom/amazon/kcp/application/AmazonDeviceType;
|
||||
|
||||
+ const-string v0, "Open DRMed book to show PID list."
|
||||
+
|
||||
+ iput-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
.line 133
|
||||
sget-object v0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->TAG:Ljava/lang/String;
|
||||
|
||||
new-instance v0, Ljava/lang/StringBuilder;
|
||||
|
||||
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
|
||||
|
||||
const-string v1, "Device Type is set to \""
|
||||
|
||||
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
@@ -1235,10 +1241,33 @@
|
||||
move-result-wide v0
|
||||
|
||||
iput-wide v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->totalMemory:J
|
||||
|
||||
.line 308
|
||||
:cond_0
|
||||
iget-wide v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->totalMemory:J
|
||||
|
||||
return-wide v0
|
||||
.end method
|
||||
+
|
||||
+.method public getPidList()Ljava/lang/String;
|
||||
+ .locals 1
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 15
|
||||
+ iget-object v0, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
+ return-object v0
|
||||
+.end method
|
||||
+
|
||||
+.method public setPidList(Ljava/lang/String;)V
|
||||
+ .locals 0
|
||||
+ .parameter "value"
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 11
|
||||
+ iput-object p1, p0, Lcom/amazon/kcp/application/AndroidDeviceInformationProvider;->pidList:Ljava/lang/String;
|
||||
+
|
||||
+ .line 12
|
||||
+ return-void
|
||||
+.end method
|
||||
+
|
||||
diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali kindle4.0.2.1/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali
|
||||
--- kindle4.0.2.1_orig/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali 2013-05-22 18:39:03.000000000 -0500
|
||||
+++ kindle4.0.2.1/smali/com/amazon/kcp/application/IDeviceInformationProvider.smali 2013-05-23 16:55:58.000000000 -0500
|
||||
@@ -23,10 +23,16 @@
|
||||
.end method
|
||||
|
||||
.method public abstract getDeviceTypeId()Ljava/lang/String;
|
||||
.end method
|
||||
|
||||
.method public abstract getOsVersion()Ljava/lang/String;
|
||||
.end method
|
||||
|
||||
.method public abstract getPid()Ljava/lang/String;
|
||||
.end method
|
||||
+
|
||||
+.method public abstract getPidList()Ljava/lang/String;
|
||||
+.end method
|
||||
+
|
||||
+.method public abstract setPidList(Ljava/lang/String;)V
|
||||
+.end method
|
||||
diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/kcp/info/AboutActivity.smali kindle4.0.2.1/smali/com/amazon/kcp/info/AboutActivity.smali
|
||||
--- kindle4.0.2.1_orig/smali/com/amazon/kcp/info/AboutActivity.smali 2013-05-22 18:39:03.000000000 -0500
|
||||
+++ kindle4.0.2.1/smali/com/amazon/kcp/info/AboutActivity.smali 2013-05-23 17:18:14.000000000 -0500
|
||||
@@ -486,20 +486,71 @@
|
||||
.end local v2 #screenDpi:Ljava/lang/String;
|
||||
:cond_0
|
||||
iget-object v5, p0, Lcom/amazon/kcp/info/AboutActivity;->detailItemList:Ljava/util/List;
|
||||
|
||||
invoke-interface {v5, v0}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
|
||||
.line 317
|
||||
return-void
|
||||
.end method
|
||||
|
||||
+.method private populatePIDList()V
|
||||
+ .locals 7
|
||||
+
|
||||
+ .prologue
|
||||
+ .line 313
|
||||
+ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
|
||||
+
|
||||
+ move-result-object v0
|
||||
+
|
||||
+ invoke-interface {v0}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->getPidList()Ljava/lang/String;
|
||||
+
|
||||
+ move-result-object v1
|
||||
+
|
||||
+ .line 314
|
||||
+ .local v1, PidList:Ljava/lang/String;
|
||||
+ iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
|
||||
+
|
||||
+ new-instance v4, Lcom/amazon/kcp/info/AboutActivity$GroupItem;
|
||||
+
|
||||
+ const-string v5, "PID List"
|
||||
+
|
||||
+ const v6, 0x1
|
||||
+
|
||||
+ invoke-direct {v4, p0, v5, v6}, Lcom/amazon/kcp/info/AboutActivity$GroupItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Z)V
|
||||
+
|
||||
+ invoke-interface {v3, v4}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 315
|
||||
+ new-instance v2, Ljava/util/ArrayList;
|
||||
+
|
||||
+ invoke-direct {v2}, Ljava/util/ArrayList;-><init>()V
|
||||
+
|
||||
+ .line 316
|
||||
+ .local v2, children:Ljava/util/List;,"Ljava/util/List<Lcom/amazon/kcp/info/AboutActivity$DetailItem;>;"
|
||||
+ new-instance v3, Lcom/amazon/kcp/info/AboutActivity$DetailItem;
|
||||
+
|
||||
+ const-string v4, "PIDs"
|
||||
+
|
||||
+ invoke-direct {v3, p0, v4, v1}, Lcom/amazon/kcp/info/AboutActivity$DetailItem;-><init>(Lcom/amazon/kcp/info/AboutActivity;Ljava/lang/String;Ljava/lang/String;)V
|
||||
+
|
||||
+ invoke-interface {v2, v3}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 317
|
||||
+ iget-object v3, p0, Lcom/amazon/kcp/info/AboutActivity;->detailItemList:Ljava/util/List;
|
||||
+
|
||||
+ invoke-interface {v3, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z
|
||||
+
|
||||
+ .line 318
|
||||
+ return-void
|
||||
+.end method
|
||||
+
|
||||
.method private populateDisplayItems()V
|
||||
.locals 1
|
||||
|
||||
.prologue
|
||||
.line 171
|
||||
iget-object v0, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
|
||||
|
||||
if-nez v0, :cond_0
|
||||
|
||||
.line 173
|
||||
@@ -531,20 +582,22 @@
|
||||
|
||||
.line 192
|
||||
invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateRamInformation()V
|
||||
|
||||
.line 193
|
||||
invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateStorageInformation()V
|
||||
|
||||
.line 194
|
||||
invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populateDisplayInformation()V
|
||||
|
||||
+ invoke-direct {p0}, Lcom/amazon/kcp/info/AboutActivity;->populatePIDList()V
|
||||
+
|
||||
.line 195
|
||||
return-void
|
||||
|
||||
.line 177
|
||||
:cond_0
|
||||
iget-object v0, p0, Lcom/amazon/kcp/info/AboutActivity;->groupItemList:Ljava/util/List;
|
||||
|
||||
invoke-interface {v0}, Ljava/util/List;->clear()V
|
||||
|
||||
goto :goto_0
|
||||
diff -r -u10 kindle4.0.2.1_orig/smali/com/amazon/system/security/Security.smali kindle4.0.2.1/smali/com/amazon/system/security/Security.smali
|
||||
--- kindle4.0.2.1_orig/smali/com/amazon/system/security/Security.smali 2013-05-22 18:39:04.000000000 -0500
|
||||
+++ kindle4.0.2.1/smali/com/amazon/system/security/Security.smali 2013-05-23 17:19:05.000000000 -0500
|
||||
@@ -920,20 +920,30 @@
|
||||
|
||||
.line 350
|
||||
:cond_2
|
||||
add-int/lit8 v8, v8, 0x1
|
||||
|
||||
.line 351
|
||||
sget-object v0, Lcom/amazon/system/security/Security;->CUSTOM_PID_FOR_BUNDLED_DICTIONARY_DRM:Ljava/lang/String;
|
||||
|
||||
aput-object v0, v6, v8
|
||||
|
||||
+ invoke-static {}, Lcom/amazon/kcp/application/DeviceInformationProviderFactory;->getProvider()Lcom/amazon/kcp/application/IDeviceInformationProvider;
|
||||
+
|
||||
+ move-result-object v5
|
||||
+
|
||||
+ invoke-static {v6}, Ljava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String;
|
||||
+
|
||||
+ move-result-object v2
|
||||
+
|
||||
+ invoke-interface {v5, v2}, Lcom/amazon/kcp/application/IDeviceInformationProvider;->setPidList(Ljava/lang/String;)V
|
||||
+
|
||||
.line 353
|
||||
return-object v6
|
||||
.end method
|
||||
|
||||
|
||||
# virtual methods
|
||||
.method public customDrmOnly()I
|
||||
.locals 1
|
||||
|
||||
.prologue
|
||||
231
Other_Tools/Kobo/obok_2.01.py
Normal file
231
Other_Tools/Kobo/obok_2.01.py
Normal file
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Updated September 2013 by Anon
|
||||
# Version 2.01
|
||||
# Incorporated minor fixes posted at Apprentice Alf's.
|
||||
#
|
||||
# Updates July 2012 by Michael Newton
|
||||
# PWSD ID is no longer a MAC address, but should always
|
||||
# be stored in the registry. Script now works with OS X
|
||||
# and checks plist for values instead of registry. Must
|
||||
# have biplist installed for OS X support.
|
||||
#
|
||||
##########################################################
|
||||
# KOBO DRM CRACK BY #
|
||||
# PHYSISTICATED #
|
||||
##########################################################
|
||||
# This app was made for Python 2.7 on Windows 32-bit
|
||||
#
|
||||
# This app needs pycrypto - get from here:
|
||||
# http://www.voidspace.org.uk/python/modules.shtml
|
||||
#
|
||||
# Usage: obok.py
|
||||
# Choose the book you want to decrypt
|
||||
#
|
||||
# Shouts to my krew - you know who you are - and one in
|
||||
# particular who gave me a lot of help with this - thank
|
||||
# you so much!
|
||||
#
|
||||
# Kopimi /K\
|
||||
# Keep sharing, keep copying, but remember that nothing is
|
||||
# for free - make sure you compensate your favorite
|
||||
# authors - and cut out the middle man whenever possible
|
||||
# ;) ;) ;)
|
||||
#
|
||||
# DRM AUTOPSY
|
||||
# The Kobo DRM was incredibly easy to crack, but it took
|
||||
# me months to get around to making this. Here's the
|
||||
# basics of how it works:
|
||||
# 1: Get MAC address of first NIC in ipconfig (sometimes
|
||||
# stored in registry as pwsdid)
|
||||
# 2: Get user ID (stored in tons of places, this gets it
|
||||
# from HKEY_CURRENT_USER\Software\Kobo\Kobo Desktop
|
||||
# Edition\Browser\cookies)
|
||||
# 3: Concatenate and SHA256, take the second half - this
|
||||
# is your master key
|
||||
# 4: Open %LOCALAPPDATA%\Kobo Desktop Editions\Kobo.sqlite
|
||||
# and dump content_keys
|
||||
# 5: Unbase64 the keys, then decode these with the master
|
||||
# key - these are your page keys
|
||||
# 6: Unzip EPUB of your choice, decrypt each page with its
|
||||
# page key, then zip back up again
|
||||
#
|
||||
# WHY USE THIS WHEN INEPT WORKS FINE? (adobe DRM stripper)
|
||||
# Inept works very well, but authors on Kobo can choose
|
||||
# what DRM they want to use - and some have chosen not to
|
||||
# let people download them with Adobe Digital Editions -
|
||||
# they would rather lock you into a single platform.
|
||||
#
|
||||
# With Obok, you can sync Kobo Desktop, decrypt all your
|
||||
# ebooks, and then use them on whatever device you want
|
||||
# - you bought them, you own them, you can do what you
|
||||
# like with them.
|
||||
#
|
||||
# Obok is Kobo backwards, but it is also means "next to"
|
||||
# in Polish.
|
||||
# When you buy a real book, it is right next to you. You
|
||||
# can read it at home, at work, on a train, you can lend
|
||||
# it to a friend, you can scribble on it, and add your own
|
||||
# explanations/translations.
|
||||
#
|
||||
# Obok gives you this power over your ebooks - no longer
|
||||
# are you restricted to one device. This allows you to
|
||||
# embed foreign fonts into your books, as older Kobo's
|
||||
# can't display them properly. You can read your books
|
||||
# on your phones, in different PC readers, and different
|
||||
# ereader devices. You can share them with your friends
|
||||
# too, if you like - you can do that with a real book
|
||||
# after all.
|
||||
#
|
||||
"""
|
||||
Decrypt Kobo encrypted EPUB books.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
if sys.platform.startswith('win'):
|
||||
import _winreg
|
||||
elif sys.platform.startswith('darwin'):
|
||||
from biplist import readPlist
|
||||
import re
|
||||
import string
|
||||
import hashlib
|
||||
import sqlite3
|
||||
import base64
|
||||
import binascii
|
||||
import zipfile
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
def SHA256(raw):
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
def RemoveAESPadding(contents):
|
||||
lastchar = binascii.b2a_hex(contents[-1:])
|
||||
strlen = int(lastchar, 16)
|
||||
padding = strlen
|
||||
if(strlen == 1):
|
||||
return contents[:-1]
|
||||
if(strlen < 16):
|
||||
for i in range(strlen):
|
||||
testchar = binascii.b2a_hex(contents[-strlen:-(strlen-1)])
|
||||
if(testchar != lastchar):
|
||||
padding = 0
|
||||
if(padding > 0):
|
||||
contents = contents[:-padding]
|
||||
return contents
|
||||
|
||||
def GetVolumeKeys(dbase, enc):
|
||||
volumekeys = {}
|
||||
for row in dbase.execute("SELECT * from content_keys"):
|
||||
if(row[0] not in volumekeys):
|
||||
volumekeys[row[0]] = {}
|
||||
volumekeys[row[0]][row[1]] = {}
|
||||
volumekeys[row[0]][row[1]]["encryptedkey"] = base64.b64decode(row[2])
|
||||
volumekeys[row[0]][row[1]]["decryptedkey"] = enc.decrypt(volumekeys[row[0]][row[1]]["encryptedkey"])
|
||||
# get book name
|
||||
for key in volumekeys.keys():
|
||||
volumekeys[key]["title"] = dbase.execute("SELECT Title from content where ContentID = '%s'" % (key)).fetchone()[0]
|
||||
return volumekeys
|
||||
|
||||
def ByteArrayToString(bytearr):
|
||||
wincheck = re.match("@ByteArray\\((.+)\\)", bytearr)
|
||||
if wincheck:
|
||||
return wincheck.group(1)
|
||||
return bytearr
|
||||
|
||||
def GetUserHexKey(prefs = ""):
|
||||
"find wsuid and pwsdid"
|
||||
wsuid = ""
|
||||
pwsdid = ""
|
||||
if sys.platform.startswith('win'):
|
||||
regkey_browser = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Kobo\\Kobo Desktop Edition\\Browser")
|
||||
cookies = _winreg.QueryValueEx(regkey_browser, "cookies")
|
||||
bytearrays = cookies[0]
|
||||
elif sys.platform.startswith('darwin'):
|
||||
cookies = readPlist(prefs)
|
||||
bytearrays = cookies["Browser.cookies"]
|
||||
for bytearr in bytearrays:
|
||||
cookie = ByteArrayToString(bytearr)
|
||||
print cookie
|
||||
wsuidcheck = re.match("^wsuid=([0-9a-f-]+)", cookie)
|
||||
if(wsuidcheck):
|
||||
wsuid = wsuidcheck.group(1)
|
||||
pwsdidcheck = re.match("^pwsdid=([0-9a-f-]+)", cookie)
|
||||
if (pwsdidcheck):
|
||||
pwsdid = pwsdidcheck.group(1)
|
||||
|
||||
if(wsuid == "" or pwsdid == ""):
|
||||
print "wsuid or pwsdid key not found :/"
|
||||
exit()
|
||||
preuserkey = string.join((pwsdid, wsuid), "")
|
||||
print SHA256(pwsdid)
|
||||
userkey = SHA256(preuserkey)
|
||||
return userkey[32:]
|
||||
|
||||
# get dirs
|
||||
if sys.platform.startswith('win'):
|
||||
delim = "\\"
|
||||
if (sys.getwindowsversion().major > 5):
|
||||
kobodir = string.join((os.environ['LOCALAPPDATA'], "Kobo\\Kobo Desktop Edition"), delim)
|
||||
else:
|
||||
kobodir = string.join((os.environ['USERPROFILE'], "Local Settings\\Application Data\\Kobo\\Kobo Desktop Edition"), delim)
|
||||
prefs = ""
|
||||
elif sys.platform.startswith('darwin'):
|
||||
delim = "/"
|
||||
kobodir = string.join((os.environ['HOME'], "Library/Application Support/Kobo/Kobo Desktop Edition"), delim)
|
||||
prefs = string.join((os.environ['HOME'], "Library/Preferences/com.kobo.Kobo Desktop Edition.plist"), delim)
|
||||
sqlitefile = string.join((kobodir, "Kobo.sqlite"), delim)
|
||||
bookdir = string.join((kobodir, "kepub"), delim)
|
||||
|
||||
# get key
|
||||
userkeyhex = GetUserHexKey(prefs)
|
||||
# load into AES
|
||||
userkey = binascii.a2b_hex(userkeyhex)
|
||||
enc = AES.new(userkey, AES.MODE_ECB)
|
||||
|
||||
# open sqlite
|
||||
conn = sqlite3.connect(sqlitefile)
|
||||
dbcursor = conn.cursor()
|
||||
# get volume keys
|
||||
volumekeys = GetVolumeKeys(dbcursor, enc)
|
||||
|
||||
# choose a volumeID
|
||||
|
||||
volumeid = ""
|
||||
print "Choose a book to decrypt:"
|
||||
i = 1
|
||||
for key in volumekeys.keys():
|
||||
print "%d: %s" % (i, volumekeys[key]["title"])
|
||||
i += 1
|
||||
|
||||
num = input("...")
|
||||
|
||||
i = 1
|
||||
for key in volumekeys.keys():
|
||||
if(i == num):
|
||||
volumeid = key
|
||||
i += 1
|
||||
|
||||
if(volumeid == ""):
|
||||
exit()
|
||||
|
||||
zippath = string.join((bookdir, volumeid), delim)
|
||||
|
||||
z = zipfile.ZipFile(zippath, "r")
|
||||
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
||||
outname = "%s.epub" % (re.sub("[^\s\w]", "", volumekeys[volumeid]["title"], 0, re.UNICODE))
|
||||
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
||||
for filename in z.namelist():
|
||||
#print filename
|
||||
# read in and decrypt
|
||||
if(filename in volumekeys[volumeid]):
|
||||
# do decrypted version
|
||||
pagekey = volumekeys[volumeid][filename]["decryptedkey"]
|
||||
penc = AES.new(pagekey, AES.MODE_ECB)
|
||||
contents = RemoveAESPadding(penc.decrypt(z.read(filename)))
|
||||
# need to fix padding
|
||||
zout.writestr(filename, contents)
|
||||
else:
|
||||
zout.writestr(filename, z.read(filename))
|
||||
|
||||
print "Book saved as %s%s%s" % (os.getcwd(), delim, outname)
|
||||
@@ -1,7 +1,7 @@
|
||||
Welcome to the tools!
|
||||
=====================
|
||||
|
||||
This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v6.0.1 archive from Apprentice Alf's Blog: http://apprenticealf.wordpress.com/
|
||||
This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v6.1.0 archive from Apprentice Alf's Blog: http://apprenticealf.wordpress.com/
|
||||
|
||||
The is archive includes tools to remove DRM from:
|
||||
|
||||
@@ -18,8 +18,7 @@ These tools do NOT work with Apple's iBooks FairPlay DRM (see end of this file.)
|
||||
|
||||
About the tools
|
||||
---------------
|
||||
These tools have been updated and maintained by Apprentice Alf, DiapDealer and some_updates.
|
||||
You can find the latest updates and get support at Apprentice Alf's blog: http://www.apprenticealf.wordpress.com/
|
||||
These tools have been updated and maintained by Apprentice Alf, DiapDealer and some_updates. You can find the latest updates and get support at Apprentice Alf's blog: http://www.apprenticealf.wordpress.com/
|
||||
If you re-post these tools, a link to the blog would be appreciated.
|
||||
|
||||
The original inept and ignoble scripts were by i♥cabbages
|
||||
@@ -29,7 +28,7 @@ The original topaz format conversion scripts were by some_updates, clarknova and
|
||||
The Scuolabooks tool is by Hex
|
||||
The calibre plugin conversions were originally by DiapDealer
|
||||
|
||||
The DeDRM plugin is by Apprentice Alf
|
||||
The DeDRM plugin is by Apprentice Alf based on plugins by DiapDealer
|
||||
The DeDRM AppleScript application is by Apprentice Alf
|
||||
The DeDRM python GUI is by some_updates and Apprentice Alf
|
||||
|
||||
@@ -37,8 +36,8 @@ Many fixes, updates and enhancements to the scripts and applicatons have been by
|
||||
|
||||
|
||||
DeDRM plugin for calibre (Mac OS X, Windows, and Linux)
|
||||
--------------------------------------------
|
||||
If you already use calibre, the quickest and easiest way, especially on Windows, to remove DRM from your ebooks is to install the DeDRM plugin from the DeDRM_plugin folder, following the instructions and configuration directions provided in the ReadMe and the help links.
|
||||
-------------------------------------------------------
|
||||
If you already use calibre, the quickest and easiest way, especially on Windows, to remove DRM from your ebooks is to install the DeDRM plugin from the DeDRM_calibre_plugin folder, following the instructions and configuration directions provided in the ReadMe and the help links.
|
||||
|
||||
Once installed and configured, you can simply add a DRM book to calibre and the DeDRMed version will be imported into the calibre database. Note that DRM removal ONLY occurs on import. If you have already imported DRM books you'll need to remove them from calibre and re-import them.
|
||||
|
||||
@@ -46,12 +45,12 @@ These plugins work for Windows, Mac OS X and Linux. For ebooks from Kindle 4 PC
|
||||
|
||||
|
||||
DeDRM application for Mac OS X users: (Mac OS X 10.4 and above)
|
||||
----------------------------------------------------------------------
|
||||
This application combines all the tools into one easy-to-use tool for Mac OS X users.
|
||||
---------------------------------------------------------------
|
||||
This application is a stand-alone application for Mac OS X users.
|
||||
|
||||
Drag the "DeDRM.app" application from the DeDRM_Application_Macintosh folder to your Desktop (or your Applications Folder, or anywhere else you find convenient). Double-click on the application to run it and it will guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe.
|
||||
Drag the "DeDRM.app" application from the DeDRM_Macintosh_Application folder to your Desktop (or your Applications Folder, or anywhere else you find convenient). Double-click on the application to run it and you will be able to enter any configuration data needed to remove DRM from your type of DRMed ebooks.
|
||||
|
||||
To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM application and it will remove the DRM of the kinds listed above.
|
||||
To use the DeDRM application simply drag ebooks and/or folders containing ebooks onto the DeDRM application and it will remove the DRM and save DRM-free versions of the books wherever you selected in the Output Folder configuration dialog.
|
||||
|
||||
For more detailed instructions, see the "DeDRM ReadMe.rtf" file in the DeDRM_Application_Macintosh folder, including details of the extra step that Mac OS X 10.4 users need to take to use the application.
|
||||
|
||||
@@ -59,23 +58,23 @@ For more detailed instructions, see the "DeDRM ReadMe.rtf" file in the DeDRM_App
|
||||
DeDRM application for Windows users: (Windows XP through Windows 8)
|
||||
------------------------------------------------------------------
|
||||
***This program requires that Python and PyCrypto be properly installed.***
|
||||
***See below for details on recommended versions are where to get them.***
|
||||
***See below for details on recommended versions and how to install them.***
|
||||
|
||||
This application combines all the tools into one easy-to-use tool for Windows users.
|
||||
This application is a stand-alone application for Windows users.
|
||||
|
||||
Drag the DeDRM_App folder that's in the DeDRM_Application_Windows folder, to your "My Documents" folder (or anywhere else you find convenient). Make a short-cut on your Desktop of the DeDRM_Drop_Target.bat file that's in the DeDRM_App folder. Double-click on the shortcut and the DeDRM application will run and guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe.
|
||||
Drag the DeDRM_App folder that's in the DeDRM_Windows_Application folder, to your "My Documents" folder (or anywhere else you find convenient). Make a short-cut on your Desktop of the DeDRM_Drop_Target.bat file that's in the DeDRM_App folder. Double-click on the shortcut and the DeDRM application will run and you will be able to enter any configuration data needed to remove DRM from your type of DRMed ebooks.
|
||||
|
||||
To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM_Drop_Target.bat shortcut and it will remove the DRM of the kinds listed above.
|
||||
To use the DeDRM application simply drag ebooks or folders containing ebooks onto the DeDRM_Drop_Target.bat shortcut and it will remove the DRM and save DRM-free versions of the books wherever you selected in the Output Folder configuration dialog.
|
||||
|
||||
For more detailed instructions, see the DeDRM_Application_ReadMe.txt file in the DeDRM_Applications/Windows folder.
|
||||
For more detailed instructions, see the DeDRM_App_ReadMe.txt file in the DeDRM_Windows_Applications folder.
|
||||
|
||||
|
||||
Other_Tools
|
||||
-----------
|
||||
This folder includes other tools that may be useful for DRMed ebooks from certain sources or for Linux users. Most users won't need any of these tools.
|
||||
This is now a separate archive and includes other tools that may be useful for DRMed ebooks from certain sources or for Linux users. Most users won't need any of these tools.
|
||||
|
||||
Key_Generation_Scripts
|
||||
This folder contains python scripts that creates a keyfiles for Barnes and Noble ePubs, Adobe Digital Editions ePubs and Kindle for Mac/PC ebooks. The will mostly be useful for Linux users using the calibre plugin.
|
||||
This folder contains python scripts that creates a keyfiles for Barnes and Noble ePubs, Adobe Digital Editions ePubs and Kindle for Mac/PC ebooks.
|
||||
|
||||
Kindle_for_Android_Patches
|
||||
Definitely only for the adventurous, this folder contains information on how to modify the Kindel for Android app to b able to get a PID for use with the other Kindle tools (DeDRM apps and calibre plugin).
|
||||
@@ -86,6 +85,9 @@ A Javascript to enable a download button at the B&N website for ebooks that norm
|
||||
Scuolabook_DRM
|
||||
A windows-only application (including source code) for removing DRM from ScuolaBooks PDFs, created by "Hex" and included with permission.
|
||||
|
||||
Rocket_ebooks
|
||||
Information about the now-obsolete Rocket ebook format and DRM, along with source for a tool to remove the DRM.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user