Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9162698f89 | ||
|
|
506d97d5f0 | ||
|
|
a76ba56cd8 | ||
|
|
8e73edc012 | ||
|
|
c386ac6e6d |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.pyc
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|||||||
@@ -2,17 +2,18 @@ From Apprentice Alf's Blog
|
|||||||
|
|
||||||
Adobe Adept ePub and PDF, .epub, .pdf
|
Adobe Adept ePub and PDF, .epub, .pdf
|
||||||
|
|
||||||
The wonderful I♥CABBAGES has produced scripts that will remove the DRM from ePubs and PDFs encryped with Adobe’s DRM. Installing these scripts is a little more complex that the Mobipocket and eReader decryption tools, as they require installation of the PyCrypto package for Windows Boxes. For Mac OS X and Linux boxes, these scripts use the already installed OpenSSL libcrypto so there is no additional requirements for these platforms.
|
This directory includes modified versions of the I♥CABBAGES Adobe Adept inept scripts for epubs. These scripts have been modified to work with OpenSSL on Windows as well as Linux and Mac OS X. His original scripts can be found in the clearly labelled folder. If a Windows User has OpenSSL installed, these scripts will make use of it in place of PyCrypto.
|
||||||
|
|
||||||
|
The wonderful I♥CABBAGES has produced scripts that will remove the DRM from ePubs and PDFs encryped with Adobe’s DRM. These scripts require installation of the PyCrypto python package *or* the OpenSSL library on Windows. For Mac OS X and Linux boxes, these scripts use the already installed OpenSSL libcrypto so there is no additional requirements for these platforms.
|
||||||
|
|
||||||
For more info, see the author's blog:
|
For more info, see the author's blog:
|
||||||
http://i-u2665-cabbages.blogspot.com/2009_02_01_archive.html
|
http://i-u2665-cabbages.blogspot.com/2009_02_01_archive.html
|
||||||
|
|
||||||
There are two scripts:
|
There are two scripts:
|
||||||
|
|
||||||
The first is called ineptkey_v5.pyw. Simply double-click to launch it and it will create a key file that is needed later to actually remove the DRM. This script need only be run once unless you change your ADE account information.
|
The first is called ineptkey_v5.1.pyw. Simply double-click to launch it and it will create a key file that is needed later to actually remove the DRM. This script need only be run once unless you change your ADE account information.
|
||||||
|
|
||||||
The second is called in ineptepub_v5.pyw. Simply double-click to launch it. It will ask for your previously generated key file and the path to the book you want to remove the DRM from.
|
|
||||||
|
|
||||||
|
The second is called in ineptepub_v5.3.pyw. Simply double-click to launch it. It will ask for your previously generated key file and the path to the book you want to remove the DRM from.
|
||||||
|
|
||||||
Both of these scripts are gui python programs. Python 2.X (32 bit) is already installed in Mac OSX. We recommend ActiveState's Active Python Version 2.X (32 bit) for Windows users.
|
Both of these scripts are gui python programs. Python 2.X (32 bit) is already installed in Mac OSX. We recommend ActiveState's Active Python Version 2.X (32 bit) for Windows users.
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
# Improve OS X support by using OpenSSL when available
|
# Improve OS X support by using OpenSSL when available
|
||||||
# 5.1 - Improve OpenSSL error checking
|
# 5.1 - Improve OpenSSL error checking
|
||||||
# 5.2 - Fix ctypes error causing segfaults on some systems
|
# 5.2 - Fix ctypes error causing segfaults on some systems
|
||||||
|
# 5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o
|
||||||
"""
|
"""
|
||||||
Decrypt Adobe ADEPT-encrypted EPUB books.
|
Decrypt Adobe ADEPT-encrypted EPUB books.
|
||||||
"""
|
"""
|
||||||
@@ -53,7 +53,11 @@ def _load_crypto_libcrypto():
|
|||||||
Structure, c_ulong, create_string_buffer, cast
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
libcrypto = find_library('crypto')
|
libcrypto = find_library('crypto')
|
||||||
|
|
||||||
if libcrypto is None:
|
if libcrypto is None:
|
||||||
raise ADEPTError('libcrypto not found')
|
raise ADEPTError('libcrypto not found')
|
||||||
libcrypto = CDLL(libcrypto)
|
libcrypto = CDLL(libcrypto)
|
||||||
@@ -116,6 +120,9 @@ def _load_crypto_libcrypto():
|
|||||||
class AES(object):
|
class AES(object):
|
||||||
def __init__(self, userkey):
|
def __init__(self, userkey):
|
||||||
self._blocksize = len(userkey)
|
self._blocksize = len(userkey)
|
||||||
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
|
raise ADEPTError('AES improper key used')
|
||||||
|
return
|
||||||
key = self._key = AES_KEY()
|
key = self._key = AES_KEY()
|
||||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
||||||
if rv < 0:
|
if rv < 0:
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
# 4.4 - Make it working on 64-bit Python
|
# 4.4 - Make it working on 64-bit Python
|
||||||
# 5 - Clean up and improve 4.x changes;
|
# 5 - Clean up and improve 4.x changes;
|
||||||
# Clean up and merge OS X support by unknown
|
# Clean up and merge OS X support by unknown
|
||||||
|
# 5.1 - add support for using OpenSSL on Windows in place of PyCrypto
|
||||||
|
# 5.2 - added support for output of key to a particular file
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Retrieve Adobe ADEPT user key.
|
Retrieve Adobe ADEPT user key.
|
||||||
@@ -53,14 +55,76 @@ class ADEPTError(Exception):
|
|||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
||||||
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
||||||
string_at, Structure, c_void_p, cast, c_size_t, memmove
|
string_at, Structure, c_void_p, cast, c_size_t, memmove, CDLL, c_int, \
|
||||||
|
c_long, c_ulong
|
||||||
|
|
||||||
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
||||||
import _winreg as winreg
|
import _winreg as winreg
|
||||||
|
|
||||||
try:
|
def _load_crypto_libcrypto():
|
||||||
from Crypto.Cipher import AES
|
from ctypes.util import find_library
|
||||||
except ImportError:
|
libcrypto = find_library('libeay32')
|
||||||
|
if libcrypto is None:
|
||||||
|
raise ADEPTError('libcrypto not found')
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
AES_MAXNR = 14
|
||||||
|
c_char_pp = POINTER(c_char_p)
|
||||||
|
c_int_p = POINTER(c_int)
|
||||||
|
class AES_KEY(Structure):
|
||||||
|
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
||||||
|
('rounds', c_int)]
|
||||||
|
AES_KEY_p = POINTER(AES_KEY)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
|
||||||
|
[c_char_p, c_int, AES_KEY_p])
|
||||||
|
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
||||||
|
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
||||||
|
c_int])
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, userkey):
|
||||||
|
self._blocksize = len(userkey)
|
||||||
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
|
raise ADEPTError('AES improper key used')
|
||||||
|
key = self._key = AES_KEY()
|
||||||
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
||||||
|
if rv < 0:
|
||||||
|
raise ADEPTError('Failed to initialize AES key')
|
||||||
|
def decrypt(self, data):
|
||||||
|
out = create_string_buffer(len(data))
|
||||||
|
iv = ("\x00" * self._blocksize)
|
||||||
|
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
||||||
|
if rv == 0:
|
||||||
|
raise ADEPTError('AES decryption failed')
|
||||||
|
return out.raw
|
||||||
|
return AES
|
||||||
|
|
||||||
|
def _load_crypto_pycrypto():
|
||||||
|
from Crypto.Cipher import AES as _AES
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, key):
|
||||||
|
self._aes = _AES.new(key, _AES.MODE_CBC)
|
||||||
|
def decrypt(self, data):
|
||||||
|
return self._aes.decrypt(data)
|
||||||
|
return AES
|
||||||
|
|
||||||
|
def _load_crypto():
|
||||||
AES = None
|
AES = None
|
||||||
|
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
|
||||||
|
try:
|
||||||
|
AES = loader()
|
||||||
|
break
|
||||||
|
except (ImportError, ADEPTError):
|
||||||
|
pass
|
||||||
|
return AES
|
||||||
|
|
||||||
|
AES = _load_crypto()
|
||||||
|
|
||||||
|
|
||||||
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
||||||
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
||||||
@@ -230,7 +294,7 @@ if sys.platform.startswith('win'):
|
|||||||
if AES is None:
|
if AES is None:
|
||||||
tkMessageBox.showerror(
|
tkMessageBox.showerror(
|
||||||
"ADEPT Key",
|
"ADEPT Key",
|
||||||
"This script requires PyCrypto, which must be installed "
|
"This script requires PyCrypto or OpenSSL which must be installed "
|
||||||
"separately. Read the top-of-script comment for details.")
|
"separately. Read the top-of-script comment for details.")
|
||||||
return False
|
return False
|
||||||
root = GetSystemDirectory().split('\\')[0] + '\\'
|
root = GetSystemDirectory().split('\\')[0] + '\\'
|
||||||
@@ -274,7 +338,8 @@ if sys.platform.startswith('win'):
|
|||||||
if userkey is None:
|
if userkey is None:
|
||||||
raise ADEPTError('Could not locate privateLicenseKey')
|
raise ADEPTError('Could not locate privateLicenseKey')
|
||||||
userkey = userkey.decode('base64')
|
userkey = userkey.decode('base64')
|
||||||
userkey = AES.new(keykey, AES.MODE_CBC).decrypt(userkey)
|
aes = AES(keykey)
|
||||||
|
userkey = aes.decrypt(userkey)
|
||||||
userkey = userkey[26:-ord(userkey[-1])]
|
userkey = userkey[26:-ord(userkey[-1])]
|
||||||
with open(keypath, 'wb') as f:
|
with open(keypath, 'wb') as f:
|
||||||
f.write(userkey)
|
f.write(userkey)
|
||||||
@@ -351,6 +416,20 @@ class ExceptionDialog(Tkinter.Frame):
|
|||||||
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||||
self.text.insert(Tkconstants.END, text)
|
self.text.insert(Tkconstants.END, text)
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
keypath = argv[1]
|
||||||
|
try:
|
||||||
|
success = retrieve_key(keypath)
|
||||||
|
except ADEPTError, e:
|
||||||
|
print "Key generation Error: " + str(e)
|
||||||
|
return 1
|
||||||
|
except Exception, e:
|
||||||
|
print "General Error: " + str(e)
|
||||||
|
return 1
|
||||||
|
if not success:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
def main(argv=sys.argv):
|
def main(argv=sys.argv):
|
||||||
root = Tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
@@ -374,4 +453,6 @@ def main(argv=sys.argv):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
sys.exit(cli_main())
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
# 1 - Initial release
|
# 1 - Initial release
|
||||||
# 2 - Added OS X support by using OpenSSL when available
|
# 2 - Added OS X support by using OpenSSL when available
|
||||||
# 3 - screen out improper key lengths to prevent segfaults on Linux
|
# 3 - screen out improper key lengths to prevent segfaults on Linux
|
||||||
|
# 3.1 - Allow Windows versions of libcrypto to be found
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
@@ -36,6 +37,9 @@ def _load_crypto_libcrypto():
|
|||||||
Structure, c_ulong, create_string_buffer, cast
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
libcrypto = find_library('crypto')
|
libcrypto = find_library('crypto')
|
||||||
if libcrypto is None:
|
if libcrypto is None:
|
||||||
raise IGNOBLEError('libcrypto not found')
|
raise IGNOBLEError('libcrypto not found')
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
# Revision history:
|
# Revision history:
|
||||||
# 1 - Initial release
|
# 1 - Initial release
|
||||||
# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
|
# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
|
||||||
|
# 2.1 - Allow Windows versions of libcrypto to be found
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||||
@@ -40,6 +41,9 @@ def _load_crypto_libcrypto():
|
|||||||
Structure, c_ulong, create_string_buffer, cast
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
libcrypto = find_library('crypto')
|
libcrypto = find_library('crypto')
|
||||||
if libcrypto is None:
|
if libcrypto is None:
|
||||||
print 'libcrypto not found'
|
print 'libcrypto not found'
|
||||||
|
|||||||
26
Calibre_Plugins/Win_OpenSSL_0.9.8o.txt
Normal file
26
Calibre_Plugins/Win_OpenSSL_0.9.8o.txt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
Installing openssl on Windows 64-bit (Windows 2000 and higher)
|
||||||
|
|
||||||
|
Win64 OpenSSL v0.9.8o (8Mb)
|
||||||
|
http://www.slproweb.com/download/Win64OpenSSL-0_9_8o.exe
|
||||||
|
(if you get an error message about missing Visual C++ redistributables... cancel the install and install the below support program from Microsoft, THEN install OpenSSL)
|
||||||
|
|
||||||
|
Visual C++ 2008 Redistributables (x64) (1.7Mb)
|
||||||
|
http://www.microsoft.com/downloads/details.aspx?familyid=bd2a6171-e2d6-4230-b809-9a8d7548c1b6
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Installing openssl on Windows 32-bit (Windows 2000 and higher)
|
||||||
|
|
||||||
|
Win32 OpenSSL v0.9.8o (8Mb)
|
||||||
|
http://www.slproweb.com/download/Win32OpenSSL-0_9_8o.exe
|
||||||
|
(if you get an error message about missing Visual C++ redistributables... cancel the install and install the below support program from Microsoft, THEN install OpenSSL)
|
||||||
|
|
||||||
|
Visual C++ 2008 Redistributables (1.7Mb)
|
||||||
|
http://www.microsoft.com/downloads/details.aspx?familyid=9B2DA534-3E03-4391-8A4D-074B9F2BC1BF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Other versions of OpenSSL (and versions for Windows older than Windows 2000) can be found on the following website.
|
||||||
|
|
||||||
|
Shining Light Productions
|
||||||
|
http://www.slproweb.com/products/Win32OpenSSL.html
|
||||||
Binary file not shown.
@@ -1,21 +0,0 @@
|
|||||||
eReader PDB2PML - eReaderPDB2PML_vXX_plugin.zip
|
|
||||||
|
|
||||||
All credit given to The Dark Reverser for the original standalone script. I had the much easier job of converting it to a Calibre plugin.
|
|
||||||
|
|
||||||
This plugin is meant to convert secure Ereader files (PDB) to unsecured PMLZ files. Calibre can then convert it to whatever format you desire. It is meant to function without having to install any dependencies... other than having Calibre installed, of course. I've included the psyco libraries (compiled for each platform) for speed. If your system can use them, great! Otherwise, they won't be used and things will just work slower.
|
|
||||||
|
|
||||||
Installation:
|
|
||||||
Go to Calibre's Preferences page... click on the Plugins button. Use the file dialog button to select the plugin's zip file (eReaderPDB2PML_vXX_plugin.zip) and click the 'Add' button. You're done.
|
|
||||||
|
|
||||||
Configuration:
|
|
||||||
Highlight the plugin (eReader PDB 2 PML under the "File type plugins" category) and click the "Customize Plugin" button on Calibre's Preferences->Plugins page. Enter your name and last 8 digits of the credit card number separated by a comma: Your Name,12341234
|
|
||||||
|
|
||||||
If you've purchased books with more than one credit card, separate the info with a colon: Your Name,12341234:Other Name,23452345 (NOTE: Do NOT put quotes around your name like you do with the original script!!)
|
|
||||||
|
|
||||||
Troubleshooting:
|
|
||||||
If you find that it's not working for you (imported pdb's are not converted to pmlz format), you can save a lot of time and trouble by trying to add the pdb to Calibre with the command line tools. This will print out a lot of helpful debugging info that can be copied into any online help requests. I'm going to ask you to do it first, anyway, so you might
|
|
||||||
as well get used to it. ;)
|
|
||||||
|
|
||||||
Open a command prompt (terminal) and change to the directory where the ebook you're trying to import resides. Then type the command "calibredb add your_ebook.pdb". Don't type the quotes and obviously change the 'your_ebook.pdb' to whatever the filename of your book is. Copy the resulting output and paste it into any online help request you make.
|
|
||||||
|
|
||||||
** Note: the Mac version of Calibre doesn't install the command line tools by default. If you go to the 'Preferences' page and click on the miscellaneous button, you'll see the option to install the command line tools.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# eReaderPDB2PML_v01_plugin.py
|
# eReaderPDB2PML_plugin.py
|
||||||
# Released under the terms of the GNU General Public Licence, version 3 or
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
# later. <http://www.gnu.org/licenses/>
|
# later. <http://www.gnu.org/licenses/>
|
||||||
#
|
#
|
||||||
@@ -30,7 +30,8 @@
|
|||||||
# NOTE: Do NOT put quotes around your name like you do with the original script!!
|
# NOTE: Do NOT put quotes around your name like you do with the original script!!
|
||||||
#
|
#
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 0.1 - Initial release
|
# 0.0.1 - Initial release
|
||||||
|
# 0.0.2 - updated to distinguish it from earlier non-openssl version
|
||||||
|
|
||||||
import sys, os
|
import sys, os
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ class eRdrDeDRM(FileTypePlugin):
|
|||||||
Credit given to The Dark Reverser for the original standalone script.'
|
Credit given to The Dark Reverser for the original standalone script.'
|
||||||
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
|
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
|
||||||
author = 'DiapDealer' # The author of this plugin
|
author = 'DiapDealer' # The author of this plugin
|
||||||
version = (0, 0, 1) # The version number of this plugin
|
version = (0, 0, 2) # The version number of this plugin
|
||||||
file_types = set(['pdb']) # The file types that this plugin will be applied to
|
file_types = set(['pdb']) # The file types that this plugin will be applied to
|
||||||
on_import = True # Run this plugin during the import
|
on_import = True # Run this plugin during the import
|
||||||
|
|
||||||
@@ -52,7 +53,6 @@ class eRdrDeDRM(FileTypePlugin):
|
|||||||
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||||
ppath = os.path.join(self.sys_insertion_path, pdir)
|
ppath = os.path.join(self.sys_insertion_path, pdir)
|
||||||
sys.path.insert(0, ppath)
|
sys.path.insert(0, ppath)
|
||||||
#sys.path.append(ppath)
|
|
||||||
|
|
||||||
global bookname, erdr2pml
|
global bookname, erdr2pml
|
||||||
import erdr2pml
|
import erdr2pml
|
||||||
|
|||||||
@@ -54,9 +54,18 @@
|
|||||||
# 0.13 - change to unbuffered stdout for use with gui front ends
|
# 0.13 - change to unbuffered stdout for use with gui front ends
|
||||||
# 0.14 - contributed enhancement to support --make-pmlz switch
|
# 0.14 - contributed enhancement to support --make-pmlz switch
|
||||||
# 0.15 - enabled high-ascii to pml character encoding. DropBook now works on Mac.
|
# 0.15 - enabled high-ascii to pml character encoding. DropBook now works on Mac.
|
||||||
|
# 0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
|
||||||
|
|
||||||
__version__='0.15'
|
Des = None
|
||||||
|
|
||||||
|
import openssl_des
|
||||||
|
Des = openssl_des.load_libcrypto()
|
||||||
|
|
||||||
|
# if that did not work then use pure python implementation
|
||||||
|
# of DES and try to speed it up with Psycho
|
||||||
|
if Des == None:
|
||||||
|
import python_des
|
||||||
|
Des = python_des.Des
|
||||||
# Import Psyco if available
|
# Import Psyco if available
|
||||||
try:
|
try:
|
||||||
# Dumb speed hack 1
|
# Dumb speed hack 1
|
||||||
@@ -66,14 +75,9 @@ try:
|
|||||||
pass
|
pass
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
try:
|
|
||||||
# Dumb speed hack 2
|
|
||||||
# All map() calls converted to list comprehension (some use zip)
|
__version__='0.16'
|
||||||
# override zip with izip - saves memory and in rough testing
|
|
||||||
# appears to be faster zip() is only used in the converted map() calls
|
|
||||||
from itertools import izip as zip
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
@@ -101,223 +105,6 @@ import logging
|
|||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
#logging.basicConfig(level=logging.DEBUG)
|
#logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
ECB = 0
|
|
||||||
CBC = 1
|
|
||||||
class Des(object):
|
|
||||||
__pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
|
|
||||||
9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
|
|
||||||
62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
|
|
||||||
13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3]
|
|
||||||
__left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
|
|
||||||
__pc2 = [13, 16, 10, 23, 0, 4,2, 27, 14, 5, 20, 9,
|
|
||||||
22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
|
|
||||||
40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
|
|
||||||
43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31]
|
|
||||||
__ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
|
|
||||||
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
|
|
||||||
56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2,
|
|
||||||
60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
|
|
||||||
__expansion_table = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8,
|
|
||||||
7, 8, 9, 10, 11, 12,11, 12, 13, 14, 15, 16,
|
|
||||||
15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
|
|
||||||
23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31, 0]
|
|
||||||
__sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
|
|
||||||
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
|
||||||
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
|
|
||||||
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
|
|
||||||
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
|
|
||||||
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
|
|
||||||
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
|
|
||||||
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
|
|
||||||
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
|
|
||||||
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
|
||||||
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
|
|
||||||
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
|
|
||||||
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
|
|
||||||
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
|
||||||
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
|
|
||||||
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
|
|
||||||
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
|
|
||||||
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
|
||||||
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
|
|
||||||
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
|
|
||||||
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
|
|
||||||
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
|
||||||
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
|
|
||||||
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
|
|
||||||
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
|
|
||||||
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
|
||||||
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
|
|
||||||
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
|
|
||||||
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
|
|
||||||
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
|
||||||
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
|
|
||||||
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
|
|
||||||
__p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
|
|
||||||
4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
|
|
||||||
__fp = [39, 7, 47, 15, 55, 23, 63, 31,38, 6, 46, 14, 54, 22, 62, 30,
|
|
||||||
37, 5, 45, 13, 53, 21, 61, 29,36, 4, 44, 12, 52, 20, 60, 28,
|
|
||||||
35, 3, 43, 11, 51, 19, 59, 27,34, 2, 42, 10, 50, 18, 58, 26,
|
|
||||||
33, 1, 41, 9, 49, 17, 57, 25,32, 0, 40, 8, 48, 16, 56, 24]
|
|
||||||
# Type of crypting being done
|
|
||||||
ENCRYPT = 0x00
|
|
||||||
DECRYPT = 0x01
|
|
||||||
def __init__(self, key, mode=ECB, IV=None):
|
|
||||||
if len(key) != 8:
|
|
||||||
raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
|
|
||||||
self.block_size = 8
|
|
||||||
self.key_size = 8
|
|
||||||
self.__padding = ''
|
|
||||||
self.setMode(mode)
|
|
||||||
if IV:
|
|
||||||
self.setIV(IV)
|
|
||||||
self.L = []
|
|
||||||
self.R = []
|
|
||||||
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
|
|
||||||
self.final = []
|
|
||||||
self.setKey(key)
|
|
||||||
def getKey(self):
|
|
||||||
return self.__key
|
|
||||||
def setKey(self, key):
|
|
||||||
self.__key = key
|
|
||||||
self.__create_sub_keys()
|
|
||||||
def getMode(self):
|
|
||||||
return self.__mode
|
|
||||||
def setMode(self, mode):
|
|
||||||
self.__mode = mode
|
|
||||||
def getIV(self):
|
|
||||||
return self.__iv
|
|
||||||
def setIV(self, IV):
|
|
||||||
if not IV or len(IV) != self.block_size:
|
|
||||||
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
|
||||||
self.__iv = IV
|
|
||||||
def getPadding(self):
|
|
||||||
return self.__padding
|
|
||||||
def __String_to_BitList(self, data):
|
|
||||||
l = len(data) * 8
|
|
||||||
result = [0] * l
|
|
||||||
pos = 0
|
|
||||||
for c in data:
|
|
||||||
i = 7
|
|
||||||
ch = ord(c)
|
|
||||||
while i >= 0:
|
|
||||||
if ch & (1 << i) != 0:
|
|
||||||
result[pos] = 1
|
|
||||||
else:
|
|
||||||
result[pos] = 0
|
|
||||||
pos += 1
|
|
||||||
i -= 1
|
|
||||||
return result
|
|
||||||
def __BitList_to_String(self, data):
|
|
||||||
result = ''
|
|
||||||
pos = 0
|
|
||||||
c = 0
|
|
||||||
while pos < len(data):
|
|
||||||
c += data[pos] << (7 - (pos % 8))
|
|
||||||
if (pos % 8) == 7:
|
|
||||||
result += chr(c)
|
|
||||||
c = 0
|
|
||||||
pos += 1
|
|
||||||
return result
|
|
||||||
def __permutate(self, table, block):
|
|
||||||
return [block[x] for x in table]
|
|
||||||
def __create_sub_keys(self):
|
|
||||||
key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
|
|
||||||
i = 0
|
|
||||||
self.L = key[:28]
|
|
||||||
self.R = key[28:]
|
|
||||||
while i < 16:
|
|
||||||
j = 0
|
|
||||||
while j < Des.__left_rotations[i]:
|
|
||||||
self.L.append(self.L[0])
|
|
||||||
del self.L[0]
|
|
||||||
self.R.append(self.R[0])
|
|
||||||
del self.R[0]
|
|
||||||
j += 1
|
|
||||||
self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
|
|
||||||
i += 1
|
|
||||||
def __des_crypt(self, block, crypt_type):
|
|
||||||
block = self.__permutate(Des.__ip, block)
|
|
||||||
self.L = block[:32]
|
|
||||||
self.R = block[32:]
|
|
||||||
if crypt_type == Des.ENCRYPT:
|
|
||||||
iteration = 0
|
|
||||||
iteration_adjustment = 1
|
|
||||||
else:
|
|
||||||
iteration = 15
|
|
||||||
iteration_adjustment = -1
|
|
||||||
i = 0
|
|
||||||
while i < 16:
|
|
||||||
tempR = self.R[:]
|
|
||||||
self.R = self.__permutate(Des.__expansion_table, self.R)
|
|
||||||
self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
|
|
||||||
B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
|
|
||||||
j = 0
|
|
||||||
Bn = [0] * 32
|
|
||||||
pos = 0
|
|
||||||
while j < 8:
|
|
||||||
m = (B[j][0] << 1) + B[j][5]
|
|
||||||
n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
|
|
||||||
v = Des.__sbox[j][(m << 4) + n]
|
|
||||||
Bn[pos] = (v & 8) >> 3
|
|
||||||
Bn[pos + 1] = (v & 4) >> 2
|
|
||||||
Bn[pos + 2] = (v & 2) >> 1
|
|
||||||
Bn[pos + 3] = v & 1
|
|
||||||
pos += 4
|
|
||||||
j += 1
|
|
||||||
self.R = self.__permutate(Des.__p, Bn)
|
|
||||||
self.R = [x ^ y for x, y in zip(self.R, self.L)]
|
|
||||||
self.L = tempR
|
|
||||||
i += 1
|
|
||||||
iteration += iteration_adjustment
|
|
||||||
self.final = self.__permutate(Des.__fp, self.R + self.L)
|
|
||||||
return self.final
|
|
||||||
def crypt(self, data, crypt_type):
|
|
||||||
if not data:
|
|
||||||
return ''
|
|
||||||
if len(data) % self.block_size != 0:
|
|
||||||
if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
|
|
||||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
|
|
||||||
if not self.getPadding():
|
|
||||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
|
|
||||||
else:
|
|
||||||
data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
|
|
||||||
if self.getMode() == CBC:
|
|
||||||
if self.getIV():
|
|
||||||
iv = self.__String_to_BitList(self.getIV())
|
|
||||||
else:
|
|
||||||
raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
|
|
||||||
i = 0
|
|
||||||
dict = {}
|
|
||||||
result = []
|
|
||||||
while i < len(data):
|
|
||||||
block = self.__String_to_BitList(data[i:i+8])
|
|
||||||
if self.getMode() == CBC:
|
|
||||||
if crypt_type == Des.ENCRYPT:
|
|
||||||
block = [x ^ y for x, y in zip(block, iv)]
|
|
||||||
processed_block = self.__des_crypt(block, crypt_type)
|
|
||||||
if crypt_type == Des.DECRYPT:
|
|
||||||
processed_block = [x ^ y for x, y in zip(processed_block, iv)]
|
|
||||||
iv = block
|
|
||||||
else:
|
|
||||||
iv = processed_block
|
|
||||||
else:
|
|
||||||
processed_block = self.__des_crypt(block, crypt_type)
|
|
||||||
result.append(self.__BitList_to_String(processed_block))
|
|
||||||
i += 8
|
|
||||||
if crypt_type == Des.DECRYPT and self.getPadding():
|
|
||||||
s = result[-1]
|
|
||||||
while s[-1] == self.getPadding():
|
|
||||||
s = s[:-1]
|
|
||||||
result[-1] = s
|
|
||||||
return ''.join(result)
|
|
||||||
def encrypt(self, data, pad=''):
|
|
||||||
self.__padding = pad
|
|
||||||
return self.crypt(data, Des.ENCRYPT)
|
|
||||||
def decrypt(self, data, pad=''):
|
|
||||||
self.__padding = pad
|
|
||||||
return self.crypt(data, Des.DECRYPT)
|
|
||||||
|
|
||||||
class Sectionizer(object):
|
class Sectionizer(object):
|
||||||
def __init__(self, filename, ident):
|
def __init__(self, filename, ident):
|
||||||
self.contents = file(filename, 'rb').read()
|
self.contents = file(filename, 'rb').read()
|
||||||
@@ -685,8 +472,5 @@ def main(argv=None):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
#import cProfile
|
|
||||||
#command = """sys.exit(main())"""
|
|
||||||
#cProfile.runctx( command, globals(), locals(), filename="cprofile.profile" )
|
|
||||||
|
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
||||||
|
|||||||
90
Calibre_Plugins/eReaderPDB2PML_plugin/openssl_des.py
Normal file
90
Calibre_Plugins/eReaderPDB2PML_plugin/openssl_des.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
|
# implement just enough of des from openssl to make erdr2pml.py happy
|
||||||
|
|
||||||
|
def load_libcrypto():
|
||||||
|
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_char, c_int, c_long, \
|
||||||
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
|
from ctypes.util import find_library
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
|
||||||
|
if libcrypto is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
|
||||||
|
# typedef struct DES_ks
|
||||||
|
# {
|
||||||
|
# union
|
||||||
|
# {
|
||||||
|
# DES_cblock cblock;
|
||||||
|
# /* make sure things are correct size on machines with
|
||||||
|
# * 8 byte longs */
|
||||||
|
# DES_LONG deslong[2];
|
||||||
|
# } ks[16];
|
||||||
|
# } DES_key_schedule;
|
||||||
|
|
||||||
|
# just create a big enough place to hold everything
|
||||||
|
# it will have alignment of structure so we should be okay (16 byte aligned?)
|
||||||
|
class DES_KEY_SCHEDULE(Structure):
|
||||||
|
_fields_ = [('DES_cblock1', c_char * 16),
|
||||||
|
('DES_cblock2', c_char * 16),
|
||||||
|
('DES_cblock3', c_char * 16),
|
||||||
|
('DES_cblock4', c_char * 16),
|
||||||
|
('DES_cblock5', c_char * 16),
|
||||||
|
('DES_cblock6', c_char * 16),
|
||||||
|
('DES_cblock7', c_char * 16),
|
||||||
|
('DES_cblock8', c_char * 16),
|
||||||
|
('DES_cblock9', c_char * 16),
|
||||||
|
('DES_cblock10', c_char * 16),
|
||||||
|
('DES_cblock11', c_char * 16),
|
||||||
|
('DES_cblock12', c_char * 16),
|
||||||
|
('DES_cblock13', c_char * 16),
|
||||||
|
('DES_cblock14', c_char * 16),
|
||||||
|
('DES_cblock15', c_char * 16),
|
||||||
|
('DES_cblock16', c_char * 16)]
|
||||||
|
|
||||||
|
DES_KEY_SCHEDULE_p = POINTER(DES_KEY_SCHEDULE)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
DES_set_key = F(None, 'DES_set_key',[c_char_p, DES_KEY_SCHEDULE_p])
|
||||||
|
DES_ecb_encrypt = F(None, 'DES_ecb_encrypt',[c_char_p, c_char_p, DES_KEY_SCHEDULE_p, c_int])
|
||||||
|
|
||||||
|
|
||||||
|
class DES(object):
|
||||||
|
def __init__(self, key):
|
||||||
|
if len(key) != 8 :
|
||||||
|
raise Error('DES improper key used')
|
||||||
|
return
|
||||||
|
self.key = key
|
||||||
|
self.keyschedule = DES_KEY_SCHEDULE()
|
||||||
|
DES_set_key(self.key, self.keyschedule)
|
||||||
|
def desdecrypt(self, data):
|
||||||
|
ob = create_string_buffer(len(data))
|
||||||
|
DES_ecb_encrypt(data, ob, self.keyschedule, 0)
|
||||||
|
return ob.raw
|
||||||
|
def decrypt(self, data):
|
||||||
|
if not data:
|
||||||
|
return ''
|
||||||
|
i = 0
|
||||||
|
result = []
|
||||||
|
while i < len(data):
|
||||||
|
block = data[i:i+8]
|
||||||
|
processed_block = self.desdecrypt(block)
|
||||||
|
result.append(processed_block)
|
||||||
|
i += 8
|
||||||
|
return ''.join(result)
|
||||||
|
|
||||||
|
return DES
|
||||||
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
K 25
|
|
||||||
svn:wc:ra_dav:version-url
|
|
||||||
V 41
|
|
||||||
/svn/!svn/ver/70200/psyco/dist/py-support
|
|
||||||
END
|
|
||||||
core.py
|
|
||||||
K 25
|
|
||||||
svn:wc:ra_dav:version-url
|
|
||||||
V 49
|
|
||||||
/svn/!svn/ver/70200/psyco/dist/py-support/core.py
|
|
||||||
END
|
|
||||||
support.py
|
|
||||||
K 25
|
|
||||||
svn:wc:ra_dav:version-url
|
|
||||||
V 52
|
|
||||||
/svn/!svn/ver/49315/psyco/dist/py-support/support.py
|
|
||||||
END
|
|
||||||
classes.py
|
|
||||||
K 25
|
|
||||||
svn:wc:ra_dav:version-url
|
|
||||||
V 52
|
|
||||||
/svn/!svn/ver/35003/psyco/dist/py-support/classes.py
|
|
||||||
END
|
|
||||||
__init__.py
|
|
||||||
K 25
|
|
||||||
svn:wc:ra_dav:version-url
|
|
||||||
V 53
|
|
||||||
/svn/!svn/ver/35003/psyco/dist/py-support/__init__.py
|
|
||||||
END
|
|
||||||
logger.py
|
|
||||||
K 25
|
|
||||||
svn:wc:ra_dav:version-url
|
|
||||||
V 51
|
|
||||||
/svn/!svn/ver/23284/psyco/dist/py-support/logger.py
|
|
||||||
END
|
|
||||||
kdictproxy.py
|
|
||||||
K 25
|
|
||||||
svn:wc:ra_dav:version-url
|
|
||||||
V 55
|
|
||||||
/svn/!svn/ver/35003/psyco/dist/py-support/kdictproxy.py
|
|
||||||
END
|
|
||||||
profiler.py
|
|
||||||
K 25
|
|
||||||
svn:wc:ra_dav:version-url
|
|
||||||
V 53
|
|
||||||
/svn/!svn/ver/70200/psyco/dist/py-support/profiler.py
|
|
||||||
END
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
K 10
|
|
||||||
svn:ignore
|
|
||||||
V 14
|
|
||||||
*~
|
|
||||||
*.pyc
|
|
||||||
*.pyo
|
|
||||||
END
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
10
|
|
||||||
|
|
||||||
dir
|
|
||||||
78269
|
|
||||||
http://codespeak.net/svn/psyco/dist/py-support
|
|
||||||
http://codespeak.net/svn
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2009-12-18T16:35:35.119276Z
|
|
||||||
70200
|
|
||||||
arigo
|
|
||||||
has-props
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fd0d7bf2-dfb6-0310-8d31-b7ecfe96aada
|
|
||||||
|
|
||||||
core.py
|
|
||||||
file
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2010-10-25T15:10:42.000000Z
|
|
||||||
3b362177a839893c9e867880b3a7cef3
|
|
||||||
2009-12-18T16:35:35.119276Z
|
|
||||||
70200
|
|
||||||
arigo
|
|
||||||
has-props
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
8144
|
|
||||||
|
|
||||||
support.py
|
|
||||||
file
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2010-10-25T15:10:42.000000Z
|
|
||||||
b0551e975d774f2f7f58a29ed4b6b90e
|
|
||||||
2007-12-03T12:27:25.632574Z
|
|
||||||
49315
|
|
||||||
arigo
|
|
||||||
has-props
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
6043
|
|
||||||
|
|
||||||
classes.py
|
|
||||||
file
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2010-10-25T15:10:42.000000Z
|
|
||||||
5932ed955198d16ec17285dfb195d341
|
|
||||||
2006-11-26T13:03:26.949973Z
|
|
||||||
35003
|
|
||||||
arigo
|
|
||||||
has-props
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1440
|
|
||||||
|
|
||||||
__init__.py
|
|
||||||
file
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2010-10-25T15:10:42.000000Z
|
|
||||||
219582b5182dfa38a9119d059a71965f
|
|
||||||
2006-11-26T13:03:26.949973Z
|
|
||||||
35003
|
|
||||||
arigo
|
|
||||||
has-props
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1895
|
|
||||||
|
|
||||||
logger.py
|
|
||||||
file
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2010-10-25T15:10:42.000000Z
|
|
||||||
aa21f905df036af43082e1ea2a2561ee
|
|
||||||
2006-02-13T15:02:51.744168Z
|
|
||||||
23284
|
|
||||||
arigo
|
|
||||||
has-props
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2678
|
|
||||||
|
|
||||||
kdictproxy.py
|
|
||||||
file
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2010-10-25T15:10:42.000000Z
|
|
||||||
1c8611748dcee5b29848bf25be3ec473
|
|
||||||
2006-11-26T13:03:26.949973Z
|
|
||||||
35003
|
|
||||||
arigo
|
|
||||||
has-props
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
4369
|
|
||||||
|
|
||||||
profiler.py
|
|
||||||
file
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2010-10-25T15:10:42.000000Z
|
|
||||||
858162366cbc39cd9e249e35e6f510c4
|
|
||||||
2009-12-18T16:35:35.119276Z
|
|
||||||
70200
|
|
||||||
arigo
|
|
||||||
has-props
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
11238
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
K 13
|
|
||||||
svn:eol-style
|
|
||||||
V 6
|
|
||||||
native
|
|
||||||
K 12
|
|
||||||
svn:keywords
|
|
||||||
V 23
|
|
||||||
Author Date Id Revision
|
|
||||||
END
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
K 13
|
|
||||||
svn:eol-style
|
|
||||||
V 6
|
|
||||||
native
|
|
||||||
K 12
|
|
||||||
svn:keywords
|
|
||||||
V 23
|
|
||||||
Author Date Id Revision
|
|
||||||
END
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
K 13
|
|
||||||
svn:eol-style
|
|
||||||
V 6
|
|
||||||
native
|
|
||||||
K 12
|
|
||||||
svn:keywords
|
|
||||||
V 23
|
|
||||||
Author Date Id Revision
|
|
||||||
END
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
K 13
|
|
||||||
svn:eol-style
|
|
||||||
V 6
|
|
||||||
native
|
|
||||||
K 12
|
|
||||||
svn:keywords
|
|
||||||
V 23
|
|
||||||
Author Date Id Revision
|
|
||||||
END
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
K 13
|
|
||||||
svn:eol-style
|
|
||||||
V 6
|
|
||||||
native
|
|
||||||
K 12
|
|
||||||
svn:keywords
|
|
||||||
V 23
|
|
||||||
Author Date Id Revision
|
|
||||||
END
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
K 13
|
|
||||||
svn:eol-style
|
|
||||||
V 6
|
|
||||||
native
|
|
||||||
K 12
|
|
||||||
svn:keywords
|
|
||||||
V 23
|
|
||||||
Author Date Id Revision
|
|
||||||
END
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
K 13
|
|
||||||
svn:eol-style
|
|
||||||
V 6
|
|
||||||
native
|
|
||||||
K 12
|
|
||||||
svn:keywords
|
|
||||||
V 23
|
|
||||||
Author Date Id Revision
|
|
||||||
END
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco top-level file of the Psyco package.
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco -- the Python Specializing Compiler.
|
|
||||||
|
|
||||||
Typical usage: add the following lines to your application's main module,
|
|
||||||
preferably after the other imports:
|
|
||||||
|
|
||||||
try:
|
|
||||||
import psyco
|
|
||||||
psyco.full()
|
|
||||||
except ImportError:
|
|
||||||
print 'Psyco not installed, the program will just run slower'
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# This module is present to make 'psyco' a package and to
|
|
||||||
# publish the main functions and variables.
|
|
||||||
#
|
|
||||||
# More documentation can be found in core.py.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Try to import the dynamic-loading _psyco and report errors
|
|
||||||
try:
|
|
||||||
import _psyco
|
|
||||||
except ImportError, e:
|
|
||||||
extramsg = ''
|
|
||||||
import sys, imp
|
|
||||||
try:
|
|
||||||
file, filename, (suffix, mode, type) = imp.find_module('_psyco', __path__)
|
|
||||||
except ImportError:
|
|
||||||
ext = [suffix for suffix, mode, type in imp.get_suffixes()
|
|
||||||
if type == imp.C_EXTENSION]
|
|
||||||
if ext:
|
|
||||||
extramsg = (" (cannot locate the compiled extension '_psyco%s' "
|
|
||||||
"in the package path '%s')" % (ext[0], '; '.join(__path__)))
|
|
||||||
else:
|
|
||||||
extramsg = (" (check that the compiled extension '%s' is for "
|
|
||||||
"the correct Python version; this is Python %s)" %
|
|
||||||
(filename, sys.version.split()[0]))
|
|
||||||
raise ImportError, str(e) + extramsg
|
|
||||||
|
|
||||||
# Publish important data by importing them in the package
|
|
||||||
from support import __version__, error, warning, _getrealframe, _getemulframe
|
|
||||||
from support import version_info, __version__ as hexversion
|
|
||||||
from core import full, profile, background, runonly, stop, cannotcompile
|
|
||||||
from core import log, bind, unbind, proxy, unproxy, dumpcodebuf
|
|
||||||
from _psyco import setfilter
|
|
||||||
from _psyco import compact, compacttype
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco class support module.
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco class support module.
|
|
||||||
|
|
||||||
'psyco.classes.psyobj' is an alternate Psyco-optimized root for classes.
|
|
||||||
Any class inheriting from it or using the metaclass '__metaclass__' might
|
|
||||||
get optimized specifically for Psyco. It is equivalent to call
|
|
||||||
psyco.bind() on the class object after its creation.
|
|
||||||
|
|
||||||
Importing everything from psyco.classes in a module will import the
|
|
||||||
'__metaclass__' name, so all classes defined after a
|
|
||||||
|
|
||||||
from psyco.classes import *
|
|
||||||
|
|
||||||
will automatically use the Psyco-optimized metaclass.
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
__all__ = ['psyobj', 'psymetaclass', '__metaclass__']
|
|
||||||
|
|
||||||
|
|
||||||
from _psyco import compacttype
|
|
||||||
import core
|
|
||||||
from types import FunctionType
|
|
||||||
|
|
||||||
class psymetaclass(compacttype):
|
|
||||||
"Psyco-optimized meta-class. Turns all methods into Psyco proxies."
|
|
||||||
|
|
||||||
def __new__(cls, name, bases, dict):
|
|
||||||
bindlist = dict.get('__psyco__bind__')
|
|
||||||
if bindlist is None:
|
|
||||||
bindlist = [key for key, value in dict.items()
|
|
||||||
if isinstance(value, FunctionType)]
|
|
||||||
for attr in bindlist:
|
|
||||||
dict[attr] = core.proxy(dict[attr])
|
|
||||||
return super(psymetaclass, cls).__new__(cls, name, bases, dict)
|
|
||||||
|
|
||||||
psyobj = psymetaclass("psyobj", (), {})
|
|
||||||
__metaclass__ = psymetaclass
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco main functions.
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco main functions.
|
|
||||||
|
|
||||||
Here are the routines that you can use from your applications.
|
|
||||||
These are mostly interfaces to the C core, but they depend on
|
|
||||||
the Python version.
|
|
||||||
|
|
||||||
You can use these functions from the 'psyco' module instead of
|
|
||||||
'psyco.core', e.g.
|
|
||||||
|
|
||||||
import psyco
|
|
||||||
psyco.log('/tmp/psyco.log')
|
|
||||||
psyco.profile()
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
import _psyco
|
|
||||||
import types
|
|
||||||
from support import *
|
|
||||||
|
|
||||||
newfunction = types.FunctionType
|
|
||||||
newinstancemethod = types.MethodType
|
|
||||||
|
|
||||||
|
|
||||||
# Default charge profiler values
|
|
||||||
default_watermark = 0.09 # between 0.0 (0%) and 1.0 (100%)
|
|
||||||
default_halflife = 0.5 # seconds
|
|
||||||
default_pollfreq_profile = 20 # Hz
|
|
||||||
default_pollfreq_background = 100 # Hz -- a maximum for sleep's resolution
|
|
||||||
default_parentframe = 0.25 # should not be more than 0.5 (50%)
|
|
||||||
|
|
||||||
|
|
||||||
def full(memory=None, time=None, memorymax=None, timemax=None):
|
|
||||||
"""Compile as much as possible.
|
|
||||||
|
|
||||||
Typical use is for small scripts performing intensive computations
|
|
||||||
or string handling."""
|
|
||||||
import profiler
|
|
||||||
p = profiler.FullCompiler()
|
|
||||||
p.run(memory, time, memorymax, timemax)
|
|
||||||
|
|
||||||
|
|
||||||
def profile(watermark = default_watermark,
|
|
||||||
halflife = default_halflife,
|
|
||||||
pollfreq = default_pollfreq_profile,
|
|
||||||
parentframe = default_parentframe,
|
|
||||||
memory=None, time=None, memorymax=None, timemax=None):
|
|
||||||
"""Turn on profiling.
|
|
||||||
|
|
||||||
The 'watermark' parameter controls how easily running functions will
|
|
||||||
be compiled. The smaller the value, the more functions are compiled."""
|
|
||||||
import profiler
|
|
||||||
p = profiler.ActivePassiveProfiler(watermark, halflife,
|
|
||||||
pollfreq, parentframe)
|
|
||||||
p.run(memory, time, memorymax, timemax)
|
|
||||||
|
|
||||||
|
|
||||||
def background(watermark = default_watermark,
|
|
||||||
halflife = default_halflife,
|
|
||||||
pollfreq = default_pollfreq_background,
|
|
||||||
parentframe = default_parentframe,
|
|
||||||
memory=None, time=None, memorymax=None, timemax=None):
|
|
||||||
"""Turn on passive profiling.
|
|
||||||
|
|
||||||
This is a very lightweight mode in which only intensively computing
|
|
||||||
functions can be detected. The smaller the 'watermark', the more functions
|
|
||||||
are compiled."""
|
|
||||||
import profiler
|
|
||||||
p = profiler.PassiveProfiler(watermark, halflife, pollfreq, parentframe)
|
|
||||||
p.run(memory, time, memorymax, timemax)
|
|
||||||
|
|
||||||
|
|
||||||
def runonly(memory=None, time=None, memorymax=None, timemax=None):
|
|
||||||
"""Nonprofiler.
|
|
||||||
|
|
||||||
XXX check if this is useful and document."""
|
|
||||||
import profiler
|
|
||||||
p = profiler.RunOnly()
|
|
||||||
p.run(memory, time, memorymax, timemax)
|
|
||||||
|
|
||||||
|
|
||||||
def stop():
|
|
||||||
"""Turn off all automatic compilation. bind() calls remain in effect."""
|
|
||||||
import profiler
|
|
||||||
profiler.go([])
|
|
||||||
|
|
||||||
|
|
||||||
def log(logfile='', mode='w', top=10):
|
|
||||||
"""Enable logging to the given file.
|
|
||||||
|
|
||||||
If the file name is unspecified, a default name is built by appending
|
|
||||||
a 'log-psyco' extension to the main script name.
|
|
||||||
|
|
||||||
Mode is 'a' to append to a possibly existing file or 'w' to overwrite
|
|
||||||
an existing file. Note that the log file may grow quickly in 'a' mode."""
|
|
||||||
import profiler, logger
|
|
||||||
if not logfile:
|
|
||||||
import os
|
|
||||||
logfile, dummy = os.path.splitext(sys.argv[0])
|
|
||||||
if os.path.basename(logfile):
|
|
||||||
logfile += '.'
|
|
||||||
logfile += 'log-psyco'
|
|
||||||
if hasattr(_psyco, 'VERBOSE_LEVEL'):
|
|
||||||
print >> sys.stderr, 'psyco: logging to', logfile
|
|
||||||
# logger.current should be a real file object; subtle problems
|
|
||||||
# will show up if its write() and flush() methods are written
|
|
||||||
# in Python, as Psyco will invoke them while compiling.
|
|
||||||
logger.current = open(logfile, mode)
|
|
||||||
logger.print_charges = top
|
|
||||||
profiler.logger = logger
|
|
||||||
logger.writedate('Logging started')
|
|
||||||
cannotcompile(logger.psycowrite)
|
|
||||||
_psyco.statwrite(logger=logger.psycowrite)
|
|
||||||
|
|
||||||
|
|
||||||
def bind(x, rec=None):
|
|
||||||
"""Enable compilation of the given function, method, or class object.
|
|
||||||
|
|
||||||
If C is a class (or anything with a '__dict__' attribute), bind(C) will
|
|
||||||
rebind all functions and methods found in C.__dict__ (which means, for
|
|
||||||
classes, all methods defined in the class but not in its parents).
|
|
||||||
|
|
||||||
The optional second argument specifies the number of recursive
|
|
||||||
compilation levels: all functions called by func are compiled
|
|
||||||
up to the given depth of indirection."""
|
|
||||||
if isinstance(x, types.MethodType):
|
|
||||||
x = x.im_func
|
|
||||||
if isinstance(x, types.FunctionType):
|
|
||||||
if rec is None:
|
|
||||||
x.func_code = _psyco.proxycode(x)
|
|
||||||
else:
|
|
||||||
x.func_code = _psyco.proxycode(x, rec)
|
|
||||||
return
|
|
||||||
if hasattr(x, '__dict__'):
|
|
||||||
funcs = [o for o in x.__dict__.values()
|
|
||||||
if isinstance(o, types.MethodType)
|
|
||||||
or isinstance(o, types.FunctionType)]
|
|
||||||
if not funcs:
|
|
||||||
raise error, ("nothing bindable found in %s object" %
|
|
||||||
type(x).__name__)
|
|
||||||
for o in funcs:
|
|
||||||
bind(o, rec)
|
|
||||||
return
|
|
||||||
raise TypeError, "cannot bind %s objects" % type(x).__name__
|
|
||||||
|
|
||||||
|
|
||||||
def unbind(x):
|
|
||||||
"""Reverse of bind()."""
|
|
||||||
if isinstance(x, types.MethodType):
|
|
||||||
x = x.im_func
|
|
||||||
if isinstance(x, types.FunctionType):
|
|
||||||
try:
|
|
||||||
f = _psyco.unproxycode(x.func_code)
|
|
||||||
except error:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
x.func_code = f.func_code
|
|
||||||
return
|
|
||||||
if hasattr(x, '__dict__'):
|
|
||||||
for o in x.__dict__.values():
|
|
||||||
if (isinstance(o, types.MethodType)
|
|
||||||
or isinstance(o, types.FunctionType)):
|
|
||||||
unbind(o)
|
|
||||||
return
|
|
||||||
raise TypeError, "cannot unbind %s objects" % type(x).__name__
|
|
||||||
|
|
||||||
|
|
||||||
def proxy(x, rec=None):
|
|
||||||
"""Return a Psyco-enabled copy of the function.
|
|
||||||
|
|
||||||
The original function is still available for non-compiled calls.
|
|
||||||
The optional second argument specifies the number of recursive
|
|
||||||
compilation levels: all functions called by func are compiled
|
|
||||||
up to the given depth of indirection."""
|
|
||||||
if isinstance(x, types.FunctionType):
|
|
||||||
if rec is None:
|
|
||||||
code = _psyco.proxycode(x)
|
|
||||||
else:
|
|
||||||
code = _psyco.proxycode(x, rec)
|
|
||||||
return newfunction(code, x.func_globals, x.func_name)
|
|
||||||
if isinstance(x, types.MethodType):
|
|
||||||
p = proxy(x.im_func, rec)
|
|
||||||
return newinstancemethod(p, x.im_self, x.im_class)
|
|
||||||
raise TypeError, "cannot proxy %s objects" % type(x).__name__
|
|
||||||
|
|
||||||
|
|
||||||
def unproxy(proxy):
|
|
||||||
"""Return a new copy of the original function of method behind a proxy.
|
|
||||||
The result behaves like the original function in that calling it
|
|
||||||
does not trigger compilation nor execution of any compiled code."""
|
|
||||||
if isinstance(proxy, types.FunctionType):
|
|
||||||
return _psyco.unproxycode(proxy.func_code)
|
|
||||||
if isinstance(proxy, types.MethodType):
|
|
||||||
f = unproxy(proxy.im_func)
|
|
||||||
return newinstancemethod(f, proxy.im_self, proxy.im_class)
|
|
||||||
raise TypeError, "%s objects cannot be proxies" % type(proxy).__name__
|
|
||||||
|
|
||||||
|
|
||||||
def cannotcompile(x):
|
|
||||||
"""Instruct Psyco never to compile the given function, method
|
|
||||||
or code object."""
|
|
||||||
if isinstance(x, types.MethodType):
|
|
||||||
x = x.im_func
|
|
||||||
if isinstance(x, types.FunctionType):
|
|
||||||
x = x.func_code
|
|
||||||
if isinstance(x, types.CodeType):
|
|
||||||
_psyco.cannotcompile(x)
|
|
||||||
else:
|
|
||||||
raise TypeError, "unexpected %s object" % type(x).__name__
|
|
||||||
|
|
||||||
|
|
||||||
def dumpcodebuf():
|
|
||||||
"""Write in file psyco.dump a copy of the emitted machine code,
|
|
||||||
provided Psyco was compiled with a non-zero CODE_DUMP.
|
|
||||||
See py-utils/httpxam.py to examine psyco.dump."""
|
|
||||||
if hasattr(_psyco, 'dumpcodebuf'):
|
|
||||||
_psyco.dumpcodebuf()
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
# Psyco variables
|
|
||||||
# error * the error raised by Psyco
|
|
||||||
# warning * the warning raised by Psyco
|
|
||||||
# __in_psyco__ * a new built-in variable which is always zero, but which
|
|
||||||
# Psyco special-cases by returning 1 instead. So
|
|
||||||
# __in_psyco__ can be used in a function to know if
|
|
||||||
# that function is being executed by Psyco or not.
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Support code for the 'psyco.compact' type.
|
|
||||||
|
|
||||||
from __future__ import generators
|
|
||||||
|
|
||||||
try:
|
|
||||||
from UserDict import DictMixin
|
|
||||||
except ImportError:
|
|
||||||
|
|
||||||
# backported from Python 2.3 to Python 2.2
|
|
||||||
class DictMixin:
|
|
||||||
# Mixin defining all dictionary methods for classes that already have
|
|
||||||
# a minimum dictionary interface including getitem, setitem, delitem,
|
|
||||||
# and keys. Without knowledge of the subclass constructor, the mixin
|
|
||||||
# does not define __init__() or copy(). In addition to the four base
|
|
||||||
# methods, progressively more efficiency comes with defining
|
|
||||||
# __contains__(), __iter__(), and iteritems().
|
|
||||||
|
|
||||||
# second level definitions support higher levels
|
|
||||||
def __iter__(self):
|
|
||||||
for k in self.keys():
|
|
||||||
yield k
|
|
||||||
def has_key(self, key):
|
|
||||||
try:
|
|
||||||
value = self[key]
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
def __contains__(self, key):
|
|
||||||
return self.has_key(key)
|
|
||||||
|
|
||||||
# third level takes advantage of second level definitions
|
|
||||||
def iteritems(self):
|
|
||||||
for k in self:
|
|
||||||
yield (k, self[k])
|
|
||||||
def iterkeys(self):
|
|
||||||
return self.__iter__()
|
|
||||||
|
|
||||||
# fourth level uses definitions from lower levels
|
|
||||||
def itervalues(self):
|
|
||||||
for _, v in self.iteritems():
|
|
||||||
yield v
|
|
||||||
def values(self):
|
|
||||||
return [v for _, v in self.iteritems()]
|
|
||||||
def items(self):
|
|
||||||
return list(self.iteritems())
|
|
||||||
def clear(self):
|
|
||||||
for key in self.keys():
|
|
||||||
del self[key]
|
|
||||||
def setdefault(self, key, default):
|
|
||||||
try:
|
|
||||||
return self[key]
|
|
||||||
except KeyError:
|
|
||||||
self[key] = default
|
|
||||||
return default
|
|
||||||
def pop(self, key, *args):
|
|
||||||
if len(args) > 1:
|
|
||||||
raise TypeError, "pop expected at most 2 arguments, got "\
|
|
||||||
+ repr(1 + len(args))
|
|
||||||
try:
|
|
||||||
value = self[key]
|
|
||||||
except KeyError:
|
|
||||||
if args:
|
|
||||||
return args[0]
|
|
||||||
raise
|
|
||||||
del self[key]
|
|
||||||
return value
|
|
||||||
def popitem(self):
|
|
||||||
try:
|
|
||||||
k, v = self.iteritems().next()
|
|
||||||
except StopIteration:
|
|
||||||
raise KeyError, 'container is empty'
|
|
||||||
del self[k]
|
|
||||||
return (k, v)
|
|
||||||
def update(self, other):
|
|
||||||
# Make progressively weaker assumptions about "other"
|
|
||||||
if hasattr(other, 'iteritems'): # iteritems saves memory and lookups
|
|
||||||
for k, v in other.iteritems():
|
|
||||||
self[k] = v
|
|
||||||
elif hasattr(other, '__iter__'): # iter saves memory
|
|
||||||
for k in other:
|
|
||||||
self[k] = other[k]
|
|
||||||
else:
|
|
||||||
for k in other.keys():
|
|
||||||
self[k] = other[k]
|
|
||||||
def get(self, key, default=None):
|
|
||||||
try:
|
|
||||||
return self[key]
|
|
||||||
except KeyError:
|
|
||||||
return default
|
|
||||||
def __repr__(self):
|
|
||||||
return repr(dict(self.iteritems()))
|
|
||||||
def __cmp__(self, other):
|
|
||||||
if other is None:
|
|
||||||
return 1
|
|
||||||
if isinstance(other, DictMixin):
|
|
||||||
other = dict(other.iteritems())
|
|
||||||
return cmp(dict(self.iteritems()), other)
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.keys())
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
from _psyco import compact
|
|
||||||
|
|
||||||
|
|
||||||
class compactdictproxy(DictMixin):
|
|
||||||
|
|
||||||
def __init__(self, ko):
|
|
||||||
self._ko = ko # compact object of which 'self' is the dict
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return compact.__getslot__(self._ko, key)
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
compact.__setslot__(self._ko, key, value)
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
|
||||||
compact.__delslot__(self._ko, key)
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return compact.__members__.__get__(self._ko)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
keys = self.keys()
|
|
||||||
keys.reverse()
|
|
||||||
for key in keys:
|
|
||||||
del self[key]
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
keys = ', '.join(self.keys())
|
|
||||||
return '<compactdictproxy object {%s}>' % (keys,)
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco logger.
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco logger.
|
|
||||||
|
|
||||||
See log() in core.py.
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
|
|
||||||
import _psyco
|
|
||||||
from time import time, localtime, strftime
|
|
||||||
|
|
||||||
|
|
||||||
current = None
|
|
||||||
print_charges = 10
|
|
||||||
dump_delay = 0.2
|
|
||||||
dump_last = 0.0
|
|
||||||
|
|
||||||
def write(s, level):
|
|
||||||
t = time()
|
|
||||||
f = t-int(t)
|
|
||||||
try:
|
|
||||||
current.write("%s.%02d %-*s%s\n" % (
|
|
||||||
strftime("%X", localtime(int(t))),
|
|
||||||
int(f*100.0), 63-level, s,
|
|
||||||
"%"*level))
|
|
||||||
current.flush()
|
|
||||||
except (OSError, IOError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def psycowrite(s):
|
|
||||||
t = time()
|
|
||||||
f = t-int(t)
|
|
||||||
try:
|
|
||||||
current.write("%s.%02d %-*s%s\n" % (
|
|
||||||
strftime("%X", localtime(int(t))),
|
|
||||||
int(f*100.0), 60, s.strip(),
|
|
||||||
"% %"))
|
|
||||||
current.flush()
|
|
||||||
except (OSError, IOError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
##def writelines(lines, level=0):
|
|
||||||
## if lines:
|
|
||||||
## t = time()
|
|
||||||
## f = t-int(t)
|
|
||||||
## timedesc = strftime("%x %X", localtime(int(t)))
|
|
||||||
## print >> current, "%s.%03d %-*s %s" % (
|
|
||||||
## timedesc, int(f*1000),
|
|
||||||
## 50-level, lines[0],
|
|
||||||
## "+"*level)
|
|
||||||
## timedesc = " " * (len(timedesc)+5)
|
|
||||||
## for line in lines[1:]:
|
|
||||||
## print >> current, timedesc, line
|
|
||||||
|
|
||||||
def writememory():
|
|
||||||
write("memory usage: %d+ kb" % _psyco.memory(), 1)
|
|
||||||
|
|
||||||
def dumpcharges():
|
|
||||||
global dump_last
|
|
||||||
if print_charges:
|
|
||||||
t = time()
|
|
||||||
if not (dump_last <= t < dump_last+dump_delay):
|
|
||||||
if t <= dump_last+1.5*dump_delay:
|
|
||||||
dump_last += dump_delay
|
|
||||||
else:
|
|
||||||
dump_last = t
|
|
||||||
#write("%s: charges:" % who, 0)
|
|
||||||
lst = _psyco.stattop(print_charges)
|
|
||||||
if lst:
|
|
||||||
f = t-int(t)
|
|
||||||
lines = ["%s.%02d ______\n" % (
|
|
||||||
strftime("%X", localtime(int(t))),
|
|
||||||
int(f*100.0))]
|
|
||||||
i = 1
|
|
||||||
for co, charge in lst:
|
|
||||||
detail = co.co_filename
|
|
||||||
if len(detail) > 19:
|
|
||||||
detail = '...' + detail[-17:]
|
|
||||||
lines.append(" #%-3d |%4.1f %%| %-26s%20s:%d\n" %
|
|
||||||
(i, charge*100.0, co.co_name, detail,
|
|
||||||
co.co_firstlineno))
|
|
||||||
i += 1
|
|
||||||
current.writelines(lines)
|
|
||||||
current.flush()
|
|
||||||
|
|
||||||
def writefinalstats():
|
|
||||||
dumpcharges()
|
|
||||||
writememory()
|
|
||||||
writedate("program exit")
|
|
||||||
|
|
||||||
def writedate(msg):
|
|
||||||
write('%s, %s' % (msg, strftime("%x")), 20)
|
|
||||||
@@ -1,379 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco profiler (Python part).
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco profiler (Python part).
|
|
||||||
|
|
||||||
The implementation of the non-time-critical parts of the profiler.
|
|
||||||
See profile() and full() in core.py for the easy interface.
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
import _psyco
|
|
||||||
from support import *
|
|
||||||
import math, time, types, atexit
|
|
||||||
now = time.time
|
|
||||||
try:
|
|
||||||
import thread
|
|
||||||
except ImportError:
|
|
||||||
import dummy_thread as thread
|
|
||||||
|
|
||||||
|
|
||||||
# current profiler instance
|
|
||||||
current = None
|
|
||||||
|
|
||||||
# enabled profilers, in order of priority
|
|
||||||
profilers = []
|
|
||||||
|
|
||||||
# logger module (when enabled by core.log())
|
|
||||||
logger = None
|
|
||||||
|
|
||||||
# a lock for a thread-safe go()
|
|
||||||
go_lock = thread.allocate_lock()
|
|
||||||
|
|
||||||
def go(stop=0):
|
|
||||||
# run the highest-priority profiler in 'profilers'
|
|
||||||
global current
|
|
||||||
go_lock.acquire()
|
|
||||||
try:
|
|
||||||
prev = current
|
|
||||||
if stop:
|
|
||||||
del profilers[:]
|
|
||||||
if prev:
|
|
||||||
if profilers and profilers[0] is prev:
|
|
||||||
return # best profiler already running
|
|
||||||
prev.stop()
|
|
||||||
current = None
|
|
||||||
for p in profilers[:]:
|
|
||||||
if p.start():
|
|
||||||
current = p
|
|
||||||
if logger: # and p is not prev:
|
|
||||||
logger.write("%s: starting" % p.__class__.__name__, 5)
|
|
||||||
return
|
|
||||||
finally:
|
|
||||||
go_lock.release()
|
|
||||||
# no profiler is running now
|
|
||||||
if stop:
|
|
||||||
if logger:
|
|
||||||
logger.writefinalstats()
|
|
||||||
else:
|
|
||||||
tag2bind()
|
|
||||||
|
|
||||||
atexit.register(go, 1)
|
|
||||||
|
|
||||||
|
|
||||||
def buildfncache(globals, cache):
|
|
||||||
if hasattr(types.IntType, '__dict__'):
|
|
||||||
clstypes = (types.ClassType, types.TypeType)
|
|
||||||
else:
|
|
||||||
clstypes = types.ClassType
|
|
||||||
for x in globals.values():
|
|
||||||
if isinstance(x, types.MethodType):
|
|
||||||
x = x.im_func
|
|
||||||
if isinstance(x, types.FunctionType):
|
|
||||||
cache[x.func_code] = x, ''
|
|
||||||
elif isinstance(x, clstypes):
|
|
||||||
for y in x.__dict__.values():
|
|
||||||
if isinstance(y, types.MethodType):
|
|
||||||
y = y.im_func
|
|
||||||
if isinstance(y, types.FunctionType):
|
|
||||||
cache[y.func_code] = y, x.__name__
|
|
||||||
|
|
||||||
# code-to-function mapping (cache)
|
|
||||||
function_cache = {}
|
|
||||||
|
|
||||||
def trytobind(co, globals, log=1):
|
|
||||||
try:
|
|
||||||
f, clsname = function_cache[co]
|
|
||||||
except KeyError:
|
|
||||||
buildfncache(globals, function_cache)
|
|
||||||
try:
|
|
||||||
f, clsname = function_cache[co]
|
|
||||||
except KeyError:
|
|
||||||
if logger:
|
|
||||||
logger.write('warning: cannot find function %s in %s' %
|
|
||||||
(co.co_name, globals.get('__name__', '?')), 3)
|
|
||||||
return # give up
|
|
||||||
if logger and log:
|
|
||||||
modulename = globals.get('__name__', '?')
|
|
||||||
if clsname:
|
|
||||||
modulename += '.' + clsname
|
|
||||||
logger.write('bind function: %s.%s' % (modulename, co.co_name), 1)
|
|
||||||
f.func_code = _psyco.proxycode(f)
|
|
||||||
|
|
||||||
|
|
||||||
# the list of code objects that have been tagged
|
|
||||||
tagged_codes = []
|
|
||||||
|
|
||||||
def tag(co, globals):
|
|
||||||
if logger:
|
|
||||||
try:
|
|
||||||
f, clsname = function_cache[co]
|
|
||||||
except KeyError:
|
|
||||||
buildfncache(globals, function_cache)
|
|
||||||
try:
|
|
||||||
f, clsname = function_cache[co]
|
|
||||||
except KeyError:
|
|
||||||
clsname = '' # give up
|
|
||||||
modulename = globals.get('__name__', '?')
|
|
||||||
if clsname:
|
|
||||||
modulename += '.' + clsname
|
|
||||||
logger.write('tag function: %s.%s' % (modulename, co.co_name), 1)
|
|
||||||
tagged_codes.append((co, globals))
|
|
||||||
_psyco.turbo_frame(co)
|
|
||||||
_psyco.turbo_code(co)
|
|
||||||
|
|
||||||
def tag2bind():
|
|
||||||
if tagged_codes:
|
|
||||||
if logger:
|
|
||||||
logger.write('profiling stopped, binding %d functions' %
|
|
||||||
len(tagged_codes), 2)
|
|
||||||
for co, globals in tagged_codes:
|
|
||||||
trytobind(co, globals, 0)
|
|
||||||
function_cache.clear()
|
|
||||||
del tagged_codes[:]
|
|
||||||
|
|
||||||
|
|
||||||
class Profiler:
|
|
||||||
MemoryTimerResolution = 0.103
|
|
||||||
|
|
||||||
def run(self, memory, time, memorymax, timemax):
|
|
||||||
self.memory = memory
|
|
||||||
self.memorymax = memorymax
|
|
||||||
self.time = time
|
|
||||||
if timemax is None:
|
|
||||||
self.endtime = None
|
|
||||||
else:
|
|
||||||
self.endtime = now() + timemax
|
|
||||||
self.alarms = []
|
|
||||||
profilers.append(self)
|
|
||||||
go()
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
curmem = _psyco.memory()
|
|
||||||
memlimits = []
|
|
||||||
if self.memorymax is not None:
|
|
||||||
if curmem >= self.memorymax:
|
|
||||||
if logger:
|
|
||||||
logger.writememory()
|
|
||||||
return self.limitreached('memorymax')
|
|
||||||
memlimits.append(self.memorymax)
|
|
||||||
if self.memory is not None:
|
|
||||||
if self.memory <= 0:
|
|
||||||
if logger:
|
|
||||||
logger.writememory()
|
|
||||||
return self.limitreached('memory')
|
|
||||||
memlimits.append(curmem + self.memory)
|
|
||||||
self.memory_at_start = curmem
|
|
||||||
|
|
||||||
curtime = now()
|
|
||||||
timelimits = []
|
|
||||||
if self.endtime is not None:
|
|
||||||
if curtime >= self.endtime:
|
|
||||||
return self.limitreached('timemax')
|
|
||||||
timelimits.append(self.endtime - curtime)
|
|
||||||
if self.time is not None:
|
|
||||||
if self.time <= 0.0:
|
|
||||||
return self.limitreached('time')
|
|
||||||
timelimits.append(self.time)
|
|
||||||
self.time_at_start = curtime
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.do_start()
|
|
||||||
except error, e:
|
|
||||||
if logger:
|
|
||||||
logger.write('%s: disabled by psyco.error:' % (
|
|
||||||
self.__class__.__name__), 4)
|
|
||||||
logger.write(' %s' % str(e), 3)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if memlimits:
|
|
||||||
self.memlimits_args = (time.sleep, (self.MemoryTimerResolution,),
|
|
||||||
self.check_memory, (min(memlimits),))
|
|
||||||
self.alarms.append(_psyco.alarm(*self.memlimits_args))
|
|
||||||
if timelimits:
|
|
||||||
self.alarms.append(_psyco.alarm(time.sleep, (min(timelimits),),
|
|
||||||
self.time_out))
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
for alarm in self.alarms:
|
|
||||||
alarm.stop(0)
|
|
||||||
for alarm in self.alarms:
|
|
||||||
alarm.stop(1) # wait for parallel threads to stop
|
|
||||||
del self.alarms[:]
|
|
||||||
if self.time is not None:
|
|
||||||
self.time -= now() - self.time_at_start
|
|
||||||
if self.memory is not None:
|
|
||||||
self.memory -= _psyco.memory() - self.memory_at_start
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.do_stop()
|
|
||||||
except error:
|
|
||||||
return 0
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def check_memory(self, limit):
|
|
||||||
if _psyco.memory() < limit:
|
|
||||||
return self.memlimits_args
|
|
||||||
go()
|
|
||||||
|
|
||||||
def time_out(self):
|
|
||||||
self.time = 0.0
|
|
||||||
go()
|
|
||||||
|
|
||||||
def limitreached(self, limitname):
|
|
||||||
try:
|
|
||||||
profilers.remove(self)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
if logger:
|
|
||||||
logger.write('%s: disabled (%s limit reached)' % (
|
|
||||||
self.__class__.__name__, limitname), 4)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
class FullCompiler(Profiler):
|
|
||||||
|
|
||||||
def do_start(self):
|
|
||||||
_psyco.profiling('f')
|
|
||||||
|
|
||||||
def do_stop(self):
|
|
||||||
_psyco.profiling('.')
|
|
||||||
|
|
||||||
|
|
||||||
class RunOnly(Profiler):
|
|
||||||
|
|
||||||
def do_start(self):
|
|
||||||
_psyco.profiling('n')
|
|
||||||
|
|
||||||
def do_stop(self):
|
|
||||||
_psyco.profiling('.')
|
|
||||||
|
|
||||||
|
|
||||||
class ChargeProfiler(Profiler):
|
|
||||||
|
|
||||||
def __init__(self, watermark, parentframe):
|
|
||||||
self.watermark = watermark
|
|
||||||
self.parent2 = parentframe * 2.0
|
|
||||||
self.lock = thread.allocate_lock()
|
|
||||||
|
|
||||||
def init_charges(self):
|
|
||||||
_psyco.statwrite(watermark = self.watermark,
|
|
||||||
parent2 = self.parent2)
|
|
||||||
|
|
||||||
def do_stop(self):
|
|
||||||
_psyco.profiling('.')
|
|
||||||
_psyco.statwrite(callback = None)
|
|
||||||
|
|
||||||
|
|
||||||
class ActiveProfiler(ChargeProfiler):
|
|
||||||
|
|
||||||
def active_start(self):
|
|
||||||
_psyco.profiling('p')
|
|
||||||
|
|
||||||
def do_start(self):
|
|
||||||
self.init_charges()
|
|
||||||
self.active_start()
|
|
||||||
_psyco.statwrite(callback = self.charge_callback)
|
|
||||||
|
|
||||||
def charge_callback(self, frame, charge):
|
|
||||||
tag(frame.f_code, frame.f_globals)
|
|
||||||
|
|
||||||
|
|
||||||
class PassiveProfiler(ChargeProfiler):
|
|
||||||
|
|
||||||
initial_charge_unit = _psyco.statread('unit')
|
|
||||||
reset_stats_after = 120 # half-lives (maximum 200!)
|
|
||||||
reset_limit = initial_charge_unit * (2.0 ** reset_stats_after)
|
|
||||||
|
|
||||||
def __init__(self, watermark, halflife, pollfreq, parentframe):
|
|
||||||
ChargeProfiler.__init__(self, watermark, parentframe)
|
|
||||||
self.pollfreq = pollfreq
|
|
||||||
# self.progress is slightly more than 1.0, and computed so that
|
|
||||||
# do_profile() will double the change_unit every 'halflife' seconds.
|
|
||||||
self.progress = 2.0 ** (1.0 / (halflife * pollfreq))
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
_psyco.statwrite(unit = self.initial_charge_unit, callback = None)
|
|
||||||
_psyco.statreset()
|
|
||||||
if logger:
|
|
||||||
logger.write("%s: resetting stats" % self.__class__.__name__, 1)
|
|
||||||
|
|
||||||
def passive_start(self):
|
|
||||||
self.passivealarm_args = (time.sleep, (1.0 / self.pollfreq,),
|
|
||||||
self.do_profile)
|
|
||||||
self.alarms.append(_psyco.alarm(*self.passivealarm_args))
|
|
||||||
|
|
||||||
def do_start(self):
|
|
||||||
tag2bind()
|
|
||||||
self.init_charges()
|
|
||||||
self.passive_start()
|
|
||||||
|
|
||||||
def do_profile(self):
|
|
||||||
_psyco.statcollect()
|
|
||||||
if logger:
|
|
||||||
logger.dumpcharges()
|
|
||||||
nunit = _psyco.statread('unit') * self.progress
|
|
||||||
if nunit > self.reset_limit:
|
|
||||||
self.reset()
|
|
||||||
else:
|
|
||||||
_psyco.statwrite(unit = nunit, callback = self.charge_callback)
|
|
||||||
return self.passivealarm_args
|
|
||||||
|
|
||||||
def charge_callback(self, frame, charge):
|
|
||||||
trytobind(frame.f_code, frame.f_globals)
|
|
||||||
|
|
||||||
|
|
||||||
class ActivePassiveProfiler(PassiveProfiler, ActiveProfiler):
|
|
||||||
|
|
||||||
def do_start(self):
|
|
||||||
self.init_charges()
|
|
||||||
self.active_start()
|
|
||||||
self.passive_start()
|
|
||||||
|
|
||||||
def charge_callback(self, frame, charge):
|
|
||||||
tag(frame.f_code, frame.f_globals)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# we register our own version of sys.settrace(), sys.setprofile()
|
|
||||||
# and thread.start_new_thread().
|
|
||||||
#
|
|
||||||
|
|
||||||
def psyco_settrace(*args, **kw):
|
|
||||||
"This is the Psyco-aware version of sys.settrace()."
|
|
||||||
result = original_settrace(*args, **kw)
|
|
||||||
go()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def psyco_setprofile(*args, **kw):
|
|
||||||
"This is the Psyco-aware version of sys.setprofile()."
|
|
||||||
result = original_setprofile(*args, **kw)
|
|
||||||
go()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def psyco_thread_stub(callable, args, kw):
|
|
||||||
_psyco.statcollect()
|
|
||||||
if kw is None:
|
|
||||||
return callable(*args)
|
|
||||||
else:
|
|
||||||
return callable(*args, **kw)
|
|
||||||
|
|
||||||
def psyco_start_new_thread(callable, args, kw=None):
|
|
||||||
"This is the Psyco-aware version of thread.start_new_thread()."
|
|
||||||
return original_start_new_thread(psyco_thread_stub, (callable, args, kw))
|
|
||||||
|
|
||||||
original_settrace = sys.settrace
|
|
||||||
original_setprofile = sys.setprofile
|
|
||||||
original_start_new_thread = thread.start_new_thread
|
|
||||||
sys.settrace = psyco_settrace
|
|
||||||
sys.setprofile = psyco_setprofile
|
|
||||||
thread.start_new_thread = psyco_start_new_thread
|
|
||||||
# hack to patch threading._start_new_thread if the module is
|
|
||||||
# already loaded
|
|
||||||
if ('threading' in sys.modules and
|
|
||||||
hasattr(sys.modules['threading'], '_start_new_thread')):
|
|
||||||
sys.modules['threading']._start_new_thread = psyco_start_new_thread
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco general support module.
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco general support module.
|
|
||||||
|
|
||||||
For internal use.
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
import sys, _psyco, __builtin__
|
|
||||||
|
|
||||||
error = _psyco.error
|
|
||||||
class warning(Warning):
|
|
||||||
pass
|
|
||||||
|
|
||||||
_psyco.NoLocalsWarning = warning
|
|
||||||
|
|
||||||
def warn(msg):
|
|
||||||
from warnings import warn
|
|
||||||
warn(msg, warning, stacklevel=2)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Version checks
|
|
||||||
#
|
|
||||||
__version__ = 0x010600f0
|
|
||||||
if _psyco.PSYVER != __version__:
|
|
||||||
raise error, "version mismatch between Psyco parts, reinstall it"
|
|
||||||
|
|
||||||
version_info = (__version__ >> 24,
|
|
||||||
(__version__ >> 16) & 0xff,
|
|
||||||
(__version__ >> 8) & 0xff,
|
|
||||||
{0xa0: 'alpha',
|
|
||||||
0xb0: 'beta',
|
|
||||||
0xc0: 'candidate',
|
|
||||||
0xf0: 'final'}[__version__ & 0xf0],
|
|
||||||
__version__ & 0xf)
|
|
||||||
|
|
||||||
|
|
||||||
VERSION_LIMITS = [0x02020200, # 2.2.2
|
|
||||||
0x02030000, # 2.3
|
|
||||||
0x02040000] # 2.4
|
|
||||||
|
|
||||||
if ([v for v in VERSION_LIMITS if v <= sys.hexversion] !=
|
|
||||||
[v for v in VERSION_LIMITS if v <= _psyco.PYVER ]):
|
|
||||||
if sys.hexversion < VERSION_LIMITS[0]:
|
|
||||||
warn("Psyco requires Python version 2.2.2 or later")
|
|
||||||
else:
|
|
||||||
warn("Psyco version does not match Python version. "
|
|
||||||
"Psyco must be updated or recompiled")
|
|
||||||
|
|
||||||
|
|
||||||
if hasattr(_psyco, 'ALL_CHECKS') and hasattr(_psyco, 'VERBOSE_LEVEL'):
|
|
||||||
print >> sys.stderr, ('psyco: running in debugging mode on %s' %
|
|
||||||
_psyco.PROCESSOR)
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
# sys._getframe() gives strange results on a mixed Psyco- and Python-style
|
|
||||||
# stack frame. Psyco provides a replacement that partially emulates Python
|
|
||||||
# frames from Psyco frames. The new sys._getframe() may return objects of
|
|
||||||
# a custom "Psyco frame" type, which is a subtype of the normal frame type.
|
|
||||||
#
|
|
||||||
# The same problems require some other built-in functions to be replaced
|
|
||||||
# as well. Note that the local variables are not available in any
|
|
||||||
# dictionary with Psyco.
|
|
||||||
|
|
||||||
|
|
||||||
class Frame:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PythonFrame(Frame):
|
|
||||||
|
|
||||||
def __init__(self, frame):
|
|
||||||
self.__dict__.update({
|
|
||||||
'_frame': frame,
|
|
||||||
})
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
if attr == 'f_back':
|
|
||||||
try:
|
|
||||||
result = embedframe(_psyco.getframe(self._frame))
|
|
||||||
except ValueError:
|
|
||||||
result = None
|
|
||||||
except error:
|
|
||||||
warn("f_back is skipping dead Psyco frames")
|
|
||||||
result = self._frame.f_back
|
|
||||||
self.__dict__['f_back'] = result
|
|
||||||
return result
|
|
||||||
else:
|
|
||||||
return getattr(self._frame, attr)
|
|
||||||
|
|
||||||
def __setattr__(self, attr, value):
|
|
||||||
setattr(self._frame, attr, value)
|
|
||||||
|
|
||||||
def __delattr__(self, attr):
|
|
||||||
delattr(self._frame, attr)
|
|
||||||
|
|
||||||
|
|
||||||
class PsycoFrame(Frame):
|
|
||||||
|
|
||||||
def __init__(self, tag):
|
|
||||||
self.__dict__.update({
|
|
||||||
'_tag' : tag,
|
|
||||||
'f_code' : tag[0],
|
|
||||||
'f_globals': tag[1],
|
|
||||||
})
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
if attr == 'f_back':
|
|
||||||
try:
|
|
||||||
result = embedframe(_psyco.getframe(self._tag))
|
|
||||||
except ValueError:
|
|
||||||
result = None
|
|
||||||
elif attr == 'f_lineno':
|
|
||||||
result = self.f_code.co_firstlineno # better than nothing
|
|
||||||
elif attr == 'f_builtins':
|
|
||||||
result = self.f_globals['__builtins__']
|
|
||||||
elif attr == 'f_restricted':
|
|
||||||
result = self.f_builtins is not __builtins__
|
|
||||||
elif attr == 'f_locals':
|
|
||||||
raise AttributeError, ("local variables of functions run by Psyco "
|
|
||||||
"cannot be accessed in any way, sorry")
|
|
||||||
else:
|
|
||||||
raise AttributeError, ("emulated Psyco frames have "
|
|
||||||
"no '%s' attribute" % attr)
|
|
||||||
self.__dict__[attr] = result
|
|
||||||
return result
|
|
||||||
|
|
||||||
def __setattr__(self, attr, value):
|
|
||||||
raise AttributeError, "Psyco frame objects are read-only"
|
|
||||||
|
|
||||||
def __delattr__(self, attr):
|
|
||||||
if attr == 'f_trace':
|
|
||||||
# for bdb which relies on CPython frames exhibiting a slightly
|
|
||||||
# buggy behavior: you can 'del f.f_trace' as often as you like
|
|
||||||
# even without having set it previously.
|
|
||||||
return
|
|
||||||
raise AttributeError, "Psyco frame objects are read-only"
|
|
||||||
|
|
||||||
|
|
||||||
def embedframe(result):
|
|
||||||
if type(result) is type(()):
|
|
||||||
return PsycoFrame(result)
|
|
||||||
else:
|
|
||||||
return PythonFrame(result)
|
|
||||||
|
|
||||||
def _getframe(depth=0):
|
|
||||||
"""Return a frame object from the call stack. This is a replacement for
|
|
||||||
sys._getframe() which is aware of Psyco frames.
|
|
||||||
|
|
||||||
The returned objects are instances of either PythonFrame or PsycoFrame
|
|
||||||
instead of being real Python-level frame object, so that they can emulate
|
|
||||||
the common attributes of frame objects.
|
|
||||||
|
|
||||||
The original sys._getframe() ignoring Psyco frames altogether is stored in
|
|
||||||
psyco._getrealframe(). See also psyco._getemulframe()."""
|
|
||||||
# 'depth+1' to account for this _getframe() Python function
|
|
||||||
return embedframe(_psyco.getframe(depth+1))
|
|
||||||
|
|
||||||
def _getemulframe(depth=0):
|
|
||||||
"""As _getframe(), but the returned objects are real Python frame objects
|
|
||||||
emulating Psyco frames. Some of their attributes can be wrong or missing,
|
|
||||||
however."""
|
|
||||||
# 'depth+1' to account for this _getemulframe() Python function
|
|
||||||
return _psyco.getframe(depth+1, 1)
|
|
||||||
|
|
||||||
def patch(name, module=__builtin__):
|
|
||||||
f = getattr(_psyco, name)
|
|
||||||
org = getattr(module, name)
|
|
||||||
if org is not f:
|
|
||||||
setattr(module, name, f)
|
|
||||||
setattr(_psyco, 'original_' + name, org)
|
|
||||||
|
|
||||||
_getrealframe = sys._getframe
|
|
||||||
sys._getframe = _getframe
|
|
||||||
patch('globals')
|
|
||||||
patch('eval')
|
|
||||||
patch('execfile')
|
|
||||||
patch('locals')
|
|
||||||
patch('vars')
|
|
||||||
patch('dir')
|
|
||||||
patch('input')
|
|
||||||
_psyco.original_raw_input = raw_input
|
|
||||||
__builtin__.__in_psyco__ = 0==1 # False
|
|
||||||
|
|
||||||
if hasattr(_psyco, 'compact'):
|
|
||||||
import kdictproxy
|
|
||||||
_psyco.compactdictproxy = kdictproxy.compactdictproxy
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco top-level file of the Psyco package.
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco -- the Python Specializing Compiler.
|
|
||||||
|
|
||||||
Typical usage: add the following lines to your application's main module,
|
|
||||||
preferably after the other imports:
|
|
||||||
|
|
||||||
try:
|
|
||||||
import psyco
|
|
||||||
psyco.full()
|
|
||||||
except ImportError:
|
|
||||||
print 'Psyco not installed, the program will just run slower'
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# This module is present to make 'psyco' a package and to
|
|
||||||
# publish the main functions and variables.
|
|
||||||
#
|
|
||||||
# More documentation can be found in core.py.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
# Try to import the dynamic-loading _psyco and report errors
|
|
||||||
try:
|
|
||||||
import _psyco
|
|
||||||
except ImportError, e:
|
|
||||||
extramsg = ''
|
|
||||||
import sys, imp
|
|
||||||
try:
|
|
||||||
file, filename, (suffix, mode, type) = imp.find_module('_psyco', __path__)
|
|
||||||
except ImportError:
|
|
||||||
ext = [suffix for suffix, mode, type in imp.get_suffixes()
|
|
||||||
if type == imp.C_EXTENSION]
|
|
||||||
if ext:
|
|
||||||
extramsg = (" (cannot locate the compiled extension '_psyco%s' "
|
|
||||||
"in the package path '%s')" % (ext[0], '; '.join(__path__)))
|
|
||||||
else:
|
|
||||||
extramsg = (" (check that the compiled extension '%s' is for "
|
|
||||||
"the correct Python version; this is Python %s)" %
|
|
||||||
(filename, sys.version.split()[0]))
|
|
||||||
raise ImportError, str(e) + extramsg
|
|
||||||
|
|
||||||
# Publish important data by importing them in the package
|
|
||||||
from support import __version__, error, warning, _getrealframe, _getemulframe
|
|
||||||
from support import version_info, __version__ as hexversion
|
|
||||||
from core import full, profile, background, runonly, stop, cannotcompile
|
|
||||||
from core import log, bind, unbind, proxy, unproxy, dumpcodebuf
|
|
||||||
from _psyco import setfilter
|
|
||||||
from _psyco import compact, compacttype
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco class support module.
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco class support module.
|
|
||||||
|
|
||||||
'psyco.classes.psyobj' is an alternate Psyco-optimized root for classes.
|
|
||||||
Any class inheriting from it or using the metaclass '__metaclass__' might
|
|
||||||
get optimized specifically for Psyco. It is equivalent to call
|
|
||||||
psyco.bind() on the class object after its creation.
|
|
||||||
|
|
||||||
Importing everything from psyco.classes in a module will import the
|
|
||||||
'__metaclass__' name, so all classes defined after a
|
|
||||||
|
|
||||||
from psyco.classes import *
|
|
||||||
|
|
||||||
will automatically use the Psyco-optimized metaclass.
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
__all__ = ['psyobj', 'psymetaclass', '__metaclass__']
|
|
||||||
|
|
||||||
|
|
||||||
from _psyco import compacttype
|
|
||||||
import core
|
|
||||||
from types import FunctionType
|
|
||||||
|
|
||||||
class psymetaclass(compacttype):
|
|
||||||
"Psyco-optimized meta-class. Turns all methods into Psyco proxies."
|
|
||||||
|
|
||||||
def __new__(cls, name, bases, dict):
|
|
||||||
bindlist = dict.get('__psyco__bind__')
|
|
||||||
if bindlist is None:
|
|
||||||
bindlist = [key for key, value in dict.items()
|
|
||||||
if isinstance(value, FunctionType)]
|
|
||||||
for attr in bindlist:
|
|
||||||
dict[attr] = core.proxy(dict[attr])
|
|
||||||
return super(psymetaclass, cls).__new__(cls, name, bases, dict)
|
|
||||||
|
|
||||||
psyobj = psymetaclass("psyobj", (), {})
|
|
||||||
__metaclass__ = psymetaclass
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco main functions.
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco main functions.
|
|
||||||
|
|
||||||
Here are the routines that you can use from your applications.
|
|
||||||
These are mostly interfaces to the C core, but they depend on
|
|
||||||
the Python version.
|
|
||||||
|
|
||||||
You can use these functions from the 'psyco' module instead of
|
|
||||||
'psyco.core', e.g.
|
|
||||||
|
|
||||||
import psyco
|
|
||||||
psyco.log('/tmp/psyco.log')
|
|
||||||
psyco.profile()
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
import _psyco
|
|
||||||
import types
|
|
||||||
from support import *
|
|
||||||
|
|
||||||
newfunction = types.FunctionType
|
|
||||||
newinstancemethod = types.MethodType
|
|
||||||
|
|
||||||
|
|
||||||
# Default charge profiler values
|
|
||||||
default_watermark = 0.09 # between 0.0 (0%) and 1.0 (100%)
|
|
||||||
default_halflife = 0.5 # seconds
|
|
||||||
default_pollfreq_profile = 20 # Hz
|
|
||||||
default_pollfreq_background = 100 # Hz -- a maximum for sleep's resolution
|
|
||||||
default_parentframe = 0.25 # should not be more than 0.5 (50%)
|
|
||||||
|
|
||||||
|
|
||||||
def full(memory=None, time=None, memorymax=None, timemax=None):
|
|
||||||
"""Compile as much as possible.
|
|
||||||
|
|
||||||
Typical use is for small scripts performing intensive computations
|
|
||||||
or string handling."""
|
|
||||||
import profiler
|
|
||||||
p = profiler.FullCompiler()
|
|
||||||
p.run(memory, time, memorymax, timemax)
|
|
||||||
|
|
||||||
|
|
||||||
def profile(watermark = default_watermark,
|
|
||||||
halflife = default_halflife,
|
|
||||||
pollfreq = default_pollfreq_profile,
|
|
||||||
parentframe = default_parentframe,
|
|
||||||
memory=None, time=None, memorymax=None, timemax=None):
|
|
||||||
"""Turn on profiling.
|
|
||||||
|
|
||||||
The 'watermark' parameter controls how easily running functions will
|
|
||||||
be compiled. The smaller the value, the more functions are compiled."""
|
|
||||||
import profiler
|
|
||||||
p = profiler.ActivePassiveProfiler(watermark, halflife,
|
|
||||||
pollfreq, parentframe)
|
|
||||||
p.run(memory, time, memorymax, timemax)
|
|
||||||
|
|
||||||
|
|
||||||
def background(watermark = default_watermark,
|
|
||||||
halflife = default_halflife,
|
|
||||||
pollfreq = default_pollfreq_background,
|
|
||||||
parentframe = default_parentframe,
|
|
||||||
memory=None, time=None, memorymax=None, timemax=None):
|
|
||||||
"""Turn on passive profiling.
|
|
||||||
|
|
||||||
This is a very lightweight mode in which only intensively computing
|
|
||||||
functions can be detected. The smaller the 'watermark', the more functions
|
|
||||||
are compiled."""
|
|
||||||
import profiler
|
|
||||||
p = profiler.PassiveProfiler(watermark, halflife, pollfreq, parentframe)
|
|
||||||
p.run(memory, time, memorymax, timemax)
|
|
||||||
|
|
||||||
|
|
||||||
def runonly(memory=None, time=None, memorymax=None, timemax=None):
|
|
||||||
"""Nonprofiler.
|
|
||||||
|
|
||||||
XXX check if this is useful and document."""
|
|
||||||
import profiler
|
|
||||||
p = profiler.RunOnly()
|
|
||||||
p.run(memory, time, memorymax, timemax)
|
|
||||||
|
|
||||||
|
|
||||||
def stop():
|
|
||||||
"""Turn off all automatic compilation. bind() calls remain in effect."""
|
|
||||||
import profiler
|
|
||||||
profiler.go([])
|
|
||||||
|
|
||||||
|
|
||||||
def log(logfile='', mode='w', top=10):
|
|
||||||
"""Enable logging to the given file.
|
|
||||||
|
|
||||||
If the file name is unspecified, a default name is built by appending
|
|
||||||
a 'log-psyco' extension to the main script name.
|
|
||||||
|
|
||||||
Mode is 'a' to append to a possibly existing file or 'w' to overwrite
|
|
||||||
an existing file. Note that the log file may grow quickly in 'a' mode."""
|
|
||||||
import profiler, logger
|
|
||||||
if not logfile:
|
|
||||||
import os
|
|
||||||
logfile, dummy = os.path.splitext(sys.argv[0])
|
|
||||||
if os.path.basename(logfile):
|
|
||||||
logfile += '.'
|
|
||||||
logfile += 'log-psyco'
|
|
||||||
if hasattr(_psyco, 'VERBOSE_LEVEL'):
|
|
||||||
print >> sys.stderr, 'psyco: logging to', logfile
|
|
||||||
# logger.current should be a real file object; subtle problems
|
|
||||||
# will show up if its write() and flush() methods are written
|
|
||||||
# in Python, as Psyco will invoke them while compiling.
|
|
||||||
logger.current = open(logfile, mode)
|
|
||||||
logger.print_charges = top
|
|
||||||
profiler.logger = logger
|
|
||||||
logger.writedate('Logging started')
|
|
||||||
cannotcompile(logger.psycowrite)
|
|
||||||
_psyco.statwrite(logger=logger.psycowrite)
|
|
||||||
|
|
||||||
|
|
||||||
def bind(x, rec=None):
|
|
||||||
"""Enable compilation of the given function, method, or class object.
|
|
||||||
|
|
||||||
If C is a class (or anything with a '__dict__' attribute), bind(C) will
|
|
||||||
rebind all functions and methods found in C.__dict__ (which means, for
|
|
||||||
classes, all methods defined in the class but not in its parents).
|
|
||||||
|
|
||||||
The optional second argument specifies the number of recursive
|
|
||||||
compilation levels: all functions called by func are compiled
|
|
||||||
up to the given depth of indirection."""
|
|
||||||
if isinstance(x, types.MethodType):
|
|
||||||
x = x.im_func
|
|
||||||
if isinstance(x, types.FunctionType):
|
|
||||||
if rec is None:
|
|
||||||
x.func_code = _psyco.proxycode(x)
|
|
||||||
else:
|
|
||||||
x.func_code = _psyco.proxycode(x, rec)
|
|
||||||
return
|
|
||||||
if hasattr(x, '__dict__'):
|
|
||||||
funcs = [o for o in x.__dict__.values()
|
|
||||||
if isinstance(o, types.MethodType)
|
|
||||||
or isinstance(o, types.FunctionType)]
|
|
||||||
if not funcs:
|
|
||||||
raise error, ("nothing bindable found in %s object" %
|
|
||||||
type(x).__name__)
|
|
||||||
for o in funcs:
|
|
||||||
bind(o, rec)
|
|
||||||
return
|
|
||||||
raise TypeError, "cannot bind %s objects" % type(x).__name__
|
|
||||||
|
|
||||||
|
|
||||||
def unbind(x):
|
|
||||||
"""Reverse of bind()."""
|
|
||||||
if isinstance(x, types.MethodType):
|
|
||||||
x = x.im_func
|
|
||||||
if isinstance(x, types.FunctionType):
|
|
||||||
try:
|
|
||||||
f = _psyco.unproxycode(x.func_code)
|
|
||||||
except error:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
x.func_code = f.func_code
|
|
||||||
return
|
|
||||||
if hasattr(x, '__dict__'):
|
|
||||||
for o in x.__dict__.values():
|
|
||||||
if (isinstance(o, types.MethodType)
|
|
||||||
or isinstance(o, types.FunctionType)):
|
|
||||||
unbind(o)
|
|
||||||
return
|
|
||||||
raise TypeError, "cannot unbind %s objects" % type(x).__name__
|
|
||||||
|
|
||||||
|
|
||||||
def proxy(x, rec=None):
|
|
||||||
"""Return a Psyco-enabled copy of the function.
|
|
||||||
|
|
||||||
The original function is still available for non-compiled calls.
|
|
||||||
The optional second argument specifies the number of recursive
|
|
||||||
compilation levels: all functions called by func are compiled
|
|
||||||
up to the given depth of indirection."""
|
|
||||||
if isinstance(x, types.FunctionType):
|
|
||||||
if rec is None:
|
|
||||||
code = _psyco.proxycode(x)
|
|
||||||
else:
|
|
||||||
code = _psyco.proxycode(x, rec)
|
|
||||||
return newfunction(code, x.func_globals, x.func_name)
|
|
||||||
if isinstance(x, types.MethodType):
|
|
||||||
p = proxy(x.im_func, rec)
|
|
||||||
return newinstancemethod(p, x.im_self, x.im_class)
|
|
||||||
raise TypeError, "cannot proxy %s objects" % type(x).__name__
|
|
||||||
|
|
||||||
|
|
||||||
def unproxy(proxy):
|
|
||||||
"""Return a new copy of the original function of method behind a proxy.
|
|
||||||
The result behaves like the original function in that calling it
|
|
||||||
does not trigger compilation nor execution of any compiled code."""
|
|
||||||
if isinstance(proxy, types.FunctionType):
|
|
||||||
return _psyco.unproxycode(proxy.func_code)
|
|
||||||
if isinstance(proxy, types.MethodType):
|
|
||||||
f = unproxy(proxy.im_func)
|
|
||||||
return newinstancemethod(f, proxy.im_self, proxy.im_class)
|
|
||||||
raise TypeError, "%s objects cannot be proxies" % type(proxy).__name__
|
|
||||||
|
|
||||||
|
|
||||||
def cannotcompile(x):
|
|
||||||
"""Instruct Psyco never to compile the given function, method
|
|
||||||
or code object."""
|
|
||||||
if isinstance(x, types.MethodType):
|
|
||||||
x = x.im_func
|
|
||||||
if isinstance(x, types.FunctionType):
|
|
||||||
x = x.func_code
|
|
||||||
if isinstance(x, types.CodeType):
|
|
||||||
_psyco.cannotcompile(x)
|
|
||||||
else:
|
|
||||||
raise TypeError, "unexpected %s object" % type(x).__name__
|
|
||||||
|
|
||||||
|
|
||||||
def dumpcodebuf():
|
|
||||||
"""Write in file psyco.dump a copy of the emitted machine code,
|
|
||||||
provided Psyco was compiled with a non-zero CODE_DUMP.
|
|
||||||
See py-utils/httpxam.py to examine psyco.dump."""
|
|
||||||
if hasattr(_psyco, 'dumpcodebuf'):
|
|
||||||
_psyco.dumpcodebuf()
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
# Psyco variables
|
|
||||||
# error * the error raised by Psyco
|
|
||||||
# warning * the warning raised by Psyco
|
|
||||||
# __in_psyco__ * a new built-in variable which is always zero, but which
|
|
||||||
# Psyco special-cases by returning 1 instead. So
|
|
||||||
# __in_psyco__ can be used in a function to know if
|
|
||||||
# that function is being executed by Psyco or not.
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Support code for the 'psyco.compact' type.
|
|
||||||
|
|
||||||
from __future__ import generators
|
|
||||||
|
|
||||||
try:
|
|
||||||
from UserDict import DictMixin
|
|
||||||
except ImportError:
|
|
||||||
|
|
||||||
# backported from Python 2.3 to Python 2.2
|
|
||||||
class DictMixin:
|
|
||||||
# Mixin defining all dictionary methods for classes that already have
|
|
||||||
# a minimum dictionary interface including getitem, setitem, delitem,
|
|
||||||
# and keys. Without knowledge of the subclass constructor, the mixin
|
|
||||||
# does not define __init__() or copy(). In addition to the four base
|
|
||||||
# methods, progressively more efficiency comes with defining
|
|
||||||
# __contains__(), __iter__(), and iteritems().
|
|
||||||
|
|
||||||
# second level definitions support higher levels
|
|
||||||
def __iter__(self):
|
|
||||||
for k in self.keys():
|
|
||||||
yield k
|
|
||||||
def has_key(self, key):
|
|
||||||
try:
|
|
||||||
value = self[key]
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
def __contains__(self, key):
|
|
||||||
return self.has_key(key)
|
|
||||||
|
|
||||||
# third level takes advantage of second level definitions
|
|
||||||
def iteritems(self):
|
|
||||||
for k in self:
|
|
||||||
yield (k, self[k])
|
|
||||||
def iterkeys(self):
|
|
||||||
return self.__iter__()
|
|
||||||
|
|
||||||
# fourth level uses definitions from lower levels
|
|
||||||
def itervalues(self):
|
|
||||||
for _, v in self.iteritems():
|
|
||||||
yield v
|
|
||||||
def values(self):
|
|
||||||
return [v for _, v in self.iteritems()]
|
|
||||||
def items(self):
|
|
||||||
return list(self.iteritems())
|
|
||||||
def clear(self):
|
|
||||||
for key in self.keys():
|
|
||||||
del self[key]
|
|
||||||
def setdefault(self, key, default):
|
|
||||||
try:
|
|
||||||
return self[key]
|
|
||||||
except KeyError:
|
|
||||||
self[key] = default
|
|
||||||
return default
|
|
||||||
def pop(self, key, *args):
|
|
||||||
if len(args) > 1:
|
|
||||||
raise TypeError, "pop expected at most 2 arguments, got "\
|
|
||||||
+ repr(1 + len(args))
|
|
||||||
try:
|
|
||||||
value = self[key]
|
|
||||||
except KeyError:
|
|
||||||
if args:
|
|
||||||
return args[0]
|
|
||||||
raise
|
|
||||||
del self[key]
|
|
||||||
return value
|
|
||||||
def popitem(self):
|
|
||||||
try:
|
|
||||||
k, v = self.iteritems().next()
|
|
||||||
except StopIteration:
|
|
||||||
raise KeyError, 'container is empty'
|
|
||||||
del self[k]
|
|
||||||
return (k, v)
|
|
||||||
def update(self, other):
|
|
||||||
# Make progressively weaker assumptions about "other"
|
|
||||||
if hasattr(other, 'iteritems'): # iteritems saves memory and lookups
|
|
||||||
for k, v in other.iteritems():
|
|
||||||
self[k] = v
|
|
||||||
elif hasattr(other, '__iter__'): # iter saves memory
|
|
||||||
for k in other:
|
|
||||||
self[k] = other[k]
|
|
||||||
else:
|
|
||||||
for k in other.keys():
|
|
||||||
self[k] = other[k]
|
|
||||||
def get(self, key, default=None):
|
|
||||||
try:
|
|
||||||
return self[key]
|
|
||||||
except KeyError:
|
|
||||||
return default
|
|
||||||
def __repr__(self):
|
|
||||||
return repr(dict(self.iteritems()))
|
|
||||||
def __cmp__(self, other):
|
|
||||||
if other is None:
|
|
||||||
return 1
|
|
||||||
if isinstance(other, DictMixin):
|
|
||||||
other = dict(other.iteritems())
|
|
||||||
return cmp(dict(self.iteritems()), other)
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.keys())
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
from _psyco import compact
|
|
||||||
|
|
||||||
|
|
||||||
class compactdictproxy(DictMixin):
|
|
||||||
|
|
||||||
def __init__(self, ko):
|
|
||||||
self._ko = ko # compact object of which 'self' is the dict
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return compact.__getslot__(self._ko, key)
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
compact.__setslot__(self._ko, key, value)
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
|
||||||
compact.__delslot__(self._ko, key)
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return compact.__members__.__get__(self._ko)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
keys = self.keys()
|
|
||||||
keys.reverse()
|
|
||||||
for key in keys:
|
|
||||||
del self[key]
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
keys = ', '.join(self.keys())
|
|
||||||
return '<compactdictproxy object {%s}>' % (keys,)
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco logger.
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco logger.
|
|
||||||
|
|
||||||
See log() in core.py.
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
|
|
||||||
import _psyco
|
|
||||||
from time import time, localtime, strftime
|
|
||||||
|
|
||||||
|
|
||||||
current = None
|
|
||||||
print_charges = 10
|
|
||||||
dump_delay = 0.2
|
|
||||||
dump_last = 0.0
|
|
||||||
|
|
||||||
def write(s, level):
|
|
||||||
t = time()
|
|
||||||
f = t-int(t)
|
|
||||||
try:
|
|
||||||
current.write("%s.%02d %-*s%s\n" % (
|
|
||||||
strftime("%X", localtime(int(t))),
|
|
||||||
int(f*100.0), 63-level, s,
|
|
||||||
"%"*level))
|
|
||||||
current.flush()
|
|
||||||
except (OSError, IOError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def psycowrite(s):
|
|
||||||
t = time()
|
|
||||||
f = t-int(t)
|
|
||||||
try:
|
|
||||||
current.write("%s.%02d %-*s%s\n" % (
|
|
||||||
strftime("%X", localtime(int(t))),
|
|
||||||
int(f*100.0), 60, s.strip(),
|
|
||||||
"% %"))
|
|
||||||
current.flush()
|
|
||||||
except (OSError, IOError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
##def writelines(lines, level=0):
|
|
||||||
## if lines:
|
|
||||||
## t = time()
|
|
||||||
## f = t-int(t)
|
|
||||||
## timedesc = strftime("%x %X", localtime(int(t)))
|
|
||||||
## print >> current, "%s.%03d %-*s %s" % (
|
|
||||||
## timedesc, int(f*1000),
|
|
||||||
## 50-level, lines[0],
|
|
||||||
## "+"*level)
|
|
||||||
## timedesc = " " * (len(timedesc)+5)
|
|
||||||
## for line in lines[1:]:
|
|
||||||
## print >> current, timedesc, line
|
|
||||||
|
|
||||||
def writememory():
|
|
||||||
write("memory usage: %d+ kb" % _psyco.memory(), 1)
|
|
||||||
|
|
||||||
def dumpcharges():
|
|
||||||
global dump_last
|
|
||||||
if print_charges:
|
|
||||||
t = time()
|
|
||||||
if not (dump_last <= t < dump_last+dump_delay):
|
|
||||||
if t <= dump_last+1.5*dump_delay:
|
|
||||||
dump_last += dump_delay
|
|
||||||
else:
|
|
||||||
dump_last = t
|
|
||||||
#write("%s: charges:" % who, 0)
|
|
||||||
lst = _psyco.stattop(print_charges)
|
|
||||||
if lst:
|
|
||||||
f = t-int(t)
|
|
||||||
lines = ["%s.%02d ______\n" % (
|
|
||||||
strftime("%X", localtime(int(t))),
|
|
||||||
int(f*100.0))]
|
|
||||||
i = 1
|
|
||||||
for co, charge in lst:
|
|
||||||
detail = co.co_filename
|
|
||||||
if len(detail) > 19:
|
|
||||||
detail = '...' + detail[-17:]
|
|
||||||
lines.append(" #%-3d |%4.1f %%| %-26s%20s:%d\n" %
|
|
||||||
(i, charge*100.0, co.co_name, detail,
|
|
||||||
co.co_firstlineno))
|
|
||||||
i += 1
|
|
||||||
current.writelines(lines)
|
|
||||||
current.flush()
|
|
||||||
|
|
||||||
def writefinalstats():
|
|
||||||
dumpcharges()
|
|
||||||
writememory()
|
|
||||||
writedate("program exit")
|
|
||||||
|
|
||||||
def writedate(msg):
|
|
||||||
write('%s, %s' % (msg, strftime("%x")), 20)
|
|
||||||
@@ -1,379 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco profiler (Python part).
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco profiler (Python part).
|
|
||||||
|
|
||||||
The implementation of the non-time-critical parts of the profiler.
|
|
||||||
See profile() and full() in core.py for the easy interface.
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
import _psyco
|
|
||||||
from support import *
|
|
||||||
import math, time, types, atexit
|
|
||||||
now = time.time
|
|
||||||
try:
|
|
||||||
import thread
|
|
||||||
except ImportError:
|
|
||||||
import dummy_thread as thread
|
|
||||||
|
|
||||||
|
|
||||||
# current profiler instance
|
|
||||||
current = None
|
|
||||||
|
|
||||||
# enabled profilers, in order of priority
|
|
||||||
profilers = []
|
|
||||||
|
|
||||||
# logger module (when enabled by core.log())
|
|
||||||
logger = None
|
|
||||||
|
|
||||||
# a lock for a thread-safe go()
|
|
||||||
go_lock = thread.allocate_lock()
|
|
||||||
|
|
||||||
def go(stop=0):
|
|
||||||
# run the highest-priority profiler in 'profilers'
|
|
||||||
global current
|
|
||||||
go_lock.acquire()
|
|
||||||
try:
|
|
||||||
prev = current
|
|
||||||
if stop:
|
|
||||||
del profilers[:]
|
|
||||||
if prev:
|
|
||||||
if profilers and profilers[0] is prev:
|
|
||||||
return # best profiler already running
|
|
||||||
prev.stop()
|
|
||||||
current = None
|
|
||||||
for p in profilers[:]:
|
|
||||||
if p.start():
|
|
||||||
current = p
|
|
||||||
if logger: # and p is not prev:
|
|
||||||
logger.write("%s: starting" % p.__class__.__name__, 5)
|
|
||||||
return
|
|
||||||
finally:
|
|
||||||
go_lock.release()
|
|
||||||
# no profiler is running now
|
|
||||||
if stop:
|
|
||||||
if logger:
|
|
||||||
logger.writefinalstats()
|
|
||||||
else:
|
|
||||||
tag2bind()
|
|
||||||
|
|
||||||
atexit.register(go, 1)
|
|
||||||
|
|
||||||
|
|
||||||
def buildfncache(globals, cache):
|
|
||||||
if hasattr(types.IntType, '__dict__'):
|
|
||||||
clstypes = (types.ClassType, types.TypeType)
|
|
||||||
else:
|
|
||||||
clstypes = types.ClassType
|
|
||||||
for x in globals.values():
|
|
||||||
if isinstance(x, types.MethodType):
|
|
||||||
x = x.im_func
|
|
||||||
if isinstance(x, types.FunctionType):
|
|
||||||
cache[x.func_code] = x, ''
|
|
||||||
elif isinstance(x, clstypes):
|
|
||||||
for y in x.__dict__.values():
|
|
||||||
if isinstance(y, types.MethodType):
|
|
||||||
y = y.im_func
|
|
||||||
if isinstance(y, types.FunctionType):
|
|
||||||
cache[y.func_code] = y, x.__name__
|
|
||||||
|
|
||||||
# code-to-function mapping (cache)
|
|
||||||
function_cache = {}
|
|
||||||
|
|
||||||
def trytobind(co, globals, log=1):
|
|
||||||
try:
|
|
||||||
f, clsname = function_cache[co]
|
|
||||||
except KeyError:
|
|
||||||
buildfncache(globals, function_cache)
|
|
||||||
try:
|
|
||||||
f, clsname = function_cache[co]
|
|
||||||
except KeyError:
|
|
||||||
if logger:
|
|
||||||
logger.write('warning: cannot find function %s in %s' %
|
|
||||||
(co.co_name, globals.get('__name__', '?')), 3)
|
|
||||||
return # give up
|
|
||||||
if logger and log:
|
|
||||||
modulename = globals.get('__name__', '?')
|
|
||||||
if clsname:
|
|
||||||
modulename += '.' + clsname
|
|
||||||
logger.write('bind function: %s.%s' % (modulename, co.co_name), 1)
|
|
||||||
f.func_code = _psyco.proxycode(f)
|
|
||||||
|
|
||||||
|
|
||||||
# the list of code objects that have been tagged
|
|
||||||
tagged_codes = []
|
|
||||||
|
|
||||||
def tag(co, globals):
|
|
||||||
if logger:
|
|
||||||
try:
|
|
||||||
f, clsname = function_cache[co]
|
|
||||||
except KeyError:
|
|
||||||
buildfncache(globals, function_cache)
|
|
||||||
try:
|
|
||||||
f, clsname = function_cache[co]
|
|
||||||
except KeyError:
|
|
||||||
clsname = '' # give up
|
|
||||||
modulename = globals.get('__name__', '?')
|
|
||||||
if clsname:
|
|
||||||
modulename += '.' + clsname
|
|
||||||
logger.write('tag function: %s.%s' % (modulename, co.co_name), 1)
|
|
||||||
tagged_codes.append((co, globals))
|
|
||||||
_psyco.turbo_frame(co)
|
|
||||||
_psyco.turbo_code(co)
|
|
||||||
|
|
||||||
def tag2bind():
|
|
||||||
if tagged_codes:
|
|
||||||
if logger:
|
|
||||||
logger.write('profiling stopped, binding %d functions' %
|
|
||||||
len(tagged_codes), 2)
|
|
||||||
for co, globals in tagged_codes:
|
|
||||||
trytobind(co, globals, 0)
|
|
||||||
function_cache.clear()
|
|
||||||
del tagged_codes[:]
|
|
||||||
|
|
||||||
|
|
||||||
class Profiler:
|
|
||||||
MemoryTimerResolution = 0.103
|
|
||||||
|
|
||||||
def run(self, memory, time, memorymax, timemax):
|
|
||||||
self.memory = memory
|
|
||||||
self.memorymax = memorymax
|
|
||||||
self.time = time
|
|
||||||
if timemax is None:
|
|
||||||
self.endtime = None
|
|
||||||
else:
|
|
||||||
self.endtime = now() + timemax
|
|
||||||
self.alarms = []
|
|
||||||
profilers.append(self)
|
|
||||||
go()
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
curmem = _psyco.memory()
|
|
||||||
memlimits = []
|
|
||||||
if self.memorymax is not None:
|
|
||||||
if curmem >= self.memorymax:
|
|
||||||
if logger:
|
|
||||||
logger.writememory()
|
|
||||||
return self.limitreached('memorymax')
|
|
||||||
memlimits.append(self.memorymax)
|
|
||||||
if self.memory is not None:
|
|
||||||
if self.memory <= 0:
|
|
||||||
if logger:
|
|
||||||
logger.writememory()
|
|
||||||
return self.limitreached('memory')
|
|
||||||
memlimits.append(curmem + self.memory)
|
|
||||||
self.memory_at_start = curmem
|
|
||||||
|
|
||||||
curtime = now()
|
|
||||||
timelimits = []
|
|
||||||
if self.endtime is not None:
|
|
||||||
if curtime >= self.endtime:
|
|
||||||
return self.limitreached('timemax')
|
|
||||||
timelimits.append(self.endtime - curtime)
|
|
||||||
if self.time is not None:
|
|
||||||
if self.time <= 0.0:
|
|
||||||
return self.limitreached('time')
|
|
||||||
timelimits.append(self.time)
|
|
||||||
self.time_at_start = curtime
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.do_start()
|
|
||||||
except error, e:
|
|
||||||
if logger:
|
|
||||||
logger.write('%s: disabled by psyco.error:' % (
|
|
||||||
self.__class__.__name__), 4)
|
|
||||||
logger.write(' %s' % str(e), 3)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if memlimits:
|
|
||||||
self.memlimits_args = (time.sleep, (self.MemoryTimerResolution,),
|
|
||||||
self.check_memory, (min(memlimits),))
|
|
||||||
self.alarms.append(_psyco.alarm(*self.memlimits_args))
|
|
||||||
if timelimits:
|
|
||||||
self.alarms.append(_psyco.alarm(time.sleep, (min(timelimits),),
|
|
||||||
self.time_out))
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
for alarm in self.alarms:
|
|
||||||
alarm.stop(0)
|
|
||||||
for alarm in self.alarms:
|
|
||||||
alarm.stop(1) # wait for parallel threads to stop
|
|
||||||
del self.alarms[:]
|
|
||||||
if self.time is not None:
|
|
||||||
self.time -= now() - self.time_at_start
|
|
||||||
if self.memory is not None:
|
|
||||||
self.memory -= _psyco.memory() - self.memory_at_start
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.do_stop()
|
|
||||||
except error:
|
|
||||||
return 0
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def check_memory(self, limit):
|
|
||||||
if _psyco.memory() < limit:
|
|
||||||
return self.memlimits_args
|
|
||||||
go()
|
|
||||||
|
|
||||||
def time_out(self):
|
|
||||||
self.time = 0.0
|
|
||||||
go()
|
|
||||||
|
|
||||||
def limitreached(self, limitname):
|
|
||||||
try:
|
|
||||||
profilers.remove(self)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
if logger:
|
|
||||||
logger.write('%s: disabled (%s limit reached)' % (
|
|
||||||
self.__class__.__name__, limitname), 4)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
class FullCompiler(Profiler):
|
|
||||||
|
|
||||||
def do_start(self):
|
|
||||||
_psyco.profiling('f')
|
|
||||||
|
|
||||||
def do_stop(self):
|
|
||||||
_psyco.profiling('.')
|
|
||||||
|
|
||||||
|
|
||||||
class RunOnly(Profiler):
|
|
||||||
|
|
||||||
def do_start(self):
|
|
||||||
_psyco.profiling('n')
|
|
||||||
|
|
||||||
def do_stop(self):
|
|
||||||
_psyco.profiling('.')
|
|
||||||
|
|
||||||
|
|
||||||
class ChargeProfiler(Profiler):
|
|
||||||
|
|
||||||
def __init__(self, watermark, parentframe):
|
|
||||||
self.watermark = watermark
|
|
||||||
self.parent2 = parentframe * 2.0
|
|
||||||
self.lock = thread.allocate_lock()
|
|
||||||
|
|
||||||
def init_charges(self):
|
|
||||||
_psyco.statwrite(watermark = self.watermark,
|
|
||||||
parent2 = self.parent2)
|
|
||||||
|
|
||||||
def do_stop(self):
|
|
||||||
_psyco.profiling('.')
|
|
||||||
_psyco.statwrite(callback = None)
|
|
||||||
|
|
||||||
|
|
||||||
class ActiveProfiler(ChargeProfiler):
|
|
||||||
|
|
||||||
def active_start(self):
|
|
||||||
_psyco.profiling('p')
|
|
||||||
|
|
||||||
def do_start(self):
|
|
||||||
self.init_charges()
|
|
||||||
self.active_start()
|
|
||||||
_psyco.statwrite(callback = self.charge_callback)
|
|
||||||
|
|
||||||
def charge_callback(self, frame, charge):
|
|
||||||
tag(frame.f_code, frame.f_globals)
|
|
||||||
|
|
||||||
|
|
||||||
class PassiveProfiler(ChargeProfiler):
|
|
||||||
|
|
||||||
initial_charge_unit = _psyco.statread('unit')
|
|
||||||
reset_stats_after = 120 # half-lives (maximum 200!)
|
|
||||||
reset_limit = initial_charge_unit * (2.0 ** reset_stats_after)
|
|
||||||
|
|
||||||
def __init__(self, watermark, halflife, pollfreq, parentframe):
|
|
||||||
ChargeProfiler.__init__(self, watermark, parentframe)
|
|
||||||
self.pollfreq = pollfreq
|
|
||||||
# self.progress is slightly more than 1.0, and computed so that
|
|
||||||
# do_profile() will double the change_unit every 'halflife' seconds.
|
|
||||||
self.progress = 2.0 ** (1.0 / (halflife * pollfreq))
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
_psyco.statwrite(unit = self.initial_charge_unit, callback = None)
|
|
||||||
_psyco.statreset()
|
|
||||||
if logger:
|
|
||||||
logger.write("%s: resetting stats" % self.__class__.__name__, 1)
|
|
||||||
|
|
||||||
def passive_start(self):
|
|
||||||
self.passivealarm_args = (time.sleep, (1.0 / self.pollfreq,),
|
|
||||||
self.do_profile)
|
|
||||||
self.alarms.append(_psyco.alarm(*self.passivealarm_args))
|
|
||||||
|
|
||||||
def do_start(self):
|
|
||||||
tag2bind()
|
|
||||||
self.init_charges()
|
|
||||||
self.passive_start()
|
|
||||||
|
|
||||||
def do_profile(self):
|
|
||||||
_psyco.statcollect()
|
|
||||||
if logger:
|
|
||||||
logger.dumpcharges()
|
|
||||||
nunit = _psyco.statread('unit') * self.progress
|
|
||||||
if nunit > self.reset_limit:
|
|
||||||
self.reset()
|
|
||||||
else:
|
|
||||||
_psyco.statwrite(unit = nunit, callback = self.charge_callback)
|
|
||||||
return self.passivealarm_args
|
|
||||||
|
|
||||||
def charge_callback(self, frame, charge):
|
|
||||||
trytobind(frame.f_code, frame.f_globals)
|
|
||||||
|
|
||||||
|
|
||||||
class ActivePassiveProfiler(PassiveProfiler, ActiveProfiler):
|
|
||||||
|
|
||||||
def do_start(self):
|
|
||||||
self.init_charges()
|
|
||||||
self.active_start()
|
|
||||||
self.passive_start()
|
|
||||||
|
|
||||||
def charge_callback(self, frame, charge):
|
|
||||||
tag(frame.f_code, frame.f_globals)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# we register our own version of sys.settrace(), sys.setprofile()
|
|
||||||
# and thread.start_new_thread().
|
|
||||||
#
|
|
||||||
|
|
||||||
def psyco_settrace(*args, **kw):
|
|
||||||
"This is the Psyco-aware version of sys.settrace()."
|
|
||||||
result = original_settrace(*args, **kw)
|
|
||||||
go()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def psyco_setprofile(*args, **kw):
|
|
||||||
"This is the Psyco-aware version of sys.setprofile()."
|
|
||||||
result = original_setprofile(*args, **kw)
|
|
||||||
go()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def psyco_thread_stub(callable, args, kw):
|
|
||||||
_psyco.statcollect()
|
|
||||||
if kw is None:
|
|
||||||
return callable(*args)
|
|
||||||
else:
|
|
||||||
return callable(*args, **kw)
|
|
||||||
|
|
||||||
def psyco_start_new_thread(callable, args, kw=None):
|
|
||||||
"This is the Psyco-aware version of thread.start_new_thread()."
|
|
||||||
return original_start_new_thread(psyco_thread_stub, (callable, args, kw))
|
|
||||||
|
|
||||||
original_settrace = sys.settrace
|
|
||||||
original_setprofile = sys.setprofile
|
|
||||||
original_start_new_thread = thread.start_new_thread
|
|
||||||
sys.settrace = psyco_settrace
|
|
||||||
sys.setprofile = psyco_setprofile
|
|
||||||
thread.start_new_thread = psyco_start_new_thread
|
|
||||||
# hack to patch threading._start_new_thread if the module is
|
|
||||||
# already loaded
|
|
||||||
if ('threading' in sys.modules and
|
|
||||||
hasattr(sys.modules['threading'], '_start_new_thread')):
|
|
||||||
sys.modules['threading']._start_new_thread = psyco_start_new_thread
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
###########################################################################
|
|
||||||
#
|
|
||||||
# Psyco general support module.
|
|
||||||
# Copyright (C) 2001-2002 Armin Rigo et.al.
|
|
||||||
|
|
||||||
"""Psyco general support module.
|
|
||||||
|
|
||||||
For internal use.
|
|
||||||
"""
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
import sys, _psyco, __builtin__
|
|
||||||
|
|
||||||
error = _psyco.error
|
|
||||||
class warning(Warning):
|
|
||||||
pass
|
|
||||||
|
|
||||||
_psyco.NoLocalsWarning = warning
|
|
||||||
|
|
||||||
def warn(msg):
|
|
||||||
from warnings import warn
|
|
||||||
warn(msg, warning, stacklevel=2)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Version checks
|
|
||||||
#
|
|
||||||
__version__ = 0x010600f0
|
|
||||||
if _psyco.PSYVER != __version__:
|
|
||||||
raise error, "version mismatch between Psyco parts, reinstall it"
|
|
||||||
|
|
||||||
version_info = (__version__ >> 24,
|
|
||||||
(__version__ >> 16) & 0xff,
|
|
||||||
(__version__ >> 8) & 0xff,
|
|
||||||
{0xa0: 'alpha',
|
|
||||||
0xb0: 'beta',
|
|
||||||
0xc0: 'candidate',
|
|
||||||
0xf0: 'final'}[__version__ & 0xf0],
|
|
||||||
__version__ & 0xf)
|
|
||||||
|
|
||||||
|
|
||||||
VERSION_LIMITS = [0x02020200, # 2.2.2
|
|
||||||
0x02030000, # 2.3
|
|
||||||
0x02040000] # 2.4
|
|
||||||
|
|
||||||
if ([v for v in VERSION_LIMITS if v <= sys.hexversion] !=
|
|
||||||
[v for v in VERSION_LIMITS if v <= _psyco.PYVER ]):
|
|
||||||
if sys.hexversion < VERSION_LIMITS[0]:
|
|
||||||
warn("Psyco requires Python version 2.2.2 or later")
|
|
||||||
else:
|
|
||||||
warn("Psyco version does not match Python version. "
|
|
||||||
"Psyco must be updated or recompiled")
|
|
||||||
|
|
||||||
|
|
||||||
if hasattr(_psyco, 'ALL_CHECKS') and hasattr(_psyco, 'VERBOSE_LEVEL'):
|
|
||||||
print >> sys.stderr, ('psyco: running in debugging mode on %s' %
|
|
||||||
_psyco.PROCESSOR)
|
|
||||||
|
|
||||||
|
|
||||||
###########################################################################
|
|
||||||
# sys._getframe() gives strange results on a mixed Psyco- and Python-style
|
|
||||||
# stack frame. Psyco provides a replacement that partially emulates Python
|
|
||||||
# frames from Psyco frames. The new sys._getframe() may return objects of
|
|
||||||
# a custom "Psyco frame" type, which is a subtype of the normal frame type.
|
|
||||||
#
|
|
||||||
# The same problems require some other built-in functions to be replaced
|
|
||||||
# as well. Note that the local variables are not available in any
|
|
||||||
# dictionary with Psyco.
|
|
||||||
|
|
||||||
|
|
||||||
class Frame:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PythonFrame(Frame):
|
|
||||||
|
|
||||||
def __init__(self, frame):
|
|
||||||
self.__dict__.update({
|
|
||||||
'_frame': frame,
|
|
||||||
})
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
if attr == 'f_back':
|
|
||||||
try:
|
|
||||||
result = embedframe(_psyco.getframe(self._frame))
|
|
||||||
except ValueError:
|
|
||||||
result = None
|
|
||||||
except error:
|
|
||||||
warn("f_back is skipping dead Psyco frames")
|
|
||||||
result = self._frame.f_back
|
|
||||||
self.__dict__['f_back'] = result
|
|
||||||
return result
|
|
||||||
else:
|
|
||||||
return getattr(self._frame, attr)
|
|
||||||
|
|
||||||
def __setattr__(self, attr, value):
|
|
||||||
setattr(self._frame, attr, value)
|
|
||||||
|
|
||||||
def __delattr__(self, attr):
|
|
||||||
delattr(self._frame, attr)
|
|
||||||
|
|
||||||
|
|
||||||
class PsycoFrame(Frame):
|
|
||||||
|
|
||||||
def __init__(self, tag):
|
|
||||||
self.__dict__.update({
|
|
||||||
'_tag' : tag,
|
|
||||||
'f_code' : tag[0],
|
|
||||||
'f_globals': tag[1],
|
|
||||||
})
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
if attr == 'f_back':
|
|
||||||
try:
|
|
||||||
result = embedframe(_psyco.getframe(self._tag))
|
|
||||||
except ValueError:
|
|
||||||
result = None
|
|
||||||
elif attr == 'f_lineno':
|
|
||||||
result = self.f_code.co_firstlineno # better than nothing
|
|
||||||
elif attr == 'f_builtins':
|
|
||||||
result = self.f_globals['__builtins__']
|
|
||||||
elif attr == 'f_restricted':
|
|
||||||
result = self.f_builtins is not __builtins__
|
|
||||||
elif attr == 'f_locals':
|
|
||||||
raise AttributeError, ("local variables of functions run by Psyco "
|
|
||||||
"cannot be accessed in any way, sorry")
|
|
||||||
else:
|
|
||||||
raise AttributeError, ("emulated Psyco frames have "
|
|
||||||
"no '%s' attribute" % attr)
|
|
||||||
self.__dict__[attr] = result
|
|
||||||
return result
|
|
||||||
|
|
||||||
def __setattr__(self, attr, value):
|
|
||||||
raise AttributeError, "Psyco frame objects are read-only"
|
|
||||||
|
|
||||||
def __delattr__(self, attr):
|
|
||||||
if attr == 'f_trace':
|
|
||||||
# for bdb which relies on CPython frames exhibiting a slightly
|
|
||||||
# buggy behavior: you can 'del f.f_trace' as often as you like
|
|
||||||
# even without having set it previously.
|
|
||||||
return
|
|
||||||
raise AttributeError, "Psyco frame objects are read-only"
|
|
||||||
|
|
||||||
|
|
||||||
def embedframe(result):
|
|
||||||
if type(result) is type(()):
|
|
||||||
return PsycoFrame(result)
|
|
||||||
else:
|
|
||||||
return PythonFrame(result)
|
|
||||||
|
|
||||||
def _getframe(depth=0):
|
|
||||||
"""Return a frame object from the call stack. This is a replacement for
|
|
||||||
sys._getframe() which is aware of Psyco frames.
|
|
||||||
|
|
||||||
The returned objects are instances of either PythonFrame or PsycoFrame
|
|
||||||
instead of being real Python-level frame object, so that they can emulate
|
|
||||||
the common attributes of frame objects.
|
|
||||||
|
|
||||||
The original sys._getframe() ignoring Psyco frames altogether is stored in
|
|
||||||
psyco._getrealframe(). See also psyco._getemulframe()."""
|
|
||||||
# 'depth+1' to account for this _getframe() Python function
|
|
||||||
return embedframe(_psyco.getframe(depth+1))
|
|
||||||
|
|
||||||
def _getemulframe(depth=0):
|
|
||||||
"""As _getframe(), but the returned objects are real Python frame objects
|
|
||||||
emulating Psyco frames. Some of their attributes can be wrong or missing,
|
|
||||||
however."""
|
|
||||||
# 'depth+1' to account for this _getemulframe() Python function
|
|
||||||
return _psyco.getframe(depth+1, 1)
|
|
||||||
|
|
||||||
def patch(name, module=__builtin__):
|
|
||||||
f = getattr(_psyco, name)
|
|
||||||
org = getattr(module, name)
|
|
||||||
if org is not f:
|
|
||||||
setattr(module, name, f)
|
|
||||||
setattr(_psyco, 'original_' + name, org)
|
|
||||||
|
|
||||||
_getrealframe = sys._getframe
|
|
||||||
sys._getframe = _getframe
|
|
||||||
patch('globals')
|
|
||||||
patch('eval')
|
|
||||||
patch('execfile')
|
|
||||||
patch('locals')
|
|
||||||
patch('vars')
|
|
||||||
patch('dir')
|
|
||||||
patch('input')
|
|
||||||
_psyco.original_raw_input = raw_input
|
|
||||||
__builtin__.__in_psyco__ = 0==1 # False
|
|
||||||
|
|
||||||
if hasattr(_psyco, 'compact'):
|
|
||||||
import kdictproxy
|
|
||||||
_psyco.compactdictproxy = kdictproxy.compactdictproxy
|
|
||||||
218
Calibre_Plugins/eReaderPDB2PML_plugin/python_des.py
Normal file
218
Calibre_Plugins/eReaderPDB2PML_plugin/python_des.py
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
ECB = 0
|
||||||
|
CBC = 1
|
||||||
|
class Des(object):
|
||||||
|
__pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
|
||||||
|
9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
|
||||||
|
62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
|
||||||
|
13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3]
|
||||||
|
__left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
|
||||||
|
__pc2 = [13, 16, 10, 23, 0, 4,2, 27, 14, 5, 20, 9,
|
||||||
|
22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
|
||||||
|
40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
|
||||||
|
43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31]
|
||||||
|
__ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
|
||||||
|
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
|
||||||
|
56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2,
|
||||||
|
60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
|
||||||
|
__expansion_table = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8,
|
||||||
|
7, 8, 9, 10, 11, 12,11, 12, 13, 14, 15, 16,
|
||||||
|
15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
|
||||||
|
23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31, 0]
|
||||||
|
__sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
|
||||||
|
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
||||||
|
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
|
||||||
|
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
|
||||||
|
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
|
||||||
|
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
|
||||||
|
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
|
||||||
|
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
|
||||||
|
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
|
||||||
|
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
||||||
|
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
|
||||||
|
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
|
||||||
|
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
|
||||||
|
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
||||||
|
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
|
||||||
|
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
|
||||||
|
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
|
||||||
|
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
||||||
|
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
|
||||||
|
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
|
||||||
|
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
|
||||||
|
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
||||||
|
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
|
||||||
|
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
|
||||||
|
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
|
||||||
|
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
||||||
|
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
|
||||||
|
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
|
||||||
|
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
|
||||||
|
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
||||||
|
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
|
||||||
|
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
|
||||||
|
__p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
|
||||||
|
4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
|
||||||
|
__fp = [39, 7, 47, 15, 55, 23, 63, 31,38, 6, 46, 14, 54, 22, 62, 30,
|
||||||
|
37, 5, 45, 13, 53, 21, 61, 29,36, 4, 44, 12, 52, 20, 60, 28,
|
||||||
|
35, 3, 43, 11, 51, 19, 59, 27,34, 2, 42, 10, 50, 18, 58, 26,
|
||||||
|
33, 1, 41, 9, 49, 17, 57, 25,32, 0, 40, 8, 48, 16, 56, 24]
|
||||||
|
# Type of crypting being done
|
||||||
|
ENCRYPT = 0x00
|
||||||
|
DECRYPT = 0x01
|
||||||
|
def __init__(self, key, mode=ECB, IV=None):
|
||||||
|
if len(key) != 8:
|
||||||
|
raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
|
||||||
|
self.block_size = 8
|
||||||
|
self.key_size = 8
|
||||||
|
self.__padding = ''
|
||||||
|
self.setMode(mode)
|
||||||
|
if IV:
|
||||||
|
self.setIV(IV)
|
||||||
|
self.L = []
|
||||||
|
self.R = []
|
||||||
|
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
|
||||||
|
self.final = []
|
||||||
|
self.setKey(key)
|
||||||
|
def getKey(self):
|
||||||
|
return self.__key
|
||||||
|
def setKey(self, key):
|
||||||
|
self.__key = key
|
||||||
|
self.__create_sub_keys()
|
||||||
|
def getMode(self):
|
||||||
|
return self.__mode
|
||||||
|
def setMode(self, mode):
|
||||||
|
self.__mode = mode
|
||||||
|
def getIV(self):
|
||||||
|
return self.__iv
|
||||||
|
def setIV(self, IV):
|
||||||
|
if not IV or len(IV) != self.block_size:
|
||||||
|
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
||||||
|
self.__iv = IV
|
||||||
|
def getPadding(self):
|
||||||
|
return self.__padding
|
||||||
|
def __String_to_BitList(self, data):
|
||||||
|
l = len(data) * 8
|
||||||
|
result = [0] * l
|
||||||
|
pos = 0
|
||||||
|
for c in data:
|
||||||
|
i = 7
|
||||||
|
ch = ord(c)
|
||||||
|
while i >= 0:
|
||||||
|
if ch & (1 << i) != 0:
|
||||||
|
result[pos] = 1
|
||||||
|
else:
|
||||||
|
result[pos] = 0
|
||||||
|
pos += 1
|
||||||
|
i -= 1
|
||||||
|
return result
|
||||||
|
def __BitList_to_String(self, data):
|
||||||
|
result = ''
|
||||||
|
pos = 0
|
||||||
|
c = 0
|
||||||
|
while pos < len(data):
|
||||||
|
c += data[pos] << (7 - (pos % 8))
|
||||||
|
if (pos % 8) == 7:
|
||||||
|
result += chr(c)
|
||||||
|
c = 0
|
||||||
|
pos += 1
|
||||||
|
return result
|
||||||
|
def __permutate(self, table, block):
|
||||||
|
return [block[x] for x in table]
|
||||||
|
def __create_sub_keys(self):
|
||||||
|
key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
|
||||||
|
i = 0
|
||||||
|
self.L = key[:28]
|
||||||
|
self.R = key[28:]
|
||||||
|
while i < 16:
|
||||||
|
j = 0
|
||||||
|
while j < Des.__left_rotations[i]:
|
||||||
|
self.L.append(self.L[0])
|
||||||
|
del self.L[0]
|
||||||
|
self.R.append(self.R[0])
|
||||||
|
del self.R[0]
|
||||||
|
j += 1
|
||||||
|
self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
|
||||||
|
i += 1
|
||||||
|
def __des_crypt(self, block, crypt_type):
|
||||||
|
block = self.__permutate(Des.__ip, block)
|
||||||
|
self.L = block[:32]
|
||||||
|
self.R = block[32:]
|
||||||
|
if crypt_type == Des.ENCRYPT:
|
||||||
|
iteration = 0
|
||||||
|
iteration_adjustment = 1
|
||||||
|
else:
|
||||||
|
iteration = 15
|
||||||
|
iteration_adjustment = -1
|
||||||
|
i = 0
|
||||||
|
while i < 16:
|
||||||
|
tempR = self.R[:]
|
||||||
|
self.R = self.__permutate(Des.__expansion_table, self.R)
|
||||||
|
self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
|
||||||
|
B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
|
||||||
|
j = 0
|
||||||
|
Bn = [0] * 32
|
||||||
|
pos = 0
|
||||||
|
while j < 8:
|
||||||
|
m = (B[j][0] << 1) + B[j][5]
|
||||||
|
n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
|
||||||
|
v = Des.__sbox[j][(m << 4) + n]
|
||||||
|
Bn[pos] = (v & 8) >> 3
|
||||||
|
Bn[pos + 1] = (v & 4) >> 2
|
||||||
|
Bn[pos + 2] = (v & 2) >> 1
|
||||||
|
Bn[pos + 3] = v & 1
|
||||||
|
pos += 4
|
||||||
|
j += 1
|
||||||
|
self.R = self.__permutate(Des.__p, Bn)
|
||||||
|
self.R = [x ^ y for x, y in zip(self.R, self.L)]
|
||||||
|
self.L = tempR
|
||||||
|
i += 1
|
||||||
|
iteration += iteration_adjustment
|
||||||
|
self.final = self.__permutate(Des.__fp, self.R + self.L)
|
||||||
|
return self.final
|
||||||
|
def crypt(self, data, crypt_type):
|
||||||
|
if not data:
|
||||||
|
return ''
|
||||||
|
if len(data) % self.block_size != 0:
|
||||||
|
if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
|
||||||
|
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
|
||||||
|
if not self.getPadding():
|
||||||
|
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
|
||||||
|
else:
|
||||||
|
data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
|
||||||
|
if self.getMode() == CBC:
|
||||||
|
if self.getIV():
|
||||||
|
iv = self.__String_to_BitList(self.getIV())
|
||||||
|
else:
|
||||||
|
raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
|
||||||
|
i = 0
|
||||||
|
dict = {}
|
||||||
|
result = []
|
||||||
|
while i < len(data):
|
||||||
|
block = self.__String_to_BitList(data[i:i+8])
|
||||||
|
if self.getMode() == CBC:
|
||||||
|
if crypt_type == Des.ENCRYPT:
|
||||||
|
block = [x ^ y for x, y in zip(block, iv)]
|
||||||
|
processed_block = self.__des_crypt(block, crypt_type)
|
||||||
|
if crypt_type == Des.DECRYPT:
|
||||||
|
processed_block = [x ^ y for x, y in zip(processed_block, iv)]
|
||||||
|
iv = block
|
||||||
|
else:
|
||||||
|
iv = processed_block
|
||||||
|
else:
|
||||||
|
processed_block = self.__des_crypt(block, crypt_type)
|
||||||
|
result.append(self.__BitList_to_String(processed_block))
|
||||||
|
i += 8
|
||||||
|
if crypt_type == Des.DECRYPT and self.getPadding():
|
||||||
|
s = result[-1]
|
||||||
|
while s[-1] == self.getPadding():
|
||||||
|
s = s[:-1]
|
||||||
|
result[-1] = s
|
||||||
|
return ''.join(result)
|
||||||
|
def encrypt(self, data, pad=''):
|
||||||
|
self.__padding = pad
|
||||||
|
return self.crypt(data, Des.ENCRYPT)
|
||||||
|
def decrypt(self, data, pad=''):
|
||||||
|
self.__padding = pad
|
||||||
|
return self.crypt(data, Des.DECRYPT)
|
||||||
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/__init__.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/__init__.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/_psyco.pyd
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/_psyco.pyd
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/builtin.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/builtin.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/classes.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/classes.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/clibrary.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/clibrary.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/core.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/core.pyo
Normal file
Binary file not shown.
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/logger.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/logger.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/profiler.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/profiler.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/support.pyo
Normal file
BIN
Calibre_Plugins/eReaderPDB2PML_plugin/windows/psyco/support.pyo
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,65 +0,0 @@
|
|||||||
Ignoble Epub DeDRM - ignobleepub_vXX_plugin.zip
|
|
||||||
Requires Calibre version 0.6.44 or higher.
|
|
||||||
|
|
||||||
All credit given to I <3 Cabbages for the original standalone scripts.
|
|
||||||
I had the much easier job of converting them to a Calibre plugin.
|
|
||||||
|
|
||||||
This plugin is meant to decrypt Barnes & Noble Epubs that are protected
|
|
||||||
with Adobe's Adept encryption. It is meant to function without having to install
|
|
||||||
any dependencies... other than having Calibre installed, of course. It will still
|
|
||||||
work if you have Python and PyCrypto already installed, but they aren't necessary.
|
|
||||||
|
|
||||||
Installation:
|
|
||||||
|
|
||||||
Go to Calibre's Preferences page... click on the Plugins button. Use the file
|
|
||||||
dialog button to select the plugin's zip file (ignobleepub_vXX_plugin.zip) and
|
|
||||||
click the 'Add' button. you're done.
|
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
1) The easiest way to configure the plugin is to enter your name (Barnes & Noble account
|
|
||||||
name) and credit card number (the one used to purchase the books) into the plugin's
|
|
||||||
customization window. It's the same info you would enter into the ignoblekeygen script.
|
|
||||||
Highlight the plugin (Ignoble Epub DeDRM) and click the "Customize Plugin" button on
|
|
||||||
Calibre's Preferences->Plugins page. Enter the name and credit card number separated
|
|
||||||
by a comma: Your Name,1234123412341234
|
|
||||||
|
|
||||||
If you've purchased books with more than one credit card, separate that other info with
|
|
||||||
a colon: Your Name,1234123412341234:Other Name,2345234523452345
|
|
||||||
|
|
||||||
** NOTE ** The above method is your only option if you don't have/can't run the original
|
|
||||||
I <3 Cabbages scripts on your particular machine.
|
|
||||||
|
|
||||||
** NOTE ** Your credit card number will be on display in Calibre's Plugin configuration
|
|
||||||
page when using the above method. If other people have access to your computer,
|
|
||||||
you may want to use the second configuration method below.
|
|
||||||
|
|
||||||
2) If you already have keyfiles generated with I <3 Cabbages' ignoblekeygen.pyw
|
|
||||||
script, you can put those keyfiles into Calibre's configuration directory. The easiest
|
|
||||||
way to find the correct directory is to go to Calibre's Preferences page... click
|
|
||||||
on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre
|
|
||||||
configuration directory' button. Paste your keyfiles in there. Just make sure that
|
|
||||||
they have different names and are saved with the '.b64' extension (like the ignoblekeygen
|
|
||||||
script produces). This directory isn't touched when upgrading Calibre, so it's quite safe
|
|
||||||
to leave then there.
|
|
||||||
|
|
||||||
All keyfiles from method 2 and all data entered from method 1 will be used to attempt
|
|
||||||
to decrypt a book. You can use method 1 or method 2, or a combination of both.
|
|
||||||
|
|
||||||
Troubleshooting:
|
|
||||||
|
|
||||||
If you find that it's not working for you (imported epubs still have DRM), you can
|
|
||||||
save a lot of time and trouble by trying to add the epub to Calibre with the command
|
|
||||||
line tools. This will print out a lot of helpful debugging info that can be copied into
|
|
||||||
any online help requests. I'm going to ask you to do it first, anyway, so you might
|
|
||||||
as well get used to it. ;)
|
|
||||||
|
|
||||||
Open a command prompt (terminal) and change to the directory where the ebook you're
|
|
||||||
trying to import resides. Then type the command "calibredb add your_ebook.epub".
|
|
||||||
Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the
|
|
||||||
filename of your book is. Copy the resulting output and paste it into any online
|
|
||||||
help request you make.
|
|
||||||
|
|
||||||
** Note: the Mac version of Calibre doesn't install the command line tools by default.
|
|
||||||
If you go to the 'Preferences' page and click on the miscellaneous button, you'll
|
|
||||||
see the option to install the command line tools.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# ignobleepub_v01_plugin.py
|
# ignobleepub_plugin.py
|
||||||
# Released under the terms of the GNU General Public Licence, version 3 or
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
# later. <http://www.gnu.org/licenses/>
|
# later. <http://www.gnu.org/licenses/>
|
||||||
#
|
#
|
||||||
@@ -41,7 +41,9 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 0.1 - Initial release
|
# 0.1.0 - Initial release
|
||||||
|
# 0.1.1 - Allow Windows users to make use of openssl if they have it installed.
|
||||||
|
# - Incorporated SomeUpdates zipfix routine.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -77,6 +79,9 @@ def _load_crypto_libcrypto():
|
|||||||
Structure, c_ulong, create_string_buffer, cast
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
libcrypto = find_library('crypto')
|
libcrypto = find_library('crypto')
|
||||||
if libcrypto is None:
|
if libcrypto is None:
|
||||||
raise IGNOBLEError('libcrypto not found')
|
raise IGNOBLEError('libcrypto not found')
|
||||||
@@ -261,7 +266,7 @@ class IgnobleDeDRM(FileTypePlugin):
|
|||||||
Credit given to I <3 Cabbages for the original stand-alone scripts.'
|
Credit given to I <3 Cabbages for the original stand-alone scripts.'
|
||||||
supported_platforms = ['linux', 'osx', 'windows']
|
supported_platforms = ['linux', 'osx', 'windows']
|
||||||
author = 'DiapDealer'
|
author = 'DiapDealer'
|
||||||
version = (0, 1, 0)
|
version = (0, 1, 1)
|
||||||
minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions.
|
minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions.
|
||||||
file_types = set(['epub'])
|
file_types = set(['epub'])
|
||||||
on_import = True
|
on_import = True
|
||||||
@@ -277,13 +282,12 @@ class IgnobleDeDRM(FileTypePlugin):
|
|||||||
# Add the included pycrypto import directory for Windows users.
|
# Add the included pycrypto import directory for Windows users.
|
||||||
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||||
ppath = os.path.join(self.sys_insertion_path, pdir)
|
ppath = os.path.join(self.sys_insertion_path, pdir)
|
||||||
#sys.path.insert(0, ppath)
|
|
||||||
sys.path.append(ppath)
|
sys.path.append(ppath)
|
||||||
|
|
||||||
AES, AES2 = _load_crypto()
|
AES, AES2 = _load_crypto()
|
||||||
|
|
||||||
if AES == None or AES2 == None:
|
if AES == None or AES2 == None:
|
||||||
# Failed to load libcrypto or PyCrypto... Adobe Epubs can\'t be decrypted.'
|
# Failed to load libcrypto or PyCrypto... Adobe Epubs can't be decrypted.'
|
||||||
sys.path.remove(ppath)
|
sys.path.remove(ppath)
|
||||||
raise IGNOBLEError('IgnobleEpub - Failed to load crypto libs.')
|
raise IGNOBLEError('IgnobleEpub - Failed to load crypto libs.')
|
||||||
return
|
return
|
||||||
@@ -337,10 +341,19 @@ class IgnobleDeDRM(FileTypePlugin):
|
|||||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||||
for userkey in userkeys:
|
for userkey in userkeys:
|
||||||
# Create a TemporaryPersistent file to work with.
|
# Create a TemporaryPersistent file to work with.
|
||||||
|
# Check original epub archive for zip errors.
|
||||||
|
import zipfix
|
||||||
|
inf = self.temporary_file('.epub')
|
||||||
|
try:
|
||||||
|
fr = zipfix.fixZip(path_to_ebook, inf.name)
|
||||||
|
fr.fix()
|
||||||
|
except Exception, e:
|
||||||
|
raise Exception(e)
|
||||||
|
return
|
||||||
of = self.temporary_file('.epub')
|
of = self.temporary_file('.epub')
|
||||||
|
|
||||||
# Give the user key, ebook and TemporaryPersistent file to the Stripper function.
|
# Give the user key, ebook and TemporaryPersistent file to the Stripper function.
|
||||||
result = plugin_main(userkey, path_to_ebook, of.name)
|
result = plugin_main(userkey, inf.name, of.name)
|
||||||
|
|
||||||
# Ebook is not a B&N Adept epub... do nothing and pass it on.
|
# Ebook is not a B&N Adept epub... do nothing and pass it on.
|
||||||
# This allows a non-encrypted epub to be imported without error messages.
|
# This allows a non-encrypted epub to be imported without error messages.
|
||||||
|
|||||||
BIN
Calibre_Plugins/ignobleepub_plugin/windows/Crypto/Cipher/AES.pyd
Normal file
BIN
Calibre_Plugins/ignobleepub_plugin/windows/Crypto/Cipher/AES.pyd
Normal file
Binary file not shown.
136
Calibre_Plugins/ignobleepub_plugin/zipfix.py
Normal file
136
Calibre_Plugins/ignobleepub_plugin/zipfix.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import zlib
|
||||||
|
import zipfile
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import getopt
|
||||||
|
from struct import unpack
|
||||||
|
|
||||||
|
|
||||||
|
_FILENAME_LEN_OFFSET = 26
|
||||||
|
_EXTRA_LEN_OFFSET = 28
|
||||||
|
_FILENAME_OFFSET = 30
|
||||||
|
_MAX_SIZE = 64 * 1024
|
||||||
|
|
||||||
|
class fixZip:
|
||||||
|
def __init__(self, zinput, zoutput):
|
||||||
|
self.inzip = zipfile.ZipFile(zinput,'r')
|
||||||
|
self.outzip = zipfile.ZipFile(zoutput,'w')
|
||||||
|
# open the input zip for reading only as a raw file
|
||||||
|
self.bzf = file(zinput,'rb')
|
||||||
|
|
||||||
|
def getlocalname(self, zi):
|
||||||
|
local_header_offset = zi.header_offset
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
||||||
|
leninfo = self.bzf.read(2)
|
||||||
|
local_name_length, = unpack('<H', leninfo)
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_OFFSET)
|
||||||
|
local_name = self.bzf.read(local_name_length)
|
||||||
|
return local_name
|
||||||
|
|
||||||
|
def uncompress(self, cmpdata):
|
||||||
|
dc = zlib.decompressobj(-15)
|
||||||
|
data = ''
|
||||||
|
while len(cmpdata) > 0:
|
||||||
|
if len(cmpdata) > _MAX_SIZE :
|
||||||
|
newdata = cmpdata[0:_MAX_SIZE]
|
||||||
|
cmpdata = cmpdata[_MAX_SIZE:]
|
||||||
|
else:
|
||||||
|
newdata = cmpdata
|
||||||
|
cmpdata = ''
|
||||||
|
newdata = dc.decompress(newdata)
|
||||||
|
unprocessed = dc.unconsumed_tail
|
||||||
|
if len(unprocessed) == 0:
|
||||||
|
newdata += dc.flush()
|
||||||
|
data += newdata
|
||||||
|
cmpdata += unprocessed
|
||||||
|
unprocessed = ''
|
||||||
|
return data
|
||||||
|
|
||||||
|
def getfiledata(self, zi):
|
||||||
|
# get file name length and exta data length to find start of file data
|
||||||
|
local_header_offset = zi.header_offset
|
||||||
|
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
||||||
|
leninfo = self.bzf.read(2)
|
||||||
|
local_name_length, = unpack('<H', leninfo)
|
||||||
|
|
||||||
|
self.bzf.seek(local_header_offset + _EXTRA_LEN_OFFSET)
|
||||||
|
exinfo = self.bzf.read(2)
|
||||||
|
extra_field_length, = unpack('<H', exinfo)
|
||||||
|
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
|
||||||
|
data = None
|
||||||
|
|
||||||
|
# if not compressed we are good to go
|
||||||
|
if zi.compress_type == zipfile.ZIP_STORED:
|
||||||
|
data = self.bzf.read(zi.file_size)
|
||||||
|
|
||||||
|
# if compressed we must decompress it using zlib
|
||||||
|
if zi.compress_type == zipfile.ZIP_DEFLATED:
|
||||||
|
cmpdata = self.bzf.read(zi.compress_size)
|
||||||
|
data = self.uncompress(cmpdata)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def fix(self):
|
||||||
|
# get the zipinfo for each member of the input archive
|
||||||
|
# and copy member over to output archive
|
||||||
|
# if problems exist with local vs central filename, fix them
|
||||||
|
|
||||||
|
for i, zinfo in enumerate(self.inzip.infolist()):
|
||||||
|
data = None
|
||||||
|
nzinfo = zinfo
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = self.inzip.read(zinfo)
|
||||||
|
except zipfile.BadZipfile or zipfile.error:
|
||||||
|
local_name = self.getlocalname(zinfo)
|
||||||
|
data = self.getfiledata(zinfo)
|
||||||
|
nzinfo.filename = local_name
|
||||||
|
|
||||||
|
nzinfo.date_time = zinfo.date_time
|
||||||
|
nzinfo.compress_type = zinfo.compress_type
|
||||||
|
nzinfo.flag_bits = 0
|
||||||
|
nzinfo.internal_attr = 0
|
||||||
|
self.outzip.writestr(nzinfo,data)
|
||||||
|
|
||||||
|
self.bzf.close()
|
||||||
|
self.inzip.close()
|
||||||
|
self.outzip.close()
|
||||||
|
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print """usage: zipfix.py inputzip outputzip
|
||||||
|
inputzip is the source zipfile to fix
|
||||||
|
outputzip is the fixed zip archive
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
if len(argv)!=3:
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
infile = None
|
||||||
|
outfile = None
|
||||||
|
infile = argv[1]
|
||||||
|
outfile = argv[2]
|
||||||
|
if not os.path.exists(infile):
|
||||||
|
print "Error: Input Zip File does not exist"
|
||||||
|
return 1
|
||||||
|
try:
|
||||||
|
fr = fixZip(infile, outfile)
|
||||||
|
fr.fix()
|
||||||
|
return 0
|
||||||
|
except Exception, e:
|
||||||
|
print "Error Occurred ", e
|
||||||
|
return 2
|
||||||
|
|
||||||
|
if __name__ == '__main__' :
|
||||||
|
sys.exit(main())
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
@@ -1,62 +0,0 @@
|
|||||||
Inept Epub DeDRM - ineptepub_vXX_plugin.zip
|
|
||||||
Requires Calibre version 0.6.44 or higher.
|
|
||||||
|
|
||||||
All credit given to I <3 Cabbages for the original standalone scripts.
|
|
||||||
I had the much easier job of converting them to a Calibre plugin.
|
|
||||||
|
|
||||||
This plugin is meant to decrypt Adobe Digital Edition Epubs that are protected
|
|
||||||
with Adobe's Adept encryption. It is meant to function without having to install
|
|
||||||
any dependencies... other than having Calibre installed, of course. It will still
|
|
||||||
work if you have Python and PyCrypto already installed, but they aren't necessary.
|
|
||||||
|
|
||||||
Installation:
|
|
||||||
|
|
||||||
Go to Calibre's Preferences page... click on the Plugins button. Use the file
|
|
||||||
dialog button to select the plugin's zip file (ineptepub_vXX_plugin.zip) and
|
|
||||||
click the 'Add' button. you're done.
|
|
||||||
|
|
||||||
Configuration:
|
|
||||||
|
|
||||||
When first run, the plugin will attempt to find your Adobe Digital Editions installation
|
|
||||||
(on Windows and Mac OS's). If successful, it will create an 'adeptkey.der' file and
|
|
||||||
save it in Calibre's configuration directory. It will use that file on subsequent runs.
|
|
||||||
If there are already '*.der' files in the directory, the plugin won't attempt to
|
|
||||||
find the Adobe Digital Editions installation installation.
|
|
||||||
|
|
||||||
So if you have Adobe Digital Editions installation installed on the same machine as Calibre...
|
|
||||||
you are ready to go. If not... keep reading.
|
|
||||||
|
|
||||||
If you already have keyfiles generated with I <3 Cabbages' ineptkey.pyw script,
|
|
||||||
you can put those keyfiles in Calibre's configuration directory. The easiest
|
|
||||||
way to find the correct directory is to go to Calibre's Preferences page... click
|
|
||||||
on the 'Miscellaneous' button (looks like a gear), and then click the 'Open Calibre
|
|
||||||
configuration directory' button. Paste your keyfiles in there. Just make sure that
|
|
||||||
they have different names and are saved with the '.der' extension (like the ineptkey
|
|
||||||
script produces). This directory isn't touched when upgrading Calibre, so it's quite
|
|
||||||
safe to leave them there.
|
|
||||||
|
|
||||||
Since there is no Linux version of Adobe Digital Editions, Linux users will have to
|
|
||||||
obtain a keyfile through other methods and put the file in Calibre's configuration directory.
|
|
||||||
|
|
||||||
All keyfiles with a '.der' extension found in Calibre's configuration directory will
|
|
||||||
be used to attempt to decrypt a book.
|
|
||||||
|
|
||||||
** NOTE ** There is no plugin customization data for the Inept Epub DeDRM plugin.
|
|
||||||
|
|
||||||
Troubleshooting:
|
|
||||||
|
|
||||||
If you find that it's not working for you (imported epubs still have DRM), you can
|
|
||||||
save a lot of time and trouble by trying to add the epub to Calibre with the command
|
|
||||||
line tools. This will print out a lot of helpful debugging info that can be copied into
|
|
||||||
any online help requests. I'm going to ask you to do it first, anyway, so you might
|
|
||||||
as well get used to it. ;)
|
|
||||||
|
|
||||||
Open a command prompt (terminal) and change to the directory where the ebook you're
|
|
||||||
trying to import resides. Then type the command "calibredb add your_ebook.epub".
|
|
||||||
Don't type the quotes and obviously change the 'your_ebook.epub' to whatever the
|
|
||||||
filename of your book is. Copy the resulting output and paste it into any online
|
|
||||||
help request you make.
|
|
||||||
|
|
||||||
** Note: the Mac version of Calibre doesn't install the command line tools by default.
|
|
||||||
If you go to the 'Preferences' page and click on the miscellaneous button, you'll
|
|
||||||
see the option to install the command line tools.
|
|
||||||
@@ -19,14 +19,76 @@ class ADEPTError(Exception):
|
|||||||
if iswindows:
|
if iswindows:
|
||||||
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
||||||
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
||||||
string_at, Structure, c_void_p, cast, c_size_t, memmove
|
string_at, Structure, c_void_p, cast, c_size_t, memmove, CDLL, c_int, \
|
||||||
|
c_long, c_ulong
|
||||||
|
|
||||||
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
||||||
import _winreg as winreg
|
import _winreg as winreg
|
||||||
|
|
||||||
|
def _load_crypto_libcrypto():
|
||||||
|
from ctypes.util import find_library
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
if libcrypto is None:
|
||||||
|
raise ADEPTError('libcrypto not found')
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
AES_MAXNR = 14
|
||||||
|
c_char_pp = POINTER(c_char_p)
|
||||||
|
c_int_p = POINTER(c_int)
|
||||||
|
class AES_KEY(Structure):
|
||||||
|
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
||||||
|
('rounds', c_int)]
|
||||||
|
AES_KEY_p = POINTER(AES_KEY)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
|
||||||
|
[c_char_p, c_int, AES_KEY_p])
|
||||||
|
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
||||||
|
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
||||||
|
c_int])
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, userkey):
|
||||||
|
self._blocksize = len(userkey)
|
||||||
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
|
raise ADEPTError('AES improper key used')
|
||||||
|
key = self._key = AES_KEY()
|
||||||
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
||||||
|
if rv < 0:
|
||||||
|
raise ADEPTError('Failed to initialize AES key')
|
||||||
|
def decrypt(self, data):
|
||||||
|
out = create_string_buffer(len(data))
|
||||||
|
iv = ("\x00" * self._blocksize)
|
||||||
|
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
||||||
|
if rv == 0:
|
||||||
|
raise ADEPTError('AES decryption failed')
|
||||||
|
return out.raw
|
||||||
|
return AES
|
||||||
|
|
||||||
|
def _load_crypto_pycrypto():
|
||||||
|
from Crypto.Cipher import AES as _AES
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, key):
|
||||||
|
self._aes = _AES.new(key, _AES.MODE_CBC)
|
||||||
|
def decrypt(self, data):
|
||||||
|
return self._aes.decrypt(data)
|
||||||
|
return AES
|
||||||
|
|
||||||
|
def _load_crypto():
|
||||||
|
AES = None
|
||||||
|
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
|
||||||
try:
|
try:
|
||||||
from Crypto.Cipher import AES as _aes
|
AES = loader()
|
||||||
except ImportError:
|
break
|
||||||
_aes = None
|
except (ImportError, ADEPTError):
|
||||||
|
pass
|
||||||
|
return AES
|
||||||
|
|
||||||
|
AES = _load_crypto()
|
||||||
|
|
||||||
|
|
||||||
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
||||||
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
||||||
@@ -193,8 +255,12 @@ if iswindows:
|
|||||||
CryptUnprotectData = CryptUnprotectData()
|
CryptUnprotectData = CryptUnprotectData()
|
||||||
|
|
||||||
def retrieve_key():
|
def retrieve_key():
|
||||||
if _aes is None:
|
if AES is None:
|
||||||
raise ADEPTError("Couldn\'t load PyCrypto")
|
tkMessageBox.showerror(
|
||||||
|
"ADEPT Key",
|
||||||
|
"This script requires PyCrypto or OpenSSL which must be installed "
|
||||||
|
"separately. Read the top-of-script comment for details.")
|
||||||
|
return False
|
||||||
root = GetSystemDirectory().split('\\')[0] + '\\'
|
root = GetSystemDirectory().split('\\')[0] + '\\'
|
||||||
serial = GetVolumeSerialNumber(root)
|
serial = GetVolumeSerialNumber(root)
|
||||||
vendor = cpuid0()
|
vendor = cpuid0()
|
||||||
@@ -236,7 +302,8 @@ if iswindows:
|
|||||||
if userkey is None:
|
if userkey is None:
|
||||||
raise ADEPTError('Could not locate privateLicenseKey')
|
raise ADEPTError('Could not locate privateLicenseKey')
|
||||||
userkey = userkey.decode('base64')
|
userkey = userkey.decode('base64')
|
||||||
userkey = _aes.new(keykey, _aes.MODE_CBC).decrypt(userkey)
|
aes = AES(keykey)
|
||||||
|
userkey = aes.decrypt(userkey)
|
||||||
userkey = userkey[26:-ord(userkey[-1])]
|
userkey = userkey[26:-ord(userkey[-1])]
|
||||||
return userkey
|
return userkey
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#! /usr/bin/python
|
#! /usr/bin/python
|
||||||
|
|
||||||
# ineptepub_v01_plugin.py
|
# ineptepub_plugin.py
|
||||||
# Released under the terms of the GNU General Public Licence, version 3 or
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
# later. <http://www.gnu.org/licenses/>
|
# later. <http://www.gnu.org/licenses/>
|
||||||
#
|
#
|
||||||
@@ -41,6 +41,8 @@
|
|||||||
#
|
#
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 0.1 - Initial release
|
# 0.1 - Initial release
|
||||||
|
# 0.1.1 - Allow Windows users to make use of openssl if they have it installed.
|
||||||
|
# - Incorporated SomeUpdates zipfix routine.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -76,6 +78,9 @@ def _load_crypto_libcrypto():
|
|||||||
Structure, c_ulong, create_string_buffer, cast
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
libcrypto = find_library('crypto')
|
libcrypto = find_library('crypto')
|
||||||
if libcrypto is None:
|
if libcrypto is None:
|
||||||
raise ADEPTError('libcrypto not found')
|
raise ADEPTError('libcrypto not found')
|
||||||
@@ -358,7 +363,7 @@ class IneptDeDRM(FileTypePlugin):
|
|||||||
Credit given to I <3 Cabbages for the original stand-alone scripts.'
|
Credit given to I <3 Cabbages for the original stand-alone scripts.'
|
||||||
supported_platforms = ['linux', 'osx', 'windows']
|
supported_platforms = ['linux', 'osx', 'windows']
|
||||||
author = 'DiapDealer'
|
author = 'DiapDealer'
|
||||||
version = (0, 1, 0)
|
version = (0, 1, 1)
|
||||||
minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions.
|
minimum_calibre_version = (0, 6, 44) # Compiled python libraries cannot be imported in earlier versions.
|
||||||
file_types = set(['epub'])
|
file_types = set(['epub'])
|
||||||
on_import = True
|
on_import = True
|
||||||
@@ -376,7 +381,6 @@ class IneptDeDRM(FileTypePlugin):
|
|||||||
# Add the included Carbon import directory for Mac users.
|
# Add the included Carbon import directory for Mac users.
|
||||||
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
pdir = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||||
ppath = os.path.join(self.sys_insertion_path, pdir)
|
ppath = os.path.join(self.sys_insertion_path, pdir)
|
||||||
#sys.path.insert(0, ppath)
|
|
||||||
sys.path.append(ppath)
|
sys.path.append(ppath)
|
||||||
|
|
||||||
AES, RSA = _load_crypto()
|
AES, RSA = _load_crypto()
|
||||||
@@ -433,10 +437,19 @@ class IneptDeDRM(FileTypePlugin):
|
|||||||
# Attempt to decrypt epub with each encryption key found.
|
# Attempt to decrypt epub with each encryption key found.
|
||||||
for userkey in userkeys:
|
for userkey in userkeys:
|
||||||
# Create a TemporaryPersistent file to work with.
|
# Create a TemporaryPersistent file to work with.
|
||||||
|
# Check original epub archive for zip errors.
|
||||||
|
import zipfix
|
||||||
|
inf = self.temporary_file('.epub')
|
||||||
|
try:
|
||||||
|
fr = zipfix.fixZip(path_to_ebook, inf.name)
|
||||||
|
fr.fix()
|
||||||
|
except Exception, e:
|
||||||
|
raise Exception(e)
|
||||||
|
return
|
||||||
of = self.temporary_file('.epub')
|
of = self.temporary_file('.epub')
|
||||||
|
|
||||||
# Give the user key, ebook and TemporaryPersistent file to the plugin_main function.
|
# Give the user key, ebook and TemporaryPersistent file to the plugin_main function.
|
||||||
result = plugin_main(userkey, path_to_ebook, of.name)
|
result = plugin_main(userkey, inf.name, of.name)
|
||||||
|
|
||||||
# Ebook is not an Adobe Adept epub... do nothing and pass it on.
|
# Ebook is not an Adobe Adept epub... do nothing and pass it on.
|
||||||
# This allows a non-encrypted epub to be imported without error messages.
|
# This allows a non-encrypted epub to be imported without error messages.
|
||||||
|
|||||||
BIN
Calibre_Plugins/ineptepub_plugin/osx/Carbon/File.pyo
Normal file
BIN
Calibre_Plugins/ineptepub_plugin/osx/Carbon/File.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folder.pyo
Normal file
BIN
Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folder.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folders.pyo
Normal file
BIN
Calibre_Plugins/ineptepub_plugin/osx/Carbon/Folders.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/ineptepub_plugin/osx/Carbon/__init__.pyo
Normal file
BIN
Calibre_Plugins/ineptepub_plugin/osx/Carbon/__init__.pyo
Normal file
Binary file not shown.
BIN
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Cipher/AES.pyd
Normal file
BIN
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Cipher/AES.pyd
Normal file
Binary file not shown.
BIN
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Hash/SHA256.pyd
Normal file
BIN
Calibre_Plugins/ineptepub_plugin/windows/Crypto/Hash/SHA256.pyd
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
136
Calibre_Plugins/ineptepub_plugin/zipfix.py
Normal file
136
Calibre_Plugins/ineptepub_plugin/zipfix.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import zlib
|
||||||
|
import zipfile
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import getopt
|
||||||
|
from struct import unpack
|
||||||
|
|
||||||
|
|
||||||
|
_FILENAME_LEN_OFFSET = 26
|
||||||
|
_EXTRA_LEN_OFFSET = 28
|
||||||
|
_FILENAME_OFFSET = 30
|
||||||
|
_MAX_SIZE = 64 * 1024
|
||||||
|
|
||||||
|
class fixZip:
|
||||||
|
def __init__(self, zinput, zoutput):
|
||||||
|
self.inzip = zipfile.ZipFile(zinput,'r')
|
||||||
|
self.outzip = zipfile.ZipFile(zoutput,'w')
|
||||||
|
# open the input zip for reading only as a raw file
|
||||||
|
self.bzf = file(zinput,'rb')
|
||||||
|
|
||||||
|
def getlocalname(self, zi):
|
||||||
|
local_header_offset = zi.header_offset
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
||||||
|
leninfo = self.bzf.read(2)
|
||||||
|
local_name_length, = unpack('<H', leninfo)
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_OFFSET)
|
||||||
|
local_name = self.bzf.read(local_name_length)
|
||||||
|
return local_name
|
||||||
|
|
||||||
|
def uncompress(self, cmpdata):
|
||||||
|
dc = zlib.decompressobj(-15)
|
||||||
|
data = ''
|
||||||
|
while len(cmpdata) > 0:
|
||||||
|
if len(cmpdata) > _MAX_SIZE :
|
||||||
|
newdata = cmpdata[0:_MAX_SIZE]
|
||||||
|
cmpdata = cmpdata[_MAX_SIZE:]
|
||||||
|
else:
|
||||||
|
newdata = cmpdata
|
||||||
|
cmpdata = ''
|
||||||
|
newdata = dc.decompress(newdata)
|
||||||
|
unprocessed = dc.unconsumed_tail
|
||||||
|
if len(unprocessed) == 0:
|
||||||
|
newdata += dc.flush()
|
||||||
|
data += newdata
|
||||||
|
cmpdata += unprocessed
|
||||||
|
unprocessed = ''
|
||||||
|
return data
|
||||||
|
|
||||||
|
def getfiledata(self, zi):
|
||||||
|
# get file name length and exta data length to find start of file data
|
||||||
|
local_header_offset = zi.header_offset
|
||||||
|
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
||||||
|
leninfo = self.bzf.read(2)
|
||||||
|
local_name_length, = unpack('<H', leninfo)
|
||||||
|
|
||||||
|
self.bzf.seek(local_header_offset + _EXTRA_LEN_OFFSET)
|
||||||
|
exinfo = self.bzf.read(2)
|
||||||
|
extra_field_length, = unpack('<H', exinfo)
|
||||||
|
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
|
||||||
|
data = None
|
||||||
|
|
||||||
|
# if not compressed we are good to go
|
||||||
|
if zi.compress_type == zipfile.ZIP_STORED:
|
||||||
|
data = self.bzf.read(zi.file_size)
|
||||||
|
|
||||||
|
# if compressed we must decompress it using zlib
|
||||||
|
if zi.compress_type == zipfile.ZIP_DEFLATED:
|
||||||
|
cmpdata = self.bzf.read(zi.compress_size)
|
||||||
|
data = self.uncompress(cmpdata)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def fix(self):
|
||||||
|
# get the zipinfo for each member of the input archive
|
||||||
|
# and copy member over to output archive
|
||||||
|
# if problems exist with local vs central filename, fix them
|
||||||
|
|
||||||
|
for i, zinfo in enumerate(self.inzip.infolist()):
|
||||||
|
data = None
|
||||||
|
nzinfo = zinfo
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = self.inzip.read(zinfo)
|
||||||
|
except zipfile.BadZipfile or zipfile.error:
|
||||||
|
local_name = self.getlocalname(zinfo)
|
||||||
|
data = self.getfiledata(zinfo)
|
||||||
|
nzinfo.filename = local_name
|
||||||
|
|
||||||
|
nzinfo.date_time = zinfo.date_time
|
||||||
|
nzinfo.compress_type = zinfo.compress_type
|
||||||
|
nzinfo.flag_bits = 0
|
||||||
|
nzinfo.internal_attr = 0
|
||||||
|
self.outzip.writestr(nzinfo,data)
|
||||||
|
|
||||||
|
self.bzf.close()
|
||||||
|
self.inzip.close()
|
||||||
|
self.outzip.close()
|
||||||
|
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print """usage: zipfix.py inputzip outputzip
|
||||||
|
inputzip is the source zipfile to fix
|
||||||
|
outputzip is the fixed zip archive
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
if len(argv)!=3:
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
infile = None
|
||||||
|
outfile = None
|
||||||
|
infile = argv[1]
|
||||||
|
outfile = argv[2]
|
||||||
|
if not os.path.exists(infile):
|
||||||
|
print "Error: Input Zip File does not exist"
|
||||||
|
return 1
|
||||||
|
try:
|
||||||
|
fr = fixZip(infile, outfile)
|
||||||
|
fr.fix()
|
||||||
|
return 0
|
||||||
|
except Exception, e:
|
||||||
|
print "Error Occurred ", e
|
||||||
|
return 2
|
||||||
|
|
||||||
|
if __name__ == '__main__' :
|
||||||
|
sys.exit(main())
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
__version__ = '1.1'
|
__version__ = '1.2'
|
||||||
|
|
||||||
class Unbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
@@ -43,6 +43,7 @@ import sys
|
|||||||
import os, csv, getopt
|
import os, csv, getopt
|
||||||
import binascii
|
import binascii
|
||||||
import zlib
|
import zlib
|
||||||
|
import re
|
||||||
from struct import pack, unpack, unpack_from
|
from struct import pack, unpack, unpack_from
|
||||||
|
|
||||||
|
|
||||||
@@ -115,9 +116,9 @@ def decode(data,map):
|
|||||||
|
|
||||||
|
|
||||||
# Parse the Kindle.info file and return the records as a list of key-values
|
# Parse the Kindle.info file and return the records as a list of key-values
|
||||||
def parseKindleInfo():
|
def parseKindleInfo(kInfoFile):
|
||||||
DB = {}
|
DB = {}
|
||||||
infoReader = openKindleInfo()
|
infoReader = openKindleInfo(kInfoFile)
|
||||||
infoReader.read(1)
|
infoReader.read(1)
|
||||||
data = infoReader.read()
|
data = infoReader.read()
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
@@ -279,11 +280,11 @@ class MobiPeek:
|
|||||||
|
|
||||||
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
|
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
|
||||||
# file to calculate the book pid.
|
# file to calculate the book pid.
|
||||||
def getK4Pids(exth, title):
|
def getK4Pids(exth, title, kInfoFile=None):
|
||||||
global kindleDatabase
|
global kindleDatabase
|
||||||
try:
|
try:
|
||||||
kindleDatabase = parseKindleInfo()
|
kindleDatabase = parseKindleInfo(kInfoFile)
|
||||||
except Exception as message:
|
except Exception, message:
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
if kindleDatabase != None :
|
if kindleDatabase != None :
|
||||||
@@ -312,6 +313,8 @@ def getK4Pids(exth, title):
|
|||||||
exth_records = {}
|
exth_records = {}
|
||||||
nitems, = unpack('>I', exth[8:12])
|
nitems, = unpack('>I', exth[8:12])
|
||||||
pos = 12
|
pos = 12
|
||||||
|
|
||||||
|
exth_records[209] = None
|
||||||
# Parse the exth records, storing data indexed by type
|
# Parse the exth records, storing data indexed by type
|
||||||
for i in xrange(nitems):
|
for i in xrange(nitems):
|
||||||
type, size = unpack('>II', exth[pos: pos + 8])
|
type, size = unpack('>II', exth[pos: pos + 8])
|
||||||
@@ -353,30 +356,50 @@ def getK4Pids(exth, title):
|
|||||||
raise DrmException("\nCould not access K4 data - Perhaps K4 is not installed/configured?")
|
raise DrmException("\nCould not access K4 data - Perhaps K4 is not installed/configured?")
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
def usage(progname):
|
||||||
|
print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
|
||||||
|
print "Usage:"
|
||||||
|
print " %s [-k <kindle.info>] [-p <pidnums>] <infile> <outfile> " % progname
|
||||||
|
|
||||||
#
|
#
|
||||||
# Main
|
# Main
|
||||||
#
|
#
|
||||||
def main(argv=sys.argv):
|
def main(argv=sys.argv):
|
||||||
global kindleDatabase
|
global kindleDatabase
|
||||||
import mobidedrm
|
import mobidedrm
|
||||||
|
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
kInfoFiles = []
|
||||||
|
pidnums = ""
|
||||||
|
|
||||||
print ('K4MobiDeDrm v%(__version__)s '
|
print ('K4MobiDeDrm v%(__version__)s '
|
||||||
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
|
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
|
||||||
|
|
||||||
if len(argv)<3:
|
try:
|
||||||
print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
|
opts, args = getopt.getopt(sys.argv[1:], "k:p:")
|
||||||
print "Usage:"
|
except getopt.GetoptError, err:
|
||||||
print " %s <infile> <outfile> [<pidnums>]" % argv[0]
|
print str(err)
|
||||||
return 1
|
usage(progname)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
if len(argv) == 4:
|
if len(args)<2:
|
||||||
pidnums = argv[3]
|
usage(progname)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
if len(argv) == 3:
|
for o, a in opts:
|
||||||
pidnums = ""
|
if o == "-k":
|
||||||
|
if a == None :
|
||||||
|
raise DrmException("Invalid parameter for -k")
|
||||||
|
kInfoFiles.append(a)
|
||||||
|
if o == "-p":
|
||||||
|
if a == None :
|
||||||
|
raise DrmException("Invalid parameter for -p")
|
||||||
|
pidnums = a
|
||||||
|
|
||||||
kindleDatabase = None
|
kindleDatabase = None
|
||||||
infile = argv[1]
|
infile = args[0]
|
||||||
outfile = argv[2]
|
outfile = args[1]
|
||||||
|
DecodeErrorString = ""
|
||||||
try:
|
try:
|
||||||
# first try with K4PC/K4M
|
# first try with K4PC/K4M
|
||||||
ex = MobiPeek(infile)
|
ex = MobiPeek(infile)
|
||||||
@@ -385,17 +408,42 @@ def main(argv=sys.argv):
|
|||||||
return 2
|
return 2
|
||||||
title = ex.getBookTitle()
|
title = ex.getBookTitle()
|
||||||
exth = ex.getexthData()
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
pid = getK4Pids(exth, title)
|
pid = getK4Pids(exth, title)
|
||||||
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||||
except DrmException:
|
except DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n"
|
||||||
pass
|
pass
|
||||||
except mobidedrm.DrmException:
|
except mobidedrm.DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n"
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
file(outfile, 'wb').write(unlocked_file)
|
file(outfile, 'wb').write(unlocked_file)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# now try from the pid list
|
# now try alternate kindle.info files
|
||||||
|
if kInfoFiles:
|
||||||
|
for infoFile in kInfoFiles:
|
||||||
|
kindleDatabase = None
|
||||||
|
try:
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title, infoFile)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||||
|
except DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file(outfile, 'wb').write(unlocked_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Lastly, try from the pid list
|
||||||
pids = pidnums.split(',')
|
pids = pidnums.split(',')
|
||||||
for pid in pids:
|
for pid in pids:
|
||||||
try:
|
try:
|
||||||
@@ -408,6 +456,7 @@ def main(argv=sys.argv):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
# we could not unencrypt book
|
# we could not unencrypt book
|
||||||
|
print DecodeErrorString
|
||||||
print "Error: Could Not Unencrypt Book"
|
print "Error: Could Not Unencrypt Book"
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
@@ -426,7 +475,7 @@ if not __name__ == "__main__" and inCalibre:
|
|||||||
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
|
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
|
||||||
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
|
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
|
||||||
author = 'DiapDealer, SomeUpdates' # The author of this plugin
|
author = 'DiapDealer, SomeUpdates' # The author of this plugin
|
||||||
version = (0, 0, 1) # The version number of this plugin
|
version = (0, 1, 3) # The version number of this plugin
|
||||||
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
|
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
|
||||||
on_import = True # Run this plugin during the import
|
on_import = True # Run this plugin during the import
|
||||||
priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
|
priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
|
||||||
@@ -442,8 +491,28 @@ if not __name__ == "__main__" and inCalibre:
|
|||||||
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
import mobidedrm
|
import mobidedrm
|
||||||
|
|
||||||
|
# Get supplied list of PIDs to try from plugin customization.
|
||||||
pidnums = self.site_customization
|
pidnums = self.site_customization
|
||||||
|
|
||||||
|
# Load any kindle info files (*.info) included Calibre's config directory.
|
||||||
|
kInfoFiles = []
|
||||||
|
try:
|
||||||
|
# Find Calibre's configuration directory.
|
||||||
|
confpath = os.path.split(os.path.split(self.plugin_path)[0])[0]
|
||||||
|
print 'K4MobiDeDRM: Calibre configuration directory = %s' % confpath
|
||||||
|
files = os.listdir(confpath)
|
||||||
|
filefilter = re.compile("\.info$", re.IGNORECASE)
|
||||||
|
files = filter(filefilter.search, files)
|
||||||
|
|
||||||
|
if files:
|
||||||
|
for filename in files:
|
||||||
|
fpath = os.path.join(confpath, filename)
|
||||||
|
kInfoFiles.append(fpath)
|
||||||
|
print 'K4MobiDeDRM: Kindle info file %s found in config folder.' % filename
|
||||||
|
except IOError:
|
||||||
|
print 'K4MobiDeDRM: Error reading kindle info files from config directory.'
|
||||||
|
pass
|
||||||
|
|
||||||
# first try with book specifc pid from K4PC or K4M
|
# first try with book specifc pid from K4PC or K4M
|
||||||
try:
|
try:
|
||||||
kindleDatabase = None
|
kindleDatabase = None
|
||||||
@@ -452,6 +521,8 @@ if not __name__ == "__main__" and inCalibre:
|
|||||||
return path_to_ebook
|
return path_to_ebook
|
||||||
title = ex.getBookTitle()
|
title = ex.getBookTitle()
|
||||||
exth = ex.getexthData()
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
pid = getK4Pids(exth, title)
|
pid = getK4Pids(exth, title)
|
||||||
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
|
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
|
||||||
except DrmException:
|
except DrmException:
|
||||||
@@ -464,6 +535,27 @@ if not __name__ == "__main__" and inCalibre:
|
|||||||
of.close()
|
of.close()
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
|
# Now try alternate kindle info files
|
||||||
|
if kInfoFiles:
|
||||||
|
for infoFile in kInfoFiles:
|
||||||
|
kindleDatabase = None
|
||||||
|
try:
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title, infoFile)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
|
||||||
|
except DrmException:
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
of = self.temporary_file('.mobi')
|
||||||
|
of.write(unlocked_file)
|
||||||
|
of.close()
|
||||||
|
return of.name
|
||||||
|
|
||||||
# now try from the pid list
|
# now try from the pid list
|
||||||
pids = pidnums.split(',')
|
pids = pidnums.split(',')
|
||||||
for pid in pids:
|
for pid in pids:
|
||||||
|
|||||||
@@ -237,24 +237,36 @@ LibCrypto = _load_crypto()
|
|||||||
#
|
#
|
||||||
|
|
||||||
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||||
# returns with the first found serial number in that class
|
# returns with the serial number of drive whose BSD Name is "disk0"
|
||||||
def GetVolumeSerialNumber():
|
def GetVolumeSerialNumber():
|
||||||
cmdline = '/usr/sbin/ioreg -r -c AppleAHCIDiskDriver'
|
sernum = os.getenv('MYSERIALNUMBER')
|
||||||
|
if sernum != None:
|
||||||
|
return sernum
|
||||||
|
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
||||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||||
poll = p.wait('wait')
|
poll = p.wait('wait')
|
||||||
results = p.read()
|
results = p.read()
|
||||||
reslst = results.split('\n')
|
reslst = results.split('\n')
|
||||||
sernum = '9999999999'
|
|
||||||
cnt = len(reslst)
|
cnt = len(reslst)
|
||||||
|
bsdname = None
|
||||||
|
sernum = None
|
||||||
|
foundIt = False
|
||||||
for j in xrange(cnt):
|
for j in xrange(cnt):
|
||||||
resline = reslst[j]
|
resline = reslst[j]
|
||||||
pp = resline.find('"Serial Number" = "')
|
pp = resline.find('"Serial Number" = "')
|
||||||
if pp >= 0:
|
if pp >= 0:
|
||||||
sernum = resline[pp+19:]
|
sernum = resline[pp+19:-1]
|
||||||
sernum = sernum[:-1]
|
sernum = sernum.strip()
|
||||||
sernum = sernum.lstrip()
|
bb = resline.find('"BSD Name" = "')
|
||||||
|
if bb >= 0:
|
||||||
|
bsdname = resline[bb+14:-1]
|
||||||
|
bsdname = bsdname.strip()
|
||||||
|
if (bsdname == 'disk0') and (sernum != None):
|
||||||
|
foundIt = True
|
||||||
break
|
break
|
||||||
|
if not foundIt:
|
||||||
|
sernum = '9999999999'
|
||||||
return sernum
|
return sernum
|
||||||
|
|
||||||
# uses unix env to get username instead of using sysctlbyname
|
# uses unix env to get username instead of using sysctlbyname
|
||||||
@@ -298,7 +310,8 @@ def CryptUnprotectData(encryptedData):
|
|||||||
return cleartext
|
return cleartext
|
||||||
|
|
||||||
# Locate and open the .kindle-info file
|
# Locate and open the .kindle-info file
|
||||||
def openKindleInfo():
|
def openKindleInfo(kInfoFile=None):
|
||||||
|
if kInfoFile == None:
|
||||||
home = os.getenv('HOME')
|
home = os.getenv('HOME')
|
||||||
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
|
||||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
@@ -317,3 +330,5 @@ def openKindleInfo():
|
|||||||
if not os.path.exists(kinfopath):
|
if not os.path.exists(kinfopath):
|
||||||
raise K4MDrmException('Error: .kindle-info file can not be found')
|
raise K4MDrmException('Error: .kindle-info file can not be found')
|
||||||
return open(kinfopath,'r')
|
return open(kinfopath,'r')
|
||||||
|
else:
|
||||||
|
return open(kInfoFile, 'r')
|
||||||
|
|||||||
@@ -101,7 +101,10 @@ CryptUnprotectData = CryptUnprotectData()
|
|||||||
#
|
#
|
||||||
# Locate and open the Kindle.info file.
|
# Locate and open the Kindle.info file.
|
||||||
#
|
#
|
||||||
def openKindleInfo():
|
def openKindleInfo(kInfoFile=None):
|
||||||
|
if kInfoFile == None:
|
||||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
||||||
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||||
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
|
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
|
||||||
|
else:
|
||||||
|
return open(kInfoFile, 'r')
|
||||||
|
|||||||
@@ -3,14 +3,6 @@
|
|||||||
# This is a python script. You need a Python interpreter to run it.
|
# This is a python script. You need a Python interpreter to run it.
|
||||||
# For example, ActiveState Python, which exists for windows.
|
# For example, ActiveState Python, which exists for windows.
|
||||||
#
|
#
|
||||||
# It can run standalone to convert files, or it can be installed as a
|
|
||||||
# plugin for Calibre (http://calibre-ebook.com/about) so that
|
|
||||||
# importing files with DRM 'Just Works'.
|
|
||||||
#
|
|
||||||
# To create a Calibre plugin, rename this file so that the filename
|
|
||||||
# ends in '_plugin.py', put it into a ZIP file and import that Calibre
|
|
||||||
# using its plugin configuration GUI.
|
|
||||||
#
|
|
||||||
# Changelog
|
# Changelog
|
||||||
# 0.01 - Initial version
|
# 0.01 - Initial version
|
||||||
# 0.02 - Huffdic compressed books were not properly decrypted
|
# 0.02 - Huffdic compressed books were not properly decrypted
|
||||||
@@ -37,14 +29,18 @@
|
|||||||
# in utf8 file are encrypted. (Although neither kind gets compressed.)
|
# in utf8 file are encrypted. (Although neither kind gets compressed.)
|
||||||
# This knowledge leads to a simplification of the test for the
|
# This knowledge leads to a simplification of the test for the
|
||||||
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
|
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
|
||||||
# 0.15 - Now outputs 'hearbeat', and is also quicker for long files.
|
# 0.15 - Now outputs 'heartbeat', and is also quicker for long files.
|
||||||
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
|
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
|
||||||
# 0.17 - added modifications to support its use as an imported python module
|
# 0.17 - added modifications to support its use as an imported python module
|
||||||
# both inside calibre and also in other places (ie K4DeDRM tools)
|
# both inside calibre and also in other places (ie K4DeDRM tools)
|
||||||
# 0.17a- disabled the standalone plugin feature since a plugin can not import
|
# 0.17a- disabled the standalone plugin feature since a plugin can not import
|
||||||
# a plugin
|
# a plugin
|
||||||
|
# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
|
||||||
|
# Removed the disabled Calibre plug-in code
|
||||||
|
# Permit use of 8-digit PIDs
|
||||||
|
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
|
||||||
|
|
||||||
__version__ = '0.17'
|
__version__ = '0.19'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
@@ -127,10 +123,11 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
|
|||||||
if testflags & 1:
|
if testflags & 1:
|
||||||
num += getSizeOfTrailingDataEntry(ptr, size - num)
|
num += getSizeOfTrailingDataEntry(ptr, size - num)
|
||||||
testflags >>= 1
|
testflags >>= 1
|
||||||
# Multibyte data, if present, is included in the encryption, so
|
# Check the low bit to see if there's multibyte data present.
|
||||||
# we do not need to check the low bit.
|
# if multibyte data is included in the encryped data, we'll
|
||||||
# if flags & 1:
|
# have already cleared this flag.
|
||||||
# num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
if flags & 1:
|
||||||
|
num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
||||||
return num
|
return num
|
||||||
|
|
||||||
class DrmStripper:
|
class DrmStripper:
|
||||||
@@ -181,9 +178,14 @@ class DrmStripper:
|
|||||||
return found_key
|
return found_key
|
||||||
|
|
||||||
def __init__(self, data_file, pid):
|
def __init__(self, data_file, pid):
|
||||||
|
if len(pid)==10:
|
||||||
if checksumPid(pid[0:-2]) != pid:
|
if checksumPid(pid[0:-2]) != pid:
|
||||||
raise DrmException("invalid PID checksum")
|
raise DrmException("invalid PID checksum")
|
||||||
pid = pid[0:-2]
|
pid = pid[0:-2]
|
||||||
|
elif len(pid)==8:
|
||||||
|
print "PID without checksum given. With checksum PID is "+checksumPid(pid)
|
||||||
|
else:
|
||||||
|
raise DrmException("Invalid PID length")
|
||||||
|
|
||||||
self.data_file = data_file
|
self.data_file = data_file
|
||||||
header = data_file[0:72]
|
header = data_file[0:72]
|
||||||
@@ -206,6 +208,10 @@ class DrmStripper:
|
|||||||
if (mobi_length >= 0xE4) and (mobi_version >= 5):
|
if (mobi_length >= 0xE4) and (mobi_version >= 5):
|
||||||
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
|
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
|
||||||
print "Extra Data Flags = %d" %extra_data_flags
|
print "Extra Data Flags = %d" %extra_data_flags
|
||||||
|
if mobi_version <= 5:
|
||||||
|
# multibyte utf8 data is included in the encryption for mobi_version 5 and below
|
||||||
|
# so clear that byte so that we leave it to be decrypted.
|
||||||
|
extra_data_flags &= 0xFFFE
|
||||||
|
|
||||||
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
|
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
|
||||||
if crypto_type == 0:
|
if crypto_type == 0:
|
||||||
@@ -282,44 +288,3 @@ def main(argv=sys.argv):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
||||||
#if not __name__ == "__main__":
|
|
||||||
if False:
|
|
||||||
|
|
||||||
# note a calibre plugin can not import code with another calibre plugin
|
|
||||||
# in it as it ends up registering two different plugins
|
|
||||||
from calibre.customize import FileTypePlugin
|
|
||||||
|
|
||||||
class MobiDeDRM(FileTypePlugin):
|
|
||||||
name = 'MobiDeDRM' # Name of the plugin
|
|
||||||
description = 'Removes DRM from secure Mobi files'
|
|
||||||
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
|
|
||||||
author = 'The Dark Reverser' # The author of this plugin
|
|
||||||
version = (0, 1, 7) # The version number of this plugin
|
|
||||||
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
|
|
||||||
on_import = True # Run this plugin during the import
|
|
||||||
|
|
||||||
def run(self, path_to_ebook):
|
|
||||||
from calibre.gui2 import is_ok_to_use_qt
|
|
||||||
from PyQt4.Qt import QMessageBox
|
|
||||||
PID = self.site_customization
|
|
||||||
data_file = file(path_to_ebook, 'rb').read()
|
|
||||||
ar = PID.split(',')
|
|
||||||
for i in ar:
|
|
||||||
try:
|
|
||||||
unlocked_file = DrmStripper(data_file, i).getResult()
|
|
||||||
except DrmException:
|
|
||||||
if is_ok_to_use_qt():
|
|
||||||
d = QMessageBox(QMessageBox.Warning, "MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
|
|
||||||
d.show()
|
|
||||||
d.raise_()
|
|
||||||
d.exec_()
|
|
||||||
raise Exception("MobiDeDRM Plugin: Error decoding ebook")
|
|
||||||
else:
|
|
||||||
of = self.temporary_file('.mobi')
|
|
||||||
of.write(unlocked_file)
|
|
||||||
of.close()
|
|
||||||
return of.name
|
|
||||||
|
|
||||||
def customization_help(self, gui=False):
|
|
||||||
return 'Enter PID (separate multiple PIDs with comma)'
|
|
||||||
|
|||||||
1154
DeDRM_Macintosh_Application/DeDRM.app.txt
Normal file
1154
DeDRM_Macintosh_Application/DeDRM.app.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,16 +23,22 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>droplet</string>
|
<string>droplet</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>DeDRM 1.2, Copyright © 2010 by Apprentice Alf.</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>droplet</string>
|
<string>droplet</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>Mobipocket Unlocker 9</string>
|
<string>DeDRM</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.2</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>dplt</string>
|
<string>dplt</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.5.0</string>
|
||||||
<key>LSRequiresCarbon</key>
|
<key>LSRequiresCarbon</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>WindowState</key>
|
<key>WindowState</key>
|
||||||
@@ -40,9 +46,9 @@
|
|||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>ScriptWindowState</string>
|
<string>ScriptWindowState</string>
|
||||||
<key>positionOfDivider</key>
|
<key>positionOfDivider</key>
|
||||||
<real>422</real>
|
<real>885</real>
|
||||||
<key>savedFrame</key>
|
<key>savedFrame</key>
|
||||||
<string>91 171 1059 678 0 0 1440 878 </string>
|
<string>1507 -64 1262 964 1440 -150 1680 1050 </string>
|
||||||
<key>selectedTabView</key>
|
<key>selectedTabView</key>
|
||||||
<string>result</string>
|
<string>result</string>
|
||||||
</dict>
|
</dict>
|
||||||
Binary file not shown.
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>DeDRM Progress</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.apprenticealf.DeDRMProgress</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>DeDRM Progress</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>NSAppleScriptEnabled</key>
|
||||||
|
<string>YES</string>
|
||||||
|
<key>NSMainNibFile</key>
|
||||||
|
<string>MainMenu</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
APPL????
|
||||||
Binary file not shown.
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IBClasses</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CLASS</key>
|
||||||
|
<string>FirstResponder</string>
|
||||||
|
<key>LANGUAGE</key>
|
||||||
|
<string>ObjC</string>
|
||||||
|
<key>SUPERCLASS</key>
|
||||||
|
<string>NSObject</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>IBVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IBFramework Version</key>
|
||||||
|
<string>680</string>
|
||||||
|
<key>IBLastKnownRelativeProjectPath</key>
|
||||||
|
<string>../Display Panel.xcodeproj</string>
|
||||||
|
<key>IBOldestOS</key>
|
||||||
|
<integer>5</integer>
|
||||||
|
<key>IBOpenObjects</key>
|
||||||
|
<array/>
|
||||||
|
<key>IBSystem Version</key>
|
||||||
|
<string>9L31a</string>
|
||||||
|
<key>targetFramework</key>
|
||||||
|
<string>IBCocoaFramework</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 362 B After Width: | Height: | Size: 362 B |
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# base64.py, version 1.0
|
||||||
|
# Copyright © 2010 Apprentice Alf
|
||||||
|
|
||||||
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
|
# later. <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
# Revision history:
|
||||||
|
# 1 - Initial release. To allow Applescript to do base64 encoding
|
||||||
|
|
||||||
|
"""
|
||||||
|
Provide base64 encoding.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
|
||||||
|
def usage(progname):
|
||||||
|
print "Applies base64 encoding to the supplied file, sending to standard output"
|
||||||
|
print "Usage:"
|
||||||
|
print " %s <infile>" % progname
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
|
||||||
|
if len(argv)<2:
|
||||||
|
usage(progname)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
keypath = argv[1]
|
||||||
|
with open(keypath, 'rb') as f:
|
||||||
|
keyder = f.read()
|
||||||
|
print keyder.encode('base64')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(cli_main())
|
||||||
|
|
||||||
@@ -0,0 +1,476 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
#
|
||||||
|
# erdr2pml.py
|
||||||
|
#
|
||||||
|
# This is a python script. You need a Python interpreter to run it.
|
||||||
|
# For example, ActiveState Python, which exists for windows.
|
||||||
|
# Changelog
|
||||||
|
#
|
||||||
|
# Based on ereader2html version 0.08 plus some later small fixes
|
||||||
|
#
|
||||||
|
# 0.01 - Initial version
|
||||||
|
# 0.02 - Support more eReader files. Support bold text and links. Fix PML decoder parsing bug.
|
||||||
|
# 0.03 - Fix incorrect variable usage at one place.
|
||||||
|
# 0.03b - enhancement by DeBockle (version 259 support)
|
||||||
|
# Custom version 0.03 - no change to eReader support, only usability changes
|
||||||
|
# - start of pep-8 indentation (spaces not tab), fix trailing blanks
|
||||||
|
# - version variable, only one place to change
|
||||||
|
# - added main routine, now callable as a library/module,
|
||||||
|
# means tools can add optional support for ereader2html
|
||||||
|
# - outdir is no longer a mandatory parameter (defaults based on input name if missing)
|
||||||
|
# - time taken output to stdout
|
||||||
|
# - Psyco support - reduces runtime by a factor of (over) 3!
|
||||||
|
# E.g. (~600Kb file) 90 secs down to 24 secs
|
||||||
|
# - newstyle classes
|
||||||
|
# - changed map call to list comprehension
|
||||||
|
# may not work with python 2.3
|
||||||
|
# without Psyco this reduces runtime to 90%
|
||||||
|
# E.g. 90 secs down to 77 secs
|
||||||
|
# Psyco with map calls takes longer, do not run with map in Psyco JIT!
|
||||||
|
# - izip calls used instead of zip (if available), further reduction
|
||||||
|
# in run time (factor of 4.5).
|
||||||
|
# E.g. (~600Kb file) 90 secs down to 20 secs
|
||||||
|
# - Python 2.6+ support, avoid DeprecationWarning with sha/sha1
|
||||||
|
# 0.04 - Footnote support, PML output, correct charset in html, support more PML tags
|
||||||
|
# - Feature change, dump out PML file
|
||||||
|
# - Added supprt for footnote tags. NOTE footnote ids appear to be bad (not usable)
|
||||||
|
# in some pdb files :-( due to the same id being used multiple times
|
||||||
|
# - Added correct charset encoding (pml is based on cp1252)
|
||||||
|
# - Added logging support.
|
||||||
|
# 0.05 - Improved type 272 support for sidebars, links, chapters, metainfo, etc
|
||||||
|
# 0.06 - Merge of 0.04 and 0.05. Improved HTML output
|
||||||
|
# Placed images in subfolder, so that it's possible to just
|
||||||
|
# drop the book.pml file onto DropBook to make an unencrypted
|
||||||
|
# copy of the eReader file.
|
||||||
|
# Using that with Calibre works a lot better than the HTML
|
||||||
|
# conversion in this code.
|
||||||
|
# 0.07 - Further Improved type 272 support for sidebars with all earlier fixes
|
||||||
|
# 0.08 - fixed typos, removed extraneous things
|
||||||
|
# 0.09 - fixed typos in first_pages to first_page to again support older formats
|
||||||
|
# 0.10 - minor cleanups
|
||||||
|
# 0.11 - fixups for using correct xml for footnotes and sidebars for use with Dropbook
|
||||||
|
# 0.12 - Fix added to prevent lowercasing of image names when the pml code itself uses a different case in the link name.
|
||||||
|
# 0.13 - change to unbuffered stdout for use with gui front ends
|
||||||
|
# 0.14 - contributed enhancement to support --make-pmlz switch
|
||||||
|
# 0.15 - enabled high-ascii to pml character encoding. DropBook now works on Mac.
|
||||||
|
# 0.16 - convert to use openssl DES (very very fast) or pure python DES if openssl's libcrypto is not available
|
||||||
|
|
||||||
|
Des = None
|
||||||
|
|
||||||
|
import openssl_des
|
||||||
|
Des = openssl_des.load_libcrypto()
|
||||||
|
|
||||||
|
# if that did not work then use pure python implementation
|
||||||
|
# of DES and try to speed it up with Psycho
|
||||||
|
if Des == None:
|
||||||
|
import python_des
|
||||||
|
Des = python_des.Des
|
||||||
|
# Import Psyco if available
|
||||||
|
try:
|
||||||
|
# Dumb speed hack 1
|
||||||
|
# http://psyco.sourceforge.net
|
||||||
|
import psyco
|
||||||
|
psyco.full()
|
||||||
|
pass
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
__version__='0.16'
|
||||||
|
|
||||||
|
class Unbuffered:
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
def write(self, data):
|
||||||
|
self.stream.write(data)
|
||||||
|
self.stream.flush()
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
|
|
||||||
|
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hashlib import sha1
|
||||||
|
except ImportError:
|
||||||
|
# older Python release
|
||||||
|
import sha
|
||||||
|
sha1 = lambda s: sha.new(s)
|
||||||
|
import cgi
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig()
|
||||||
|
#logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
class Sectionizer(object):
|
||||||
|
def __init__(self, filename, ident):
|
||||||
|
self.contents = file(filename, 'rb').read()
|
||||||
|
self.header = self.contents[0:72]
|
||||||
|
self.num_sections, = struct.unpack('>H', self.contents[76:78])
|
||||||
|
if self.header[0x3C:0x3C+8] != ident:
|
||||||
|
raise ValueError('Invalid file format')
|
||||||
|
self.sections = []
|
||||||
|
for i in xrange(self.num_sections):
|
||||||
|
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8])
|
||||||
|
flags, val = a1, a2<<16|a3<<8|a4
|
||||||
|
self.sections.append( (offset, flags, val) )
|
||||||
|
def loadSection(self, section):
|
||||||
|
if section + 1 == self.num_sections:
|
||||||
|
end_off = len(self.contents)
|
||||||
|
else:
|
||||||
|
end_off = self.sections[section + 1][0]
|
||||||
|
off = self.sections[section][0]
|
||||||
|
return self.contents[off:end_off]
|
||||||
|
|
||||||
|
def sanitizeFileName(s):
|
||||||
|
r = ''
|
||||||
|
for c in s:
|
||||||
|
if c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-":
|
||||||
|
r += c
|
||||||
|
return r
|
||||||
|
|
||||||
|
def fixKey(key):
|
||||||
|
def fixByte(b):
|
||||||
|
return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
|
||||||
|
return "".join([chr(fixByte(ord(a))) for a in key])
|
||||||
|
|
||||||
|
def deXOR(text, sp, table):
|
||||||
|
r=''
|
||||||
|
j = sp
|
||||||
|
for i in xrange(len(text)):
|
||||||
|
r += chr(ord(table[j]) ^ ord(text[i]))
|
||||||
|
j = j + 1
|
||||||
|
if j == len(table):
|
||||||
|
j = 0
|
||||||
|
return r
|
||||||
|
|
||||||
|
class EreaderProcessor(object):
|
||||||
|
def __init__(self, section_reader, username, creditcard):
|
||||||
|
self.section_reader = section_reader
|
||||||
|
data = section_reader(0)
|
||||||
|
version, = struct.unpack('>H', data[0:2])
|
||||||
|
self.version = version
|
||||||
|
logging.info('eReader file format version %s', version)
|
||||||
|
if version != 272 and version != 260 and version != 259:
|
||||||
|
raise ValueError('incorrect eReader version %d (error 1)' % version)
|
||||||
|
data = section_reader(1)
|
||||||
|
self.data = data
|
||||||
|
des = Des(fixKey(data[0:8]))
|
||||||
|
cookie_shuf, cookie_size = struct.unpack('>LL', des.decrypt(data[-8:]))
|
||||||
|
if cookie_shuf < 3 or cookie_shuf > 0x14 or cookie_size < 0xf0 or cookie_size > 0x200:
|
||||||
|
raise ValueError('incorrect eReader version (error 2)')
|
||||||
|
input = des.decrypt(data[-cookie_size:])
|
||||||
|
def unshuff(data, shuf):
|
||||||
|
r = [''] * len(data)
|
||||||
|
j = 0
|
||||||
|
for i in xrange(len(data)):
|
||||||
|
j = (j + shuf) % len(data)
|
||||||
|
r[j] = data[i]
|
||||||
|
assert len("".join(r)) == len(data)
|
||||||
|
return "".join(r)
|
||||||
|
r = unshuff(input[0:-8], cookie_shuf)
|
||||||
|
|
||||||
|
def fixUsername(s):
|
||||||
|
r = ''
|
||||||
|
for c in s.lower():
|
||||||
|
if (c >= 'a' and c <= 'z' or c >= '0' and c <= '9'):
|
||||||
|
r += c
|
||||||
|
return r
|
||||||
|
|
||||||
|
user_key = struct.pack('>LL', binascii.crc32(fixUsername(username)) & 0xffffffff, binascii.crc32(creditcard[-8:])& 0xffffffff)
|
||||||
|
drm_sub_version = struct.unpack('>H', r[0:2])[0]
|
||||||
|
self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1
|
||||||
|
self.num_image_pages = struct.unpack('>H', r[26:26+2])[0]
|
||||||
|
self.first_image_page = struct.unpack('>H', r[24:24+2])[0]
|
||||||
|
if self.version == 272:
|
||||||
|
self.num_footnote_pages = struct.unpack('>H', r[46:46+2])[0]
|
||||||
|
self.first_footnote_page = struct.unpack('>H', r[44:44+2])[0]
|
||||||
|
self.num_sidebar_pages = struct.unpack('>H', r[38:38+2])[0]
|
||||||
|
self.first_sidebar_page = struct.unpack('>H', r[36:36+2])[0]
|
||||||
|
# self.num_bookinfo_pages = struct.unpack('>H', r[34:34+2])[0]
|
||||||
|
# self.first_bookinfo_page = struct.unpack('>H', r[32:32+2])[0]
|
||||||
|
# self.num_chapter_pages = struct.unpack('>H', r[22:22+2])[0]
|
||||||
|
# self.first_chapter_page = struct.unpack('>H', r[20:20+2])[0]
|
||||||
|
# self.num_link_pages = struct.unpack('>H', r[30:30+2])[0]
|
||||||
|
# self.first_link_page = struct.unpack('>H', r[28:28+2])[0]
|
||||||
|
# self.num_xtextsize_pages = struct.unpack('>H', r[54:54+2])[0]
|
||||||
|
# self.first_xtextsize_page = struct.unpack('>H', r[52:52+2])[0]
|
||||||
|
|
||||||
|
# **before** data record 1 was decrypted and unshuffled, it contained data
|
||||||
|
# to create an XOR table and which is used to fix footnote record 0, link records, chapter records, etc
|
||||||
|
self.xortable_offset = struct.unpack('>H', r[40:40+2])[0]
|
||||||
|
self.xortable_size = struct.unpack('>H', r[42:42+2])[0]
|
||||||
|
self.xortable = self.data[self.xortable_offset:self.xortable_offset + self.xortable_size]
|
||||||
|
else:
|
||||||
|
self.num_footnote_pages = 0
|
||||||
|
self.num_sidebar_pages = 0
|
||||||
|
self.first_footnote_page = -1
|
||||||
|
self.first_sidebar_page = -1
|
||||||
|
# self.num_bookinfo_pages = 0
|
||||||
|
# self.num_chapter_pages = 0
|
||||||
|
# self.num_link_pages = 0
|
||||||
|
# self.num_xtextsize_pages = 0
|
||||||
|
# self.first_bookinfo_page = -1
|
||||||
|
# self.first_chapter_page = -1
|
||||||
|
# self.first_link_page = -1
|
||||||
|
# self.first_xtextsize_page = -1
|
||||||
|
|
||||||
|
logging.debug('self.num_text_pages %d', self.num_text_pages)
|
||||||
|
logging.debug('self.num_footnote_pages %d, self.first_footnote_page %d', self.num_footnote_pages , self.first_footnote_page)
|
||||||
|
logging.debug('self.num_sidebar_pages %d, self.first_sidebar_page %d', self.num_sidebar_pages , self.first_sidebar_page)
|
||||||
|
self.flags = struct.unpack('>L', r[4:8])[0]
|
||||||
|
reqd_flags = (1<<9) | (1<<7) | (1<<10)
|
||||||
|
if (self.flags & reqd_flags) != reqd_flags:
|
||||||
|
print "Flags: 0x%X" % self.flags
|
||||||
|
raise ValueError('incompatible eReader file')
|
||||||
|
des = Des(fixKey(user_key))
|
||||||
|
if version == 259:
|
||||||
|
if drm_sub_version != 7:
|
||||||
|
raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version)
|
||||||
|
encrypted_key_sha = r[44:44+20]
|
||||||
|
encrypted_key = r[64:64+8]
|
||||||
|
elif version == 260:
|
||||||
|
if drm_sub_version != 13:
|
||||||
|
raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version)
|
||||||
|
encrypted_key = r[44:44+8]
|
||||||
|
encrypted_key_sha = r[52:52+20]
|
||||||
|
elif version == 272:
|
||||||
|
encrypted_key = r[172:172+8]
|
||||||
|
encrypted_key_sha = r[56:56+20]
|
||||||
|
self.content_key = des.decrypt(encrypted_key)
|
||||||
|
if sha1(self.content_key).digest() != encrypted_key_sha:
|
||||||
|
raise ValueError('Incorrect Name and/or Credit Card')
|
||||||
|
|
||||||
|
def getNumImages(self):
|
||||||
|
return self.num_image_pages
|
||||||
|
|
||||||
|
def getImage(self, i):
|
||||||
|
sect = self.section_reader(self.first_image_page + i)
|
||||||
|
name = sect[4:4+32].strip('\0')
|
||||||
|
data = sect[62:]
|
||||||
|
return sanitizeFileName(name), data
|
||||||
|
|
||||||
|
|
||||||
|
# def getChapterNamePMLOffsetData(self):
|
||||||
|
# cv = ''
|
||||||
|
# if self.num_chapter_pages > 0:
|
||||||
|
# for i in xrange(self.num_chapter_pages):
|
||||||
|
# chaps = self.section_reader(self.first_chapter_page + i)
|
||||||
|
# j = i % self.xortable_size
|
||||||
|
# offname = deXOR(chaps, j, self.xortable)
|
||||||
|
# offset = struct.unpack('>L', offname[0:4])[0]
|
||||||
|
# name = offname[4:].strip('\0')
|
||||||
|
# cv += '%d|%s\n' % (offset, name)
|
||||||
|
# return cv
|
||||||
|
|
||||||
|
# def getLinkNamePMLOffsetData(self):
|
||||||
|
# lv = ''
|
||||||
|
# if self.num_link_pages > 0:
|
||||||
|
# for i in xrange(self.num_link_pages):
|
||||||
|
# links = self.section_reader(self.first_link_page + i)
|
||||||
|
# j = i % self.xortable_size
|
||||||
|
# offname = deXOR(links, j, self.xortable)
|
||||||
|
# offset = struct.unpack('>L', offname[0:4])[0]
|
||||||
|
# name = offname[4:].strip('\0')
|
||||||
|
# lv += '%d|%s\n' % (offset, name)
|
||||||
|
# return lv
|
||||||
|
|
||||||
|
# def getExpandedTextSizesData(self):
|
||||||
|
# ts = ''
|
||||||
|
# if self.num_xtextsize_pages > 0:
|
||||||
|
# tsize = deXOR(self.section_reader(self.first_xtextsize_page), 0, self.xortable)
|
||||||
|
# for i in xrange(self.num_text_pages):
|
||||||
|
# xsize = struct.unpack('>H', tsize[0:2])[0]
|
||||||
|
# ts += "%d\n" % xsize
|
||||||
|
# tsize = tsize[2:]
|
||||||
|
# return ts
|
||||||
|
|
||||||
|
# def getBookInfo(self):
|
||||||
|
# bkinfo = ''
|
||||||
|
# if self.num_bookinfo_pages > 0:
|
||||||
|
# info = self.section_reader(self.first_bookinfo_page)
|
||||||
|
# bkinfo = deXOR(info, 0, self.xortable)
|
||||||
|
# bkinfo = bkinfo.replace('\0','|')
|
||||||
|
# bkinfo += '\n'
|
||||||
|
# return bkinfo
|
||||||
|
|
||||||
|
def getText(self):
|
||||||
|
des = Des(fixKey(self.content_key))
|
||||||
|
r = ''
|
||||||
|
for i in xrange(self.num_text_pages):
|
||||||
|
logging.debug('get page %d', i)
|
||||||
|
r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
|
||||||
|
|
||||||
|
# now handle footnotes pages
|
||||||
|
if self.num_footnote_pages > 0:
|
||||||
|
r += '\n'
|
||||||
|
# the record 0 of the footnote section must pass through the Xor Table to make it useful
|
||||||
|
sect = self.section_reader(self.first_footnote_page)
|
||||||
|
fnote_ids = deXOR(sect, 0, self.xortable)
|
||||||
|
# the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated
|
||||||
|
des = Des(fixKey(self.content_key))
|
||||||
|
for i in xrange(1,self.num_footnote_pages):
|
||||||
|
logging.debug('get footnotepage %d', i)
|
||||||
|
id_len = ord(fnote_ids[2])
|
||||||
|
id = fnote_ids[3:3+id_len]
|
||||||
|
fmarker = '<footnote id="%s">\n' % id
|
||||||
|
fmarker += zlib.decompress(des.decrypt(self.section_reader(self.first_footnote_page + i)))
|
||||||
|
fmarker += '\n</footnote>\n'
|
||||||
|
r += fmarker
|
||||||
|
fnote_ids = fnote_ids[id_len+4:]
|
||||||
|
|
||||||
|
# now handle sidebar pages
|
||||||
|
if self.num_sidebar_pages > 0:
|
||||||
|
r += '\n'
|
||||||
|
# the record 0 of the sidebar section must pass through the Xor Table to make it useful
|
||||||
|
sect = self.section_reader(self.first_sidebar_page)
|
||||||
|
sbar_ids = deXOR(sect, 0, self.xortable)
|
||||||
|
# the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated
|
||||||
|
des = Des(fixKey(self.content_key))
|
||||||
|
for i in xrange(1,self.num_sidebar_pages):
|
||||||
|
id_len = ord(sbar_ids[2])
|
||||||
|
id = sbar_ids[3:3+id_len]
|
||||||
|
smarker = '<sidebar id="%s">\n' % id
|
||||||
|
smarker += zlib.decompress(des.decrypt(self.section_reader(self.first_footnote_page + i)))
|
||||||
|
smarker += '\n</sidebar>\n'
|
||||||
|
r += smarker
|
||||||
|
sbar_ids = sbar_ids[id_len+4:]
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
def cleanPML(pml):
|
||||||
|
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
|
||||||
|
pml2 = pml
|
||||||
|
for k in xrange(128,256):
|
||||||
|
badChar = chr(k)
|
||||||
|
pml2 = pml2.replace(badChar, '\\a%03d' % k)
|
||||||
|
return pml2
|
||||||
|
|
||||||
|
def convertEreaderToPml(infile, name, cc, outdir):
|
||||||
|
if not os.path.exists(outdir):
|
||||||
|
os.makedirs(outdir)
|
||||||
|
|
||||||
|
print " Decoding File"
|
||||||
|
sect = Sectionizer(infile, 'PNRdPPrs')
|
||||||
|
er = EreaderProcessor(sect.loadSection, name, cc)
|
||||||
|
|
||||||
|
if er.getNumImages() > 0:
|
||||||
|
print " Extracting images"
|
||||||
|
imagedir = bookname + '_img/'
|
||||||
|
imagedirpath = os.path.join(outdir,imagedir)
|
||||||
|
if not os.path.exists(imagedirpath):
|
||||||
|
os.makedirs(imagedirpath)
|
||||||
|
for i in xrange(er.getNumImages()):
|
||||||
|
name, contents = er.getImage(i)
|
||||||
|
file(os.path.join(imagedirpath, name), 'wb').write(contents)
|
||||||
|
|
||||||
|
print " Extracting pml"
|
||||||
|
pml_string = er.getText()
|
||||||
|
pmlfilename = bookname + ".pml"
|
||||||
|
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
|
||||||
|
|
||||||
|
# bkinfo = er.getBookInfo()
|
||||||
|
# if bkinfo != '':
|
||||||
|
# print " Extracting book meta information"
|
||||||
|
# file(os.path.join(outdir, 'bookinfo.txt'),'wb').write(bkinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print "Converts DRMed eReader books to PML Source"
|
||||||
|
print "Usage:"
|
||||||
|
print " erdr2pml [options] infile.pdb [outdir] \"your name\" credit_card_number "
|
||||||
|
print " "
|
||||||
|
print "Options: "
|
||||||
|
print " -h prints this message"
|
||||||
|
print " --make-pmlz create PMLZ instead of using output directory"
|
||||||
|
print " "
|
||||||
|
print "Note:"
|
||||||
|
print " if ommitted, outdir defaults based on 'infile.pdb'"
|
||||||
|
print " It's enough to enter the last 8 digits of the credit card number"
|
||||||
|
return
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
global bookname
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "h", ["make-pmlz"])
|
||||||
|
except getopt.GetoptError, err:
|
||||||
|
print str(err)
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
make_pmlz = False
|
||||||
|
zipname = None
|
||||||
|
for o, a in opts:
|
||||||
|
if o == "-h":
|
||||||
|
usage()
|
||||||
|
return 0
|
||||||
|
elif o == "--make-pmlz":
|
||||||
|
make_pmlz = True
|
||||||
|
zipname = ''
|
||||||
|
|
||||||
|
print "eRdr2Pml v%s. Copyright (c) 2009 The Dark Reverser" % __version__
|
||||||
|
|
||||||
|
if len(args)!=3 and len(args)!=4:
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
if len(args)==3:
|
||||||
|
infile, name, cc = args[0], args[1], args[2]
|
||||||
|
outdir = infile[:-4] + '_Source'
|
||||||
|
elif len(args)==4:
|
||||||
|
infile, outdir, name, cc = args[0], args[1], args[2], args[3]
|
||||||
|
|
||||||
|
if make_pmlz :
|
||||||
|
# ignore specified outdir, use tempdir instead
|
||||||
|
outdir = tempfile.mkdtemp()
|
||||||
|
|
||||||
|
bookname = os.path.splitext(os.path.basename(infile))[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
print "Processing..."
|
||||||
|
import time
|
||||||
|
start_time = time.time()
|
||||||
|
convertEreaderToPml(infile, name, cc, outdir)
|
||||||
|
|
||||||
|
if make_pmlz :
|
||||||
|
import zipfile
|
||||||
|
import shutil
|
||||||
|
print " Creating PMLZ file"
|
||||||
|
zipname = infile[:-4] + '.pmlz'
|
||||||
|
myZipFile = zipfile.ZipFile(zipname,'w',zipfile.ZIP_STORED, False)
|
||||||
|
list = os.listdir(outdir)
|
||||||
|
for file in list:
|
||||||
|
localname = file
|
||||||
|
filePath = os.path.join(outdir,file)
|
||||||
|
if os.path.isfile(filePath):
|
||||||
|
myZipFile.write(filePath, localname)
|
||||||
|
elif os.path.isdir(filePath):
|
||||||
|
imageList = os.listdir(filePath)
|
||||||
|
localimgdir = os.path.basename(filePath)
|
||||||
|
for image in imageList:
|
||||||
|
localname = os.path.join(localimgdir,image)
|
||||||
|
imagePath = os.path.join(filePath,image)
|
||||||
|
if os.path.isfile(imagePath):
|
||||||
|
myZipFile.write(imagePath, localname)
|
||||||
|
myZipFile.close()
|
||||||
|
# remove temporary directory
|
||||||
|
shutil.rmtree(outdir)
|
||||||
|
|
||||||
|
end_time = time.time()
|
||||||
|
search_time = end_time - start_time
|
||||||
|
print 'elapsed time: %.2f seconds' % (search_time, )
|
||||||
|
if make_pmlz :
|
||||||
|
print 'output is %s' % zipname
|
||||||
|
else :
|
||||||
|
print 'output in %s' % outdir
|
||||||
|
print "done"
|
||||||
|
except ValueError, e:
|
||||||
|
print "Error: %s" % e
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
#! /usr/bin/python
|
||||||
|
|
||||||
|
# ignobleepub.pyw, version 3
|
||||||
|
|
||||||
|
# To run this program install Python 2.6 from <http://www.python.org/download/>
|
||||||
|
# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||||
|
# (make sure to install the version for Python 2.6). Save this script file as
|
||||||
|
# ignobleepub.pyw and double-click on it to run it.
|
||||||
|
|
||||||
|
# Revision history:
|
||||||
|
# 1 - Initial release
|
||||||
|
# 2 - Added OS X support by using OpenSSL when available
|
||||||
|
# 3 - screen out improper key lengths to prevent segfaults on Linux
|
||||||
|
# 3.1 - Allow Windows versions of libcrypto to be found
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import zlib
|
||||||
|
import zipfile
|
||||||
|
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||||
|
from contextlib import closing
|
||||||
|
import xml.etree.ElementTree as etree
|
||||||
|
import Tkinter
|
||||||
|
import Tkconstants
|
||||||
|
import tkFileDialog
|
||||||
|
import tkMessageBox
|
||||||
|
|
||||||
|
class IGNOBLEError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _load_crypto_libcrypto():
|
||||||
|
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
||||||
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
if libcrypto is None:
|
||||||
|
raise IGNOBLEError('libcrypto not found')
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
|
||||||
|
AES_MAXNR = 14
|
||||||
|
|
||||||
|
c_char_pp = POINTER(c_char_p)
|
||||||
|
c_int_p = POINTER(c_int)
|
||||||
|
|
||||||
|
class AES_KEY(Structure):
|
||||||
|
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
||||||
|
('rounds', c_int)]
|
||||||
|
AES_KEY_p = POINTER(AES_KEY)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
||||||
|
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
||||||
|
c_int])
|
||||||
|
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
|
||||||
|
[c_char_p, c_int, AES_KEY_p])
|
||||||
|
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
||||||
|
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
||||||
|
c_int])
|
||||||
|
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, userkey):
|
||||||
|
self._blocksize = len(userkey)
|
||||||
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
|
raise IGNOBLEError('AES improper key used')
|
||||||
|
return
|
||||||
|
key = self._key = AES_KEY()
|
||||||
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
||||||
|
if rv < 0:
|
||||||
|
raise IGNOBLEError('Failed to initialize AES key')
|
||||||
|
|
||||||
|
def decrypt(self, data):
|
||||||
|
out = create_string_buffer(len(data))
|
||||||
|
iv = ("\x00" * self._blocksize)
|
||||||
|
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
||||||
|
if rv == 0:
|
||||||
|
raise IGNOBLEError('AES decryption failed')
|
||||||
|
return out.raw
|
||||||
|
|
||||||
|
return AES
|
||||||
|
|
||||||
|
def _load_crypto_pycrypto():
|
||||||
|
from Crypto.Cipher import AES as _AES
|
||||||
|
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, key):
|
||||||
|
self._aes = _AES.new(key, _AES.MODE_CBC)
|
||||||
|
|
||||||
|
def decrypt(self, data):
|
||||||
|
return self._aes.decrypt(data)
|
||||||
|
|
||||||
|
return AES
|
||||||
|
|
||||||
|
def _load_crypto():
|
||||||
|
AES = None
|
||||||
|
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
|
||||||
|
try:
|
||||||
|
AES = loader()
|
||||||
|
break
|
||||||
|
except (ImportError, IGNOBLEError):
|
||||||
|
pass
|
||||||
|
return AES
|
||||||
|
AES = _load_crypto()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Decrypt Barnes & Noble ADEPT encrypted EPUB books.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
|
||||||
|
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
||||||
|
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
||||||
|
|
||||||
|
class ZipInfo(zipfile.ZipInfo):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if 'compress_type' in kwargs:
|
||||||
|
compress_type = kwargs.pop('compress_type')
|
||||||
|
super(ZipInfo, self).__init__(*args, **kwargs)
|
||||||
|
self.compress_type = compress_type
|
||||||
|
|
||||||
|
class Decryptor(object):
|
||||||
|
def __init__(self, bookkey, encryption):
|
||||||
|
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
|
||||||
|
# self._aes = AES.new(bookkey, AES.MODE_CBC)
|
||||||
|
self._aes = AES(bookkey)
|
||||||
|
encryption = etree.fromstring(encryption)
|
||||||
|
self._encrypted = encrypted = set()
|
||||||
|
expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
|
||||||
|
enc('CipherReference'))
|
||||||
|
for elem in encryption.findall(expr):
|
||||||
|
path = elem.get('URI', None)
|
||||||
|
if path is not None:
|
||||||
|
encrypted.add(path)
|
||||||
|
|
||||||
|
def decompress(self, bytes):
|
||||||
|
dc = zlib.decompressobj(-15)
|
||||||
|
bytes = dc.decompress(bytes)
|
||||||
|
ex = dc.decompress('Z') + dc.flush()
|
||||||
|
if ex:
|
||||||
|
bytes = bytes + ex
|
||||||
|
return bytes
|
||||||
|
|
||||||
|
def decrypt(self, path, data):
|
||||||
|
if path in self._encrypted:
|
||||||
|
data = self._aes.decrypt(data)[16:]
|
||||||
|
data = data[:-ord(data[-1])]
|
||||||
|
data = self.decompress(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
if AES is None:
|
||||||
|
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
||||||
|
"separately. Read the top-of-script comment for details." % \
|
||||||
|
(progname,)
|
||||||
|
return 1
|
||||||
|
if len(argv) != 4:
|
||||||
|
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
|
||||||
|
return 1
|
||||||
|
keypath, inpath, outpath = argv[1:]
|
||||||
|
with open(keypath, 'rb') as f:
|
||||||
|
keyb64 = f.read()
|
||||||
|
key = keyb64.decode('base64')[:16]
|
||||||
|
# aes = AES.new(key, AES.MODE_CBC)
|
||||||
|
aes = AES(key)
|
||||||
|
|
||||||
|
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||||
|
namelist = set(inf.namelist())
|
||||||
|
if 'META-INF/rights.xml' not in namelist or \
|
||||||
|
'META-INF/encryption.xml' not in namelist:
|
||||||
|
raise IGNOBLEError('%s: not an B&N ADEPT EPUB' % (inpath,))
|
||||||
|
for name in META_NAMES:
|
||||||
|
namelist.remove(name)
|
||||||
|
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||||
|
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||||
|
expr = './/%s' % (adept('encryptedKey'),)
|
||||||
|
bookkey = ''.join(rights.findtext(expr))
|
||||||
|
bookkey = aes.decrypt(bookkey.decode('base64'))
|
||||||
|
bookkey = bookkey[:-ord(bookkey[-1])]
|
||||||
|
encryption = inf.read('META-INF/encryption.xml')
|
||||||
|
decryptor = Decryptor(bookkey[-16:], encryption)
|
||||||
|
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
||||||
|
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
|
||||||
|
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
|
||||||
|
outf.writestr(zi, inf.read('mimetype'))
|
||||||
|
for path in namelist:
|
||||||
|
data = inf.read(path)
|
||||||
|
outf.writestr(path, decryptor.decrypt(path, data))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
|
def __init__(self, root):
|
||||||
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
|
self.status = Tkinter.Label(self, text='Select files for decryption')
|
||||||
|
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||||
|
body = Tkinter.Frame(self)
|
||||||
|
body.pack(fill=Tkconstants.X, expand=1)
|
||||||
|
sticky = Tkconstants.E + Tkconstants.W
|
||||||
|
body.grid_columnconfigure(1, weight=2)
|
||||||
|
Tkinter.Label(body, text='Key file').grid(row=0)
|
||||||
|
self.keypath = Tkinter.Entry(body, width=30)
|
||||||
|
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||||
|
if os.path.exists('bnepubkey.b64'):
|
||||||
|
self.keypath.insert(0, 'bnepubkey.b64')
|
||||||
|
button = Tkinter.Button(body, text="...", command=self.get_keypath)
|
||||||
|
button.grid(row=0, column=2)
|
||||||
|
Tkinter.Label(body, text='Input file').grid(row=1)
|
||||||
|
self.inpath = Tkinter.Entry(body, width=30)
|
||||||
|
self.inpath.grid(row=1, column=1, sticky=sticky)
|
||||||
|
button = Tkinter.Button(body, text="...", command=self.get_inpath)
|
||||||
|
button.grid(row=1, column=2)
|
||||||
|
Tkinter.Label(body, text='Output file').grid(row=2)
|
||||||
|
self.outpath = Tkinter.Entry(body, width=30)
|
||||||
|
self.outpath.grid(row=2, column=1, sticky=sticky)
|
||||||
|
button = Tkinter.Button(body, text="...", command=self.get_outpath)
|
||||||
|
button.grid(row=2, column=2)
|
||||||
|
buttons = Tkinter.Frame(self)
|
||||||
|
buttons.pack()
|
||||||
|
botton = Tkinter.Button(
|
||||||
|
buttons, text="Decrypt", width=10, command=self.decrypt)
|
||||||
|
botton.pack(side=Tkconstants.LEFT)
|
||||||
|
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||||
|
button = Tkinter.Button(
|
||||||
|
buttons, text="Quit", width=10, command=self.quit)
|
||||||
|
button.pack(side=Tkconstants.RIGHT)
|
||||||
|
|
||||||
|
def get_keypath(self):
|
||||||
|
keypath = tkFileDialog.askopenfilename(
|
||||||
|
parent=None, title='Select B&N EPUB key file',
|
||||||
|
defaultextension='.b64',
|
||||||
|
filetypes=[('base64-encoded files', '.b64'),
|
||||||
|
('All Files', '.*')])
|
||||||
|
if keypath:
|
||||||
|
keypath = os.path.normpath(keypath)
|
||||||
|
self.keypath.delete(0, Tkconstants.END)
|
||||||
|
self.keypath.insert(0, keypath)
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_inpath(self):
|
||||||
|
inpath = tkFileDialog.askopenfilename(
|
||||||
|
parent=None, title='Select B&N-encrypted EPUB file to decrypt',
|
||||||
|
defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
|
||||||
|
('All files', '.*')])
|
||||||
|
if inpath:
|
||||||
|
inpath = os.path.normpath(inpath)
|
||||||
|
self.inpath.delete(0, Tkconstants.END)
|
||||||
|
self.inpath.insert(0, inpath)
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_outpath(self):
|
||||||
|
outpath = tkFileDialog.asksaveasfilename(
|
||||||
|
parent=None, title='Select unencrypted EPUB file to produce',
|
||||||
|
defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
|
||||||
|
('All files', '.*')])
|
||||||
|
if outpath:
|
||||||
|
outpath = os.path.normpath(outpath)
|
||||||
|
self.outpath.delete(0, Tkconstants.END)
|
||||||
|
self.outpath.insert(0, outpath)
|
||||||
|
return
|
||||||
|
|
||||||
|
def decrypt(self):
|
||||||
|
keypath = self.keypath.get()
|
||||||
|
inpath = self.inpath.get()
|
||||||
|
outpath = self.outpath.get()
|
||||||
|
if not keypath or not os.path.exists(keypath):
|
||||||
|
self.status['text'] = 'Specified key file does not exist'
|
||||||
|
return
|
||||||
|
if not inpath or not os.path.exists(inpath):
|
||||||
|
self.status['text'] = 'Specified input file does not exist'
|
||||||
|
return
|
||||||
|
if not outpath:
|
||||||
|
self.status['text'] = 'Output file not specified'
|
||||||
|
return
|
||||||
|
if inpath == outpath:
|
||||||
|
self.status['text'] = 'Must have different input and output files'
|
||||||
|
return
|
||||||
|
argv = [sys.argv[0], keypath, inpath, outpath]
|
||||||
|
self.status['text'] = 'Decrypting...'
|
||||||
|
try:
|
||||||
|
cli_main(argv)
|
||||||
|
except Exception, e:
|
||||||
|
self.status['text'] = 'Error: ' + str(e)
|
||||||
|
return
|
||||||
|
self.status['text'] = 'File successfully decrypted'
|
||||||
|
|
||||||
|
def gui_main():
|
||||||
|
root = Tkinter.Tk()
|
||||||
|
if AES is None:
|
||||||
|
root.withdraw()
|
||||||
|
tkMessageBox.showerror(
|
||||||
|
"Ignoble EPUB Decrypter",
|
||||||
|
"This script requires OpenSSL or PyCrypto, which must be installed "
|
||||||
|
"separately. Read the top-of-script comment for details.")
|
||||||
|
return 1
|
||||||
|
root.title('Ignoble EPUB Decrypter')
|
||||||
|
root.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,232 @@
|
|||||||
|
#! /usr/bin/python
|
||||||
|
|
||||||
|
# ignoblekeygen.pyw, version 2
|
||||||
|
|
||||||
|
# To run this program install Python 2.6 from <http://www.python.org/download/>
|
||||||
|
# and OpenSSL or PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||||
|
# (make sure to install the version for Python 2.6). Save this script file as
|
||||||
|
# ignoblekeygen.pyw and double-click on it to run it.
|
||||||
|
|
||||||
|
# Revision history:
|
||||||
|
# 1 - Initial release
|
||||||
|
# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
|
||||||
|
# 2.1 - Allow Windows versions of libcrypto to be found
|
||||||
|
|
||||||
|
"""
|
||||||
|
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import hashlib
|
||||||
|
import Tkinter
|
||||||
|
import Tkconstants
|
||||||
|
import tkFileDialog
|
||||||
|
import tkMessageBox
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# use openssl's libcrypt if it exists in place of pycrypto
|
||||||
|
# code extracted from the Adobe Adept DRM removal code also by I HeartCabbages
|
||||||
|
class IGNOBLEError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _load_crypto_libcrypto():
|
||||||
|
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
||||||
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
if libcrypto is None:
|
||||||
|
print 'libcrypto not found'
|
||||||
|
raise IGNOBLEError('libcrypto not found')
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
|
||||||
|
AES_MAXNR = 14
|
||||||
|
|
||||||
|
c_char_pp = POINTER(c_char_p)
|
||||||
|
c_int_p = POINTER(c_int)
|
||||||
|
|
||||||
|
class AES_KEY(Structure):
|
||||||
|
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
||||||
|
('rounds', c_int)]
|
||||||
|
AES_KEY_p = POINTER(AES_KEY)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key',
|
||||||
|
[c_char_p, c_int, AES_KEY_p])
|
||||||
|
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
||||||
|
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
||||||
|
c_int])
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, userkey, iv):
|
||||||
|
self._blocksize = len(userkey)
|
||||||
|
self._iv = iv
|
||||||
|
key = self._key = AES_KEY()
|
||||||
|
rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
|
||||||
|
if rv < 0:
|
||||||
|
raise IGNOBLEError('Failed to initialize AES Encrypt key')
|
||||||
|
|
||||||
|
def encrypt(self, data):
|
||||||
|
out = create_string_buffer(len(data))
|
||||||
|
rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
|
||||||
|
if rv == 0:
|
||||||
|
raise IGNOBLEError('AES encryption failed')
|
||||||
|
return out.raw
|
||||||
|
|
||||||
|
return AES
|
||||||
|
|
||||||
|
|
||||||
|
def _load_crypto_pycrypto():
|
||||||
|
from Crypto.Cipher import AES as _AES
|
||||||
|
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, key, iv):
|
||||||
|
self._aes = _AES.new(key, _AES.MODE_CBC, iv)
|
||||||
|
|
||||||
|
def encrypt(self, data):
|
||||||
|
return self._aes.encrypt(data)
|
||||||
|
|
||||||
|
return AES
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _load_crypto():
|
||||||
|
AES = None
|
||||||
|
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
|
||||||
|
try:
|
||||||
|
AES = loader()
|
||||||
|
break
|
||||||
|
except (ImportError, IGNOBLEError):
|
||||||
|
pass
|
||||||
|
return AES
|
||||||
|
|
||||||
|
AES = _load_crypto()
|
||||||
|
|
||||||
|
def normalize_name(name):
|
||||||
|
return ''.join(x for x in name.lower() if x != ' ')
|
||||||
|
|
||||||
|
def generate_keyfile(name, ccn, outpath):
|
||||||
|
name = normalize_name(name) + '\x00'
|
||||||
|
ccn = ccn + '\x00'
|
||||||
|
name_sha = hashlib.sha1(name).digest()[:16]
|
||||||
|
ccn_sha = hashlib.sha1(ccn).digest()[:16]
|
||||||
|
both_sha = hashlib.sha1(name + ccn).digest()
|
||||||
|
aes = AES(ccn_sha, name_sha)
|
||||||
|
crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
|
||||||
|
userkey = hashlib.sha1(crypt).digest()
|
||||||
|
with open(outpath, 'wb') as f:
|
||||||
|
f.write(userkey.encode('base64'))
|
||||||
|
return userkey
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
if AES is None:
|
||||||
|
print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
||||||
|
"separately. Read the top-of-script comment for details." % \
|
||||||
|
(progname,)
|
||||||
|
return 1
|
||||||
|
if len(argv) != 4:
|
||||||
|
print "usage: %s NAME CC# OUTFILE" % (progname,)
|
||||||
|
return 1
|
||||||
|
name, ccn, outpath = argv[1:]
|
||||||
|
generate_keyfile(name, ccn, outpath)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
|
def __init__(self, root):
|
||||||
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
|
self.status = Tkinter.Label(self, text='Enter parameters')
|
||||||
|
self.status.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='Name').grid(row=1)
|
||||||
|
self.name = Tkinter.Entry(body, width=30)
|
||||||
|
self.name.grid(row=1, column=1, sticky=sticky)
|
||||||
|
Tkinter.Label(body, text='CC#').grid(row=2)
|
||||||
|
self.ccn = Tkinter.Entry(body, width=30)
|
||||||
|
self.ccn.grid(row=2, column=1, sticky=sticky)
|
||||||
|
Tkinter.Label(body, text='Output file').grid(row=0)
|
||||||
|
self.keypath = Tkinter.Entry(body, width=30)
|
||||||
|
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||||
|
self.keypath.insert(0, 'bnepubkey.b64')
|
||||||
|
button = Tkinter.Button(body, text="...", command=self.get_keypath)
|
||||||
|
button.grid(row=0, column=2)
|
||||||
|
buttons = Tkinter.Frame(self)
|
||||||
|
buttons.pack()
|
||||||
|
botton = Tkinter.Button(
|
||||||
|
buttons, text="Generate", width=10, command=self.generate)
|
||||||
|
botton.pack(side=Tkconstants.LEFT)
|
||||||
|
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||||
|
button = Tkinter.Button(
|
||||||
|
buttons, text="Quit", width=10, command=self.quit)
|
||||||
|
button.pack(side=Tkconstants.RIGHT)
|
||||||
|
|
||||||
|
def get_keypath(self):
|
||||||
|
keypath = tkFileDialog.asksaveasfilename(
|
||||||
|
parent=None, title='Select B&N EPUB key file to produce',
|
||||||
|
defaultextension='.b64',
|
||||||
|
filetypes=[('base64-encoded files', '.b64'),
|
||||||
|
('All Files', '.*')])
|
||||||
|
if keypath:
|
||||||
|
keypath = os.path.normpath(keypath)
|
||||||
|
self.keypath.delete(0, Tkconstants.END)
|
||||||
|
self.keypath.insert(0, keypath)
|
||||||
|
return
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
name = self.name.get()
|
||||||
|
ccn = self.ccn.get()
|
||||||
|
keypath = self.keypath.get()
|
||||||
|
if not name:
|
||||||
|
self.status['text'] = 'Name not specified'
|
||||||
|
return
|
||||||
|
if not ccn:
|
||||||
|
self.status['text'] = 'Credit card number not specified'
|
||||||
|
return
|
||||||
|
if not keypath:
|
||||||
|
self.status['text'] = 'Output keyfile path not specified'
|
||||||
|
return
|
||||||
|
self.status['text'] = 'Generating...'
|
||||||
|
try:
|
||||||
|
generate_keyfile(name, ccn, keypath)
|
||||||
|
except Exception, e:
|
||||||
|
self.status['text'] = 'Error: ' + str(e)
|
||||||
|
return
|
||||||
|
self.status['text'] = 'Keyfile successfully generated'
|
||||||
|
|
||||||
|
def gui_main():
|
||||||
|
root = Tkinter.Tk()
|
||||||
|
if AES is None:
|
||||||
|
root.withdraw()
|
||||||
|
tkMessageBox.showerror(
|
||||||
|
"Ignoble EPUB Keyfile Generator",
|
||||||
|
"This script requires OpenSSL or PyCrypto, which must be installed "
|
||||||
|
"separately. Read the top-of-script comment for details.")
|
||||||
|
return 1
|
||||||
|
root.title('Ignoble EPUB Keyfile Generator')
|
||||||
|
root.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,462 @@
|
|||||||
|
#! /usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# ineptepub.pyw, version 5.2
|
||||||
|
# Copyright © 2009-2010 i♥cabbages
|
||||||
|
|
||||||
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
|
# later. <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
# Windows users: Before running this program, you must first install Python 2.6
|
||||||
|
# from <http://www.python.org/download/> and PyCrypto from
|
||||||
|
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
|
||||||
|
# install the version for Python 2.6). Save this script file as
|
||||||
|
# ineptepub.pyw and double-click on it to run it.
|
||||||
|
#
|
||||||
|
# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
|
||||||
|
# program from the command line (pythonw ineptepub.pyw) or by double-clicking
|
||||||
|
# it when it has been associated with PythonLauncher.
|
||||||
|
|
||||||
|
# Revision history:
|
||||||
|
# 1 - Initial release
|
||||||
|
# 2 - Rename to INEPT, fix exit code
|
||||||
|
# 5 - Version bump to avoid (?) confusion;
|
||||||
|
# Improve OS X support by using OpenSSL when available
|
||||||
|
# 5.1 - Improve OpenSSL error checking
|
||||||
|
# 5.2 - Fix ctypes error causing segfaults on some systems
|
||||||
|
# 5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o
|
||||||
|
"""
|
||||||
|
Decrypt Adobe ADEPT-encrypted EPUB books.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import zlib
|
||||||
|
import zipfile
|
||||||
|
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||||
|
from contextlib import closing
|
||||||
|
import xml.etree.ElementTree as etree
|
||||||
|
import Tkinter
|
||||||
|
import Tkconstants
|
||||||
|
import tkFileDialog
|
||||||
|
import tkMessageBox
|
||||||
|
|
||||||
|
class ADEPTError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _load_crypto_libcrypto():
|
||||||
|
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
||||||
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
|
||||||
|
if libcrypto is None:
|
||||||
|
raise ADEPTError('libcrypto not found')
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
|
||||||
|
RSA_NO_PADDING = 3
|
||||||
|
AES_MAXNR = 14
|
||||||
|
|
||||||
|
c_char_pp = POINTER(c_char_p)
|
||||||
|
c_int_p = POINTER(c_int)
|
||||||
|
|
||||||
|
class RSA(Structure):
|
||||||
|
pass
|
||||||
|
RSA_p = POINTER(RSA)
|
||||||
|
|
||||||
|
class AES_KEY(Structure):
|
||||||
|
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
||||||
|
('rounds', c_int)]
|
||||||
|
AES_KEY_p = POINTER(AES_KEY)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
|
||||||
|
[RSA_p, c_char_pp, c_long])
|
||||||
|
RSA_size = F(c_int, 'RSA_size', [RSA_p])
|
||||||
|
RSA_private_decrypt = F(c_int, 'RSA_private_decrypt',
|
||||||
|
[c_int, c_char_p, c_char_p, RSA_p, c_int])
|
||||||
|
RSA_free = F(None, 'RSA_free', [RSA_p])
|
||||||
|
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
|
||||||
|
[c_char_p, c_int, AES_KEY_p])
|
||||||
|
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
||||||
|
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
||||||
|
c_int])
|
||||||
|
|
||||||
|
class RSA(object):
|
||||||
|
def __init__(self, der):
|
||||||
|
buf = create_string_buffer(der)
|
||||||
|
pp = c_char_pp(cast(buf, c_char_p))
|
||||||
|
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
||||||
|
if rsa is None:
|
||||||
|
raise ADEPTError('Error parsing ADEPT user key DER')
|
||||||
|
|
||||||
|
def decrypt(self, from_):
|
||||||
|
rsa = self._rsa
|
||||||
|
to = create_string_buffer(RSA_size(rsa))
|
||||||
|
dlen = RSA_private_decrypt(len(from_), from_, to, rsa,
|
||||||
|
RSA_NO_PADDING)
|
||||||
|
if dlen < 0:
|
||||||
|
raise ADEPTError('RSA decryption failed')
|
||||||
|
return to[:dlen]
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if self._rsa is not None:
|
||||||
|
RSA_free(self._rsa)
|
||||||
|
self._rsa = None
|
||||||
|
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, userkey):
|
||||||
|
self._blocksize = len(userkey)
|
||||||
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
|
raise ADEPTError('AES improper key used')
|
||||||
|
return
|
||||||
|
key = self._key = AES_KEY()
|
||||||
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
||||||
|
if rv < 0:
|
||||||
|
raise ADEPTError('Failed to initialize AES key')
|
||||||
|
|
||||||
|
def decrypt(self, data):
|
||||||
|
out = create_string_buffer(len(data))
|
||||||
|
iv = ("\x00" * self._blocksize)
|
||||||
|
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
||||||
|
if rv == 0:
|
||||||
|
raise ADEPTError('AES decryption failed')
|
||||||
|
return out.raw
|
||||||
|
|
||||||
|
return (AES, RSA)
|
||||||
|
|
||||||
|
def _load_crypto_pycrypto():
|
||||||
|
from Crypto.Cipher import AES as _AES
|
||||||
|
from Crypto.PublicKey import RSA as _RSA
|
||||||
|
|
||||||
|
# ASN.1 parsing code from tlslite
|
||||||
|
class ASN1Error(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ASN1Parser(object):
|
||||||
|
class Parser(object):
|
||||||
|
def __init__(self, bytes):
|
||||||
|
self.bytes = bytes
|
||||||
|
self.index = 0
|
||||||
|
|
||||||
|
def get(self, length):
|
||||||
|
if self.index + length > len(self.bytes):
|
||||||
|
raise ASN1Error("Error decoding ASN.1")
|
||||||
|
x = 0
|
||||||
|
for count in range(length):
|
||||||
|
x <<= 8
|
||||||
|
x |= self.bytes[self.index]
|
||||||
|
self.index += 1
|
||||||
|
return x
|
||||||
|
|
||||||
|
def getFixBytes(self, lengthBytes):
|
||||||
|
bytes = self.bytes[self.index : self.index+lengthBytes]
|
||||||
|
self.index += lengthBytes
|
||||||
|
return bytes
|
||||||
|
|
||||||
|
def getVarBytes(self, lengthLength):
|
||||||
|
lengthBytes = self.get(lengthLength)
|
||||||
|
return self.getFixBytes(lengthBytes)
|
||||||
|
|
||||||
|
def getFixList(self, length, lengthList):
|
||||||
|
l = [0] * lengthList
|
||||||
|
for x in range(lengthList):
|
||||||
|
l[x] = self.get(length)
|
||||||
|
return l
|
||||||
|
|
||||||
|
def getVarList(self, length, lengthLength):
|
||||||
|
lengthList = self.get(lengthLength)
|
||||||
|
if lengthList % length != 0:
|
||||||
|
raise ASN1Error("Error decoding ASN.1")
|
||||||
|
lengthList = int(lengthList/length)
|
||||||
|
l = [0] * lengthList
|
||||||
|
for x in range(lengthList):
|
||||||
|
l[x] = self.get(length)
|
||||||
|
return l
|
||||||
|
|
||||||
|
def startLengthCheck(self, lengthLength):
|
||||||
|
self.lengthCheck = self.get(lengthLength)
|
||||||
|
self.indexCheck = self.index
|
||||||
|
|
||||||
|
def setLengthCheck(self, length):
|
||||||
|
self.lengthCheck = length
|
||||||
|
self.indexCheck = self.index
|
||||||
|
|
||||||
|
def stopLengthCheck(self):
|
||||||
|
if (self.index - self.indexCheck) != self.lengthCheck:
|
||||||
|
raise ASN1Error("Error decoding ASN.1")
|
||||||
|
|
||||||
|
def atLengthCheck(self):
|
||||||
|
if (self.index - self.indexCheck) < self.lengthCheck:
|
||||||
|
return False
|
||||||
|
elif (self.index - self.indexCheck) == self.lengthCheck:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
raise ASN1Error("Error decoding ASN.1")
|
||||||
|
|
||||||
|
def __init__(self, bytes):
|
||||||
|
p = self.Parser(bytes)
|
||||||
|
p.get(1)
|
||||||
|
self.length = self._getASN1Length(p)
|
||||||
|
self.value = p.getFixBytes(self.length)
|
||||||
|
|
||||||
|
def getChild(self, which):
|
||||||
|
p = self.Parser(self.value)
|
||||||
|
for x in range(which+1):
|
||||||
|
markIndex = p.index
|
||||||
|
p.get(1)
|
||||||
|
length = self._getASN1Length(p)
|
||||||
|
p.getFixBytes(length)
|
||||||
|
return ASN1Parser(p.bytes[markIndex:p.index])
|
||||||
|
|
||||||
|
def _getASN1Length(self, p):
|
||||||
|
firstLength = p.get(1)
|
||||||
|
if firstLength<=127:
|
||||||
|
return firstLength
|
||||||
|
else:
|
||||||
|
lengthLength = firstLength & 0x7F
|
||||||
|
return p.get(lengthLength)
|
||||||
|
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, key):
|
||||||
|
self._aes = _AES.new(key, _AES.MODE_CBC)
|
||||||
|
|
||||||
|
def decrypt(self, data):
|
||||||
|
return self._aes.decrypt(data)
|
||||||
|
|
||||||
|
class RSA(object):
|
||||||
|
def __init__(self, der):
|
||||||
|
key = ASN1Parser([ord(x) for x in der])
|
||||||
|
key = [key.getChild(x).value for x in xrange(1, 4)]
|
||||||
|
key = [self.bytesToNumber(v) for v in key]
|
||||||
|
self._rsa = _RSA.construct(key)
|
||||||
|
|
||||||
|
def bytesToNumber(self, bytes):
|
||||||
|
total = 0L
|
||||||
|
for byte in bytes:
|
||||||
|
total = (total << 8) + byte
|
||||||
|
return total
|
||||||
|
|
||||||
|
def decrypt(self, data):
|
||||||
|
return self._rsa.decrypt(data)
|
||||||
|
|
||||||
|
return (AES, RSA)
|
||||||
|
|
||||||
|
def _load_crypto():
|
||||||
|
AES = RSA = None
|
||||||
|
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
|
||||||
|
try:
|
||||||
|
AES, RSA = loader()
|
||||||
|
break
|
||||||
|
except (ImportError, ADEPTError):
|
||||||
|
pass
|
||||||
|
return (AES, RSA)
|
||||||
|
AES, RSA = _load_crypto()
|
||||||
|
|
||||||
|
META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
|
||||||
|
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
||||||
|
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
||||||
|
|
||||||
|
class ZipInfo(zipfile.ZipInfo):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if 'compress_type' in kwargs:
|
||||||
|
compress_type = kwargs.pop('compress_type')
|
||||||
|
super(ZipInfo, self).__init__(*args, **kwargs)
|
||||||
|
self.compress_type = compress_type
|
||||||
|
|
||||||
|
class Decryptor(object):
|
||||||
|
def __init__(self, bookkey, encryption):
|
||||||
|
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
|
||||||
|
self._aes = AES(bookkey)
|
||||||
|
encryption = etree.fromstring(encryption)
|
||||||
|
self._encrypted = encrypted = set()
|
||||||
|
expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
|
||||||
|
enc('CipherReference'))
|
||||||
|
for elem in encryption.findall(expr):
|
||||||
|
path = elem.get('URI', None)
|
||||||
|
if path is not None:
|
||||||
|
encrypted.add(path)
|
||||||
|
|
||||||
|
def decompress(self, bytes):
|
||||||
|
dc = zlib.decompressobj(-15)
|
||||||
|
bytes = dc.decompress(bytes)
|
||||||
|
ex = dc.decompress('Z') + dc.flush()
|
||||||
|
if ex:
|
||||||
|
bytes = bytes + ex
|
||||||
|
return bytes
|
||||||
|
|
||||||
|
def decrypt(self, path, data):
|
||||||
|
if path in self._encrypted:
|
||||||
|
data = self._aes.decrypt(data)[16:]
|
||||||
|
data = data[:-ord(data[-1])]
|
||||||
|
data = self.decompress(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
if AES is None:
|
||||||
|
print "%s: This script requires OpenSSL or PyCrypto, which must be" \
|
||||||
|
" installed separately. Read the top-of-script comment for" \
|
||||||
|
" details." % (progname,)
|
||||||
|
return 1
|
||||||
|
if len(argv) != 4:
|
||||||
|
print "usage: %s KEYFILE INBOOK OUTBOOK" % (progname,)
|
||||||
|
return 1
|
||||||
|
keypath, inpath, outpath = argv[1:]
|
||||||
|
with open(keypath, 'rb') as f:
|
||||||
|
keyder = f.read()
|
||||||
|
rsa = RSA(keyder)
|
||||||
|
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||||
|
namelist = set(inf.namelist())
|
||||||
|
if 'META-INF/rights.xml' not in namelist or \
|
||||||
|
'META-INF/encryption.xml' not in namelist:
|
||||||
|
raise ADEPTError('%s: not an ADEPT EPUB' % (inpath,))
|
||||||
|
for name in META_NAMES:
|
||||||
|
namelist.remove(name)
|
||||||
|
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||||
|
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||||
|
expr = './/%s' % (adept('encryptedKey'),)
|
||||||
|
bookkey = ''.join(rights.findtext(expr))
|
||||||
|
bookkey = rsa.decrypt(bookkey.decode('base64'))
|
||||||
|
# Padded as per RSAES-PKCS1-v1_5
|
||||||
|
if bookkey[-17] != '\x00':
|
||||||
|
raise ADEPTError('problem decrypting session key')
|
||||||
|
encryption = inf.read('META-INF/encryption.xml')
|
||||||
|
decryptor = Decryptor(bookkey[-16:], encryption)
|
||||||
|
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
||||||
|
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
|
||||||
|
zi = ZipInfo('mimetype', compress_type=ZIP_STORED)
|
||||||
|
outf.writestr(zi, inf.read('mimetype'))
|
||||||
|
for path in namelist:
|
||||||
|
data = inf.read(path)
|
||||||
|
outf.writestr(path, decryptor.decrypt(path, data))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
|
def __init__(self, root):
|
||||||
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
|
self.status = Tkinter.Label(self, text='Select files for decryption')
|
||||||
|
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||||
|
body = Tkinter.Frame(self)
|
||||||
|
body.pack(fill=Tkconstants.X, expand=1)
|
||||||
|
sticky = Tkconstants.E + Tkconstants.W
|
||||||
|
body.grid_columnconfigure(1, weight=2)
|
||||||
|
Tkinter.Label(body, text='Key file').grid(row=0)
|
||||||
|
self.keypath = Tkinter.Entry(body, width=30)
|
||||||
|
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||||
|
if os.path.exists('adeptkey.der'):
|
||||||
|
self.keypath.insert(0, 'adeptkey.der')
|
||||||
|
button = Tkinter.Button(body, text="...", command=self.get_keypath)
|
||||||
|
button.grid(row=0, column=2)
|
||||||
|
Tkinter.Label(body, text='Input file').grid(row=1)
|
||||||
|
self.inpath = Tkinter.Entry(body, width=30)
|
||||||
|
self.inpath.grid(row=1, column=1, sticky=sticky)
|
||||||
|
button = Tkinter.Button(body, text="...", command=self.get_inpath)
|
||||||
|
button.grid(row=1, column=2)
|
||||||
|
Tkinter.Label(body, text='Output file').grid(row=2)
|
||||||
|
self.outpath = Tkinter.Entry(body, width=30)
|
||||||
|
self.outpath.grid(row=2, column=1, sticky=sticky)
|
||||||
|
button = Tkinter.Button(body, text="...", command=self.get_outpath)
|
||||||
|
button.grid(row=2, column=2)
|
||||||
|
buttons = Tkinter.Frame(self)
|
||||||
|
buttons.pack()
|
||||||
|
botton = Tkinter.Button(
|
||||||
|
buttons, text="Decrypt", width=10, command=self.decrypt)
|
||||||
|
botton.pack(side=Tkconstants.LEFT)
|
||||||
|
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||||
|
button = Tkinter.Button(
|
||||||
|
buttons, text="Quit", width=10, command=self.quit)
|
||||||
|
button.pack(side=Tkconstants.RIGHT)
|
||||||
|
|
||||||
|
def get_keypath(self):
|
||||||
|
keypath = tkFileDialog.askopenfilename(
|
||||||
|
parent=None, title='Select ADEPT key file',
|
||||||
|
defaultextension='.der', filetypes=[('DER-encoded files', '.der'),
|
||||||
|
('All Files', '.*')])
|
||||||
|
if keypath:
|
||||||
|
keypath = os.path.normpath(keypath)
|
||||||
|
self.keypath.delete(0, Tkconstants.END)
|
||||||
|
self.keypath.insert(0, keypath)
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_inpath(self):
|
||||||
|
inpath = tkFileDialog.askopenfilename(
|
||||||
|
parent=None, title='Select ADEPT-encrypted EPUB file to decrypt',
|
||||||
|
defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
|
||||||
|
('All files', '.*')])
|
||||||
|
if inpath:
|
||||||
|
inpath = os.path.normpath(inpath)
|
||||||
|
self.inpath.delete(0, Tkconstants.END)
|
||||||
|
self.inpath.insert(0, inpath)
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_outpath(self):
|
||||||
|
outpath = tkFileDialog.asksaveasfilename(
|
||||||
|
parent=None, title='Select unencrypted EPUB file to produce',
|
||||||
|
defaultextension='.epub', filetypes=[('EPUB files', '.epub'),
|
||||||
|
('All files', '.*')])
|
||||||
|
if outpath:
|
||||||
|
outpath = os.path.normpath(outpath)
|
||||||
|
self.outpath.delete(0, Tkconstants.END)
|
||||||
|
self.outpath.insert(0, outpath)
|
||||||
|
return
|
||||||
|
|
||||||
|
def decrypt(self):
|
||||||
|
keypath = self.keypath.get()
|
||||||
|
inpath = self.inpath.get()
|
||||||
|
outpath = self.outpath.get()
|
||||||
|
if not keypath or not os.path.exists(keypath):
|
||||||
|
self.status['text'] = 'Specified key file does not exist'
|
||||||
|
return
|
||||||
|
if not inpath or not os.path.exists(inpath):
|
||||||
|
self.status['text'] = 'Specified input file does not exist'
|
||||||
|
return
|
||||||
|
if not outpath:
|
||||||
|
self.status['text'] = 'Output file not specified'
|
||||||
|
return
|
||||||
|
if inpath == outpath:
|
||||||
|
self.status['text'] = 'Must have different input and output files'
|
||||||
|
return
|
||||||
|
argv = [sys.argv[0], keypath, inpath, outpath]
|
||||||
|
self.status['text'] = 'Decrypting...'
|
||||||
|
try:
|
||||||
|
cli_main(argv)
|
||||||
|
except Exception, e:
|
||||||
|
self.status['text'] = 'Error: ' + str(e)
|
||||||
|
return
|
||||||
|
self.status['text'] = 'File successfully decrypted'
|
||||||
|
|
||||||
|
def gui_main():
|
||||||
|
root = Tkinter.Tk()
|
||||||
|
if AES is None:
|
||||||
|
root.withdraw()
|
||||||
|
tkMessageBox.showerror(
|
||||||
|
"INEPT EPUB Decrypter",
|
||||||
|
"This script requires OpenSSL or PyCrypto, which must be"
|
||||||
|
" installed separately. Read the top-of-script comment for"
|
||||||
|
" details.")
|
||||||
|
return 1
|
||||||
|
root.title('INEPT EPUB Decrypter')
|
||||||
|
root.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,458 @@
|
|||||||
|
#! /usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# ineptkey.pyw, version 5
|
||||||
|
# Copyright © 2009-2010 i♥cabbages
|
||||||
|
|
||||||
|
# Released under the terms of the GNU General Public Licence, version 3 or
|
||||||
|
# later. <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
# Windows users: Before running this program, you must first install Python 2.6
|
||||||
|
# from <http://www.python.org/download/> and PyCrypto from
|
||||||
|
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make certain
|
||||||
|
# to install the version for Python 2.6). Then save this script file as
|
||||||
|
# ineptkey.pyw and double-click on it to run it. It will create a file named
|
||||||
|
# adeptkey.der in the same directory. This is your ADEPT user key.
|
||||||
|
#
|
||||||
|
# Mac OS X users: Save this script file as ineptkey.pyw. You can run this
|
||||||
|
# program from the command line (pythonw ineptkey.pyw) or by double-clicking
|
||||||
|
# it when it has been associated with PythonLauncher. It will create a file
|
||||||
|
# named adeptkey.der in the same directory. This is your ADEPT user key.
|
||||||
|
|
||||||
|
# Revision history:
|
||||||
|
# 1 - Initial release, for Adobe Digital Editions 1.7
|
||||||
|
# 2 - Better algorithm for finding pLK; improved error handling
|
||||||
|
# 3 - Rename to INEPT
|
||||||
|
# 4 - Series of changes by joblack (and others?) --
|
||||||
|
# 4.1 - quick beta fix for ADE 1.7.2 (anon)
|
||||||
|
# 4.2 - added old 1.7.1 processing
|
||||||
|
# 4.3 - better key search
|
||||||
|
# 4.4 - Make it working on 64-bit Python
|
||||||
|
# 5 - Clean up and improve 4.x changes;
|
||||||
|
# Clean up and merge OS X support by unknown
|
||||||
|
# 5.1 - add support for using OpenSSL on Windows in place of PyCrypto
|
||||||
|
# 5.2 - added support for output of key to a particular file
|
||||||
|
|
||||||
|
"""
|
||||||
|
Retrieve Adobe ADEPT user key.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import Tkinter
|
||||||
|
import Tkconstants
|
||||||
|
import tkMessageBox
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
class ADEPTError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
||||||
|
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
||||||
|
string_at, Structure, c_void_p, cast, c_size_t, memmove, CDLL, c_int, \
|
||||||
|
c_long, c_ulong
|
||||||
|
|
||||||
|
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
||||||
|
import _winreg as winreg
|
||||||
|
|
||||||
|
def _load_crypto_libcrypto():
|
||||||
|
from ctypes.util import find_library
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
if libcrypto is None:
|
||||||
|
raise ADEPTError('libcrypto not found')
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
AES_MAXNR = 14
|
||||||
|
c_char_pp = POINTER(c_char_p)
|
||||||
|
c_int_p = POINTER(c_int)
|
||||||
|
class AES_KEY(Structure):
|
||||||
|
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
||||||
|
('rounds', c_int)]
|
||||||
|
AES_KEY_p = POINTER(AES_KEY)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
|
||||||
|
[c_char_p, c_int, AES_KEY_p])
|
||||||
|
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
||||||
|
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
||||||
|
c_int])
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, userkey):
|
||||||
|
self._blocksize = len(userkey)
|
||||||
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
|
raise ADEPTError('AES improper key used')
|
||||||
|
key = self._key = AES_KEY()
|
||||||
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
||||||
|
if rv < 0:
|
||||||
|
raise ADEPTError('Failed to initialize AES key')
|
||||||
|
def decrypt(self, data):
|
||||||
|
out = create_string_buffer(len(data))
|
||||||
|
iv = ("\x00" * self._blocksize)
|
||||||
|
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
||||||
|
if rv == 0:
|
||||||
|
raise ADEPTError('AES decryption failed')
|
||||||
|
return out.raw
|
||||||
|
return AES
|
||||||
|
|
||||||
|
def _load_crypto_pycrypto():
|
||||||
|
from Crypto.Cipher import AES as _AES
|
||||||
|
class AES(object):
|
||||||
|
def __init__(self, key):
|
||||||
|
self._aes = _AES.new(key, _AES.MODE_CBC)
|
||||||
|
def decrypt(self, data):
|
||||||
|
return self._aes.decrypt(data)
|
||||||
|
return AES
|
||||||
|
|
||||||
|
def _load_crypto():
|
||||||
|
AES = None
|
||||||
|
for loader in (_load_crypto_libcrypto, _load_crypto_pycrypto):
|
||||||
|
try:
|
||||||
|
AES = loader()
|
||||||
|
break
|
||||||
|
except (ImportError, ADEPTError):
|
||||||
|
pass
|
||||||
|
return AES
|
||||||
|
|
||||||
|
AES = _load_crypto()
|
||||||
|
|
||||||
|
|
||||||
|
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
||||||
|
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
||||||
|
|
||||||
|
MAX_PATH = 255
|
||||||
|
|
||||||
|
kernel32 = windll.kernel32
|
||||||
|
advapi32 = windll.advapi32
|
||||||
|
crypt32 = windll.crypt32
|
||||||
|
|
||||||
|
def GetSystemDirectory():
|
||||||
|
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
|
||||||
|
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
|
||||||
|
GetSystemDirectoryW.restype = c_uint
|
||||||
|
def GetSystemDirectory():
|
||||||
|
buffer = create_unicode_buffer(MAX_PATH + 1)
|
||||||
|
GetSystemDirectoryW(buffer, len(buffer))
|
||||||
|
return buffer.value
|
||||||
|
return GetSystemDirectory
|
||||||
|
GetSystemDirectory = GetSystemDirectory()
|
||||||
|
|
||||||
|
def GetVolumeSerialNumber():
|
||||||
|
GetVolumeInformationW = kernel32.GetVolumeInformationW
|
||||||
|
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
|
||||||
|
POINTER(c_uint), POINTER(c_uint),
|
||||||
|
POINTER(c_uint), c_wchar_p, c_uint]
|
||||||
|
GetVolumeInformationW.restype = c_uint
|
||||||
|
def GetVolumeSerialNumber(path):
|
||||||
|
vsn = c_uint(0)
|
||||||
|
GetVolumeInformationW(
|
||||||
|
path, None, 0, byref(vsn), None, None, None, 0)
|
||||||
|
return vsn.value
|
||||||
|
return GetVolumeSerialNumber
|
||||||
|
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||||
|
|
||||||
|
def GetUserName():
|
||||||
|
GetUserNameW = advapi32.GetUserNameW
|
||||||
|
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
|
||||||
|
GetUserNameW.restype = c_uint
|
||||||
|
def GetUserName():
|
||||||
|
buffer = create_unicode_buffer(32)
|
||||||
|
size = c_uint(len(buffer))
|
||||||
|
while not GetUserNameW(buffer, byref(size)):
|
||||||
|
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||||
|
size.value = len(buffer)
|
||||||
|
return buffer.value.encode('utf-16-le')[::2]
|
||||||
|
return GetUserName
|
||||||
|
GetUserName = GetUserName()
|
||||||
|
|
||||||
|
PAGE_EXECUTE_READWRITE = 0x40
|
||||||
|
MEM_COMMIT = 0x1000
|
||||||
|
MEM_RESERVE = 0x2000
|
||||||
|
|
||||||
|
def VirtualAlloc():
|
||||||
|
_VirtualAlloc = kernel32.VirtualAlloc
|
||||||
|
_VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD]
|
||||||
|
_VirtualAlloc.restype = LPVOID
|
||||||
|
def VirtualAlloc(addr, size, alloctype=(MEM_COMMIT | MEM_RESERVE),
|
||||||
|
protect=PAGE_EXECUTE_READWRITE):
|
||||||
|
return _VirtualAlloc(addr, size, alloctype, protect)
|
||||||
|
return VirtualAlloc
|
||||||
|
VirtualAlloc = VirtualAlloc()
|
||||||
|
|
||||||
|
MEM_RELEASE = 0x8000
|
||||||
|
|
||||||
|
def VirtualFree():
|
||||||
|
_VirtualFree = kernel32.VirtualFree
|
||||||
|
_VirtualFree.argtypes = [LPVOID, c_size_t, DWORD]
|
||||||
|
_VirtualFree.restype = BOOL
|
||||||
|
def VirtualFree(addr, size=0, freetype=MEM_RELEASE):
|
||||||
|
return _VirtualFree(addr, size, freetype)
|
||||||
|
return VirtualFree
|
||||||
|
VirtualFree = VirtualFree()
|
||||||
|
|
||||||
|
class NativeFunction(object):
|
||||||
|
def __init__(self, restype, argtypes, insns):
|
||||||
|
self._buf = buf = VirtualAlloc(None, len(insns))
|
||||||
|
memmove(buf, insns, len(insns))
|
||||||
|
ftype = CFUNCTYPE(restype, *argtypes)
|
||||||
|
self._native = ftype(buf)
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return self._native(*args)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if self._buf is not None:
|
||||||
|
VirtualFree(self._buf)
|
||||||
|
self._buf = None
|
||||||
|
|
||||||
|
if struct.calcsize("P") == 4:
|
||||||
|
CPUID0_INSNS = (
|
||||||
|
"\x53" # push %ebx
|
||||||
|
"\x31\xc0" # xor %eax,%eax
|
||||||
|
"\x0f\xa2" # cpuid
|
||||||
|
"\x8b\x44\x24\x08" # mov 0x8(%esp),%eax
|
||||||
|
"\x89\x18" # mov %ebx,0x0(%eax)
|
||||||
|
"\x89\x50\x04" # mov %edx,0x4(%eax)
|
||||||
|
"\x89\x48\x08" # mov %ecx,0x8(%eax)
|
||||||
|
"\x5b" # pop %ebx
|
||||||
|
"\xc3" # ret
|
||||||
|
)
|
||||||
|
CPUID1_INSNS = (
|
||||||
|
"\x53" # push %ebx
|
||||||
|
"\x31\xc0" # xor %eax,%eax
|
||||||
|
"\x40" # inc %eax
|
||||||
|
"\x0f\xa2" # cpuid
|
||||||
|
"\x5b" # pop %ebx
|
||||||
|
"\xc3" # ret
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
CPUID0_INSNS = (
|
||||||
|
"\x49\x89\xd8" # mov %rbx,%r8
|
||||||
|
"\x49\x89\xc9" # mov %rcx,%r9
|
||||||
|
"\x48\x31\xc0" # xor %rax,%rax
|
||||||
|
"\x0f\xa2" # cpuid
|
||||||
|
"\x4c\x89\xc8" # mov %r9,%rax
|
||||||
|
"\x89\x18" # mov %ebx,0x0(%rax)
|
||||||
|
"\x89\x50\x04" # mov %edx,0x4(%rax)
|
||||||
|
"\x89\x48\x08" # mov %ecx,0x8(%rax)
|
||||||
|
"\x4c\x89\xc3" # mov %r8,%rbx
|
||||||
|
"\xc3" # retq
|
||||||
|
)
|
||||||
|
CPUID1_INSNS = (
|
||||||
|
"\x53" # push %rbx
|
||||||
|
"\x48\x31\xc0" # xor %rax,%rax
|
||||||
|
"\x48\xff\xc0" # inc %rax
|
||||||
|
"\x0f\xa2" # cpuid
|
||||||
|
"\x5b" # pop %rbx
|
||||||
|
"\xc3" # retq
|
||||||
|
)
|
||||||
|
|
||||||
|
def cpuid0():
|
||||||
|
_cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS)
|
||||||
|
buf = create_string_buffer(12)
|
||||||
|
def cpuid0():
|
||||||
|
_cpuid0(buf)
|
||||||
|
return buf.raw
|
||||||
|
return cpuid0
|
||||||
|
cpuid0 = cpuid0()
|
||||||
|
|
||||||
|
cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS)
|
||||||
|
|
||||||
|
class DataBlob(Structure):
|
||||||
|
_fields_ = [('cbData', c_uint),
|
||||||
|
('pbData', c_void_p)]
|
||||||
|
DataBlob_p = POINTER(DataBlob)
|
||||||
|
|
||||||
|
def CryptUnprotectData():
|
||||||
|
_CryptUnprotectData = crypt32.CryptUnprotectData
|
||||||
|
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
|
||||||
|
c_void_p, c_void_p, c_uint, DataBlob_p]
|
||||||
|
_CryptUnprotectData.restype = c_uint
|
||||||
|
def CryptUnprotectData(indata, entropy):
|
||||||
|
indatab = create_string_buffer(indata)
|
||||||
|
indata = DataBlob(len(indata), cast(indatab, c_void_p))
|
||||||
|
entropyb = create_string_buffer(entropy)
|
||||||
|
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
|
||||||
|
outdata = DataBlob()
|
||||||
|
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
|
||||||
|
None, None, 0, byref(outdata)):
|
||||||
|
raise ADEPTError("Failed to decrypt user key key (sic)")
|
||||||
|
return string_at(outdata.pbData, outdata.cbData)
|
||||||
|
return CryptUnprotectData
|
||||||
|
CryptUnprotectData = CryptUnprotectData()
|
||||||
|
|
||||||
|
def retrieve_key(keypath):
|
||||||
|
if AES is None:
|
||||||
|
tkMessageBox.showerror(
|
||||||
|
"ADEPT Key",
|
||||||
|
"This script requires PyCrypto or OpenSSL which must be installed "
|
||||||
|
"separately. Read the top-of-script comment for details.")
|
||||||
|
return False
|
||||||
|
root = GetSystemDirectory().split('\\')[0] + '\\'
|
||||||
|
serial = GetVolumeSerialNumber(root)
|
||||||
|
vendor = cpuid0()
|
||||||
|
signature = struct.pack('>I', cpuid1())[1:]
|
||||||
|
user = GetUserName()
|
||||||
|
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
|
||||||
|
cuser = winreg.HKEY_CURRENT_USER
|
||||||
|
try:
|
||||||
|
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
|
||||||
|
except WindowsError:
|
||||||
|
raise ADEPTError("Adobe Digital Editions not activated")
|
||||||
|
device = winreg.QueryValueEx(regkey, 'key')[0]
|
||||||
|
keykey = CryptUnprotectData(device, entropy)
|
||||||
|
userkey = None
|
||||||
|
try:
|
||||||
|
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
|
||||||
|
except WindowsError:
|
||||||
|
raise ADEPTError("Could not locate ADE activation")
|
||||||
|
for i in xrange(0, 16):
|
||||||
|
try:
|
||||||
|
plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
|
||||||
|
except WindowsError:
|
||||||
|
break
|
||||||
|
ktype = winreg.QueryValueEx(plkparent, None)[0]
|
||||||
|
if ktype != 'credentials':
|
||||||
|
continue
|
||||||
|
for j in xrange(0, 16):
|
||||||
|
try:
|
||||||
|
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
|
||||||
|
except WindowsError:
|
||||||
|
break
|
||||||
|
ktype = winreg.QueryValueEx(plkkey, None)[0]
|
||||||
|
if ktype != 'privateLicenseKey':
|
||||||
|
continue
|
||||||
|
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
|
||||||
|
break
|
||||||
|
if userkey is not None:
|
||||||
|
break
|
||||||
|
if userkey is None:
|
||||||
|
raise ADEPTError('Could not locate privateLicenseKey')
|
||||||
|
userkey = userkey.decode('base64')
|
||||||
|
aes = AES(keykey)
|
||||||
|
userkey = aes.decrypt(userkey)
|
||||||
|
userkey = userkey[26:-ord(userkey[-1])]
|
||||||
|
with open(keypath, 'wb') as f:
|
||||||
|
f.write(userkey)
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif sys.platform.startswith('darwin'):
|
||||||
|
import xml.etree.ElementTree as etree
|
||||||
|
import Carbon.File
|
||||||
|
import Carbon.Folder
|
||||||
|
import Carbon.Folders
|
||||||
|
import MacOS
|
||||||
|
|
||||||
|
ACTIVATION_PATH = 'Adobe/Digital Editions/activation.dat'
|
||||||
|
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
||||||
|
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
||||||
|
|
||||||
|
def find_folder(domain, dtype):
|
||||||
|
try:
|
||||||
|
fsref = Carbon.Folder.FSFindFolder(domain, dtype, False)
|
||||||
|
return Carbon.File.pathname(fsref)
|
||||||
|
except MacOS.Error:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_app_support_file(subpath):
|
||||||
|
dtype = Carbon.Folders.kApplicationSupportFolderType
|
||||||
|
for domain in Carbon.Folders.kUserDomain, Carbon.Folders.kLocalDomain:
|
||||||
|
path = find_folder(domain, dtype)
|
||||||
|
if path is None:
|
||||||
|
continue
|
||||||
|
path = os.path.join(path, subpath)
|
||||||
|
if os.path.isfile(path):
|
||||||
|
return path
|
||||||
|
return None
|
||||||
|
|
||||||
|
def retrieve_key(keypath):
|
||||||
|
actpath = find_app_support_file(ACTIVATION_PATH)
|
||||||
|
if actpath is None:
|
||||||
|
raise ADEPTError("Could not locate ADE activation")
|
||||||
|
tree = etree.parse(actpath)
|
||||||
|
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||||
|
expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey'))
|
||||||
|
userkey = tree.findtext(expr)
|
||||||
|
userkey = userkey.decode('base64')
|
||||||
|
userkey = userkey[26:]
|
||||||
|
with open(keypath, 'wb') as f:
|
||||||
|
f.write(userkey)
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif sys.platform.startswith('cygwin'):
|
||||||
|
def retrieve_key(keypath):
|
||||||
|
tkMessageBox.showerror(
|
||||||
|
"ADEPT Key",
|
||||||
|
"This script requires a Windows-native Python, and cannot be run "
|
||||||
|
"under Cygwin. Please install a Windows-native Python and/or "
|
||||||
|
"check your file associations.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
def retrieve_key(keypath):
|
||||||
|
tkMessageBox.showerror(
|
||||||
|
"ADEPT Key",
|
||||||
|
"This script only supports Windows and Mac OS X. For Linux "
|
||||||
|
"you should be able to run ADE and this script under Wine (with "
|
||||||
|
"an appropriate version of Windows Python installed).")
|
||||||
|
return False
|
||||||
|
|
||||||
|
class ExceptionDialog(Tkinter.Frame):
|
||||||
|
def __init__(self, root, text):
|
||||||
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
|
label = Tkinter.Label(self, text="Unexpected error:",
|
||||||
|
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
|
||||||
|
label.pack(fill=Tkconstants.X, expand=0)
|
||||||
|
self.text = Tkinter.Text(self)
|
||||||
|
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||||
|
self.text.insert(Tkconstants.END, text)
|
||||||
|
|
||||||
|
def cli_main(argv=sys.argv):
|
||||||
|
keypath = argv[1]
|
||||||
|
try:
|
||||||
|
success = retrieve_key(keypath)
|
||||||
|
except ADEPTError, e:
|
||||||
|
print "Key generation Error: " + str(e)
|
||||||
|
return 1
|
||||||
|
except Exception, e:
|
||||||
|
print "General Error: " + str(e)
|
||||||
|
return 1
|
||||||
|
if not success:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
root = Tkinter.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
keypath = 'adeptkey.der'
|
||||||
|
success = False
|
||||||
|
try:
|
||||||
|
success = retrieve_key(keypath)
|
||||||
|
except ADEPTError, e:
|
||||||
|
tkMessageBox.showerror("ADEPT Key", "Error: " + str(e))
|
||||||
|
except Exception:
|
||||||
|
root.wm_state('normal')
|
||||||
|
root.title('ADEPT Key')
|
||||||
|
text = traceback.format_exc()
|
||||||
|
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
|
||||||
|
root.mainloop()
|
||||||
|
if not success:
|
||||||
|
return 1
|
||||||
|
tkMessageBox.showinfo(
|
||||||
|
"ADEPT Key", "Key successfully retrieved to %s" % (keypath))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
sys.exit(cli_main())
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,582 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# engine to remove drm from Kindle for Mac and Kindle for PC books
|
||||||
|
# for personal use for archiving and converting your ebooks
|
||||||
|
|
||||||
|
# PLEASE DO NOT PIRATE EBOOKS!
|
||||||
|
|
||||||
|
# We want all authors and publishers, and eBook stores to live
|
||||||
|
# long and prosperous lives but at the same time we just want to
|
||||||
|
# be able to read OUR books on whatever device we want and to keep
|
||||||
|
# readable for a long, long time
|
||||||
|
|
||||||
|
# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
|
||||||
|
# unswindle, DarkReverser, ApprenticeAlf, DiapDealer, some_updates
|
||||||
|
# and many many others
|
||||||
|
|
||||||
|
# It can run standalone to convert K4M/K4PC/Mobi files, or it can be installed as a
|
||||||
|
# plugin for Calibre (http://calibre-ebook.com/about) so that importing
|
||||||
|
# K4 or Mobi with DRM is no londer a multi-step process.
|
||||||
|
#
|
||||||
|
# ***NOTE*** If you are using this script as a calibre plugin for a K4M or K4PC ebook
|
||||||
|
# then calibre must be installed on the same machine and in the same account as K4PC or K4M
|
||||||
|
# for the plugin version to function properly.
|
||||||
|
#
|
||||||
|
# To create a Calibre plugin, rename this file so that the filename
|
||||||
|
# ends in '_plugin.py', put it into a ZIP file with all its supporting python routines
|
||||||
|
# and import that ZIP into Calibre using its plugin configuration GUI.
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
__version__ = '1.2'
|
||||||
|
|
||||||
|
class Unbuffered:
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
def write(self, data):
|
||||||
|
self.stream.write(data)
|
||||||
|
self.stream.flush()
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os, csv, getopt
|
||||||
|
import binascii
|
||||||
|
import zlib
|
||||||
|
import re
|
||||||
|
from struct import pack, unpack, unpack_from
|
||||||
|
|
||||||
|
|
||||||
|
#Exception Handling
|
||||||
|
class DrmException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
#
|
||||||
|
# crypto digestroutines
|
||||||
|
#
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
def MD5(message):
|
||||||
|
ctx = hashlib.md5()
|
||||||
|
ctx.update(message)
|
||||||
|
return ctx.digest()
|
||||||
|
|
||||||
|
def SHA1(message):
|
||||||
|
ctx = hashlib.sha1()
|
||||||
|
ctx.update(message)
|
||||||
|
return ctx.digest()
|
||||||
|
|
||||||
|
# determine if we are running as a calibre plugin
|
||||||
|
if 'calibre' in sys.modules:
|
||||||
|
inCalibre = True
|
||||||
|
global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
else:
|
||||||
|
inCalibre = False
|
||||||
|
|
||||||
|
#
|
||||||
|
# start of Kindle specific routines
|
||||||
|
#
|
||||||
|
|
||||||
|
if not inCalibre:
|
||||||
|
import mobidedrm
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
|
||||||
|
global kindleDatabase
|
||||||
|
|
||||||
|
# Encode the bytes in data with the characters in map
|
||||||
|
def encode(data, map):
|
||||||
|
result = ""
|
||||||
|
for char in data:
|
||||||
|
value = ord(char)
|
||||||
|
Q = (value ^ 0x80) // len(map)
|
||||||
|
R = value % len(map)
|
||||||
|
result += map[Q]
|
||||||
|
result += map[R]
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Hash the bytes in data and then encode the digest with the characters in map
|
||||||
|
def encodeHash(data,map):
|
||||||
|
return encode(MD5(data),map)
|
||||||
|
|
||||||
|
# Decode the string in data with the characters in map. Returns the decoded bytes
|
||||||
|
def decode(data,map):
|
||||||
|
result = ""
|
||||||
|
for i in range (0,len(data)-1,2):
|
||||||
|
high = map.find(data[i])
|
||||||
|
low = map.find(data[i+1])
|
||||||
|
if (high == -1) or (low == -1) :
|
||||||
|
break
|
||||||
|
value = (((high * len(map)) ^ 0x80) & 0xFF) + low
|
||||||
|
result += pack("B",value)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Parse the Kindle.info file and return the records as a list of key-values
|
||||||
|
def parseKindleInfo(kInfoFile):
|
||||||
|
DB = {}
|
||||||
|
infoReader = openKindleInfo(kInfoFile)
|
||||||
|
infoReader.read(1)
|
||||||
|
data = infoReader.read()
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
items = data.split('{')
|
||||||
|
else :
|
||||||
|
items = data.split('[')
|
||||||
|
for item in items:
|
||||||
|
splito = item.split(':')
|
||||||
|
DB[splito[0]] =splito[1]
|
||||||
|
return DB
|
||||||
|
|
||||||
|
# Get a record from the Kindle.info file for the key "hashedKey" (already hashed and encoded). Return the decoded and decrypted record
|
||||||
|
def getKindleInfoValueForHash(hashedKey):
|
||||||
|
global kindleDatabase
|
||||||
|
encryptedValue = decode(kindleDatabase[hashedKey],charMap2)
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
return CryptUnprotectData(encryptedValue,"")
|
||||||
|
else:
|
||||||
|
cleartext = CryptUnprotectData(encryptedValue)
|
||||||
|
return decode(cleartext, charMap1)
|
||||||
|
|
||||||
|
# Get a record from the Kindle.info file for the string in "key" (plaintext). Return the decoded and decrypted record
|
||||||
|
def getKindleInfoValueForKey(key):
|
||||||
|
return getKindleInfoValueForHash(encodeHash(key,charMap2))
|
||||||
|
|
||||||
|
# Find if the original string for a hashed/encoded string is known. If so return the original string othwise return an empty string.
|
||||||
|
def findNameForHash(hash):
|
||||||
|
names = ["kindle.account.tokens","kindle.cookie.item","eulaVersionAccepted","login_date","kindle.token.item","login","kindle.key.item","kindle.name.info","kindle.device.info", "MazamaRandomNumber"]
|
||||||
|
result = ""
|
||||||
|
for name in names:
|
||||||
|
if hash == encodeHash(name, charMap2):
|
||||||
|
result = name
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Print all the records from the kindle.info file (option -i)
|
||||||
|
def printKindleInfo():
|
||||||
|
for record in kindleDatabase:
|
||||||
|
name = findNameForHash(record)
|
||||||
|
if name != "" :
|
||||||
|
print (name)
|
||||||
|
print ("--------------------------")
|
||||||
|
else :
|
||||||
|
print ("Unknown Record")
|
||||||
|
print getKindleInfoValueForHash(record)
|
||||||
|
print "\n"
|
||||||
|
|
||||||
|
#
|
||||||
|
# PID generation routines
|
||||||
|
#
|
||||||
|
|
||||||
|
# Returns two bit at offset from a bit field
|
||||||
|
def getTwoBitsFromBitField(bitField,offset):
|
||||||
|
byteNumber = offset // 4
|
||||||
|
bitPosition = 6 - 2*(offset % 4)
|
||||||
|
return ord(bitField[byteNumber]) >> bitPosition & 3
|
||||||
|
|
||||||
|
# Returns the six bits at offset from a bit field
|
||||||
|
def getSixBitsFromBitField(bitField,offset):
|
||||||
|
offset *= 3
|
||||||
|
value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
|
||||||
|
return value
|
||||||
|
|
||||||
|
# 8 bits to six bits encoding from hash to generate PID string
|
||||||
|
def encodePID(hash):
|
||||||
|
global charMap3
|
||||||
|
PID = ""
|
||||||
|
for position in range (0,8):
|
||||||
|
PID += charMap3[getSixBitsFromBitField(hash,position)]
|
||||||
|
return PID
|
||||||
|
|
||||||
|
# Encryption table used to generate the device PID
|
||||||
|
def generatePidEncryptionTable() :
|
||||||
|
table = []
|
||||||
|
for counter1 in range (0,0x100):
|
||||||
|
value = counter1
|
||||||
|
for counter2 in range (0,8):
|
||||||
|
if (value & 1 == 0) :
|
||||||
|
value = value >> 1
|
||||||
|
else :
|
||||||
|
value = value >> 1
|
||||||
|
value = value ^ 0xEDB88320
|
||||||
|
table.append(value)
|
||||||
|
return table
|
||||||
|
|
||||||
|
# Seed value used to generate the device PID
|
||||||
|
def generatePidSeed(table,dsn) :
|
||||||
|
value = 0
|
||||||
|
for counter in range (0,4) :
|
||||||
|
index = (ord(dsn[counter]) ^ value) &0xFF
|
||||||
|
value = (value >> 8) ^ table[index]
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Generate the device PID
|
||||||
|
def generateDevicePID(table,dsn,nbRoll):
|
||||||
|
seed = generatePidSeed(table,dsn)
|
||||||
|
pidAscii = ""
|
||||||
|
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
|
||||||
|
index = 0
|
||||||
|
for counter in range (0,nbRoll):
|
||||||
|
pid[index] = pid[index] ^ ord(dsn[counter])
|
||||||
|
index = (index+1) %8
|
||||||
|
for counter in range (0,8):
|
||||||
|
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
||||||
|
pidAscii += charMap4[index]
|
||||||
|
return pidAscii
|
||||||
|
|
||||||
|
# convert from 8 digit PID to 10 digit PID with checksum
|
||||||
|
def checksumPid(s):
|
||||||
|
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class MobiPeek:
|
||||||
|
def loadSection(self, section):
|
||||||
|
before, after = self.sections[section:section+2]
|
||||||
|
self.f.seek(before)
|
||||||
|
return self.f.read(after - before)
|
||||||
|
def __init__(self, filename):
|
||||||
|
self.f = file(filename, 'rb')
|
||||||
|
self.header = self.f.read(78)
|
||||||
|
self.ident = self.header[0x3C:0x3C+8]
|
||||||
|
if self.ident != 'BOOKMOBI' and self.ident != 'TEXtREAd':
|
||||||
|
raise DrmException('invalid file format')
|
||||||
|
self.num_sections, = unpack_from('>H', self.header, 76)
|
||||||
|
sections = self.f.read(self.num_sections*8)
|
||||||
|
self.sections = unpack_from('>%dL' % (self.num_sections*2), sections, 0)[::2] + (0xfffffff, )
|
||||||
|
self.sect0 = self.loadSection(0)
|
||||||
|
self.f.close()
|
||||||
|
def getBookTitle(self):
|
||||||
|
# get book title
|
||||||
|
toff, tlen = unpack('>II', self.sect0[0x54:0x5c])
|
||||||
|
tend = toff + tlen
|
||||||
|
title = self.sect0[toff:tend]
|
||||||
|
return title
|
||||||
|
def getexthData(self):
|
||||||
|
# if exth region exists then grab it
|
||||||
|
# get length of this header
|
||||||
|
length, type, codepage, unique_id, version = unpack('>LLLLL', self.sect0[20:40])
|
||||||
|
exth_flag, = unpack('>L', self.sect0[0x80:0x84])
|
||||||
|
exth = ''
|
||||||
|
if exth_flag & 0x40:
|
||||||
|
exth = self.sect0[16 + length:]
|
||||||
|
return exth
|
||||||
|
def isNotEncrypted(self):
|
||||||
|
lock_type, = unpack('>H', self.sect0[0xC:0xC+2])
|
||||||
|
if lock_type == 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# DiapDealer's stuff: Parse the EXTH header records and parse the Kindleinfo
|
||||||
|
# file to calculate the book pid.
|
||||||
|
def getK4Pids(exth, title, kInfoFile=None):
|
||||||
|
global kindleDatabase
|
||||||
|
try:
|
||||||
|
kindleDatabase = parseKindleInfo(kInfoFile)
|
||||||
|
except Exception, message:
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
if kindleDatabase != None :
|
||||||
|
# Get the Mazama Random number
|
||||||
|
MazamaRandomNumber = getKindleInfoValueForKey("MazamaRandomNumber")
|
||||||
|
|
||||||
|
# Get the HDD serial
|
||||||
|
encodedSystemVolumeSerialNumber = encodeHash(GetVolumeSerialNumber(),charMap1)
|
||||||
|
|
||||||
|
# Get the current user name
|
||||||
|
encodedUsername = encodeHash(GetUserName(),charMap1)
|
||||||
|
|
||||||
|
# concat, hash and encode to calculate the DSN
|
||||||
|
DSN = encode(SHA1(MazamaRandomNumber+encodedSystemVolumeSerialNumber+encodedUsername),charMap1)
|
||||||
|
|
||||||
|
print("\nDSN: " + DSN)
|
||||||
|
|
||||||
|
# Compute the device PID (for which I can tell, is used for nothing).
|
||||||
|
# But hey, stuff being printed out is apparently cool.
|
||||||
|
table = generatePidEncryptionTable()
|
||||||
|
devicePID = generateDevicePID(table,DSN,4)
|
||||||
|
|
||||||
|
print("Device PID: " + checksumPid(devicePID))
|
||||||
|
|
||||||
|
# Compute book PID
|
||||||
|
exth_records = {}
|
||||||
|
nitems, = unpack('>I', exth[8:12])
|
||||||
|
pos = 12
|
||||||
|
|
||||||
|
exth_records[209] = None
|
||||||
|
# Parse the exth records, storing data indexed by type
|
||||||
|
for i in xrange(nitems):
|
||||||
|
type, size = unpack('>II', exth[pos: pos + 8])
|
||||||
|
content = exth[pos + 8: pos + size]
|
||||||
|
|
||||||
|
exth_records[type] = content
|
||||||
|
pos += size
|
||||||
|
|
||||||
|
# Grab the contents of the type 209 exth record
|
||||||
|
if exth_records[209] != None:
|
||||||
|
data = exth_records[209]
|
||||||
|
else:
|
||||||
|
raise DrmException("\nNo EXTH record type 209 - Perhaps not a K4 file?")
|
||||||
|
|
||||||
|
# Parse the 209 data to find the the exth record with the token data.
|
||||||
|
# The last character of the 209 data points to the record with the token.
|
||||||
|
# Always 208 from my experience, but I'll leave the logic in case that changes.
|
||||||
|
for i in xrange(len(data)):
|
||||||
|
if ord(data[i]) != 0:
|
||||||
|
if exth_records[ord(data[i])] != None:
|
||||||
|
token = exth_records[ord(data[i])]
|
||||||
|
|
||||||
|
# Get the kindle account token
|
||||||
|
kindleAccountToken = getKindleInfoValueForKey("kindle.account.tokens")
|
||||||
|
|
||||||
|
print("Account Token: " + kindleAccountToken)
|
||||||
|
|
||||||
|
pidHash = SHA1(DSN+kindleAccountToken+exth_records[209]+token)
|
||||||
|
|
||||||
|
bookPID = encodePID(pidHash)
|
||||||
|
bookPID = checksumPid(bookPID)
|
||||||
|
|
||||||
|
if exth_records[503] != None:
|
||||||
|
print "Pid for " + exth_records[503] + ": " + bookPID
|
||||||
|
else:
|
||||||
|
print "Pid for " + title + ":" + bookPID
|
||||||
|
return bookPID
|
||||||
|
|
||||||
|
raise DrmException("\nCould not access K4 data - Perhaps K4 is not installed/configured?")
|
||||||
|
return null
|
||||||
|
|
||||||
|
def usage(progname):
|
||||||
|
print "Removes DRM protection from K4PC, K4M, and Mobi ebooks"
|
||||||
|
print "Usage:"
|
||||||
|
print " %s [-k <kindle.info>] [-p <pidnums>] <infile> <outfile> " % progname
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main
|
||||||
|
#
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
global kindleDatabase
|
||||||
|
import mobidedrm
|
||||||
|
|
||||||
|
progname = os.path.basename(argv[0])
|
||||||
|
kInfoFiles = []
|
||||||
|
pidnums = ""
|
||||||
|
|
||||||
|
print ('K4MobiDeDrm v%(__version__)s '
|
||||||
|
'provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc .' % globals())
|
||||||
|
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], "k:p:")
|
||||||
|
except getopt.GetoptError, err:
|
||||||
|
print str(err)
|
||||||
|
usage(progname)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
if len(args)<2:
|
||||||
|
usage(progname)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
for o, a in opts:
|
||||||
|
if o == "-k":
|
||||||
|
if a == None :
|
||||||
|
raise DrmException("Invalid parameter for -k")
|
||||||
|
kInfoFiles.append(a)
|
||||||
|
if o == "-p":
|
||||||
|
if a == None :
|
||||||
|
raise DrmException("Invalid parameter for -p")
|
||||||
|
pidnums = a
|
||||||
|
|
||||||
|
kindleDatabase = None
|
||||||
|
infile = args[0]
|
||||||
|
outfile = args[1]
|
||||||
|
DecodeErrorString = ""
|
||||||
|
try:
|
||||||
|
# first try with K4PC/K4M
|
||||||
|
ex = MobiPeek(infile)
|
||||||
|
if ex.isNotEncrypted():
|
||||||
|
print "File was Not Encrypted"
|
||||||
|
return 2
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||||
|
except DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying default K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file(outfile, 'wb').write(unlocked_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# now try alternate kindle.info files
|
||||||
|
if kInfoFiles:
|
||||||
|
for infoFile in kInfoFiles:
|
||||||
|
kindleDatabase = None
|
||||||
|
try:
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title, infoFile)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||||
|
except DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException, e:
|
||||||
|
DecodeErrorString += "Error trying " + infoFile + " K4 info: " + str(e) + "\n"
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file(outfile, 'wb').write(unlocked_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Lastly, try from the pid list
|
||||||
|
pids = pidnums.split(',')
|
||||||
|
for pid in pids:
|
||||||
|
try:
|
||||||
|
print 'Trying: "'+ pid + '"'
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(infile, pid)
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file(outfile, 'wb').write(unlocked_file)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# we could not unencrypt book
|
||||||
|
print DecodeErrorString
|
||||||
|
print "Error: Could Not Unencrypt Book"
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
|
sys.exit(main())
|
||||||
|
|
||||||
|
|
||||||
|
if not __name__ == "__main__" and inCalibre:
|
||||||
|
from calibre.customize import FileTypePlugin
|
||||||
|
|
||||||
|
class K4DeDRM(FileTypePlugin):
|
||||||
|
name = 'K4PC, K4Mac, Mobi DeDRM' # Name of the plugin
|
||||||
|
description = 'Removes DRM from K4PC, K4Mac, and Mobi files. \
|
||||||
|
Provided by the work of many including DiapDealer, SomeUpdates, IHeartCabbages, CMBDTC, Skindle, DarkReverser, ApprenticeAlf, etc.'
|
||||||
|
supported_platforms = ['osx', 'windows', 'linux'] # Platforms this plugin will run on
|
||||||
|
author = 'DiapDealer, SomeUpdates' # The author of this plugin
|
||||||
|
version = (0, 1, 2) # The version number of this plugin
|
||||||
|
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
|
||||||
|
on_import = True # Run this plugin during the import
|
||||||
|
priority = 200 # run this plugin before mobidedrm, k4pcdedrm, k4dedrm
|
||||||
|
|
||||||
|
def run(self, path_to_ebook):
|
||||||
|
from calibre.gui2 import is_ok_to_use_qt
|
||||||
|
from PyQt4.Qt import QMessageBox
|
||||||
|
global kindleDatabase
|
||||||
|
global openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
from k4pcutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
if sys.platform.startswith('darwin'):
|
||||||
|
from k4mutils import openKindleInfo, CryptUnprotectData, GetUserName, GetVolumeSerialNumber, charMap1, charMap2, charMap3, charMap4
|
||||||
|
import mobidedrm
|
||||||
|
|
||||||
|
# Get supplied list of PIDs to try from plugin customization.
|
||||||
|
pidnums = self.site_customization
|
||||||
|
|
||||||
|
# Load any kindle info files (*.info) included Calibre's config directory.
|
||||||
|
kInfoFiles = []
|
||||||
|
try:
|
||||||
|
# Find Calibre's configuration directory.
|
||||||
|
confpath = os.path.split(os.path.split(self.plugin_path)[0])[0]
|
||||||
|
print 'K4MobiDeDRM: Calibre configuration directory = %s' % confpath
|
||||||
|
files = os.listdir(confpath)
|
||||||
|
filefilter = re.compile("\.info$", re.IGNORECASE)
|
||||||
|
files = filter(filefilter.search, files)
|
||||||
|
|
||||||
|
if files:
|
||||||
|
for filename in files:
|
||||||
|
fpath = os.path.join(confpath, filename)
|
||||||
|
kInfoFiles.append(fpath)
|
||||||
|
print 'K4MobiDeDRM: Kindle info file %s found in config folder.' % filename
|
||||||
|
except IOError:
|
||||||
|
print 'K4MobiDeDRM: Error reading kindle info files from config directory.'
|
||||||
|
pass
|
||||||
|
|
||||||
|
# first try with book specifc pid from K4PC or K4M
|
||||||
|
try:
|
||||||
|
kindleDatabase = None
|
||||||
|
ex = MobiPeek(path_to_ebook)
|
||||||
|
if ex.isNotEncrypted():
|
||||||
|
return path_to_ebook
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
|
||||||
|
except DrmException:
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
of = self.temporary_file('.mobi')
|
||||||
|
of.write(unlocked_file)
|
||||||
|
of.close()
|
||||||
|
return of.name
|
||||||
|
|
||||||
|
# Now try alternate kindle info files
|
||||||
|
if kInfoFiles:
|
||||||
|
for infoFile in kInfoFiles:
|
||||||
|
kindleDatabase = None
|
||||||
|
try:
|
||||||
|
title = ex.getBookTitle()
|
||||||
|
exth = ex.getexthData()
|
||||||
|
if exth=='':
|
||||||
|
raise DrmException("Not a Kindle Mobipocket file")
|
||||||
|
pid = getK4Pids(exth, title, infoFile)
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook,pid)
|
||||||
|
except DrmException:
|
||||||
|
pass
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
of = self.temporary_file('.mobi')
|
||||||
|
of.write(unlocked_file)
|
||||||
|
of.close()
|
||||||
|
return of.name
|
||||||
|
|
||||||
|
# now try from the pid list
|
||||||
|
pids = pidnums.split(',')
|
||||||
|
for pid in pids:
|
||||||
|
try:
|
||||||
|
unlocked_file = mobidedrm.getUnencryptedBook(path_to_ebook, pid)
|
||||||
|
except mobidedrm.DrmException:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
of = self.temporary_file('.mobi')
|
||||||
|
of.write(unlocked_file)
|
||||||
|
of.close()
|
||||||
|
return of.name
|
||||||
|
|
||||||
|
#if you reached here then no luck raise and exception
|
||||||
|
if is_ok_to_use_qt():
|
||||||
|
d = QMessageBox(QMessageBox.Warning, "K4MobiDeDRM Plugin", "Error decoding: %s\n" % path_to_ebook)
|
||||||
|
d.show()
|
||||||
|
d.raise_()
|
||||||
|
d.exec_()
|
||||||
|
raise Exception("K4MobiDeDRM plugin could not decode the file")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def customization_help(self, gui=False):
|
||||||
|
return 'Enter each 10 character PID separated by a comma (no spaces).'
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
# standlone set of Mac OSX specific routines needed for K4DeDRM
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
#Exception Handling
|
||||||
|
class K4MDrmException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
# interface to needed routines in openssl's libcrypto
|
||||||
|
def _load_crypto_libcrypto():
|
||||||
|
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
||||||
|
Structure, c_ulong, create_string_buffer, addressof, string_at, cast
|
||||||
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
if libcrypto is None:
|
||||||
|
raise K4MDrmException('libcrypto not found')
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
|
||||||
|
AES_MAXNR = 14
|
||||||
|
c_char_pp = POINTER(c_char_p)
|
||||||
|
c_int_p = POINTER(c_int)
|
||||||
|
|
||||||
|
class AES_KEY(Structure):
|
||||||
|
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
|
||||||
|
AES_KEY_p = POINTER(AES_KEY)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
|
||||||
|
|
||||||
|
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
|
||||||
|
|
||||||
|
PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
|
||||||
|
[c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
||||||
|
|
||||||
|
class LibCrypto(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._blocksize = 0
|
||||||
|
self._keyctx = None
|
||||||
|
self.iv = 0
|
||||||
|
|
||||||
|
def set_decrypt_key(self, userkey, iv):
|
||||||
|
self._blocksize = len(userkey)
|
||||||
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
|
raise K4MDrmException('AES improper key used')
|
||||||
|
return
|
||||||
|
keyctx = self._keyctx = AES_KEY()
|
||||||
|
self.iv = iv
|
||||||
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
|
||||||
|
if rv < 0:
|
||||||
|
raise K4MDrmException('Failed to initialize AES key')
|
||||||
|
|
||||||
|
def decrypt(self, data):
|
||||||
|
out = create_string_buffer(len(data))
|
||||||
|
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self.iv, 0)
|
||||||
|
if rv == 0:
|
||||||
|
raise K4MDrmException('AES decryption failed')
|
||||||
|
return out.raw
|
||||||
|
|
||||||
|
def keyivgen(self, passwd):
|
||||||
|
salt = '16743'
|
||||||
|
saltlen = 5
|
||||||
|
passlen = len(passwd)
|
||||||
|
iter = 0x3e8
|
||||||
|
keylen = 80
|
||||||
|
out = create_string_buffer(keylen)
|
||||||
|
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
|
||||||
|
return out.raw
|
||||||
|
return LibCrypto
|
||||||
|
|
||||||
|
def _load_crypto():
|
||||||
|
LibCrypto = None
|
||||||
|
try:
|
||||||
|
LibCrypto = _load_crypto_libcrypto()
|
||||||
|
except (ImportError, K4MDrmException):
|
||||||
|
pass
|
||||||
|
return LibCrypto
|
||||||
|
|
||||||
|
LibCrypto = _load_crypto()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Utility Routines
|
||||||
|
#
|
||||||
|
|
||||||
|
# uses a sub process to get the Hard Drive Serial Number using ioreg
|
||||||
|
# returns with the serial number of drive whose BSD Name is "disk0"
|
||||||
|
def GetVolumeSerialNumber():
|
||||||
|
sernum = os.getenv('MYSERIALNUMBER')
|
||||||
|
if sernum != None:
|
||||||
|
return sernum
|
||||||
|
cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
|
||||||
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
|
p = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||||
|
poll = p.wait('wait')
|
||||||
|
results = p.read()
|
||||||
|
reslst = results.split('\n')
|
||||||
|
cnt = len(reslst)
|
||||||
|
bsdname = None
|
||||||
|
sernum = None
|
||||||
|
foundIt = False
|
||||||
|
for j in xrange(cnt):
|
||||||
|
resline = reslst[j]
|
||||||
|
pp = resline.find('"Serial Number" = "')
|
||||||
|
if pp >= 0:
|
||||||
|
sernum = resline[pp+19:-1]
|
||||||
|
sernum = sernum.strip()
|
||||||
|
bb = resline.find('"BSD Name" = "')
|
||||||
|
if bb >= 0:
|
||||||
|
bsdname = resline[bb+14:-1]
|
||||||
|
bsdname = bsdname.strip()
|
||||||
|
if (bsdname == 'disk0') and (sernum != None):
|
||||||
|
foundIt = True
|
||||||
|
break
|
||||||
|
if not foundIt:
|
||||||
|
sernum = '9999999999'
|
||||||
|
return sernum
|
||||||
|
|
||||||
|
# uses unix env to get username instead of using sysctlbyname
|
||||||
|
def GetUserName():
|
||||||
|
username = os.getenv('USER')
|
||||||
|
return username
|
||||||
|
|
||||||
|
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||||||
|
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||||
|
charMap2 = "ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM"
|
||||||
|
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||||
|
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
|
||||||
|
def encode(data, map):
|
||||||
|
result = ""
|
||||||
|
for char in data:
|
||||||
|
value = ord(char)
|
||||||
|
Q = (value ^ 0x80) // len(map)
|
||||||
|
R = value % len(map)
|
||||||
|
result += map[Q]
|
||||||
|
result += map[R]
|
||||||
|
return result
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
def SHA256(message):
|
||||||
|
ctx = hashlib.sha256()
|
||||||
|
ctx.update(message)
|
||||||
|
return ctx.digest()
|
||||||
|
|
||||||
|
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||||
|
def CryptUnprotectData(encryptedData):
|
||||||
|
sp = GetVolumeSerialNumber() + '!@#' + GetUserName()
|
||||||
|
passwdData = encode(SHA256(sp),charMap1)
|
||||||
|
crp = LibCrypto()
|
||||||
|
key_iv = crp.keyivgen(passwdData)
|
||||||
|
key = key_iv[0:32]
|
||||||
|
iv = key_iv[32:48]
|
||||||
|
crp.set_decrypt_key(key,iv)
|
||||||
|
cleartext = crp.decrypt(encryptedData)
|
||||||
|
return cleartext
|
||||||
|
|
||||||
|
# Locate and open the .kindle-info file
|
||||||
|
def openKindleInfo(kInfoFile=None):
|
||||||
|
if kInfoFile == None:
|
||||||
|
home = os.getenv('HOME')
|
||||||
|
cmdline = 'find "' + home + '/Library/Application Support" -name ".kindle-info"'
|
||||||
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
|
p1 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
|
||||||
|
poll = p1.wait('wait')
|
||||||
|
results = p1.read()
|
||||||
|
reslst = results.split('\n')
|
||||||
|
kinfopath = 'NONE'
|
||||||
|
cnt = len(reslst)
|
||||||
|
for j in xrange(cnt):
|
||||||
|
resline = reslst[j]
|
||||||
|
pp = resline.find('.kindle-info')
|
||||||
|
if pp >= 0:
|
||||||
|
kinfopath = resline
|
||||||
|
break
|
||||||
|
if not os.path.exists(kinfopath):
|
||||||
|
raise K4MDrmException('Error: .kindle-info file can not be found')
|
||||||
|
return open(kinfopath,'r')
|
||||||
|
else:
|
||||||
|
return open(kInfoFile, 'r')
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# K4PC Windows specific routines
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
||||||
|
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
||||||
|
string_at, Structure, c_void_p, cast
|
||||||
|
|
||||||
|
import _winreg as winreg
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
MAX_PATH = 255
|
||||||
|
|
||||||
|
kernel32 = windll.kernel32
|
||||||
|
advapi32 = windll.advapi32
|
||||||
|
crypt32 = windll.crypt32
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||||||
|
#
|
||||||
|
charMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||||
|
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
||||||
|
charMap3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||||
|
charMap4 = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Exceptions for all the problems that might happen during the script
|
||||||
|
#
|
||||||
|
class DrmException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DataBlob(Structure):
|
||||||
|
_fields_ = [('cbData', c_uint),
|
||||||
|
('pbData', c_void_p)]
|
||||||
|
DataBlob_p = POINTER(DataBlob)
|
||||||
|
|
||||||
|
|
||||||
|
def GetSystemDirectory():
|
||||||
|
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
|
||||||
|
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
|
||||||
|
GetSystemDirectoryW.restype = c_uint
|
||||||
|
def GetSystemDirectory():
|
||||||
|
buffer = create_unicode_buffer(MAX_PATH + 1)
|
||||||
|
GetSystemDirectoryW(buffer, len(buffer))
|
||||||
|
return buffer.value
|
||||||
|
return GetSystemDirectory
|
||||||
|
GetSystemDirectory = GetSystemDirectory()
|
||||||
|
|
||||||
|
def GetVolumeSerialNumber():
|
||||||
|
GetVolumeInformationW = kernel32.GetVolumeInformationW
|
||||||
|
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
|
||||||
|
POINTER(c_uint), POINTER(c_uint),
|
||||||
|
POINTER(c_uint), c_wchar_p, c_uint]
|
||||||
|
GetVolumeInformationW.restype = c_uint
|
||||||
|
def GetVolumeSerialNumber(path = GetSystemDirectory().split('\\')[0] + '\\'):
|
||||||
|
vsn = c_uint(0)
|
||||||
|
GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
|
||||||
|
return str(vsn.value)
|
||||||
|
return GetVolumeSerialNumber
|
||||||
|
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
||||||
|
|
||||||
|
|
||||||
|
def GetUserName():
|
||||||
|
GetUserNameW = advapi32.GetUserNameW
|
||||||
|
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
|
||||||
|
GetUserNameW.restype = c_uint
|
||||||
|
def GetUserName():
|
||||||
|
buffer = create_unicode_buffer(32)
|
||||||
|
size = c_uint(len(buffer))
|
||||||
|
while not GetUserNameW(buffer, byref(size)):
|
||||||
|
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||||
|
size.value = len(buffer)
|
||||||
|
return buffer.value.encode('utf-16-le')[::2]
|
||||||
|
return GetUserName
|
||||||
|
GetUserName = GetUserName()
|
||||||
|
|
||||||
|
|
||||||
|
def CryptUnprotectData():
|
||||||
|
_CryptUnprotectData = crypt32.CryptUnprotectData
|
||||||
|
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
|
||||||
|
c_void_p, c_void_p, c_uint, DataBlob_p]
|
||||||
|
_CryptUnprotectData.restype = c_uint
|
||||||
|
def CryptUnprotectData(indata, entropy):
|
||||||
|
indatab = create_string_buffer(indata)
|
||||||
|
indata = DataBlob(len(indata), cast(indatab, c_void_p))
|
||||||
|
entropyb = create_string_buffer(entropy)
|
||||||
|
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
|
||||||
|
outdata = DataBlob()
|
||||||
|
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
|
||||||
|
None, None, 0, byref(outdata)):
|
||||||
|
raise DrmException("Failed to Unprotect Data")
|
||||||
|
return string_at(outdata.pbData, outdata.cbData)
|
||||||
|
return CryptUnprotectData
|
||||||
|
CryptUnprotectData = CryptUnprotectData()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Locate and open the Kindle.info file.
|
||||||
|
#
|
||||||
|
def openKindleInfo(kInfoFile=None):
|
||||||
|
if kInfoFile == None:
|
||||||
|
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
|
||||||
|
path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
|
||||||
|
return open(path+'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info','r')
|
||||||
|
else:
|
||||||
|
return open(kInfoFile, 'r')
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# This is a python script. You need a Python interpreter to run it.
|
||||||
|
# For example, ActiveState Python, which exists for windows.
|
||||||
|
#
|
||||||
|
# Changelog
|
||||||
|
# 0.01 - Initial version
|
||||||
|
# 0.02 - Huffdic compressed books were not properly decrypted
|
||||||
|
# 0.03 - Wasn't checking MOBI header length
|
||||||
|
# 0.04 - Wasn't sanity checking size of data record
|
||||||
|
# 0.05 - It seems that the extra data flags take two bytes not four
|
||||||
|
# 0.06 - And that low bit does mean something after all :-)
|
||||||
|
# 0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size
|
||||||
|
# 0.08 - ...and also not in Mobi header version < 6
|
||||||
|
# 0.09 - ...but they are there with Mobi header version 6, header size 0xE4!
|
||||||
|
# 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre
|
||||||
|
# import filter it works when importing unencrypted files.
|
||||||
|
# Also now handles encrypted files that don't need a specific PID.
|
||||||
|
# 0.11 - use autoflushed stdout and proper return values
|
||||||
|
# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors
|
||||||
|
# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace
|
||||||
|
# and extra blank lines, converted CR/LF pairs at ends of each line,
|
||||||
|
# and other cosmetic fixes.
|
||||||
|
# 0.14 - Working out when the extra data flags are present has been problematic
|
||||||
|
# Versions 7 through 9 have tried to tweak the conditions, but have been
|
||||||
|
# only partially successful. Closer examination of lots of sample
|
||||||
|
# files reveals that a confusin has arisen because trailing data entries
|
||||||
|
# are not encrypted, but it turns out that the multibyte entries
|
||||||
|
# in utf8 file are encrypted. (Although neither kind gets compressed.)
|
||||||
|
# This knowledge leads to a simplification of the test for the
|
||||||
|
# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
|
||||||
|
# 0.15 - Now outputs 'heartbeat', and is also quicker for long files.
|
||||||
|
# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
|
||||||
|
# 0.17 - added modifications to support its use as an imported python module
|
||||||
|
# both inside calibre and also in other places (ie K4DeDRM tools)
|
||||||
|
# 0.17a- disabled the standalone plugin feature since a plugin can not import
|
||||||
|
# a plugin
|
||||||
|
# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
|
||||||
|
# Removed the disabled Calibre plug-in code
|
||||||
|
# Permit use of 8-digit PIDs
|
||||||
|
# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
|
||||||
|
|
||||||
|
__version__ = '0.19'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
import binascii
|
||||||
|
|
||||||
|
class Unbuffered:
|
||||||
|
def __init__(self, stream):
|
||||||
|
self.stream = stream
|
||||||
|
def write(self, data):
|
||||||
|
self.stream.write(data)
|
||||||
|
self.stream.flush()
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
|
class DrmException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Implementation of Pukall Cipher 1
|
||||||
|
def PC1(key, src, decryption=True):
|
||||||
|
sum1 = 0;
|
||||||
|
sum2 = 0;
|
||||||
|
keyXorVal = 0;
|
||||||
|
if len(key)!=16:
|
||||||
|
print "Bad key length!"
|
||||||
|
return None
|
||||||
|
wkey = []
|
||||||
|
for i in xrange(8):
|
||||||
|
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
||||||
|
|
||||||
|
dst = ""
|
||||||
|
for i in xrange(len(src)):
|
||||||
|
temp1 = 0;
|
||||||
|
byteXorVal = 0;
|
||||||
|
for j in xrange(8):
|
||||||
|
temp1 ^= wkey[j]
|
||||||
|
sum2 = (sum2+j)*20021 + sum1
|
||||||
|
sum1 = (temp1*346)&0xFFFF
|
||||||
|
sum2 = (sum2+sum1)&0xFFFF
|
||||||
|
temp1 = (temp1*20021+1)&0xFFFF
|
||||||
|
byteXorVal ^= temp1 ^ sum2
|
||||||
|
curByte = ord(src[i])
|
||||||
|
if not decryption:
|
||||||
|
keyXorVal = curByte * 257;
|
||||||
|
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
||||||
|
if decryption:
|
||||||
|
keyXorVal = curByte * 257;
|
||||||
|
for j in xrange(8):
|
||||||
|
wkey[j] ^= keyXorVal;
|
||||||
|
dst+=chr(curByte)
|
||||||
|
return dst
|
||||||
|
|
||||||
|
def checksumPid(s):
|
||||||
|
letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"
|
||||||
|
crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||||
|
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 getSizeOfTrailingDataEntries(ptr, size, flags):
|
||||||
|
def getSizeOfTrailingDataEntry(ptr, size):
|
||||||
|
bitpos, result = 0, 0
|
||||||
|
if size <= 0:
|
||||||
|
return result
|
||||||
|
while True:
|
||||||
|
v = ord(ptr[size-1])
|
||||||
|
result |= (v & 0x7F) << bitpos
|
||||||
|
bitpos += 7
|
||||||
|
size -= 1
|
||||||
|
if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
|
||||||
|
return result
|
||||||
|
num = 0
|
||||||
|
testflags = flags >> 1
|
||||||
|
while testflags:
|
||||||
|
if testflags & 1:
|
||||||
|
num += getSizeOfTrailingDataEntry(ptr, size - num)
|
||||||
|
testflags >>= 1
|
||||||
|
# Check the low bit to see if there's multibyte data present.
|
||||||
|
# if multibyte data is included in the encryped data, we'll
|
||||||
|
# have already cleared this flag.
|
||||||
|
if flags & 1:
|
||||||
|
num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
||||||
|
return num
|
||||||
|
|
||||||
|
class DrmStripper:
|
||||||
|
def loadSection(self, section):
|
||||||
|
if (section + 1 == self.num_sections):
|
||||||
|
endoff = len(self.data_file)
|
||||||
|
else:
|
||||||
|
endoff = self.sections[section + 1][0]
|
||||||
|
off = self.sections[section][0]
|
||||||
|
return self.data_file[off:endoff]
|
||||||
|
|
||||||
|
def patch(self, off, new):
|
||||||
|
self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
|
||||||
|
|
||||||
|
def patchSection(self, section, new, in_off = 0):
|
||||||
|
if (section + 1 == self.num_sections):
|
||||||
|
endoff = len(self.data_file)
|
||||||
|
else:
|
||||||
|
endoff = self.sections[section + 1][0]
|
||||||
|
off = self.sections[section][0]
|
||||||
|
assert off + in_off + len(new) <= endoff
|
||||||
|
self.patch(off + in_off, new)
|
||||||
|
|
||||||
|
def parseDRM(self, data, count, pid):
|
||||||
|
pid = pid.ljust(16,'\0')
|
||||||
|
keyvec1 = "\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96"
|
||||||
|
temp_key = PC1(keyvec1, pid, False)
|
||||||
|
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
||||||
|
found_key = None
|
||||||
|
for i in xrange(count):
|
||||||
|
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
||||||
|
cookie = PC1(temp_key, cookie)
|
||||||
|
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
|
||||||
|
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
|
||||||
|
found_key = finalkey
|
||||||
|
break
|
||||||
|
if not found_key:
|
||||||
|
# Then try the default encoding that doesn't require a PID
|
||||||
|
temp_key = keyvec1
|
||||||
|
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
||||||
|
for i in xrange(count):
|
||||||
|
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
||||||
|
cookie = PC1(temp_key, cookie)
|
||||||
|
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
|
||||||
|
if verification == ver and cksum == temp_key_sum:
|
||||||
|
found_key = finalkey
|
||||||
|
break
|
||||||
|
return found_key
|
||||||
|
|
||||||
|
def __init__(self, data_file, pid):
|
||||||
|
if len(pid)==10:
|
||||||
|
if checksumPid(pid[0:-2]) != pid:
|
||||||
|
raise DrmException("invalid PID checksum")
|
||||||
|
pid = pid[0:-2]
|
||||||
|
elif len(pid)==8:
|
||||||
|
print "PID without checksum given. With checksum PID is "+checksumPid(pid)
|
||||||
|
else:
|
||||||
|
raise DrmException("Invalid PID length")
|
||||||
|
|
||||||
|
self.data_file = data_file
|
||||||
|
header = data_file[0:72]
|
||||||
|
if header[0x3C:0x3C+8] != 'BOOKMOBI':
|
||||||
|
raise DrmException("invalid file format")
|
||||||
|
self.num_sections, = struct.unpack('>H', data_file[76:78])
|
||||||
|
|
||||||
|
self.sections = []
|
||||||
|
for i in xrange(self.num_sections):
|
||||||
|
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', data_file[78+i*8:78+i*8+8])
|
||||||
|
flags, val = a1, a2<<16|a3<<8|a4
|
||||||
|
self.sections.append( (offset, flags, val) )
|
||||||
|
|
||||||
|
sect = self.loadSection(0)
|
||||||
|
records, = struct.unpack('>H', sect[0x8:0x8+2])
|
||||||
|
mobi_length, = struct.unpack('>L',sect[0x14:0x18])
|
||||||
|
mobi_version, = struct.unpack('>L',sect[0x68:0x6C])
|
||||||
|
extra_data_flags = 0
|
||||||
|
print "MOBI header version = %d, length = %d" %(mobi_version, mobi_length)
|
||||||
|
if (mobi_length >= 0xE4) and (mobi_version >= 5):
|
||||||
|
extra_data_flags, = struct.unpack('>H', sect[0xF2:0xF4])
|
||||||
|
print "Extra Data Flags = %d" %extra_data_flags
|
||||||
|
if mobi_version <= 5:
|
||||||
|
# multibyte utf8 data is included in the encryption for mobi_version 5 and below
|
||||||
|
# so clear that byte so that we leave it to be decrypted.
|
||||||
|
extra_data_flags &= 0xFFFE
|
||||||
|
|
||||||
|
crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
|
||||||
|
if crypto_type == 0:
|
||||||
|
print "This book is not encrypted."
|
||||||
|
else:
|
||||||
|
if crypto_type == 1:
|
||||||
|
raise DrmException("cannot decode Mobipocket encryption type 1")
|
||||||
|
if crypto_type != 2:
|
||||||
|
raise DrmException("unknown encryption type: %d" % crypto_type)
|
||||||
|
|
||||||
|
# calculate the keys
|
||||||
|
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
|
||||||
|
if drm_count == 0:
|
||||||
|
raise DrmException("no PIDs found in this file")
|
||||||
|
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
|
||||||
|
if not found_key:
|
||||||
|
raise DrmException("no key found. maybe the PID is incorrect")
|
||||||
|
|
||||||
|
# kill the drm keys
|
||||||
|
self.patchSection(0, "\0" * drm_size, drm_ptr)
|
||||||
|
# kill the drm pointers
|
||||||
|
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
|
||||||
|
# clear the crypto type
|
||||||
|
self.patchSection(0, "\0" * 2, 0xC)
|
||||||
|
|
||||||
|
# decrypt sections
|
||||||
|
print "Decrypting. Please wait . . .",
|
||||||
|
new_data = self.data_file[:self.sections[1][0]]
|
||||||
|
for i in xrange(1, records+1):
|
||||||
|
data = self.loadSection(i)
|
||||||
|
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
|
||||||
|
if i%100 == 0:
|
||||||
|
print ".",
|
||||||
|
# print "record %d, extra_size %d" %(i,extra_size)
|
||||||
|
new_data += PC1(found_key, data[0:len(data) - extra_size])
|
||||||
|
if extra_size > 0:
|
||||||
|
new_data += data[-extra_size:]
|
||||||
|
#self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
|
||||||
|
if self.num_sections > records+1:
|
||||||
|
new_data += self.data_file[self.sections[records+1][0]:]
|
||||||
|
self.data_file = new_data
|
||||||
|
print "done"
|
||||||
|
|
||||||
|
def getResult(self):
|
||||||
|
return self.data_file
|
||||||
|
|
||||||
|
def getUnencryptedBook(infile,pid):
|
||||||
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
|
data_file = file(infile, 'rb').read()
|
||||||
|
strippedFile = DrmStripper(data_file, pid)
|
||||||
|
return strippedFile.getResult()
|
||||||
|
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
|
print ('MobiDeDrm v%(__version__)s. '
|
||||||
|
'Copyright 2008-2010 The Dark Reverser.' % globals())
|
||||||
|
if len(argv)<4:
|
||||||
|
print "Removes protection from Mobipocket books"
|
||||||
|
print "Usage:"
|
||||||
|
print " %s <infile> <outfile> <PID>" % sys.argv[0]
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
infile = argv[1]
|
||||||
|
outfile = argv[2]
|
||||||
|
pid = argv[3]
|
||||||
|
try:
|
||||||
|
stripped_file = getUnencryptedBook(infile, pid)
|
||||||
|
file(outfile, 'wb').write(stripped_file)
|
||||||
|
except DrmException, e:
|
||||||
|
print "Error: %s" % e
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
|
# implement just enough of des from openssl to make erdr2pml.py happy
|
||||||
|
|
||||||
|
def load_libcrypto():
|
||||||
|
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_char, c_int, c_long, \
|
||||||
|
Structure, c_ulong, create_string_buffer, cast
|
||||||
|
from ctypes.util import find_library
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
libcrypto = find_library('libeay32')
|
||||||
|
else:
|
||||||
|
libcrypto = find_library('crypto')
|
||||||
|
|
||||||
|
if libcrypto is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
|
||||||
|
# typedef struct DES_ks
|
||||||
|
# {
|
||||||
|
# union
|
||||||
|
# {
|
||||||
|
# DES_cblock cblock;
|
||||||
|
# /* make sure things are correct size on machines with
|
||||||
|
# * 8 byte longs */
|
||||||
|
# DES_LONG deslong[2];
|
||||||
|
# } ks[16];
|
||||||
|
# } DES_key_schedule;
|
||||||
|
|
||||||
|
# just create a big enough place to hold everything
|
||||||
|
# it will have alignment of structure so we should be okay (16 byte aligned?)
|
||||||
|
class DES_KEY_SCHEDULE(Structure):
|
||||||
|
_fields_ = [('DES_cblock1', c_char * 16),
|
||||||
|
('DES_cblock2', c_char * 16),
|
||||||
|
('DES_cblock3', c_char * 16),
|
||||||
|
('DES_cblock4', c_char * 16),
|
||||||
|
('DES_cblock5', c_char * 16),
|
||||||
|
('DES_cblock6', c_char * 16),
|
||||||
|
('DES_cblock7', c_char * 16),
|
||||||
|
('DES_cblock8', c_char * 16),
|
||||||
|
('DES_cblock9', c_char * 16),
|
||||||
|
('DES_cblock10', c_char * 16),
|
||||||
|
('DES_cblock11', c_char * 16),
|
||||||
|
('DES_cblock12', c_char * 16),
|
||||||
|
('DES_cblock13', c_char * 16),
|
||||||
|
('DES_cblock14', c_char * 16),
|
||||||
|
('DES_cblock15', c_char * 16),
|
||||||
|
('DES_cblock16', c_char * 16)]
|
||||||
|
|
||||||
|
DES_KEY_SCHEDULE_p = POINTER(DES_KEY_SCHEDULE)
|
||||||
|
|
||||||
|
def F(restype, name, argtypes):
|
||||||
|
func = getattr(libcrypto, name)
|
||||||
|
func.restype = restype
|
||||||
|
func.argtypes = argtypes
|
||||||
|
return func
|
||||||
|
|
||||||
|
DES_set_key = F(None, 'DES_set_key',[c_char_p, DES_KEY_SCHEDULE_p])
|
||||||
|
DES_ecb_encrypt = F(None, 'DES_ecb_encrypt',[c_char_p, c_char_p, DES_KEY_SCHEDULE_p, c_int])
|
||||||
|
|
||||||
|
|
||||||
|
class DES(object):
|
||||||
|
def __init__(self, key):
|
||||||
|
if len(key) != 8 :
|
||||||
|
raise Error('DES improper key used')
|
||||||
|
return
|
||||||
|
self.key = key
|
||||||
|
self.keyschedule = DES_KEY_SCHEDULE()
|
||||||
|
DES_set_key(self.key, self.keyschedule)
|
||||||
|
def desdecrypt(self, data):
|
||||||
|
ob = create_string_buffer(len(data))
|
||||||
|
DES_ecb_encrypt(data, ob, self.keyschedule, 0)
|
||||||
|
return ob.raw
|
||||||
|
def decrypt(self, data):
|
||||||
|
if not data:
|
||||||
|
return ''
|
||||||
|
i = 0
|
||||||
|
result = []
|
||||||
|
while i < len(data):
|
||||||
|
block = data[i:i+8]
|
||||||
|
processed_block = self.desdecrypt(block)
|
||||||
|
result.append(processed_block)
|
||||||
|
i += 8
|
||||||
|
return ''.join(result)
|
||||||
|
|
||||||
|
return DES
|
||||||
|
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
#!/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()
|
||||||
|
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import zlib
|
||||||
|
import zipfile
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import getopt
|
||||||
|
from struct import unpack
|
||||||
|
|
||||||
|
|
||||||
|
_FILENAME_LEN_OFFSET = 26
|
||||||
|
_EXTRA_LEN_OFFSET = 28
|
||||||
|
_FILENAME_OFFSET = 30
|
||||||
|
_MAX_SIZE = 64 * 1024
|
||||||
|
|
||||||
|
class fixZip:
|
||||||
|
def __init__(self, zinput, zoutput):
|
||||||
|
self.inzip = zipfile.ZipFile(zinput,'r')
|
||||||
|
self.outzip = zipfile.ZipFile(zoutput,'w')
|
||||||
|
# open the input zip for reading only as a raw file
|
||||||
|
self.bzf = file(zinput,'rb')
|
||||||
|
|
||||||
|
def getlocalname(self, zi):
|
||||||
|
local_header_offset = zi.header_offset
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
||||||
|
leninfo = self.bzf.read(2)
|
||||||
|
local_name_length, = unpack('<H', leninfo)
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_OFFSET)
|
||||||
|
local_name = self.bzf.read(local_name_length)
|
||||||
|
return local_name
|
||||||
|
|
||||||
|
def uncompress(self, cmpdata):
|
||||||
|
dc = zlib.decompressobj(-15)
|
||||||
|
data = ''
|
||||||
|
while len(cmpdata) > 0:
|
||||||
|
if len(cmpdata) > _MAX_SIZE :
|
||||||
|
newdata = cmpdata[0:_MAX_SIZE]
|
||||||
|
cmpdata = cmpdata[_MAX_SIZE:]
|
||||||
|
else:
|
||||||
|
newdata = cmpdata
|
||||||
|
cmpdata = ''
|
||||||
|
newdata = dc.decompress(newdata)
|
||||||
|
unprocessed = dc.unconsumed_tail
|
||||||
|
if len(unprocessed) == 0:
|
||||||
|
newdata += dc.flush()
|
||||||
|
data += newdata
|
||||||
|
cmpdata += unprocessed
|
||||||
|
unprocessed = ''
|
||||||
|
return data
|
||||||
|
|
||||||
|
def getfiledata(self, zi):
|
||||||
|
# get file name length and exta data length to find start of file data
|
||||||
|
local_header_offset = zi.header_offset
|
||||||
|
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
|
||||||
|
leninfo = self.bzf.read(2)
|
||||||
|
local_name_length, = unpack('<H', leninfo)
|
||||||
|
|
||||||
|
self.bzf.seek(local_header_offset + _EXTRA_LEN_OFFSET)
|
||||||
|
exinfo = self.bzf.read(2)
|
||||||
|
extra_field_length, = unpack('<H', exinfo)
|
||||||
|
|
||||||
|
self.bzf.seek(local_header_offset + _FILENAME_OFFSET + local_name_length + extra_field_length)
|
||||||
|
data = None
|
||||||
|
|
||||||
|
# if not compressed we are good to go
|
||||||
|
if zi.compress_type == zipfile.ZIP_STORED:
|
||||||
|
data = self.bzf.read(zi.file_size)
|
||||||
|
|
||||||
|
# if compressed we must decompress it using zlib
|
||||||
|
if zi.compress_type == zipfile.ZIP_DEFLATED:
|
||||||
|
cmpdata = self.bzf.read(zi.compress_size)
|
||||||
|
data = self.uncompress(cmpdata)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def fix(self):
|
||||||
|
# get the zipinfo for each member of the input archive
|
||||||
|
# and copy member over to output archive
|
||||||
|
# if problems exist with local vs central filename, fix them
|
||||||
|
|
||||||
|
for zinfo in self.inzip.infolist():
|
||||||
|
data = None
|
||||||
|
nzinfo = zinfo
|
||||||
|
try:
|
||||||
|
data = self.inzip.read(zinfo.filename)
|
||||||
|
except zipfile.BadZipfile or zipfile.error:
|
||||||
|
local_name = self.getlocalname(zinfo)
|
||||||
|
data = self.getfiledata(zinfo)
|
||||||
|
nzinfo.filename = local_name
|
||||||
|
|
||||||
|
nzinfo.date_time = zinfo.date_time
|
||||||
|
nzinfo.compress_type = zinfo.compress_type
|
||||||
|
nzinfo.flag_bits = 0
|
||||||
|
nzinfo.internal_attr = 0
|
||||||
|
self.outzip.writestr(nzinfo,data)
|
||||||
|
|
||||||
|
self.bzf.close()
|
||||||
|
self.inzip.close()
|
||||||
|
self.outzip.close()
|
||||||
|
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print """usage: zipfix.py inputzip outputzip
|
||||||
|
inputzip is the source zipfile to fix
|
||||||
|
outputzip is the fixed zip archive
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=sys.argv):
|
||||||
|
if len(argv)!=3:
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
infile = None
|
||||||
|
outfile = None
|
||||||
|
infile = argv[1]
|
||||||
|
outfile = argv[2]
|
||||||
|
if not os.path.exists(infile):
|
||||||
|
print "Error: Input Zip File does not exist"
|
||||||
|
return 1
|
||||||
|
try:
|
||||||
|
fr = fixZip(infile, outfile)
|
||||||
|
fr.fix()
|
||||||
|
return 0
|
||||||
|
except Exception, e:
|
||||||
|
print "Error Occurred ", e
|
||||||
|
return 2
|
||||||
|
|
||||||
|
if __name__ == '__main__' :
|
||||||
|
sys.exit(main())
|
||||||
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user