mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-21 13:28:56 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2170b4260 | ||
|
|
054ddc894b | ||
|
|
8cd4be6fb0 | ||
|
|
d67e05cf04 | ||
|
|
a5197a6abb | ||
|
|
cfc13db6c5 | ||
|
|
8aa2157d55 | ||
|
|
81b08dcf05 | ||
|
|
ca42e028a7 | ||
|
|
10963f6011 | ||
|
|
72968d2124 | ||
|
|
3e95168972 | ||
|
|
ecf1d76d90 | ||
|
|
e59f0f346d | ||
|
|
b6046d3f4b | ||
|
|
a863a4856a | ||
|
|
a13d08c3bc | ||
|
|
9434751a72 | ||
|
|
fc156852a4 | ||
|
|
0c67fd43a2 |
Binary file not shown.
@@ -24,7 +24,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>droplet</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>DeDRM AppleScript 6.3.4 Written 2010–2015 by Apprentice Alf et al.</string>
|
||||
<string>DeDRM AppleScript 6.4.1 Written 2010–2016 by Apprentice Alf et al.</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>DeDRM</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -36,13 +36,13 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>6.3.4</string>
|
||||
<string>6.4.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>dplt</string>
|
||||
<key>LSRequiresCarbon</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2010–2015 Apprentice Alf and Apprentice Harper</string>
|
||||
<string>Copyright © 2010–2016 Apprentice Alf and Apprentice Harper</string>
|
||||
<key>WindowState</key>
|
||||
<dict>
|
||||
<key>bundleDividerCollapsed</key>
|
||||
|
||||
@@ -9,8 +9,6 @@ __docformat__ = 'restructuredtext en'
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
#
|
||||
# Requires Calibre version 0.7.55 or higher.
|
||||
#
|
||||
# All credit given to i♥cabbages and The Dark Reverser for the original standalone scripts.
|
||||
# We had the much easier job of converting them to a calibre plugin.
|
||||
#
|
||||
@@ -46,6 +44,10 @@ __docformat__ = 'restructuredtext en'
|
||||
# 6.3.2 - Fixed Kindle for Android help file
|
||||
# 6.3.3 - Bug fix for Kindle for PC support
|
||||
# 6.3.4 - Fixes for Kindle for Android, Linux, and Kobo 3.17
|
||||
# 6.3.5 - Fixes for Linux, and Kobo 3.19 and more logging
|
||||
# 6.3.6 - Fixes for ADE ePub and PDF introduced in 6.3.5
|
||||
# 6.4.0 - Updated for new Kindle for PC encryption
|
||||
# 6.4.1 - Fix for some new tags in Topaz ebooks.
|
||||
|
||||
|
||||
"""
|
||||
@@ -53,7 +55,7 @@ Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 3, 4)
|
||||
PLUGIN_VERSION_TUPLE = (6, 4, 1)
|
||||
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'
|
||||
@@ -96,7 +98,7 @@ class DeDRM(FileTypePlugin):
|
||||
supported_platforms = ['linux', 'osx', 'windows']
|
||||
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||
version = PLUGIN_VERSION_TUPLE
|
||||
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
|
||||
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||
on_import = True
|
||||
priority = 600
|
||||
@@ -296,11 +298,15 @@ class DeDRM(FileTypePlugin):
|
||||
traceback.print_exc()
|
||||
result = 1
|
||||
|
||||
of.close()
|
||||
try:
|
||||
of.close()
|
||||
except:
|
||||
print u"{0} v{1}: Exception closing temporary file after {2:.1f} seconds. Ignored.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
if result == 0:
|
||||
# Decryption was successful.
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
print u"{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||
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)
|
||||
@@ -360,6 +366,7 @@ class DeDRM(FileTypePlugin):
|
||||
except:
|
||||
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
traceback.print_exc()
|
||||
print u"{0} v{1}: Decrypted with new default key after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ class PageParser(object):
|
||||
self.id = os.path.basename(filename).replace('.dat','')
|
||||
self.dict = dict
|
||||
self.debug = debug
|
||||
self.first_unknown = True
|
||||
self.flat_xml = flat_xml
|
||||
self.tagpath = []
|
||||
self.doc = []
|
||||
@@ -262,6 +263,9 @@ class PageParser(object):
|
||||
'img.w' : (1, 'scalar_number', 0, 0),
|
||||
'img.src' : (1, 'scalar_number', 0, 0),
|
||||
'img.color_src' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.image_type' : (1, 'scalar_number', 0, 0),
|
||||
@@ -506,8 +510,9 @@ class PageParser(object):
|
||||
# or an out of sync condition
|
||||
else:
|
||||
result = []
|
||||
if (self.debug):
|
||||
if (self.debug or self.first_unknown):
|
||||
print 'Unknown Token:', token
|
||||
self.first_unknown = False
|
||||
self.tag_pop()
|
||||
return result
|
||||
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 6.1
|
||||
# ineptepub.pyw, version 6.5
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015–2016 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.6
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||
# install the version for Python 2.6). Save this script file as
|
||||
# install the version for Python 2.7). Save this script file as
|
||||
# ineptepub.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
|
||||
@@ -38,13 +39,16 @@ from __future__ import with_statement
|
||||
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.1 - Work if TkInter is missing
|
||||
# 6.2 - Handle UTF-8 file names inside an ePub, fix by Jose Luis
|
||||
# 6.3 - Add additional check on DER file sanity
|
||||
# 6.4 - Remove erroneous check on DER file sanity
|
||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "6.2"
|
||||
__version__ = "6.5"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptpdf.pyw, version 7.11
|
||||
# ineptpdf.pyw, version 8.0.4
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015-2016 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.6
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||
# install the version for Python 2.6). Save this script file as
|
||||
# install the version for Python 2.7). Save this script file as
|
||||
# ineptpdf.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this
|
||||
@@ -53,13 +54,17 @@ from __future__ import with_statement
|
||||
# 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.
|
||||
# 8.0.2 - Add additional check on DER file sanity
|
||||
# 8.0.3 - Remove erroneous check on DER file sanity
|
||||
# 8.0.4 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
|
||||
"""
|
||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "8.0.1"
|
||||
__version__ = "8.0.4"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -199,9 +199,6 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
|
||||
# Get the kindle account token
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
|
||||
# Get the IDString used to decode the Kindle Info file
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
@@ -212,6 +209,14 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
return pids
|
||||
|
||||
try:
|
||||
# Get the kindle account token, if present
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
|
||||
except KeyError:
|
||||
kindleAccountToken=""
|
||||
pass
|
||||
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(IDString,charMap1)
|
||||
|
||||
|
||||
@@ -993,7 +993,22 @@ if iswindows:
|
||||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF']
|
||||
names = [\
|
||||
'kindle.account.tokens',\
|
||||
'kindle.cookie.item',\
|
||||
'eulaVersionAccepted',\
|
||||
'login_date',\
|
||||
'kindle.token.item',\
|
||||
'login',\
|
||||
'kindle.key.item',\
|
||||
'kindle.name.info',\
|
||||
'kindle.device.info',\
|
||||
'MazamaRandomNumber',\
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
]
|
||||
|
||||
DB = {}
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
hdr = infoReader.read(1)
|
||||
@@ -1134,6 +1149,8 @@ if iswindows:
|
||||
if encodeHash(name,testMap8) == keyhash:
|
||||
keyname = name
|
||||
break
|
||||
if keyname == "unknown":
|
||||
keyname = keyhash
|
||||
|
||||
# the testMap8 encoded contents data has had a length
|
||||
# of chars (always odd) cut off of the front and moved
|
||||
@@ -1158,14 +1175,17 @@ if iswindows:
|
||||
# decode using new testMap8 to get the original CryptProtect Data
|
||||
encryptedValue = decode(encdata,testMap8)
|
||||
cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||
DB[keyname] = cleartext
|
||||
if len(cleartext)>0:
|
||||
DB[keyname] = cleartext
|
||||
#print keyname, cleartext
|
||||
|
||||
if 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))
|
||||
else:
|
||||
print u"Couldn't decrypt file."
|
||||
DB = {}
|
||||
return DB
|
||||
elif isosx:
|
||||
@@ -1577,7 +1597,21 @@ elif isosx:
|
||||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF']
|
||||
names = [\
|
||||
'kindle.account.tokens',\
|
||||
'kindle.cookie.item',\
|
||||
'eulaVersionAccepted',\
|
||||
'login_date',\
|
||||
'kindle.token.item',\
|
||||
'login',\
|
||||
'kindle.key.item',\
|
||||
'kindle.name.info',\
|
||||
'kindle.device.info',\
|
||||
'MazamaRandomNumber',\
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
]
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
filehdr = infoReader.read(1)
|
||||
filedata = infoReader.read()
|
||||
@@ -1683,7 +1717,7 @@ elif isosx:
|
||||
if len(cleartext) > 0:
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
break
|
||||
else:
|
||||
# the latest .kinf2011 version for K4M 1.9.1
|
||||
@@ -1772,11 +1806,11 @@ elif isosx:
|
||||
if len(cleartext) > 0:
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
# store values used in decryption
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())
|
||||
DB['IDString'] = IDString
|
||||
|
||||
@@ -448,6 +448,8 @@ class MobiBook:
|
||||
goodpids.append(pid[0:-2])
|
||||
elif len(pid)==8:
|
||||
goodpids.append(pid)
|
||||
else:
|
||||
print u"Warning: PID {0} has wrong number of digits".format(pid)
|
||||
|
||||
if self.crypto_type == 1:
|
||||
t1_keyvec = 'QDCVEPMU675RUBSZ'
|
||||
@@ -530,7 +532,7 @@ def cli_main():
|
||||
stripped_file = getUnencryptedBook(infile, pidlist)
|
||||
file(outfile, 'wb').write(stripped_file)
|
||||
except DrmException, e:
|
||||
print u"MobiDeDRM v{0} Error: {0:s}".format(__version__,e.args[0])
|
||||
print u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0])
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||
if not os.path.exists(outdirpath):
|
||||
os.makedirs(outdirpath)
|
||||
|
||||
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
||||
if wineprefix != "" and os.path.exists(wineprefix):
|
||||
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||
else:
|
||||
@@ -38,8 +39,20 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||
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 []
|
||||
if wineprefix != "" and os.path.exists(wineprefix):
|
||||
cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||
else:
|
||||
cmdline = u"wine C:\\Python27\\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])
|
||||
|
||||
# try finding winekeys anyway, even if above code errored
|
||||
winekeys = []
|
||||
# get any files with extension in the output dir
|
||||
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# DeDRM.pyw
|
||||
# Copyright 2010-2015 some_updates, Apprentice Alf and Apprentice Harper
|
||||
# Copyright 2010-2016 some_updates, Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Revision history:
|
||||
# 6.0.0 - Release along with unified plugin
|
||||
@@ -19,8 +19,12 @@
|
||||
# 6.3.2 - Version bump to match plugin
|
||||
# 6.3.3 - Version bump to match plugin
|
||||
# 6.3.4 - Version bump to match plugin
|
||||
# 6.3.5 - Version bump to match plugin
|
||||
# 6.3.6 - Version bump to match plugin
|
||||
# 6.4.0 - Fix for Kindle for PC encryption change
|
||||
# 6.4.1 - Fix for new tags in Topaz ebooks
|
||||
|
||||
__version__ = '6.3.4'
|
||||
__version__ = '6.4.1'
|
||||
|
||||
import sys
|
||||
import os, os.path
|
||||
|
||||
@@ -9,8 +9,6 @@ __docformat__ = 'restructuredtext en'
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
#
|
||||
# Requires Calibre version 0.7.55 or higher.
|
||||
#
|
||||
# All credit given to i♥cabbages and The Dark Reverser for the original standalone scripts.
|
||||
# We had the much easier job of converting them to a calibre plugin.
|
||||
#
|
||||
@@ -46,6 +44,10 @@ __docformat__ = 'restructuredtext en'
|
||||
# 6.3.2 - Fixed Kindle for Android help file
|
||||
# 6.3.3 - Bug fix for Kindle for PC support
|
||||
# 6.3.4 - Fixes for Kindle for Android, Linux, and Kobo 3.17
|
||||
# 6.3.5 - Fixes for Linux, and Kobo 3.19 and more logging
|
||||
# 6.3.6 - Fixes for ADE ePub and PDF introduced in 6.3.5
|
||||
# 6.4.0 - Updated for new Kindle for PC encryption
|
||||
# 6.4.1 - Fix for some new tags in Topaz ebooks.
|
||||
|
||||
|
||||
"""
|
||||
@@ -53,7 +55,7 @@ Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 3, 4)
|
||||
PLUGIN_VERSION_TUPLE = (6, 4, 1)
|
||||
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'
|
||||
@@ -96,7 +98,7 @@ class DeDRM(FileTypePlugin):
|
||||
supported_platforms = ['linux', 'osx', 'windows']
|
||||
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||
version = PLUGIN_VERSION_TUPLE
|
||||
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
|
||||
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||
on_import = True
|
||||
priority = 600
|
||||
@@ -296,11 +298,15 @@ class DeDRM(FileTypePlugin):
|
||||
traceback.print_exc()
|
||||
result = 1
|
||||
|
||||
of.close()
|
||||
try:
|
||||
of.close()
|
||||
except:
|
||||
print u"{0} v{1}: Exception closing temporary file after {2:.1f} seconds. Ignored.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
if result == 0:
|
||||
# Decryption was successful.
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
print u"{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||
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)
|
||||
@@ -360,6 +366,7 @@ class DeDRM(FileTypePlugin):
|
||||
except:
|
||||
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
traceback.print_exc()
|
||||
print u"{0} v{1}: Decrypted with new default key after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ class PageParser(object):
|
||||
self.id = os.path.basename(filename).replace('.dat','')
|
||||
self.dict = dict
|
||||
self.debug = debug
|
||||
self.first_unknown = True
|
||||
self.flat_xml = flat_xml
|
||||
self.tagpath = []
|
||||
self.doc = []
|
||||
@@ -262,6 +263,9 @@ class PageParser(object):
|
||||
'img.w' : (1, 'scalar_number', 0, 0),
|
||||
'img.src' : (1, 'scalar_number', 0, 0),
|
||||
'img.color_src' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.image_type' : (1, 'scalar_number', 0, 0),
|
||||
@@ -506,8 +510,9 @@ class PageParser(object):
|
||||
# or an out of sync condition
|
||||
else:
|
||||
result = []
|
||||
if (self.debug):
|
||||
if (self.debug or self.first_unknown):
|
||||
print 'Unknown Token:', token
|
||||
self.first_unknown = False
|
||||
self.tag_pop()
|
||||
return result
|
||||
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 6.1
|
||||
# ineptepub.pyw, version 6.5
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015–2016 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.6
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||
# install the version for Python 2.6). Save this script file as
|
||||
# install the version for Python 2.7). Save this script file as
|
||||
# ineptepub.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
|
||||
@@ -38,13 +39,16 @@ from __future__ import with_statement
|
||||
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.1 - Work if TkInter is missing
|
||||
# 6.2 - Handle UTF-8 file names inside an ePub, fix by Jose Luis
|
||||
# 6.3 - Add additional check on DER file sanity
|
||||
# 6.4 - Remove erroneous check on DER file sanity
|
||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "6.2"
|
||||
__version__ = "6.5"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptpdf.pyw, version 7.11
|
||||
# ineptpdf.pyw, version 8.0.4
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015-2016 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.6
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||
# install the version for Python 2.6). Save this script file as
|
||||
# install the version for Python 2.7). Save this script file as
|
||||
# ineptpdf.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this
|
||||
@@ -53,13 +54,17 @@ from __future__ import with_statement
|
||||
# 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.
|
||||
# 8.0.2 - Add additional check on DER file sanity
|
||||
# 8.0.3 - Remove erroneous check on DER file sanity
|
||||
# 8.0.4 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
|
||||
"""
|
||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "8.0.1"
|
||||
__version__ = "8.0.4"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -199,9 +199,6 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
|
||||
# Get the kindle account token
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
|
||||
# Get the IDString used to decode the Kindle Info file
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
@@ -212,6 +209,14 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
return pids
|
||||
|
||||
try:
|
||||
# Get the kindle account token, if present
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
|
||||
except KeyError:
|
||||
kindleAccountToken=""
|
||||
pass
|
||||
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(IDString,charMap1)
|
||||
|
||||
|
||||
@@ -993,7 +993,22 @@ if iswindows:
|
||||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF']
|
||||
names = [\
|
||||
'kindle.account.tokens',\
|
||||
'kindle.cookie.item',\
|
||||
'eulaVersionAccepted',\
|
||||
'login_date',\
|
||||
'kindle.token.item',\
|
||||
'login',\
|
||||
'kindle.key.item',\
|
||||
'kindle.name.info',\
|
||||
'kindle.device.info',\
|
||||
'MazamaRandomNumber',\
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
]
|
||||
|
||||
DB = {}
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
hdr = infoReader.read(1)
|
||||
@@ -1134,6 +1149,8 @@ if iswindows:
|
||||
if encodeHash(name,testMap8) == keyhash:
|
||||
keyname = name
|
||||
break
|
||||
if keyname == "unknown":
|
||||
keyname = keyhash
|
||||
|
||||
# the testMap8 encoded contents data has had a length
|
||||
# of chars (always odd) cut off of the front and moved
|
||||
@@ -1158,14 +1175,17 @@ if iswindows:
|
||||
# decode using new testMap8 to get the original CryptProtect Data
|
||||
encryptedValue = decode(encdata,testMap8)
|
||||
cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||
DB[keyname] = cleartext
|
||||
if len(cleartext)>0:
|
||||
DB[keyname] = cleartext
|
||||
#print keyname, cleartext
|
||||
|
||||
if 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))
|
||||
else:
|
||||
print u"Couldn't decrypt file."
|
||||
DB = {}
|
||||
return DB
|
||||
elif isosx:
|
||||
@@ -1577,7 +1597,21 @@ elif isosx:
|
||||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF']
|
||||
names = [\
|
||||
'kindle.account.tokens',\
|
||||
'kindle.cookie.item',\
|
||||
'eulaVersionAccepted',\
|
||||
'login_date',\
|
||||
'kindle.token.item',\
|
||||
'login',\
|
||||
'kindle.key.item',\
|
||||
'kindle.name.info',\
|
||||
'kindle.device.info',\
|
||||
'MazamaRandomNumber',\
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
]
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
filehdr = infoReader.read(1)
|
||||
filedata = infoReader.read()
|
||||
@@ -1683,7 +1717,7 @@ elif isosx:
|
||||
if len(cleartext) > 0:
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
break
|
||||
else:
|
||||
# the latest .kinf2011 version for K4M 1.9.1
|
||||
@@ -1772,11 +1806,11 @@ elif isosx:
|
||||
if len(cleartext) > 0:
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
# store values used in decryption
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())
|
||||
DB['IDString'] = IDString
|
||||
|
||||
@@ -448,6 +448,8 @@ class MobiBook:
|
||||
goodpids.append(pid[0:-2])
|
||||
elif len(pid)==8:
|
||||
goodpids.append(pid)
|
||||
else:
|
||||
print u"Warning: PID {0} has wrong number of digits".format(pid)
|
||||
|
||||
if self.crypto_type == 1:
|
||||
t1_keyvec = 'QDCVEPMU675RUBSZ'
|
||||
@@ -530,7 +532,7 @@ def cli_main():
|
||||
stripped_file = getUnencryptedBook(infile, pidlist)
|
||||
file(outfile, 'wb').write(stripped_file)
|
||||
except DrmException, e:
|
||||
print u"MobiDeDRM v{0} Error: {0:s}".format(__version__,e.args[0])
|
||||
print u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0])
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||
if not os.path.exists(outdirpath):
|
||||
os.makedirs(outdirpath)
|
||||
|
||||
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
||||
if wineprefix != "" and os.path.exists(wineprefix):
|
||||
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||
else:
|
||||
@@ -38,8 +39,20 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||
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 []
|
||||
if wineprefix != "" and os.path.exists(wineprefix):
|
||||
cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||
else:
|
||||
cmdline = u"wine C:\\Python27\\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])
|
||||
|
||||
# try finding winekeys anyway, even if above code errored
|
||||
winekeys = []
|
||||
# get any files with extension in the output dir
|
||||
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
||||
|
||||
Binary file not shown.
@@ -9,8 +9,6 @@ __docformat__ = 'restructuredtext en'
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
#
|
||||
# Requires Calibre version 0.7.55 or higher.
|
||||
#
|
||||
# All credit given to i♥cabbages and The Dark Reverser for the original standalone scripts.
|
||||
# We had the much easier job of converting them to a calibre plugin.
|
||||
#
|
||||
@@ -46,6 +44,10 @@ __docformat__ = 'restructuredtext en'
|
||||
# 6.3.2 - Fixed Kindle for Android help file
|
||||
# 6.3.3 - Bug fix for Kindle for PC support
|
||||
# 6.3.4 - Fixes for Kindle for Android, Linux, and Kobo 3.17
|
||||
# 6.3.5 - Fixes for Linux, and Kobo 3.19 and more logging
|
||||
# 6.3.6 - Fixes for ADE ePub and PDF introduced in 6.3.5
|
||||
# 6.4.0 - Updated for new Kindle for PC encryption
|
||||
# 6.4.1 - Fix for some new tags in Topaz ebooks.
|
||||
|
||||
|
||||
"""
|
||||
@@ -53,7 +55,7 @@ Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 3, 4)
|
||||
PLUGIN_VERSION_TUPLE = (6, 4, 1)
|
||||
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'
|
||||
@@ -96,7 +98,7 @@ class DeDRM(FileTypePlugin):
|
||||
supported_platforms = ['linux', 'osx', 'windows']
|
||||
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||
version = PLUGIN_VERSION_TUPLE
|
||||
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
|
||||
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||
on_import = True
|
||||
priority = 600
|
||||
@@ -296,11 +298,15 @@ class DeDRM(FileTypePlugin):
|
||||
traceback.print_exc()
|
||||
result = 1
|
||||
|
||||
of.close()
|
||||
try:
|
||||
of.close()
|
||||
except:
|
||||
print u"{0} v{1}: Exception closing temporary file after {2:.1f} seconds. Ignored.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
|
||||
if result == 0:
|
||||
# Decryption was successful.
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
print u"{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||
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)
|
||||
@@ -360,6 +366,7 @@ class DeDRM(FileTypePlugin):
|
||||
except:
|
||||
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||
traceback.print_exc()
|
||||
print u"{0} v{1}: Decrypted with new default key after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
# Return the modified PersistentTemporary file to calibre.
|
||||
return of.name
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ class PageParser(object):
|
||||
self.id = os.path.basename(filename).replace('.dat','')
|
||||
self.dict = dict
|
||||
self.debug = debug
|
||||
self.first_unknown = True
|
||||
self.flat_xml = flat_xml
|
||||
self.tagpath = []
|
||||
self.doc = []
|
||||
@@ -262,6 +263,9 @@ class PageParser(object):
|
||||
'img.w' : (1, 'scalar_number', 0, 0),
|
||||
'img.src' : (1, 'scalar_number', 0, 0),
|
||||
'img.color_src' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridSize' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||
'img.image_type' : (1, 'scalar_number', 0, 0),
|
||||
@@ -506,8 +510,9 @@ class PageParser(object):
|
||||
# or an out of sync condition
|
||||
else:
|
||||
result = []
|
||||
if (self.debug):
|
||||
if (self.debug or self.first_unknown):
|
||||
print 'Unknown Token:', token
|
||||
self.first_unknown = False
|
||||
self.tag_pop()
|
||||
return result
|
||||
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptepub.pyw, version 6.1
|
||||
# ineptepub.pyw, version 6.5
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015–2016 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.6
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||
# install the version for Python 2.6). Save this script file as
|
||||
# install the version for Python 2.7). Save this script file as
|
||||
# ineptepub.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
|
||||
@@ -38,13 +39,16 @@ from __future__ import with_statement
|
||||
# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||
# 6.1 - Work if TkInter is missing
|
||||
# 6.2 - Handle UTF-8 file names inside an ePub, fix by Jose Luis
|
||||
# 6.3 - Add additional check on DER file sanity
|
||||
# 6.4 - Remove erroneous check on DER file sanity
|
||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "6.2"
|
||||
__version__ = "6.5"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ineptpdf.pyw, version 7.11
|
||||
# ineptpdf.pyw, version 8.0.4
|
||||
# Copyright © 2009-2010 by i♥cabbages
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||
# Modified 2015-2016 by Apprentice Harper
|
||||
|
||||
# Windows users: Before running this program, you must first install Python 2.6
|
||||
# Windows users: Before running this program, you must first install Python 2.7
|
||||
# from <http://www.python.org/download/> and PyCrypto from
|
||||
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||
# install the version for Python 2.6). Save this script file as
|
||||
# install the version for Python 2.7). Save this script file as
|
||||
# ineptpdf.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this
|
||||
@@ -53,13 +54,17 @@ from __future__ import with_statement
|
||||
# 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.
|
||||
# 8.0.2 - Add additional check on DER file sanity
|
||||
# 8.0.3 - Remove erroneous check on DER file sanity
|
||||
# 8.0.4 - Completely remove erroneous check on DER file sanity
|
||||
|
||||
|
||||
"""
|
||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "8.0.1"
|
||||
__version__ = "8.0.4"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -199,9 +199,6 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
|
||||
# Get the kindle account token
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
|
||||
# Get the IDString used to decode the Kindle Info file
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
@@ -212,6 +209,14 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
return pids
|
||||
|
||||
try:
|
||||
# Get the kindle account token, if present
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
|
||||
except KeyError:
|
||||
kindleAccountToken=""
|
||||
pass
|
||||
|
||||
# Get the ID string used
|
||||
encodedIDString = encodeHash(IDString,charMap1)
|
||||
|
||||
|
||||
@@ -993,7 +993,22 @@ if iswindows:
|
||||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF']
|
||||
names = [\
|
||||
'kindle.account.tokens',\
|
||||
'kindle.cookie.item',\
|
||||
'eulaVersionAccepted',\
|
||||
'login_date',\
|
||||
'kindle.token.item',\
|
||||
'login',\
|
||||
'kindle.key.item',\
|
||||
'kindle.name.info',\
|
||||
'kindle.device.info',\
|
||||
'MazamaRandomNumber',\
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
]
|
||||
|
||||
DB = {}
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
hdr = infoReader.read(1)
|
||||
@@ -1134,6 +1149,8 @@ if iswindows:
|
||||
if encodeHash(name,testMap8) == keyhash:
|
||||
keyname = name
|
||||
break
|
||||
if keyname == "unknown":
|
||||
keyname = keyhash
|
||||
|
||||
# the testMap8 encoded contents data has had a length
|
||||
# of chars (always odd) cut off of the front and moved
|
||||
@@ -1158,14 +1175,17 @@ if iswindows:
|
||||
# decode using new testMap8 to get the original CryptProtect Data
|
||||
encryptedValue = decode(encdata,testMap8)
|
||||
cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||
DB[keyname] = cleartext
|
||||
if len(cleartext)>0:
|
||||
DB[keyname] = cleartext
|
||||
#print keyname, cleartext
|
||||
|
||||
if 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))
|
||||
else:
|
||||
print u"Couldn't decrypt file."
|
||||
DB = {}
|
||||
return DB
|
||||
elif isosx:
|
||||
@@ -1577,7 +1597,21 @@ elif isosx:
|
||||
# determine type of kindle info provided and return a
|
||||
# database of keynames and values
|
||||
def getDBfromFile(kInfoFile):
|
||||
names = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber', 'max_date', 'SIGVERIF']
|
||||
names = [\
|
||||
'kindle.account.tokens',\
|
||||
'kindle.cookie.item',\
|
||||
'eulaVersionAccepted',\
|
||||
'login_date',\
|
||||
'kindle.token.item',\
|
||||
'login',\
|
||||
'kindle.key.item',\
|
||||
'kindle.name.info',\
|
||||
'kindle.device.info',\
|
||||
'MazamaRandomNumber',\
|
||||
'max_date',\
|
||||
'SIGVERIF',\
|
||||
'build_version',\
|
||||
]
|
||||
with open(kInfoFile, 'rb') as infoReader:
|
||||
filehdr = infoReader.read(1)
|
||||
filedata = infoReader.read()
|
||||
@@ -1683,7 +1717,7 @@ elif isosx:
|
||||
if len(cleartext) > 0:
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
break
|
||||
else:
|
||||
# the latest .kinf2011 version for K4M 1.9.1
|
||||
@@ -1772,11 +1806,11 @@ elif isosx:
|
||||
if len(cleartext) > 0:
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'MazamaRandomNumber' in DB and 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if 'kindle.account.tokens' in DB:
|
||||
if len(DB)>4:
|
||||
# store values used in decryption
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())
|
||||
DB['IDString'] = IDString
|
||||
|
||||
@@ -448,6 +448,8 @@ class MobiBook:
|
||||
goodpids.append(pid[0:-2])
|
||||
elif len(pid)==8:
|
||||
goodpids.append(pid)
|
||||
else:
|
||||
print u"Warning: PID {0} has wrong number of digits".format(pid)
|
||||
|
||||
if self.crypto_type == 1:
|
||||
t1_keyvec = 'QDCVEPMU675RUBSZ'
|
||||
@@ -530,7 +532,7 @@ def cli_main():
|
||||
stripped_file = getUnencryptedBook(infile, pidlist)
|
||||
file(outfile, 'wb').write(stripped_file)
|
||||
except DrmException, e:
|
||||
print u"MobiDeDRM v{0} Error: {0:s}".format(__version__,e.args[0])
|
||||
print u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0])
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
@@ -39,8 +39,20 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||
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 []
|
||||
if wineprefix != "" and os.path.exists(wineprefix):
|
||||
cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||
else:
|
||||
cmdline = u"wine C:\\Python27\\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])
|
||||
|
||||
# try finding winekeys anyway, even if above code errored
|
||||
winekeys = []
|
||||
# get any files with extension in the output dir
|
||||
files = [f for f in os.listdir(outdirpath) if f.endswith(extension)]
|
||||
|
||||
Binary file not shown.
@@ -19,7 +19,7 @@ except NameError:
|
||||
PLUGIN_NAME = 'Obok DeDRM'
|
||||
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
|
||||
PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.')
|
||||
PLUGIN_VERSION_TUPLE = (3, 1, 6)
|
||||
PLUGIN_VERSION_TUPLE = (6, 3, 6)
|
||||
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
|
||||
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
|
||||
PLUGIN_AUTHORS = 'Anon'
|
||||
|
||||
@@ -34,15 +34,6 @@ from calibre_plugins.obok_dedrm.utilities import (
|
||||
from calibre_plugins.obok_dedrm.obok.obok import KoboLibrary
|
||||
from calibre_plugins.obok_dedrm.obok.legacy_obok import legacy_obok
|
||||
|
||||
can_parse_xml = True
|
||||
try:
|
||||
from xml.etree import ElementTree as ET
|
||||
debug_print("using xml.etree for xml parsing")
|
||||
except ImportError:
|
||||
can_parse_xml = False
|
||||
debug_print("Cannot find xml.etree, disabling extraction of serial numbers")
|
||||
|
||||
|
||||
PLUGIN_ICONS = ['images/obok.png']
|
||||
|
||||
try:
|
||||
@@ -90,28 +81,16 @@ class InterfacePluginAction(InterfaceAction):
|
||||
#
|
||||
# search for connected device in case serials are saved
|
||||
tmpserials = cfg['kobo_serials']
|
||||
device = self.parent().device_manager.connected_device
|
||||
device_path = None
|
||||
if (device):
|
||||
device_path = device._main_prefix
|
||||
debug_print("get_device_settings - device_path=", device_path)
|
||||
# get serial from device_path/.adobe-digital-editions/device.xml
|
||||
if can_parse_xml:
|
||||
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
|
||||
debug_print("trying to load %s" % devicexml)
|
||||
if (os.path.exists(devicexml)):
|
||||
debug_print("trying to parse %s" % devicexml)
|
||||
xmltree = ET.parse(devicexml)
|
||||
for node in xmltree.iter():
|
||||
if "deviceSerial" in node.tag:
|
||||
serial = node.text
|
||||
debug_print ("found serial %s" % serial)
|
||||
tmpserials.append(serial)
|
||||
break
|
||||
|
||||
|
||||
else:
|
||||
debug_print("didn't find device")
|
||||
try:
|
||||
device = self.parent().device_manager.connected_device
|
||||
if (device):
|
||||
device_path = device._main_prefix
|
||||
debug_print("get_device_settings - device_path=", device_path)
|
||||
else:
|
||||
debug_print("didn't find device")
|
||||
except:
|
||||
debug_print("Exception getting device path. Probably not an E-Ink Kobo device")
|
||||
|
||||
# Get the Kobo Library object (obok v3.01)
|
||||
self.library = KoboLibrary(tmpserials, device_path)
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Version 3.2.0 January 2016
|
||||
# Update for latest version of Windows Desktop app.
|
||||
# Support Kobo devices in the command line version.
|
||||
#
|
||||
# Version 3.1.9 November 2015
|
||||
# Handle Kobo Desktop under wine on Linux
|
||||
#
|
||||
# Version 3.1.8 November 2015
|
||||
# Handle the case of Kobo Arc or Vox device (i.e. don't crash).
|
||||
#
|
||||
# Version 3.1.7 October 2015
|
||||
# Handle the case of no device or database more gracefully.
|
||||
#
|
||||
# Version 3.1.6 September 2015
|
||||
# Enable support for Kobo devices
|
||||
# More character encoding fixes (unicode strings)
|
||||
@@ -123,7 +136,8 @@
|
||||
#
|
||||
"""Manage all Kobo books, either encrypted or DRM-free."""
|
||||
|
||||
__version__ = '3.1.6'
|
||||
__version__ = '3.1.9'
|
||||
__about__ = u"Obok v{0}\nCopyright © 2012-2015 Physisticated et al.".format(__version__)
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -137,6 +151,18 @@ import hashlib
|
||||
import xml.etree.ElementTree as ET
|
||||
import string
|
||||
import shutil
|
||||
import argparse
|
||||
|
||||
can_parse_xml = True
|
||||
try:
|
||||
from xml.etree import ElementTree as ET
|
||||
# print u"using xml.etree for xml parsing"
|
||||
except ImportError:
|
||||
can_parse_xml = False
|
||||
# print u"Cannot find xml.etree, disabling extraction of serial numbers"
|
||||
|
||||
# List of all known hash keys
|
||||
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook']
|
||||
|
||||
class ENCRYPTIONError(Exception):
|
||||
pass
|
||||
@@ -249,24 +275,59 @@ class KoboLibrary(object):
|
||||
of books, their titles, and the user's encryption key(s)."""
|
||||
|
||||
def __init__ (self, serials = [], device_path = None):
|
||||
print u"Obok v{0}\nCopyright © 2012-2015 Physisticated et al.".format(__version__)
|
||||
print __about__
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
# - first check whether serials have been found or are provided
|
||||
# and a device is connected. In this case, use the device
|
||||
# - otherwise fall back to Kobo Desktop Application for Windows and Mac
|
||||
if (device_path and (len(serials) > 0)):
|
||||
# Order of checks
|
||||
# 1. first check if a device_path has been passed in, and whether
|
||||
# we can find the sqlite db in the respective place
|
||||
# 2. if 1., and we got some serials passed in (from saved
|
||||
# settings in calibre), just use it
|
||||
# 3. if 1. worked, but we didn't get serials, try to parse them
|
||||
# from the device, if this didn't work, unset everything
|
||||
# 4. if by now we don't have kobodir set, give up on device and
|
||||
# try to use the Desktop app.
|
||||
|
||||
# step 1. check whether this looks like a real device
|
||||
if (device_path):
|
||||
# we got a device path
|
||||
self.kobodir = os.path.join(device_path, u".kobo")
|
||||
# devices use KoboReader.sqlite
|
||||
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite")
|
||||
if (not(os.path.exists(kobodb))):
|
||||
# give up here, we haven't found anything useful
|
||||
if (not(os.path.isfile(kobodb))):
|
||||
# device path seems to be wrong, unset it
|
||||
device_path = u""
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
if (self.kobodir):
|
||||
# step 3. we found a device but didn't get serials, try to get them
|
||||
if (len(serials) == 0):
|
||||
# we got a device path but no saved serial
|
||||
# try to get the serial from the device
|
||||
# print u"get_device_settings - device_path = {0}".format(device_path)
|
||||
# get serial from device_path/.adobe-digital-editions/device.xml
|
||||
if can_parse_xml:
|
||||
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
|
||||
# print u"trying to load {0}".format(devicexml)
|
||||
if (os.path.exists(devicexml)):
|
||||
# print u"trying to parse {0}".format(devicexml)
|
||||
xmltree = ET.parse(devicexml)
|
||||
for node in xmltree.iter():
|
||||
if "deviceSerial" in node.tag:
|
||||
serial = node.text
|
||||
# print u"found serial {0}".format(serial)
|
||||
serials.append(serial)
|
||||
break
|
||||
else:
|
||||
# print u"cannot get serials from device."
|
||||
device_path = u""
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
if (self.kobodir == u""):
|
||||
# we haven't found a device with serials, so try desktop apps
|
||||
# step 4. we haven't found a device with serials, so try desktop apps
|
||||
if sys.platform.startswith('win'):
|
||||
import _winreg as winreg
|
||||
if sys.getwindowsversion().major > 5:
|
||||
@@ -280,8 +341,17 @@ class KoboLibrary(object):
|
||||
self.kobodir = os.path.join(self.kobodir, u"Kobo", u"Kobo Desktop Edition")
|
||||
elif sys.platform.startswith('darwin'):
|
||||
self.kobodir = os.path.join(os.environ['HOME'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition")
|
||||
elif linux_path != None:
|
||||
# Probably Linux, let's get the wine prefix and path to Kobo.
|
||||
self.kobodir = os.path.join(linux_path, u"Local Settings", u"Application Data", u"Kobo", u"Kobo Desktop Edition")
|
||||
# desktop versions use Kobo.sqlite
|
||||
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite")
|
||||
# check for existence of file
|
||||
if (not(os.path.isfile(kobodb))):
|
||||
# give up here, we haven't found anything useful
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
|
||||
if (self.kobodir != u""):
|
||||
self.bookdir = os.path.join(self.kobodir, u"kepub")
|
||||
@@ -349,6 +419,13 @@ class KoboLibrary(object):
|
||||
for m in matches:
|
||||
# print u"m:{0}".format(m[0])
|
||||
macaddrs.append(m[0].upper())
|
||||
else:
|
||||
# probably linux, let's try ipconfig under wine
|
||||
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||
for line in os.popen('ipconfig /all'):
|
||||
m = c.search(line)
|
||||
if m:
|
||||
macaddrs.append(re.sub("-", ":", m.group(1)).upper())
|
||||
|
||||
# extend the list of macaddrs in any case with the serials
|
||||
# cannot hurt ;-)
|
||||
@@ -372,16 +449,11 @@ class KoboLibrary(object):
|
||||
def __getuserkeys (self, macaddr):
|
||||
userids = self.__getuserids()
|
||||
userkeys = []
|
||||
# This version is used for versions before 3.17.0.
|
||||
deviceid = hashlib.sha256('NoCanLook' + macaddr).hexdigest()
|
||||
for userid in userids:
|
||||
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
||||
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
||||
# This version is used for 3.17.0 and later.
|
||||
deviceid = hashlib.sha256('XzUhGYdFp' + macaddr).hexdigest()
|
||||
for userid in userids:
|
||||
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
||||
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
||||
for hash in KOBO_HASH_KEYS:
|
||||
deviceid = hashlib.sha256(hash + macaddr).hexdigest()
|
||||
for userid in userids:
|
||||
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
||||
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
||||
return userkeys
|
||||
|
||||
class KoboBook(object):
|
||||
@@ -518,7 +590,17 @@ class KoboFile(object):
|
||||
return contents
|
||||
|
||||
def cli_main():
|
||||
lib = KoboLibrary()
|
||||
description = __about__
|
||||
epilog = u"Parsing of arguments failed."
|
||||
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
||||
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
||||
args = vars(parser.parse_args())
|
||||
serials = []
|
||||
devicedir = u""
|
||||
if args['devicedir']:
|
||||
devicedir = args['devicedir']
|
||||
|
||||
lib = KoboLibrary(serials, devicedir)
|
||||
|
||||
for i, book in enumerate(lib.books):
|
||||
print u"{0}: {1}".format(i + 1, book.title)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
obok_plugin.zip
|
||||
================
|
||||
|
||||
This plugin will remove the DRM from Kobo ebooks download on Mac or Windows using the Kobo desktop application, or from Kobo ebooks on an attached Kobo reader. If both are available, ebooks will be read from the attached Kobo reader. To import from the desktop application, unplug the Kobo reader.
|
||||
This plugin will remove the DRM from Kobo ebooks download on Mac or Windows using the Kobo desktop application, or from Kobo ebooks on an attached E-Ink Kobo reader (but not a Kobo Arc or Kobo Vox). If both are available, ebooks will be read from the attached E-Ink Kobo reader. To import from the desktop application, unplug the Kobo reader.
|
||||
|
||||
|
||||
Installation
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Version 3.2.0 January 2016
|
||||
# Update for latest version of Windows Desktop app.
|
||||
# Support Kobo devices in the command line version.
|
||||
#
|
||||
# Version 3.1.9 November 2015
|
||||
# Handle Kobo Desktop under wine on Linux
|
||||
#
|
||||
# Version 3.1.8 November 2015
|
||||
# Handle the case of Kobo Arc or Vox device (i.e. don't crash).
|
||||
#
|
||||
# Version 3.1.7 October 2015
|
||||
# Handle the case of no device or database more gracefully.
|
||||
#
|
||||
# Version 3.1.6 September 2015
|
||||
# Enable support for Kobo devices
|
||||
# More character encoding fixes (unicode strings)
|
||||
@@ -123,7 +136,8 @@
|
||||
#
|
||||
"""Manage all Kobo books, either encrypted or DRM-free."""
|
||||
|
||||
__version__ = '3.1.6'
|
||||
__version__ = '3.1.9'
|
||||
__about__ = u"Obok v{0}\nCopyright © 2012-2015 Physisticated et al.".format(__version__)
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -137,6 +151,18 @@ import hashlib
|
||||
import xml.etree.ElementTree as ET
|
||||
import string
|
||||
import shutil
|
||||
import argparse
|
||||
|
||||
can_parse_xml = True
|
||||
try:
|
||||
from xml.etree import ElementTree as ET
|
||||
# print u"using xml.etree for xml parsing"
|
||||
except ImportError:
|
||||
can_parse_xml = False
|
||||
# print u"Cannot find xml.etree, disabling extraction of serial numbers"
|
||||
|
||||
# List of all known hash keys
|
||||
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook']
|
||||
|
||||
class ENCRYPTIONError(Exception):
|
||||
pass
|
||||
@@ -249,24 +275,59 @@ class KoboLibrary(object):
|
||||
of books, their titles, and the user's encryption key(s)."""
|
||||
|
||||
def __init__ (self, serials = [], device_path = None):
|
||||
print u"Obok v{0}\nCopyright © 2012-2015 Physisticated et al.".format(__version__)
|
||||
print __about__
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
# - first check whether serials have been found or are provided
|
||||
# and a device is connected. In this case, use the device
|
||||
# - otherwise fall back to Kobo Desktop Application for Windows and Mac
|
||||
if (device_path and (len(serials) > 0)):
|
||||
# Order of checks
|
||||
# 1. first check if a device_path has been passed in, and whether
|
||||
# we can find the sqlite db in the respective place
|
||||
# 2. if 1., and we got some serials passed in (from saved
|
||||
# settings in calibre), just use it
|
||||
# 3. if 1. worked, but we didn't get serials, try to parse them
|
||||
# from the device, if this didn't work, unset everything
|
||||
# 4. if by now we don't have kobodir set, give up on device and
|
||||
# try to use the Desktop app.
|
||||
|
||||
# step 1. check whether this looks like a real device
|
||||
if (device_path):
|
||||
# we got a device path
|
||||
self.kobodir = os.path.join(device_path, u".kobo")
|
||||
# devices use KoboReader.sqlite
|
||||
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite")
|
||||
if (not(os.path.exists(kobodb))):
|
||||
# give up here, we haven't found anything useful
|
||||
if (not(os.path.isfile(kobodb))):
|
||||
# device path seems to be wrong, unset it
|
||||
device_path = u""
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
if (self.kobodir):
|
||||
# step 3. we found a device but didn't get serials, try to get them
|
||||
if (len(serials) == 0):
|
||||
# we got a device path but no saved serial
|
||||
# try to get the serial from the device
|
||||
# print u"get_device_settings - device_path = {0}".format(device_path)
|
||||
# get serial from device_path/.adobe-digital-editions/device.xml
|
||||
if can_parse_xml:
|
||||
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
|
||||
# print u"trying to load {0}".format(devicexml)
|
||||
if (os.path.exists(devicexml)):
|
||||
# print u"trying to parse {0}".format(devicexml)
|
||||
xmltree = ET.parse(devicexml)
|
||||
for node in xmltree.iter():
|
||||
if "deviceSerial" in node.tag:
|
||||
serial = node.text
|
||||
# print u"found serial {0}".format(serial)
|
||||
serials.append(serial)
|
||||
break
|
||||
else:
|
||||
# print u"cannot get serials from device."
|
||||
device_path = u""
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
if (self.kobodir == u""):
|
||||
# we haven't found a device with serials, so try desktop apps
|
||||
# step 4. we haven't found a device with serials, so try desktop apps
|
||||
if sys.platform.startswith('win'):
|
||||
import _winreg as winreg
|
||||
if sys.getwindowsversion().major > 5:
|
||||
@@ -280,8 +341,17 @@ class KoboLibrary(object):
|
||||
self.kobodir = os.path.join(self.kobodir, u"Kobo", u"Kobo Desktop Edition")
|
||||
elif sys.platform.startswith('darwin'):
|
||||
self.kobodir = os.path.join(os.environ['HOME'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition")
|
||||
elif linux_path != None:
|
||||
# Probably Linux, let's get the wine prefix and path to Kobo.
|
||||
self.kobodir = os.path.join(linux_path, u"Local Settings", u"Application Data", u"Kobo", u"Kobo Desktop Edition")
|
||||
# desktop versions use Kobo.sqlite
|
||||
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite")
|
||||
# check for existence of file
|
||||
if (not(os.path.isfile(kobodb))):
|
||||
# give up here, we haven't found anything useful
|
||||
self.kobodir = u""
|
||||
kobodb = u""
|
||||
|
||||
|
||||
if (self.kobodir != u""):
|
||||
self.bookdir = os.path.join(self.kobodir, u"kepub")
|
||||
@@ -349,6 +419,13 @@ class KoboLibrary(object):
|
||||
for m in matches:
|
||||
# print u"m:{0}".format(m[0])
|
||||
macaddrs.append(m[0].upper())
|
||||
else:
|
||||
# probably linux, let's try ipconfig under wine
|
||||
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||
for line in os.popen('ipconfig /all'):
|
||||
m = c.search(line)
|
||||
if m:
|
||||
macaddrs.append(re.sub("-", ":", m.group(1)).upper())
|
||||
|
||||
# extend the list of macaddrs in any case with the serials
|
||||
# cannot hurt ;-)
|
||||
@@ -372,16 +449,11 @@ class KoboLibrary(object):
|
||||
def __getuserkeys (self, macaddr):
|
||||
userids = self.__getuserids()
|
||||
userkeys = []
|
||||
# This version is used for versions before 3.17.0.
|
||||
deviceid = hashlib.sha256('NoCanLook' + macaddr).hexdigest()
|
||||
for userid in userids:
|
||||
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
||||
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
||||
# This version is used for 3.17.0 and later.
|
||||
deviceid = hashlib.sha256('XzUhGYdFp' + macaddr).hexdigest()
|
||||
for userid in userids:
|
||||
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
||||
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
||||
for hash in KOBO_HASH_KEYS:
|
||||
deviceid = hashlib.sha256(hash + macaddr).hexdigest()
|
||||
for userid in userids:
|
||||
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
||||
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
||||
return userkeys
|
||||
|
||||
class KoboBook(object):
|
||||
@@ -518,7 +590,17 @@ class KoboFile(object):
|
||||
return contents
|
||||
|
||||
def cli_main():
|
||||
lib = KoboLibrary()
|
||||
description = __about__
|
||||
epilog = u"Parsing of arguments failed."
|
||||
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
||||
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
||||
args = vars(parser.parse_args())
|
||||
serials = []
|
||||
devicedir = u""
|
||||
if args['devicedir']:
|
||||
devicedir = args['devicedir']
|
||||
|
||||
lib = KoboLibrary(serials, devicedir)
|
||||
|
||||
for i, book in enumerate(lib.books):
|
||||
print u"{0}: {1}".format(i + 1, book.title)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,10 +1,4 @@
|
||||
How-to:
|
||||
1) Make sure you can read all PDF files on Scuolabook Reader.
|
||||
2) Run Scuolabook DRM Remover.
|
||||
3) Get your free books from the directory where you started the program.
|
||||
The latest Scuolabook tool can be found at Hex's own blog:
|
||||
https://thisishex.wordpress.com/scuolabook-drm-remover/
|
||||
|
||||
Note:
|
||||
It is recommended to use Scuolabook version 2.0.1 and refuse all updates
|
||||
because the encryption algorithm may change making this tool useless.
|
||||
|
||||
Hex
|
||||
Harper.
|
||||
@@ -12,7 +12,7 @@ The is archive includes tools to remove DRM from:
|
||||
- Adobe Digital Editions PDFs
|
||||
- Mobipocket ebooks
|
||||
- eReader PDB books
|
||||
- Scuolabooks (Windows only solution by Hex)
|
||||
- Scuolabooks (Link to solution by Hex)
|
||||
|
||||
These tools do NOT work with Apple's iBooks FairPlay DRM (see end of this file.)
|
||||
|
||||
@@ -78,7 +78,7 @@ Rocket_ebooks
|
||||
Information about the now-obsolete Rocket ebook format and DRM, along with source for a tool to remove the DRM.
|
||||
|
||||
Scuolabook_DRM
|
||||
A windows-only application (including source code) for removing DRM from ScuolaBooks PDFs, created by "Hex" and included with permission.
|
||||
A link to the tool for removing DRM from ScuolaBooks PDFs, created by "Hex".
|
||||
|
||||
|
||||
Windows and Python
|
||||
|
||||
Reference in New Issue
Block a user