mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-24 14:38:56 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
464788a3f1 | ||
|
|
218539f131 | ||
|
|
cdab22e59c |
33
.github/ISSUE_TEMPLATE/QUESTION.md
vendored
33
.github/ISSUE_TEMPLATE/QUESTION.md
vendored
@@ -1,33 +0,0 @@
|
|||||||
---
|
|
||||||
name: Question
|
|
||||||
about: Questions for DeDRM Project
|
|
||||||
title: "[QUESTION] Title"
|
|
||||||
labels: Question
|
|
||||||
---
|
|
||||||
|
|
||||||
## CheckList
|
|
||||||
<!-- Check with `[x]` -->
|
|
||||||
- [ ] `The Title` and The `Log Title` are setted correctly.
|
|
||||||
- [ ] Clarified about `my environment`.
|
|
||||||
- [ ] Code block is used for `the log`.
|
|
||||||
<!-- If you don't know the version, please specify 'Unknown'. -->
|
|
||||||
<!-- In case of markdown To use the code block, enclose it in ```. -->
|
|
||||||
<!-- If you don't need Log, please delete the log section. -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Title
|
|
||||||
<!-- content -->
|
|
||||||
|
|
||||||
## My Environment
|
|
||||||
### Calibre: `Version`
|
|
||||||
### Kindle: `Version`
|
|
||||||
### DeDRM: `Version`
|
|
||||||
|
|
||||||
## Log
|
|
||||||
<details><summary>Log Title</summary>
|
|
||||||
|
|
||||||
```log
|
|
||||||
PUT YOUR LOG
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
2
.github/workflows/Python_tests.yml
vendored
2
.github/workflows/Python_tests.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest]
|
os: [macos-latest]
|
||||||
python-version: [2.7, 3.8]
|
python-version: [2.7] # , 3.8]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ platforms.
|
|||||||
|
|
||||||
#### Enter your keys
|
#### Enter your keys
|
||||||
- Figure out what format DeDRM wants your key in by looking in
|
- Figure out what format DeDRM wants your key in by looking in
|
||||||
[the code that handles that](DeDRM_plugin/prefs.py).
|
[the code that handles that](dedrm_src/prefs.py).
|
||||||
- For Kindle eInk devices, DeDRM expects you to put a list of serial
|
- For Kindle eInk devices, DeDRM expects you to put a list of serial
|
||||||
numbers in the `serials` field: `"serials": ["012345689abcdef"]` or
|
numbers in the `serials` field: `"serials": ["012345689abcdef"]` or
|
||||||
`"serials": ["1111111111111111", "2222222222222222"]`.
|
`"serials": ["1111111111111111", "2222222222222222"]`.
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
# __init__.py for DeDRM_plugin
|
# __init__.py for DeDRM_plugin
|
||||||
# Copyright © 2008-2020 Apprentice Harper et al.
|
# Copyright © 2008-2020 Apprentice Harper et al.
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '7.0.0'
|
__version__ = '6.8.1'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
@@ -69,19 +71,18 @@ __docformat__ = 'restructuredtext en'
|
|||||||
# 6.6.3 - More cleanup of kindle book names and start of support for .kinf2018
|
# 6.6.3 - More cleanup of kindle book names and start of support for .kinf2018
|
||||||
# 6.7.0 - Handle new library in calibre.
|
# 6.7.0 - Handle new library in calibre.
|
||||||
# 6.8.0 - Full support for .kinf2018 and new KFX encryption (Kindle for PC/Mac 2.5+)
|
# 6.8.0 - Full support for .kinf2018 and new KFX encryption (Kindle for PC/Mac 2.5+)
|
||||||
# 7.0.0 - Switched to Python 3 for calibre 5.0. Thanks to all who comtibuted
|
# 6.8.1 - Kindle key fix for Mac OS X Big Syr
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt DRMed ebooks.
|
Decrypt DRMed ebooks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PLUGIN_NAME = "DeDRM"
|
PLUGIN_NAME = u"DeDRM"
|
||||||
PLUGIN_VERSION_TUPLE = (7, 0, 0)
|
PLUGIN_VERSION_TUPLE = (6, 8, 0)
|
||||||
PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
|
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||||
|
|
||||||
import codecs
|
|
||||||
import sys, os, re
|
import sys, os, re
|
||||||
import time
|
import time
|
||||||
import zipfile
|
import zipfile
|
||||||
@@ -107,11 +108,11 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data,str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
try:
|
try:
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
except:
|
except:
|
||||||
# We can do nothing if a write fails
|
# We can do nothing if a write fails
|
||||||
pass
|
pass
|
||||||
@@ -120,14 +121,13 @@ class SafeUnbuffered:
|
|||||||
|
|
||||||
class DeDRM(FileTypePlugin):
|
class DeDRM(FileTypePlugin):
|
||||||
name = PLUGIN_NAME
|
name = PLUGIN_NAME
|
||||||
description = "Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
|
description = u"Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
|
||||||
supported_platforms = ['linux', 'osx', 'windows']
|
supported_platforms = ['linux', 'osx', 'windows']
|
||||||
author = "Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
author = u"Apprentice Alf, Aprentice Harper, The Dark Reverser and i♥cabbages"
|
||||||
version = PLUGIN_VERSION_TUPLE
|
version = PLUGIN_VERSION_TUPLE
|
||||||
minimum_calibre_version = (5, 0, 0) # Python 3.
|
minimum_calibre_version = (1, 0, 0) # Compiled python libraries cannot be imported in earlier versions.
|
||||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','azw8','tpz','kfx','kfx-zip'])
|
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','azw8','tpz','kfx','kfx-zip'])
|
||||||
on_import = True
|
on_import = True
|
||||||
on_preprocess = True
|
|
||||||
priority = 600
|
priority = 600
|
||||||
|
|
||||||
|
|
||||||
@@ -144,29 +144,29 @@ class DeDRM(FileTypePlugin):
|
|||||||
Also perform upgrade of preferences once per version
|
Also perform upgrade of preferences once per version
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.pluginsdir = os.path.join(config_dir,"plugins")
|
self.pluginsdir = os.path.join(config_dir,u"plugins")
|
||||||
if not os.path.exists(self.pluginsdir):
|
if not os.path.exists(self.pluginsdir):
|
||||||
os.mkdir(self.pluginsdir)
|
os.mkdir(self.pluginsdir)
|
||||||
self.maindir = os.path.join(self.pluginsdir,"DeDRM")
|
self.maindir = os.path.join(self.pluginsdir,u"DeDRM")
|
||||||
if not os.path.exists(self.maindir):
|
if not os.path.exists(self.maindir):
|
||||||
os.mkdir(self.maindir)
|
os.mkdir(self.maindir)
|
||||||
self.helpdir = os.path.join(self.maindir,"help")
|
self.helpdir = os.path.join(self.maindir,u"help")
|
||||||
if not os.path.exists(self.helpdir):
|
if not os.path.exists(self.helpdir):
|
||||||
os.mkdir(self.helpdir)
|
os.mkdir(self.helpdir)
|
||||||
self.alfdir = os.path.join(self.maindir,"libraryfiles")
|
self.alfdir = os.path.join(self.maindir,u"libraryfiles")
|
||||||
if not os.path.exists(self.alfdir):
|
if not os.path.exists(self.alfdir):
|
||||||
os.mkdir(self.alfdir)
|
os.mkdir(self.alfdir)
|
||||||
# only continue if we've never run this version of the plugin before
|
# only continue if we've never run this version of the plugin before
|
||||||
self.verdir = os.path.join(self.maindir,PLUGIN_VERSION)
|
self.verdir = os.path.join(self.maindir,PLUGIN_VERSION)
|
||||||
if not os.path.exists(self.verdir):
|
if not os.path.exists(self.verdir):
|
||||||
if iswindows:
|
if iswindows:
|
||||||
names = ["alfcrypto.dll","alfcrypto64.dll"]
|
names = [u"alfcrypto.dll",u"alfcrypto64.dll"]
|
||||||
elif isosx:
|
elif isosx:
|
||||||
names = ["libalfcrypto.dylib"]
|
names = [u"libalfcrypto.dylib"]
|
||||||
else:
|
else:
|
||||||
names = ["libalfcrypto32.so","libalfcrypto64.so","kindlekey.py","adobekey.py","subasyncio.py"]
|
names = [u"libalfcrypto32.so",u"libalfcrypto64.so",u"kindlekey.py",u"adobekey.py",u"subasyncio.py"]
|
||||||
lib_dict = self.load_resources(names)
|
lib_dict = self.load_resources(names)
|
||||||
print("{0} v{1}: Copying needed library files from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Copying needed library files from plugin's zip".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
|
|
||||||
for entry, data in lib_dict.items():
|
for entry, data in lib_dict.items():
|
||||||
file_path = os.path.join(self.alfdir, entry)
|
file_path = os.path.join(self.alfdir, entry)
|
||||||
@@ -178,7 +178,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
try:
|
try:
|
||||||
open(file_path,'wb').write(data)
|
open(file_path,'wb').write(data)
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when copying needed library files".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Exception when copying needed library files".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
|
|
||||||
# mark that this version has been initialized
|
# mark that this version has been initialized
|
||||||
os.mkdir(self.verdir)
|
os.mkdir(self.verdir)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -197,13 +197,13 @@ class DeDRM(FileTypePlugin):
|
|||||||
# Check original epub archive for zip errors.
|
# Check original epub archive for zip errors.
|
||||||
import calibre_plugins.dedrm.zipfix
|
import calibre_plugins.dedrm.zipfix
|
||||||
|
|
||||||
inf = self.temporary_file(".epub")
|
inf = self.temporary_file(u".epub")
|
||||||
try:
|
try:
|
||||||
print("{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Verifying zip archive integrity".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
fr = zipfix.fixZip(path_to_ebook, inf.name)
|
fr = zipfix.fixZip(path_to_ebook, inf.name)
|
||||||
fr.fix()
|
fr.fix()
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
print("{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
|
print u"{0} v{1}: Error \'{2}\' when checking zip archive".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
# import the decryption keys
|
# import the decryption keys
|
||||||
@@ -216,19 +216,19 @@ class DeDRM(FileTypePlugin):
|
|||||||
|
|
||||||
#check the book
|
#check the book
|
||||||
if ignobleepub.ignobleBook(inf.name):
|
if ignobleepub.ignobleBook(inf.name):
|
||||||
print("{0} v{1}: “{2}” is a secure Barnes & Noble ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
print u"{0} v{1}: “{2}” is a secure Barnes & Noble ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||||
|
|
||||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||||
for keyname, userkey in dedrmprefs['bandnkeys'].items():
|
for keyname, userkey in dedrmprefs['bandnkeys'].items():
|
||||||
keyname_masked = "".join(("X" if (x.isdigit()) else x) for x in keyname)
|
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
|
||||||
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked))
|
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)
|
||||||
of = self.temporary_file(".epub")
|
of = self.temporary_file(u".epub")
|
||||||
|
|
||||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||||
try:
|
try:
|
||||||
result = ignobleepub.decryptBook(userkey, inf.name, of.name)
|
result = ignobleepub.decryptBook(userkey, inf.name, of.name)
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
result = 1
|
result = 1
|
||||||
|
|
||||||
@@ -239,10 +239,10 @@ class DeDRM(FileTypePlugin):
|
|||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime))
|
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||||
|
|
||||||
# perhaps we should see if we can get a key from a log file
|
# perhaps we should see if we can get a key from a log file
|
||||||
print("{0} v{1}: Looking for new NOOK Study Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Looking for new NOOK Study Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
|
|
||||||
# get the default NOOK Study keys
|
# get the default NOOK Study keys
|
||||||
defaultkeys = []
|
defaultkeys = []
|
||||||
@@ -253,13 +253,13 @@ class DeDRM(FileTypePlugin):
|
|||||||
|
|
||||||
defaultkeys = nookkeys()
|
defaultkeys = nookkeys()
|
||||||
else: # linux
|
else: # linux
|
||||||
from .wineutils import WineGetKeys
|
from wineutils import WineGetKeys
|
||||||
|
|
||||||
scriptpath = os.path.join(self.alfdir,"ignoblekey.py")
|
scriptpath = os.path.join(self.alfdir,u"ignoblekey.py")
|
||||||
defaultkeys = WineGetKeys(scriptpath, ".b64",dedrmprefs['adobewineprefix'])
|
defaultkeys = WineGetKeys(scriptpath, u".b64",dedrmprefs['adobewineprefix'])
|
||||||
|
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when getting default NOOK Study Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when getting default NOOK Study Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
newkeys = []
|
newkeys = []
|
||||||
@@ -270,15 +270,15 @@ class DeDRM(FileTypePlugin):
|
|||||||
if len(newkeys) > 0:
|
if len(newkeys) > 0:
|
||||||
try:
|
try:
|
||||||
for i,userkey in enumerate(newkeys):
|
for i,userkey in enumerate(newkeys):
|
||||||
print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
|
|
||||||
of = self.temporary_file(".epub")
|
of = self.temporary_file(u".epub")
|
||||||
|
|
||||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||||
try:
|
try:
|
||||||
result = ignobleepub.decryptBook(userkey, inf.name, of.name)
|
result = ignobleepub.decryptBook(userkey, inf.name, of.name)
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when trying to decrypt after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
result = 1
|
result = 1
|
||||||
|
|
||||||
@@ -287,59 +287,59 @@ class DeDRM(FileTypePlugin):
|
|||||||
if result == 0:
|
if result == 0:
|
||||||
# Decryption was a success
|
# Decryption was a success
|
||||||
# Store the new successful key in the defaults
|
# Store the new successful key in the defaults
|
||||||
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
try:
|
try:
|
||||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue)
|
dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue)
|
||||||
dedrmprefs.writeprefs()
|
dedrmprefs.writeprefs()
|
||||||
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
print("{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||||
|
|
||||||
# import the Adobe Adept ePub handler
|
# import the Adobe Adept ePub handler
|
||||||
import calibre_plugins.dedrm.ineptepub as ineptepub
|
import calibre_plugins.dedrm.ineptepub as ineptepub
|
||||||
|
|
||||||
if ineptepub.adeptBook(inf.name):
|
if ineptepub.adeptBook(inf.name):
|
||||||
print("{0} v{1}: {2} is a secure Adobe Adept ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
print u"{0} v{1}: {2} is a secure Adobe Adept ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||||
|
|
||||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||||
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
||||||
userkey = codecs.decode(userkeyhex, 'hex')
|
userkey = userkeyhex.decode('hex')
|
||||||
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
|
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
|
||||||
of = self.temporary_file(".epub")
|
of = self.temporary_file(u".epub")
|
||||||
|
|
||||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||||
try:
|
try:
|
||||||
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
result = 1
|
result = 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
of.close()
|
of.close()
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception closing temporary file after {2:.1f} seconds. Ignored.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception closing temporary file after {2:.1f} seconds. Ignored.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
|
|
||||||
if result == 0:
|
if result == 0:
|
||||||
# Decryption was successful.
|
# Decryption was successful.
|
||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
print("{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime))
|
print u"{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime))
|
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||||
|
|
||||||
# perhaps we need to get a new default ADE key
|
# perhaps we need to get a new default ADE key
|
||||||
print("{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
|
|
||||||
# get the default Adobe keys
|
# get the default Adobe keys
|
||||||
defaultkeys = []
|
defaultkeys = []
|
||||||
@@ -350,33 +350,33 @@ class DeDRM(FileTypePlugin):
|
|||||||
|
|
||||||
defaultkeys = adeptkeys()
|
defaultkeys = adeptkeys()
|
||||||
else: # linux
|
else: # linux
|
||||||
from .wineutils import WineGetKeys
|
from wineutils import WineGetKeys
|
||||||
|
|
||||||
scriptpath = os.path.join(self.alfdir,"adobekey.py")
|
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
||||||
defaultkeys = WineGetKeys(scriptpath, ".der",dedrmprefs['adobewineprefix'])
|
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
||||||
|
|
||||||
self.default_key = defaultkeys[0]
|
self.default_key = defaultkeys[0]
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.default_key = ""
|
self.default_key = u""
|
||||||
|
|
||||||
newkeys = []
|
newkeys = []
|
||||||
for keyvalue in defaultkeys:
|
for keyvalue in defaultkeys:
|
||||||
if codecs.encode(keyvalue, 'hex').decode('ascii') not in dedrmprefs['adeptkeys'].values():
|
if keyvalue.encode('hex') not in dedrmprefs['adeptkeys'].values():
|
||||||
newkeys.append(keyvalue)
|
newkeys.append(keyvalue)
|
||||||
|
|
||||||
if len(newkeys) > 0:
|
if len(newkeys) > 0:
|
||||||
try:
|
try:
|
||||||
for i,userkey in enumerate(newkeys):
|
for i,userkey in enumerate(newkeys):
|
||||||
print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
of = self.temporary_file(".epub")
|
of = self.temporary_file(u".epub")
|
||||||
|
|
||||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||||
try:
|
try:
|
||||||
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
result = ineptepub.decryptBook(userkey, inf.name, of.name)
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
result = 1
|
result = 1
|
||||||
|
|
||||||
@@ -385,32 +385,32 @@ class DeDRM(FileTypePlugin):
|
|||||||
if result == 0:
|
if result == 0:
|
||||||
# Decryption was a success
|
# Decryption was a success
|
||||||
# Store the new successful key in the defaults
|
# Store the new successful key in the defaults
|
||||||
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
try:
|
try:
|
||||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',codecs.encode(keyvalue, 'hex').decode('ascii'))
|
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||||
dedrmprefs.writeprefs()
|
dedrmprefs.writeprefs()
|
||||||
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print("{0} v{1}: Decrypted with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Decrypted with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
print("{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
print("{0} v{1}: Unexpected Exception trying a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Unexpected Exception trying a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Something went wrong with decryption.
|
# Something went wrong with decryption.
|
||||||
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||||
|
|
||||||
# Not a Barnes & Noble nor an Adobe Adept
|
# Not a Barnes & Noble nor an Adobe Adept
|
||||||
# Import the fixed epub.
|
# Import the fixed epub.
|
||||||
print("{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
print u"{0} v{1}: “{2}” is neither an Adobe Adept nor a Barnes & Noble encrypted ePub".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||||
raise DeDRMError("{0} v{1}: Couldn't decrypt after {2:.1f} seconds. DRM free perhaps?".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
raise DeDRMError(u"{0} v{1}: Couldn't decrypt after {2:.1f} seconds. DRM free perhaps?".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||||
|
|
||||||
def PDFDecrypt(self,path_to_ebook):
|
def PDFDecrypt(self,path_to_ebook):
|
||||||
import calibre_plugins.dedrm.prefs as prefs
|
import calibre_plugins.dedrm.prefs as prefs
|
||||||
@@ -418,17 +418,17 @@ class DeDRM(FileTypePlugin):
|
|||||||
|
|
||||||
dedrmprefs = prefs.DeDRM_Prefs()
|
dedrmprefs = prefs.DeDRM_Prefs()
|
||||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||||
print("{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
print u"{0} v{1}: {2} is a PDF ebook".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||||
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
for keyname, userkeyhex in dedrmprefs['adeptkeys'].items():
|
||||||
userkey = userkeyhex.decode('hex')
|
userkey = userkeyhex.decode('hex')
|
||||||
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname))
|
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
|
||||||
of = self.temporary_file(".pdf")
|
of = self.temporary_file(u".pdf")
|
||||||
|
|
||||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||||
try:
|
try:
|
||||||
result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name)
|
result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name)
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
result = 1
|
result = 1
|
||||||
|
|
||||||
@@ -439,10 +439,10 @@ class DeDRM(FileTypePlugin):
|
|||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime))
|
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)
|
||||||
|
|
||||||
# perhaps we need to get a new default ADE key
|
# perhaps we need to get a new default ADE key
|
||||||
print("{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Looking for new default Adobe Digital Editions Keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
|
|
||||||
# get the default Adobe keys
|
# get the default Adobe keys
|
||||||
defaultkeys = []
|
defaultkeys = []
|
||||||
@@ -453,16 +453,16 @@ class DeDRM(FileTypePlugin):
|
|||||||
|
|
||||||
defaultkeys = adeptkeys()
|
defaultkeys = adeptkeys()
|
||||||
else: # linux
|
else: # linux
|
||||||
from .wineutils import WineGetKeys
|
from wineutils import WineGetKeys
|
||||||
|
|
||||||
scriptpath = os.path.join(self.alfdir,"adobekey.py")
|
scriptpath = os.path.join(self.alfdir,u"adobekey.py")
|
||||||
defaultkeys = WineGetKeys(scriptpath, ".der",dedrmprefs['adobewineprefix'])
|
defaultkeys = WineGetKeys(scriptpath, u".der",dedrmprefs['adobewineprefix'])
|
||||||
|
|
||||||
self.default_key = defaultkeys[0]
|
self.default_key = defaultkeys[0]
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when getting default Adobe Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.default_key = ""
|
self.default_key = u""
|
||||||
|
|
||||||
newkeys = []
|
newkeys = []
|
||||||
for keyvalue in defaultkeys:
|
for keyvalue in defaultkeys:
|
||||||
@@ -472,14 +472,14 @@ class DeDRM(FileTypePlugin):
|
|||||||
if len(newkeys) > 0:
|
if len(newkeys) > 0:
|
||||||
try:
|
try:
|
||||||
for i,userkey in enumerate(newkeys):
|
for i,userkey in enumerate(newkeys):
|
||||||
print("{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Trying a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
of = self.temporary_file(".pdf")
|
of = self.temporary_file(u".pdf")
|
||||||
|
|
||||||
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
# Give the user key, ebook and TemporaryPersistent file to the decryption function.
|
||||||
try:
|
try:
|
||||||
result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name)
|
result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name)
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
result = 1
|
result = 1
|
||||||
|
|
||||||
@@ -488,24 +488,24 @@ class DeDRM(FileTypePlugin):
|
|||||||
if result == 0:
|
if result == 0:
|
||||||
# Decryption was a success
|
# Decryption was a success
|
||||||
# Store the new successful key in the defaults
|
# Store the new successful key in the defaults
|
||||||
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
try:
|
try:
|
||||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
dedrmprefs.addnamedvaluetoprefs('adeptkeys','default_key',keyvalue.encode('hex'))
|
||||||
dedrmprefs.writeprefs()
|
dedrmprefs.writeprefs()
|
||||||
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when saving a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
# Return the modified PersistentTemporary file to calibre.
|
# Return the modified PersistentTemporary file to calibre.
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
print("{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Failed to decrypt with new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Something went wrong with decryption.
|
# Something went wrong with decryption.
|
||||||
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||||
|
|
||||||
|
|
||||||
def KindleMobiDecrypt(self,path_to_ebook):
|
def KindleMobiDecrypt(self,path_to_ebook):
|
||||||
@@ -527,16 +527,16 @@ class DeDRM(FileTypePlugin):
|
|||||||
serials.extend(android_serials_list)
|
serials.extend(android_serials_list)
|
||||||
#print serials
|
#print serials
|
||||||
androidFiles = []
|
androidFiles = []
|
||||||
kindleDatabases = list(dedrmprefs['kindlekeys'].items())
|
kindleDatabases = dedrmprefs['kindlekeys'].items()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime)
|
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,kindleDatabases,androidFiles,serials,pids,self.starttime)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
decoded = False
|
decoded = False
|
||||||
# perhaps we need to get a new default Kindle for Mac/PC key
|
# perhaps we need to get a new default Kindle for Mac/PC key
|
||||||
defaultkeys = []
|
defaultkeys = []
|
||||||
print("{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0]))
|
print u"{0} v{1}: Failed to decrypt with error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION,e.args[0])
|
||||||
print("{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Looking for new default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if iswindows or isosx:
|
if iswindows or isosx:
|
||||||
@@ -544,36 +544,36 @@ class DeDRM(FileTypePlugin):
|
|||||||
|
|
||||||
defaultkeys = kindlekeys()
|
defaultkeys = kindlekeys()
|
||||||
else: # linux
|
else: # linux
|
||||||
from .wineutils import WineGetKeys
|
from wineutils import WineGetKeys
|
||||||
|
|
||||||
scriptpath = os.path.join(self.alfdir,"kindlekey.py")
|
scriptpath = os.path.join(self.alfdir,u"kindlekey.py")
|
||||||
defaultkeys = WineGetKeys(scriptpath, ".k4i",dedrmprefs['kindlewineprefix'])
|
defaultkeys = WineGetKeys(scriptpath, u".k4i",dedrmprefs['kindlewineprefix'])
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Exception when getting default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
print u"{0} v{1}: Exception when getting default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
newkeys = {}
|
newkeys = {}
|
||||||
for i,keyvalue in enumerate(defaultkeys):
|
for i,keyvalue in enumerate(defaultkeys):
|
||||||
keyname = "default_key_{0:d}".format(i+1)
|
keyname = u"default_key_{0:d}".format(i+1)
|
||||||
if keyvalue not in dedrmprefs['kindlekeys'].values():
|
if keyvalue not in dedrmprefs['kindlekeys'].values():
|
||||||
newkeys[keyname] = keyvalue
|
newkeys[keyname] = keyvalue
|
||||||
if len(newkeys) > 0:
|
if len(newkeys) > 0:
|
||||||
print("{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), "key" if len(newkeys)==1 else "keys"))
|
print u"{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||||
try:
|
try:
|
||||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,list(newkeys.items()),[],[],[],self.starttime)
|
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime)
|
||||||
decoded = True
|
decoded = True
|
||||||
# store the new successful keys in the defaults
|
# store the new successful keys in the defaults
|
||||||
print("{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), "key" if len(newkeys)==1 else "keys"))
|
print u"{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), u"key" if len(newkeys)==1 else u"keys")
|
||||||
for keyvalue in newkeys.values():
|
for keyvalue in newkeys.values():
|
||||||
dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
|
dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
|
||||||
dedrmprefs.writeprefs()
|
dedrmprefs.writeprefs()
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
pass
|
pass
|
||||||
if not decoded:
|
if not decoded:
|
||||||
#if you reached here then no luck raise and exception
|
#if you reached here then no luck raise and exception
|
||||||
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
||||||
|
|
||||||
of = self.temporary_file(book.getBookExtension())
|
of = self.temporary_file(book.getBookExtension())
|
||||||
book.getFile(of.name)
|
book.getFile(of.name)
|
||||||
@@ -590,9 +590,9 @@ class DeDRM(FileTypePlugin):
|
|||||||
dedrmprefs = prefs.DeDRM_Prefs()
|
dedrmprefs = prefs.DeDRM_Prefs()
|
||||||
# Attempt to decrypt epub with each encryption key (generated or provided).
|
# Attempt to decrypt epub with each encryption key (generated or provided).
|
||||||
for keyname, userkey in dedrmprefs['ereaderkeys'].items():
|
for keyname, userkey in dedrmprefs['ereaderkeys'].items():
|
||||||
keyname_masked = "".join(("X" if (x.isdigit()) else x) for x in keyname)
|
keyname_masked = u"".join((u'X' if (x.isdigit()) else x) for x in keyname)
|
||||||
print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked))
|
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)
|
||||||
of = self.temporary_file(".pmlz")
|
of = self.temporary_file(u".pmlz")
|
||||||
|
|
||||||
# Give the userkey, ebook and TemporaryPersistent file to the decryption function.
|
# Give the userkey, ebook and TemporaryPersistent file to the decryption function.
|
||||||
result = erdr2pml.decryptBook(path_to_ebook, of.name, True, userkey.decode('hex'))
|
result = erdr2pml.decryptBook(path_to_ebook, of.name, True, userkey.decode('hex'))
|
||||||
@@ -602,13 +602,13 @@ class DeDRM(FileTypePlugin):
|
|||||||
# Decryption was successful return the modified PersistentTemporary
|
# Decryption was successful return the modified PersistentTemporary
|
||||||
# file to Calibre's import process.
|
# file to Calibre's import process.
|
||||||
if result == 0:
|
if result == 0:
|
||||||
print("{0} v{1}: Successfully decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime))
|
print u"{0} v{1}: Successfully decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||||
return of.name
|
return of.name
|
||||||
|
|
||||||
print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime))
|
print u"{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname_masked,time.time()-self.starttime)
|
||||||
|
|
||||||
print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
raise DeDRMError(u"{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||||
|
|
||||||
|
|
||||||
def run(self, path_to_ebook):
|
def run(self, path_to_ebook):
|
||||||
@@ -617,7 +617,7 @@ class DeDRM(FileTypePlugin):
|
|||||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
|
|
||||||
print("{0} v{1}: Trying to decrypt {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook)))
|
print u"{0} v{1}: Trying to decrypt {2}".format(PLUGIN_NAME, PLUGIN_VERSION, os.path.basename(path_to_ebook))
|
||||||
self.starttime = time.time()
|
self.starttime = time.time()
|
||||||
|
|
||||||
booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
|
booktype = os.path.splitext(path_to_ebook)[1].lower()[1:]
|
||||||
@@ -636,9 +636,9 @@ class DeDRM(FileTypePlugin):
|
|||||||
# Adobe Adept or B&N ePub
|
# Adobe Adept or B&N ePub
|
||||||
decrypted_ebook = self.ePubDecrypt(path_to_ebook)
|
decrypted_ebook = self.ePubDecrypt(path_to_ebook)
|
||||||
else:
|
else:
|
||||||
print("Unknown booktype {0}. Passing back to calibre unchanged".format(booktype))
|
print u"Unknown booktype {0}. Passing back to calibre unchanged".format(booktype)
|
||||||
return path_to_ebook
|
return path_to_ebook
|
||||||
print("{0} v{1}: Finished after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
|
print u"{0} v{1}: Finished after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||||
return decrypted_ebook
|
return decrypted_ebook
|
||||||
|
|
||||||
def is_customizable(self):
|
def is_customizable(self):
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import sys
|
import sys
|
||||||
import tkinter
|
import Tkinter
|
||||||
import tkinter.constants
|
import Tkconstants
|
||||||
|
|
||||||
class ActivityBar(tkinter.Frame):
|
class ActivityBar(Tkinter.Frame):
|
||||||
|
|
||||||
def __init__(self, master, length=300, height=20, barwidth=15, interval=50, bg='white', fillcolor='orchid1',\
|
def __init__(self, master, length=300, height=20, barwidth=15, interval=50, bg='white', fillcolor='orchid1',\
|
||||||
bd=2, relief=tkinter.constants.GROOVE, *args, **kw):
|
bd=2, relief=Tkconstants.GROOVE, *args, **kw):
|
||||||
tkinter.Frame.__init__(self, master, bg=bg, width=length, height=height, *args, **kw)
|
Tkinter.Frame.__init__(self, master, bg=bg, width=length, height=height, *args, **kw)
|
||||||
self._master = master
|
self._master = master
|
||||||
self._interval = interval
|
self._interval = interval
|
||||||
self._maximum = length
|
self._maximum = length
|
||||||
@@ -20,7 +20,7 @@ class ActivityBar(tkinter.Frame):
|
|||||||
stopx = self._maximum
|
stopx = self._maximum
|
||||||
# self._canv = Tkinter.Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
|
# self._canv = Tkinter.Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
|
||||||
# highlightthickness=0, relief='flat', bd=0)
|
# highlightthickness=0, relief='flat', bd=0)
|
||||||
self._canv = tkinter.Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
|
self._canv = Tkinter.Canvas(self, bg=self['bg'], width=self['width'], height=self['height'],\
|
||||||
highlightthickness=0, relief=relief, bd=bd)
|
highlightthickness=0, relief=relief, bd=bd)
|
||||||
self._canv.pack(fill='both', expand=1)
|
self._canv.pack(fill='both', expand=1)
|
||||||
self._rect = self._canv.create_rectangle(0, 0, self._canv.winfo_reqwidth(), self._canv.winfo_reqheight(), fill=fillcolor, width=0)
|
self._rect = self._canv.create_rectangle(0, 0, self._canv.winfo_reqwidth(), self._canv.winfo_reqheight(), fill=fillcolor, width=0)
|
||||||
|
|||||||
@@ -1,12 +1,32 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
# adobekey.pyw, version 6.0
|
# adobekey.pyw, version 6.0
|
||||||
# Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al.
|
# Copyright © 2009-2010 i♥cabbages
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
# Modified 2010–2016 by several people
|
||||||
|
|
||||||
|
# Windows users: Before running this program, you must first install Python.
|
||||||
|
# We recommend ActiveState Python 2.7.X for Windows (x86) from
|
||||||
|
# http://www.activestate.com/activepython/downloads.
|
||||||
|
# You must also install PyCrypto from
|
||||||
|
# http://www.voidspace.org.uk/python/modules.shtml#pycrypto
|
||||||
|
# (make certain to install the version for Python 2.7).
|
||||||
|
# Then save this script file as adobekey.pyw and double-click on it to run it.
|
||||||
|
# It will create a file named adobekey_1.der in in the same directory as the script.
|
||||||
|
# This is your Adobe Digital Editions user key.
|
||||||
|
#
|
||||||
|
# Mac OS X users: Save this script file as adobekey.pyw. You can run this
|
||||||
|
# program from the command line (python adobekey.pyw) or by double-clicking
|
||||||
|
# it when it has been associated with PythonLauncher. It will create a file
|
||||||
|
# named adobekey_1.der in the same directory as the script.
|
||||||
|
# This is your Adobe Digital Editions user key.
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 1 - Initial release, for Adobe Digital Editions 1.7
|
# 1 - Initial release, for Adobe Digital Editions 1.7
|
||||||
# 2 - Better algorithm for finding pLK; improved error handling
|
# 2 - Better algorithm for finding pLK; improved error handling
|
||||||
@@ -28,18 +48,16 @@
|
|||||||
# 5.8 - Added getkey interface for Windows DeDRM application
|
# 5.8 - Added getkey interface for Windows DeDRM application
|
||||||
# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 5.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 6.0 - Work if TkInter is missing
|
# 6.0 - Work if TkInter is missing
|
||||||
# 7.0 - Python 3 for calibre 5
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Retrieve Adobe ADEPT user key.
|
Retrieve Adobe ADEPT user key.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '7.0'
|
__version__ = '6.0'
|
||||||
|
|
||||||
import sys, os, struct, getopt
|
import sys, os, struct, getopt
|
||||||
from base64 import b64decode
|
|
||||||
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
# and also make sure that any unicode strings get
|
# and also make sure that any unicode strings get
|
||||||
@@ -51,11 +69,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -93,13 +110,15 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["adobekey.py"]
|
return [u"adobekey.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
class ADEPTError(Exception):
|
class ADEPTError(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -111,7 +130,7 @@ if iswindows:
|
|||||||
c_long, c_ulong
|
c_long, c_ulong
|
||||||
|
|
||||||
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
||||||
import winreg
|
import _winreg as winreg
|
||||||
|
|
||||||
def _load_crypto_libcrypto():
|
def _load_crypto_libcrypto():
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
@@ -149,7 +168,7 @@ if iswindows:
|
|||||||
raise ADEPTError('Failed to initialize AES key')
|
raise ADEPTError('Failed to initialize AES key')
|
||||||
def decrypt(self, data):
|
def decrypt(self, data):
|
||||||
out = create_string_buffer(len(data))
|
out = create_string_buffer(len(data))
|
||||||
iv = (b"\x00" * self._blocksize)
|
iv = ("\x00" * self._blocksize)
|
||||||
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
raise ADEPTError('AES decryption failed')
|
raise ADEPTError('AES decryption failed')
|
||||||
@@ -160,7 +179,7 @@ if iswindows:
|
|||||||
from Crypto.Cipher import AES as _AES
|
from Crypto.Cipher import AES as _AES
|
||||||
class AES(object):
|
class AES(object):
|
||||||
def __init__(self, key):
|
def __init__(self, key):
|
||||||
self._aes = _AES.new(key, _AES.MODE_CBC, b'\x00'*16)
|
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
|
||||||
def decrypt(self, data):
|
def decrypt(self, data):
|
||||||
return self._aes.decrypt(data)
|
return self._aes.decrypt(data)
|
||||||
return AES
|
return AES
|
||||||
@@ -268,44 +287,44 @@ if iswindows:
|
|||||||
|
|
||||||
if struct.calcsize("P") == 4:
|
if struct.calcsize("P") == 4:
|
||||||
CPUID0_INSNS = (
|
CPUID0_INSNS = (
|
||||||
b"\x53" # push %ebx
|
"\x53" # push %ebx
|
||||||
b"\x31\xc0" # xor %eax,%eax
|
"\x31\xc0" # xor %eax,%eax
|
||||||
b"\x0f\xa2" # cpuid
|
"\x0f\xa2" # cpuid
|
||||||
b"\x8b\x44\x24\x08" # mov 0x8(%esp),%eax
|
"\x8b\x44\x24\x08" # mov 0x8(%esp),%eax
|
||||||
b"\x89\x18" # mov %ebx,0x0(%eax)
|
"\x89\x18" # mov %ebx,0x0(%eax)
|
||||||
b"\x89\x50\x04" # mov %edx,0x4(%eax)
|
"\x89\x50\x04" # mov %edx,0x4(%eax)
|
||||||
b"\x89\x48\x08" # mov %ecx,0x8(%eax)
|
"\x89\x48\x08" # mov %ecx,0x8(%eax)
|
||||||
b"\x5b" # pop %ebx
|
"\x5b" # pop %ebx
|
||||||
b"\xc3" # ret
|
"\xc3" # ret
|
||||||
)
|
)
|
||||||
CPUID1_INSNS = (
|
CPUID1_INSNS = (
|
||||||
b"\x53" # push %ebx
|
"\x53" # push %ebx
|
||||||
b"\x31\xc0" # xor %eax,%eax
|
"\x31\xc0" # xor %eax,%eax
|
||||||
b"\x40" # inc %eax
|
"\x40" # inc %eax
|
||||||
b"\x0f\xa2" # cpuid
|
"\x0f\xa2" # cpuid
|
||||||
b"\x5b" # pop %ebx
|
"\x5b" # pop %ebx
|
||||||
b"\xc3" # ret
|
"\xc3" # ret
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
CPUID0_INSNS = (
|
CPUID0_INSNS = (
|
||||||
b"\x49\x89\xd8" # mov %rbx,%r8
|
"\x49\x89\xd8" # mov %rbx,%r8
|
||||||
b"\x49\x89\xc9" # mov %rcx,%r9
|
"\x49\x89\xc9" # mov %rcx,%r9
|
||||||
b"\x48\x31\xc0" # xor %rax,%rax
|
"\x48\x31\xc0" # xor %rax,%rax
|
||||||
b"\x0f\xa2" # cpuid
|
"\x0f\xa2" # cpuid
|
||||||
b"\x4c\x89\xc8" # mov %r9,%rax
|
"\x4c\x89\xc8" # mov %r9,%rax
|
||||||
b"\x89\x18" # mov %ebx,0x0(%rax)
|
"\x89\x18" # mov %ebx,0x0(%rax)
|
||||||
b"\x89\x50\x04" # mov %edx,0x4(%rax)
|
"\x89\x50\x04" # mov %edx,0x4(%rax)
|
||||||
b"\x89\x48\x08" # mov %ecx,0x8(%rax)
|
"\x89\x48\x08" # mov %ecx,0x8(%rax)
|
||||||
b"\x4c\x89\xc3" # mov %r8,%rbx
|
"\x4c\x89\xc3" # mov %r8,%rbx
|
||||||
b"\xc3" # retq
|
"\xc3" # retq
|
||||||
)
|
)
|
||||||
CPUID1_INSNS = (
|
CPUID1_INSNS = (
|
||||||
b"\x53" # push %rbx
|
"\x53" # push %rbx
|
||||||
b"\x48\x31\xc0" # xor %rax,%rax
|
"\x48\x31\xc0" # xor %rax,%rax
|
||||||
b"\x48\xff\xc0" # inc %rax
|
"\x48\xff\xc0" # inc %rax
|
||||||
b"\x0f\xa2" # cpuid
|
"\x0f\xa2" # cpuid
|
||||||
b"\x5b" # pop %rbx
|
"\x5b" # pop %rbx
|
||||||
b"\xc3" # retq
|
"\xc3" # retq
|
||||||
)
|
)
|
||||||
|
|
||||||
def cpuid0():
|
def cpuid0():
|
||||||
@@ -364,7 +383,7 @@ if iswindows:
|
|||||||
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
|
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
|
||||||
except WindowsError:
|
except WindowsError:
|
||||||
raise ADEPTError("Could not locate ADE activation")
|
raise ADEPTError("Could not locate ADE activation")
|
||||||
for i in range(0, 16):
|
for i in xrange(0, 16):
|
||||||
try:
|
try:
|
||||||
plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
|
plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
|
||||||
except WindowsError:
|
except WindowsError:
|
||||||
@@ -372,7 +391,7 @@ if iswindows:
|
|||||||
ktype = winreg.QueryValueEx(plkparent, None)[0]
|
ktype = winreg.QueryValueEx(plkparent, None)[0]
|
||||||
if ktype != 'credentials':
|
if ktype != 'credentials':
|
||||||
continue
|
continue
|
||||||
for j in range(0, 16):
|
for j in xrange(0, 16):
|
||||||
try:
|
try:
|
||||||
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
|
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
|
||||||
except WindowsError:
|
except WindowsError:
|
||||||
@@ -381,15 +400,15 @@ if iswindows:
|
|||||||
if ktype != 'privateLicenseKey':
|
if ktype != 'privateLicenseKey':
|
||||||
continue
|
continue
|
||||||
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
|
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
|
||||||
userkey = b64decode(userkey)
|
userkey = userkey.decode('base64')
|
||||||
aes = AES(keykey)
|
aes = AES(keykey)
|
||||||
userkey = aes.decrypt(userkey)
|
userkey = aes.decrypt(userkey)
|
||||||
userkey = userkey[26:-ord(userkey[-1:])]
|
userkey = userkey[26:-ord(userkey[-1])]
|
||||||
#print "found key:",userkey.encode('hex')
|
#print "found key:",userkey.encode('hex')
|
||||||
keys.append(userkey)
|
keys.append(userkey)
|
||||||
if len(keys) == 0:
|
if len(keys) == 0:
|
||||||
raise ADEPTError('Could not locate privateLicenseKey')
|
raise ADEPTError('Could not locate privateLicenseKey')
|
||||||
print("Found {0:d} keys".format(len(keys)))
|
print(u"Found {0:d} keys".format(len(keys)))
|
||||||
return keys
|
return keys
|
||||||
|
|
||||||
|
|
||||||
@@ -409,12 +428,12 @@ elif isosx:
|
|||||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
p2 = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
out1, out2 = p2.communicate()
|
out1, out2 = p2.communicate()
|
||||||
reslst = out1.split(b'\n')
|
reslst = out1.split('\n')
|
||||||
cnt = len(reslst)
|
cnt = len(reslst)
|
||||||
ActDatPath = b"activation.dat"
|
ActDatPath = "activation.dat"
|
||||||
for j in range(cnt):
|
for j in xrange(cnt):
|
||||||
resline = reslst[j]
|
resline = reslst[j]
|
||||||
pp = resline.find(b'activation.dat')
|
pp = resline.find('activation.dat')
|
||||||
if pp >= 0:
|
if pp >= 0:
|
||||||
ActDatPath = resline
|
ActDatPath = resline
|
||||||
break
|
break
|
||||||
@@ -430,7 +449,7 @@ elif isosx:
|
|||||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||||
expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey'))
|
expr = '//%s/%s' % (adept('credentials'), adept('privateLicenseKey'))
|
||||||
userkey = tree.findtext(expr)
|
userkey = tree.findtext(expr)
|
||||||
userkey = b64decode(userkey)
|
userkey = userkey.decode('base64')
|
||||||
userkey = userkey[26:]
|
userkey = userkey[26:]
|
||||||
return [userkey]
|
return [userkey]
|
||||||
|
|
||||||
@@ -445,41 +464,41 @@ def getkey(outpath):
|
|||||||
if len(keys) > 0:
|
if len(keys) > 0:
|
||||||
if not os.path.isdir(outpath):
|
if not os.path.isdir(outpath):
|
||||||
outfile = outpath
|
outfile = outpath
|
||||||
with open(outfile, 'wb') as keyfileout:
|
with file(outfile, 'wb') as keyfileout:
|
||||||
keyfileout.write(keys[0])
|
keyfileout.write(keys[0])
|
||||||
print("Saved a key to {0}".format(outfile))
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
else:
|
else:
|
||||||
keycount = 0
|
keycount = 0
|
||||||
for key in keys:
|
for key in keys:
|
||||||
while True:
|
while True:
|
||||||
keycount += 1
|
keycount += 1
|
||||||
outfile = os.path.join(outpath,"adobekey_{0:d}.der".format(keycount))
|
outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount))
|
||||||
if not os.path.exists(outfile):
|
if not os.path.exists(outfile):
|
||||||
break
|
break
|
||||||
with open(outfile, 'wb') as keyfileout:
|
with file(outfile, 'wb') as keyfileout:
|
||||||
keyfileout.write(key)
|
keyfileout.write(key)
|
||||||
print("Saved a key to {0}".format(outfile))
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def usage(progname):
|
def usage(progname):
|
||||||
print("Finds, decrypts and saves the default Adobe Adept encryption key(s).")
|
print(u"Finds, decrypts and saves the default Adobe Adept encryption key(s).")
|
||||||
print("Keys are saved to the current directory, or a specified output directory.")
|
print(u"Keys are saved to the current directory, or a specified output directory.")
|
||||||
print("If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
print(u"If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
||||||
print("Usage:")
|
print(u"Usage:")
|
||||||
print(" {0:s} [-h] [<outpath>]".format(progname))
|
print(u" {0:s} [-h] [<outpath>]".format(progname))
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
print("{0} v{1}\nCopyright © 2009-2020 i♥cabbages, Apprentice Harper et al.".format(progname,__version__))
|
print(u"{0} v{1}\nCopyright © 2009-2013 i♥cabbages and Apprentice Alf".format(progname,__version__))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "h")
|
opts, args = getopt.getopt(argv[1:], "h")
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError, err:
|
||||||
print("Error in options or arguments: {0}".format(err.args[0]))
|
print(u"Error in options or arguments: {0}".format(err.args[0]))
|
||||||
usage(progname)
|
usage(progname)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
@@ -508,48 +527,48 @@ def cli_main():
|
|||||||
if len(keys) > 0:
|
if len(keys) > 0:
|
||||||
if not os.path.isdir(outpath):
|
if not os.path.isdir(outpath):
|
||||||
outfile = outpath
|
outfile = outpath
|
||||||
with open(outfile, 'wb') as keyfileout:
|
with file(outfile, 'wb') as keyfileout:
|
||||||
keyfileout.write(keys[0])
|
keyfileout.write(keys[0])
|
||||||
print("Saved a key to {0}".format(outfile))
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
else:
|
else:
|
||||||
keycount = 0
|
keycount = 0
|
||||||
for key in keys:
|
for key in keys:
|
||||||
while True:
|
while True:
|
||||||
keycount += 1
|
keycount += 1
|
||||||
outfile = os.path.join(outpath,"adobekey_{0:d}.der".format(keycount))
|
outfile = os.path.join(outpath,u"adobekey_{0:d}.der".format(keycount))
|
||||||
if not os.path.exists(outfile):
|
if not os.path.exists(outfile):
|
||||||
break
|
break
|
||||||
with open(outfile, 'wb') as keyfileout:
|
with file(outfile, 'wb') as keyfileout:
|
||||||
keyfileout.write(key)
|
keyfileout.write(key)
|
||||||
print("Saved a key to {0}".format(outfile))
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
else:
|
else:
|
||||||
print("Could not retrieve Adobe Adept key.")
|
print(u"Could not retrieve Adobe Adept key.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import Tkinter
|
||||||
import tkinter.constants
|
import Tkconstants
|
||||||
import tkinter.messagebox
|
import tkMessageBox
|
||||||
import traceback
|
import traceback
|
||||||
except:
|
except:
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class ExceptionDialog(tkinter.Frame):
|
class ExceptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root, text):
|
def __init__(self, root, text):
|
||||||
tkinter.Frame.__init__(self, root, border=5)
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
label = tkinter.Label(self, text="Unexpected error:",
|
label = Tkinter.Label(self, text=u"Unexpected error:",
|
||||||
anchor=tkinter.constants.W, justify=tkinter.constants.LEFT)
|
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
|
||||||
label.pack(fill=tkinter.constants.X, expand=0)
|
label.pack(fill=Tkconstants.X, expand=0)
|
||||||
self.text = tkinter.Text(self)
|
self.text = Tkinter.Text(self)
|
||||||
self.text.pack(fill=tkinter.constants.BOTH, expand=1)
|
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||||
|
|
||||||
self.text.insert(tkinter.constants.END, text)
|
self.text.insert(Tkconstants.END, text)
|
||||||
|
|
||||||
|
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
root = tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
progpath, progname = os.path.split(argv[0])
|
progpath, progname = os.path.split(argv[0])
|
||||||
success = False
|
success = False
|
||||||
@@ -559,21 +578,21 @@ def gui_main():
|
|||||||
for key in keys:
|
for key in keys:
|
||||||
while True:
|
while True:
|
||||||
keycount += 1
|
keycount += 1
|
||||||
outfile = os.path.join(progpath,"adobekey_{0:d}.der".format(keycount))
|
outfile = os.path.join(progpath,u"adobekey_{0:d}.der".format(keycount))
|
||||||
if not os.path.exists(outfile):
|
if not os.path.exists(outfile):
|
||||||
break
|
break
|
||||||
|
|
||||||
with open(outfile, 'wb') as keyfileout:
|
with file(outfile, 'wb') as keyfileout:
|
||||||
keyfileout.write(key)
|
keyfileout.write(key)
|
||||||
success = True
|
success = True
|
||||||
tkinter.messagebox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
|
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||||
except ADEPTError as e:
|
except ADEPTError, e:
|
||||||
tkinter.messagebox.showerror(progname, "Error: {0}".format(str(e)))
|
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||||
except Exception:
|
except Exception:
|
||||||
root.wm_state('normal')
|
root.wm_state('normal')
|
||||||
root.title(progname)
|
root.title(progname)
|
||||||
text = traceback.format_exc()
|
text = traceback.format_exc()
|
||||||
ExceptionDialog(root, text).pack(fill=tkinter.constants.BOTH, expand=1)
|
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
if not success:
|
if not success:
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#! /usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Routines for doing AES CBC in one file
|
Routines for doing AES CBC in one file
|
||||||
@@ -14,8 +13,6 @@
|
|||||||
CryptoPy Artisitic License Version 1.0
|
CryptoPy Artisitic License Version 1.0
|
||||||
See the wonderful pure python package cryptopy-1.2.5
|
See the wonderful pure python package cryptopy-1.2.5
|
||||||
and read its LICENSE.txt for complete license details.
|
and read its LICENSE.txt for complete license details.
|
||||||
|
|
||||||
Adjusted for Python 3, September 2020
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class CryptoError(Exception):
|
class CryptoError(Exception):
|
||||||
@@ -104,7 +101,7 @@ class BlockCipher:
|
|||||||
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
|
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
|
||||||
if more == None: # no more calls to decrypt, should have all the data
|
if more == None: # no more calls to decrypt, should have all the data
|
||||||
if numExtraBytes != 0:
|
if numExtraBytes != 0:
|
||||||
raise DecryptNotBlockAlignedError('Data not block aligned on decrypt')
|
raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt'
|
||||||
|
|
||||||
# hold back some bytes in case last decrypt has zero len
|
# hold back some bytes in case last decrypt has zero len
|
||||||
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
|
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
|
||||||
@@ -146,7 +143,7 @@ class padWithPadLen(Pad):
|
|||||||
def removePad(self, paddedBinaryString, blockSize):
|
def removePad(self, paddedBinaryString, blockSize):
|
||||||
""" Remove padding from a binary string """
|
""" Remove padding from a binary string """
|
||||||
if not(0<len(paddedBinaryString)):
|
if not(0<len(paddedBinaryString)):
|
||||||
raise DecryptNotBlockAlignedError('Expected More Data')
|
raise DecryptNotBlockAlignedError, 'Expected More Data'
|
||||||
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
|
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
|
||||||
|
|
||||||
class noPadding(Pad):
|
class noPadding(Pad):
|
||||||
@@ -176,8 +173,8 @@ class Rijndael(BlockCipher):
|
|||||||
self.blockSize = blockSize # blockSize is in bytes
|
self.blockSize = blockSize # blockSize is in bytes
|
||||||
self.padding = padding # change default to noPadding() to get normal ECB behavior
|
self.padding = padding # change default to noPadding() to get normal ECB behavior
|
||||||
|
|
||||||
assert( keySize%4==0 and keySize/4 in NrTable[4]),'key size must be 16,20,24,29 or 32 bytes'
|
assert( keySize%4==0 and NrTable[4].has_key(keySize/4)),'key size must be 16,20,24,29 or 32 bytes'
|
||||||
assert( blockSize%4==0 and blockSize/4 in NrTable), 'block size must be 16,20,24,29 or 32 bytes'
|
assert( blockSize%4==0 and NrTable.has_key(blockSize/4)), 'block size must be 16,20,24,29 or 32 bytes'
|
||||||
|
|
||||||
self.Nb = self.blockSize/4 # Nb is number of columns of 32 bit words
|
self.Nb = self.blockSize/4 # Nb is number of columns of 32 bit words
|
||||||
self.Nk = keySize/4 # Nk is the key length in 32-bit words
|
self.Nk = keySize/4 # Nk is the key length in 32-bit words
|
||||||
@@ -454,7 +451,7 @@ class AES(Rijndael):
|
|||||||
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
|
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
|
||||||
""" Initialize AES, keySize is in bytes """
|
""" Initialize AES, keySize is in bytes """
|
||||||
if not (keySize == 16 or keySize == 24 or keySize == 32) :
|
if not (keySize == 16 or keySize == 24 or keySize == 32) :
|
||||||
raise BadKeySizeError('Illegal AES key size, must be 16, 24, or 32 bytes')
|
raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes'
|
||||||
|
|
||||||
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
|
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# crypto library mainly by some_updates
|
# crypto library mainly by some_updates
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
|
# pbkdf2.py Copyright © 2009 Daniel Holth <dholth@fastmail.fm>
|
||||||
# pbkdf2.py This code may be freely used and modified for any purpose.
|
# pbkdf2.py This code may be freely used and modified for any purpose.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
import sys, os
|
import sys, os
|
||||||
import hmac
|
import hmac
|
||||||
from struct import pack
|
from struct import pack
|
||||||
@@ -158,7 +159,7 @@ def _load_libalfcrypto():
|
|||||||
topazCryptoDecrypt(ctx, data, out, len(data))
|
topazCryptoDecrypt(ctx, data, out, len(data))
|
||||||
return out.raw
|
return out.raw
|
||||||
|
|
||||||
print("Using Library AlfCrypto DLL/DYLIB/SO")
|
print(u"Using Library AlfCrypto DLL/DYLIB/SO")
|
||||||
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
||||||
|
|
||||||
|
|
||||||
@@ -177,13 +178,13 @@ def _load_python_alfcrypto():
|
|||||||
if len(key)!=16:
|
if len(key)!=16:
|
||||||
raise Exception('Pukall_Cipher: Bad key length.')
|
raise Exception('Pukall_Cipher: Bad key length.')
|
||||||
wkey = []
|
wkey = []
|
||||||
for i in range(8):
|
for i in xrange(8):
|
||||||
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
||||||
dst = ""
|
dst = ""
|
||||||
for i in range(len(src)):
|
for i in xrange(len(src)):
|
||||||
temp1 = 0;
|
temp1 = 0;
|
||||||
byteXorVal = 0;
|
byteXorVal = 0;
|
||||||
for j in range(8):
|
for j in xrange(8):
|
||||||
temp1 ^= wkey[j]
|
temp1 ^= wkey[j]
|
||||||
sum2 = (sum2+j)*20021 + sum1
|
sum2 = (sum2+j)*20021 + sum1
|
||||||
sum1 = (temp1*346)&0xFFFF
|
sum1 = (temp1*346)&0xFFFF
|
||||||
@@ -196,7 +197,7 @@ def _load_python_alfcrypto():
|
|||||||
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
||||||
if decryption:
|
if decryption:
|
||||||
keyXorVal = curByte * 257;
|
keyXorVal = curByte * 257;
|
||||||
for j in range(8):
|
for j in xrange(8):
|
||||||
wkey[j] ^= keyXorVal;
|
wkey[j] ^= keyXorVal;
|
||||||
dst+=chr(curByte)
|
dst+=chr(curByte)
|
||||||
return dst
|
return dst
|
||||||
@@ -244,7 +245,7 @@ def _load_python_alfcrypto():
|
|||||||
cleartext = self.aes.decrypt(iv + data)
|
cleartext = self.aes.decrypt(iv + data)
|
||||||
return cleartext
|
return cleartext
|
||||||
|
|
||||||
print("Using Library AlfCrypto Python")
|
print(u"Using Library AlfCrypto Python")
|
||||||
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
return (AES_CBC, Pukall_Cipher, Topaz_Cipher)
|
||||||
|
|
||||||
|
|
||||||
@@ -268,10 +269,10 @@ class KeyIVGen(object):
|
|||||||
# [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
# [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
||||||
def pbkdf2(self, passwd, salt, iter, keylen):
|
def pbkdf2(self, passwd, salt, iter, keylen):
|
||||||
|
|
||||||
def xorbytes( a, b ):
|
def xorstr( a, b ):
|
||||||
if len(a) != len(b):
|
if len(a) != len(b):
|
||||||
raise Exception("xorbytes(): lengths differ")
|
raise Exception("xorstr(): lengths differ")
|
||||||
return bytes([x ^ y for x, y in zip(a, b)])
|
return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
|
||||||
|
|
||||||
def prf( h, data ):
|
def prf( h, data ):
|
||||||
hm = h.copy()
|
hm = h.copy()
|
||||||
@@ -283,17 +284,17 @@ class KeyIVGen(object):
|
|||||||
T = U
|
T = U
|
||||||
for i in range(2, itercount+1):
|
for i in range(2, itercount+1):
|
||||||
U = prf( h, U )
|
U = prf( h, U )
|
||||||
T = xorbytes( T, U )
|
T = xorstr( T, U )
|
||||||
return T
|
return T
|
||||||
|
|
||||||
sha = hashlib.sha1
|
sha = hashlib.sha1
|
||||||
digest_size = sha().digest_size
|
digest_size = sha().digest_size
|
||||||
# l - number of output blocks to produce
|
# l - number of output blocks to produce
|
||||||
l = keylen // digest_size
|
l = keylen / digest_size
|
||||||
if keylen % digest_size != 0:
|
if keylen % digest_size != 0:
|
||||||
l += 1
|
l += 1
|
||||||
h = hmac.new( passwd, None, sha )
|
h = hmac.new( passwd, None, sha )
|
||||||
T = b""
|
T = ""
|
||||||
for i in range(1, l+1):
|
for i in range(1, l+1):
|
||||||
T += pbkdf2_F( h, salt, iter, i )
|
T += pbkdf2_F( h, salt, iter, i )
|
||||||
return T[0: keylen]
|
return T[0: keylen]
|
||||||
|
|||||||
183
DeDRM_plugin/androidkindlekey.py
Executable file → Normal file
183
DeDRM_plugin/androidkindlekey.py
Executable file → Normal file
@@ -1,8 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
# androidkindlekey.py
|
# androidkindlekey.py
|
||||||
# Copyright © 2010-20 by Thom, Apprentice et al.
|
# Copyright © 2013-15 by Thom and Apprentice Harper
|
||||||
|
# Some portions Copyright © 2010-15 by some_updates and Apprentice Alf
|
||||||
|
#
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 1.0 - AmazonSecureStorage.xml decryption to serial number
|
# 1.0 - AmazonSecureStorage.xml decryption to serial number
|
||||||
@@ -13,14 +17,14 @@
|
|||||||
# 1.3 - added in TkInter interface, output to a file
|
# 1.3 - added in TkInter interface, output to a file
|
||||||
# 1.4 - Fix some problems identified by Aldo Bleeker
|
# 1.4 - Fix some problems identified by Aldo Bleeker
|
||||||
# 1.5 - Fix another problem identified by Aldo Bleeker
|
# 1.5 - Fix another problem identified by Aldo Bleeker
|
||||||
# 2.0 - Python 3 compatibility
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Retrieve Kindle for Android Serial Number.
|
Retrieve Kindle for Android Serial Number.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '2.0'
|
__version__ = '1.5'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -30,10 +34,7 @@ import tempfile
|
|||||||
import zlib
|
import zlib
|
||||||
import tarfile
|
import tarfile
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
try:
|
from cStringIO import StringIO
|
||||||
from cStringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
from io import BytesIO as StringIO
|
|
||||||
from binascii import a2b_hex, b2a_hex
|
from binascii import a2b_hex, b2a_hex
|
||||||
|
|
||||||
# Routines common to Mac and PC
|
# Routines common to Mac and PC
|
||||||
@@ -48,11 +49,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data,str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -90,20 +90,22 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["kindlekey.py"]
|
return [u"kindlekey.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
STORAGE = "backup.ab"
|
STORAGE = u"backup.ab"
|
||||||
STORAGE1 = "AmazonSecureStorage.xml"
|
STORAGE1 = u"AmazonSecureStorage.xml"
|
||||||
STORAGE2 = "map_data_storage.db"
|
STORAGE2 = u"map_data_storage.db"
|
||||||
|
|
||||||
class AndroidObfuscation(object):
|
class AndroidObfuscation(object):
|
||||||
'''AndroidObfuscation
|
'''AndroidObfuscation
|
||||||
@@ -136,7 +138,7 @@ class AndroidObfuscationV2(AndroidObfuscation):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
count = 503
|
count = 503
|
||||||
password = b'Thomsun was here!'
|
password = 'Thomsun was here!'
|
||||||
|
|
||||||
def __init__(self, salt):
|
def __init__(self, salt):
|
||||||
key = self.password + salt
|
key = self.password + salt
|
||||||
@@ -182,7 +184,7 @@ def get_serials1(path=STORAGE1):
|
|||||||
obfuscation = AndroidObfuscation()
|
obfuscation = AndroidObfuscation()
|
||||||
|
|
||||||
def get_value(key):
|
def get_value(key):
|
||||||
encrypted_key = obfuscation.encrypt(a2b_hex(key))
|
encrypted_key = obfuscation.encrypt(key)
|
||||||
encrypted_value = storage.get(encrypted_key)
|
encrypted_value = storage.get(encrypted_key)
|
||||||
if encrypted_value:
|
if encrypted_value:
|
||||||
return obfuscation.decrypt(encrypted_value)
|
return obfuscation.decrypt(encrypted_value)
|
||||||
@@ -217,14 +219,15 @@ def get_serials2(path=STORAGE2):
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
connection = sqlite3.connect(path)
|
connection = sqlite3.connect(path)
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute('''select device_data_value from device_data where device_data_key like '%serial.number%' ''')
|
cursor.execute('''select userdata_value from userdata where userdata_key like '%/%token.device.deviceserialname%' ''')
|
||||||
device_data_keys = cursor.fetchall()
|
userdata_keys = cursor.fetchall()
|
||||||
dsns = []
|
dsns = []
|
||||||
for device_data_row in device_data_keys:
|
for userdata_row in userdata_keys:
|
||||||
try:
|
try:
|
||||||
if device_data_row and device_data_row[0]:
|
if userdata_row and userdata_row[0]:
|
||||||
if len(device_data_row[0]) > 0:
|
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||||
dsns.append(device_data_row[0])
|
if len(userdata_utf8) > 0:
|
||||||
|
dsns.append(userdata_utf8)
|
||||||
except:
|
except:
|
||||||
print("Error getting one of the device serial name keys")
|
print("Error getting one of the device serial name keys")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -237,24 +240,22 @@ def get_serials2(path=STORAGE2):
|
|||||||
for userdata_row in userdata_keys:
|
for userdata_row in userdata_keys:
|
||||||
try:
|
try:
|
||||||
if userdata_row and userdata_row[0]:
|
if userdata_row and userdata_row[0]:
|
||||||
if len(userdata_row[0]) > 0:
|
userdata_utf8 = userdata_row[0].encode('utf8')
|
||||||
if ',' in userdata_row[0]:
|
if len(userdata_utf8) > 0:
|
||||||
splits = userdata_row[0].split(',')
|
tokens.append(userdata_utf8)
|
||||||
for split in splits:
|
|
||||||
tokens.append(split)
|
|
||||||
tokens.append(userdata_row[0])
|
|
||||||
except:
|
except:
|
||||||
print("Error getting one of the account token keys")
|
print("Error getting one of the account token keys")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
tokens = list(set(tokens))
|
tokens = list(set(tokens))
|
||||||
|
|
||||||
serials = []
|
serials = []
|
||||||
for x in dsns:
|
for x in dsns:
|
||||||
|
serials.append(x)
|
||||||
for y in tokens:
|
for y in tokens:
|
||||||
serials.append(x)
|
serials.append('%s%s' % (x, y))
|
||||||
serials.append(y)
|
for y in tokens:
|
||||||
serials.append(x+y)
|
serials.append(y)
|
||||||
return serials
|
return serials
|
||||||
|
|
||||||
def get_serials(path=STORAGE):
|
def get_serials(path=STORAGE):
|
||||||
@@ -276,7 +277,7 @@ def get_serials(path=STORAGE):
|
|||||||
try :
|
try :
|
||||||
read = open(path, 'rb')
|
read = open(path, 'rb')
|
||||||
head = read.read(24)
|
head = read.read(24)
|
||||||
if head[:14] == b'ANDROID BACKUP':
|
if head[:14] == 'ANDROID BACKUP':
|
||||||
output = StringIO(zlib.decompress(read.read()))
|
output = StringIO(zlib.decompress(read.read()))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -312,22 +313,22 @@ __all__ = [ 'get_serials', 'getkey']
|
|||||||
def getkey(outfile, inpath):
|
def getkey(outfile, inpath):
|
||||||
keys = get_serials(inpath)
|
keys = get_serials(inpath)
|
||||||
if len(keys) > 0:
|
if len(keys) > 0:
|
||||||
with open(outfile, 'w') as keyfileout:
|
with file(outfile, 'w') as keyfileout:
|
||||||
for key in keys:
|
for key in keys:
|
||||||
keyfileout.write(b2a_hex(key))
|
keyfileout.write(key)
|
||||||
keyfileout.write("\n")
|
keyfileout.write("\n")
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def usage(progname):
|
def usage(progname):
|
||||||
print("Decrypts the serial number(s) of Kindle For Android from Android backup or file")
|
print(u"Decrypts the serial number(s) of Kindle For Android from Android backup or file")
|
||||||
print("Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+.")
|
print(u"Get backup.ab file using adb backup com.amazon.kindle for Android 4.0+.")
|
||||||
print("Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml")
|
print(u"Otherwise extract AmazonSecureStorage.xml from /data/data/com.amazon.kindle/shared_prefs/AmazonSecureStorage.xml")
|
||||||
print("Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db")
|
print(u"Or map_data_storage.db from /data/data/com.amazon.kindle/databases/map_data_storage.db")
|
||||||
print("")
|
print(u"")
|
||||||
print("Usage:")
|
print(u"Usage:")
|
||||||
print(" {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname))
|
print(u" {0:s} [-h] [-b <backup.ab>] [<outfile.k4a>]".format(progname))
|
||||||
|
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
@@ -335,13 +336,13 @@ def cli_main():
|
|||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
print("{0} v{1}\nCopyright © 2010-2020 Thom, Apprentice Harper et al.".format(progname,__version__))
|
print(u"{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "hb:")
|
opts, args = getopt.getopt(argv[1:], "hb:")
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError, err:
|
||||||
usage(progname)
|
usage(progname)
|
||||||
print("\nError in options or arguments: {0}".format(err.args[0]))
|
print(u"\nError in options or arguments: {0}".format(err.args[0]))
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
inpath = ""
|
inpath = ""
|
||||||
@@ -373,92 +374,92 @@ def cli_main():
|
|||||||
|
|
||||||
if not os.path.isfile(inpath):
|
if not os.path.isfile(inpath):
|
||||||
usage(progname)
|
usage(progname)
|
||||||
print("\n{0:s} file not found".format(inpath))
|
print(u"\n{0:s} file not found".format(inpath))
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
if getkey(outfile, inpath):
|
if getkey(outfile, inpath):
|
||||||
print("\nSaved Kindle for Android key to {0}".format(outfile))
|
print(u"\nSaved Kindle for Android key to {0}".format(outfile))
|
||||||
else:
|
else:
|
||||||
print("\nCould not retrieve Kindle for Android key.")
|
print(u"\nCould not retrieve Kindle for Android key.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import Tkinter
|
||||||
import tkinter.constants
|
import Tkconstants
|
||||||
import tkinter.messagebox
|
import tkMessageBox
|
||||||
import tkinter.filedialog
|
import tkFileDialog
|
||||||
except:
|
except:
|
||||||
print("tkinter not installed")
|
print("Tkinter not installed")
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class DecryptionDialog(tkinter.Frame):
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
tkinter.Frame.__init__(self, root, border=5)
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
self.status = tkinter.Label(self, text="Select backup.ab file")
|
self.status = Tkinter.Label(self, text=u"Select backup.ab file")
|
||||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||||
body = tkinter.Frame(self)
|
body = Tkinter.Frame(self)
|
||||||
body.pack(fill=tkinter.constants.X, expand=1)
|
body.pack(fill=Tkconstants.X, expand=1)
|
||||||
sticky = tkinter.constants.E + tkinter.constants.W
|
sticky = Tkconstants.E + Tkconstants.W
|
||||||
body.grid_columnconfigure(1, weight=2)
|
body.grid_columnconfigure(1, weight=2)
|
||||||
tkinter.Label(body, text="Backup file").grid(row=0, column=0)
|
Tkinter.Label(body, text=u"Backup file").grid(row=0, column=0)
|
||||||
self.keypath = tkinter.Entry(body, width=40)
|
self.keypath = Tkinter.Entry(body, width=40)
|
||||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||||
self.keypath.insert(2, "backup.ab")
|
self.keypath.insert(2, u"backup.ab")
|
||||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||||
button.grid(row=0, column=2)
|
button.grid(row=0, column=2)
|
||||||
buttons = tkinter.Frame(self)
|
buttons = Tkinter.Frame(self)
|
||||||
buttons.pack()
|
buttons.pack()
|
||||||
button2 = tkinter.Button(
|
button2 = Tkinter.Button(
|
||||||
buttons, text="Extract", width=10, command=self.generate)
|
buttons, text=u"Extract", width=10, command=self.generate)
|
||||||
button2.pack(side=tkinter.constants.LEFT)
|
button2.pack(side=Tkconstants.LEFT)
|
||||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||||
button3 = tkinter.Button(
|
button3 = Tkinter.Button(
|
||||||
buttons, text="Quit", width=10, command=self.quit)
|
buttons, text=u"Quit", width=10, command=self.quit)
|
||||||
button3.pack(side=tkinter.constants.RIGHT)
|
button3.pack(side=Tkconstants.RIGHT)
|
||||||
|
|
||||||
def get_keypath(self):
|
def get_keypath(self):
|
||||||
keypath = tkinter.filedialog.askopenfilename(
|
keypath = tkFileDialog.askopenfilename(
|
||||||
parent=None, title="Select backup.ab file",
|
parent=None, title=u"Select backup.ab file",
|
||||||
defaultextension=".ab",
|
defaultextension=u".ab",
|
||||||
filetypes=[('adb backup com.amazon.kindle', '.ab'),
|
filetypes=[('adb backup com.amazon.kindle', '.ab'),
|
||||||
('All Files', '.*')])
|
('All Files', '.*')])
|
||||||
if keypath:
|
if keypath:
|
||||||
keypath = os.path.normpath(keypath)
|
keypath = os.path.normpath(keypath)
|
||||||
self.keypath.delete(0, tkinter.constants.END)
|
self.keypath.delete(0, Tkconstants.END)
|
||||||
self.keypath.insert(0, keypath)
|
self.keypath.insert(0, keypath)
|
||||||
return
|
return
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
inpath = self.keypath.get()
|
inpath = self.keypath.get()
|
||||||
self.status['text'] = "Getting key..."
|
self.status['text'] = u"Getting key..."
|
||||||
try:
|
try:
|
||||||
keys = get_serials(inpath)
|
keys = get_serials(inpath)
|
||||||
keycount = 0
|
keycount = 0
|
||||||
for key in keys:
|
for key in keys:
|
||||||
while True:
|
while True:
|
||||||
keycount += 1
|
keycount += 1
|
||||||
outfile = os.path.join(progpath,"kindlekey{0:d}.k4a".format(keycount))
|
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4a".format(keycount))
|
||||||
if not os.path.exists(outfile):
|
if not os.path.exists(outfile):
|
||||||
break
|
break
|
||||||
|
|
||||||
with open(outfile, 'w') as keyfileout:
|
with file(outfile, 'w') as keyfileout:
|
||||||
keyfileout.write(key)
|
keyfileout.write(key)
|
||||||
success = True
|
success = True
|
||||||
tkinter.messagebox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
|
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
self.status['text'] = "Error: {0}".format(e.args[0])
|
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||||
return
|
return
|
||||||
self.status['text'] = "Select backup.ab file"
|
self.status['text'] = u"Select backup.ab file"
|
||||||
|
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progpath, progname = os.path.split(argv[0])
|
progpath, progname = os.path.split(argv[0])
|
||||||
root = tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
root.title("Kindle for Android Key Extraction v.{0}".format(__version__))
|
root.title(u"Kindle for Android Key Extraction v.{0}".format(__version__))
|
||||||
root.resizable(True, False)
|
root.resizable(True, False)
|
||||||
root.minsize(300, 0)
|
root.minsize(300, 0)
|
||||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import sys, os
|
import sys, os
|
||||||
import locale
|
import locale
|
||||||
import codecs
|
import codecs
|
||||||
import importlib
|
|
||||||
|
|
||||||
# get sys.argv arguments and encode them into utf-8
|
# get sys.argv arguments and encode them into utf-8
|
||||||
def unicode_argv():
|
def unicode_argv():
|
||||||
@@ -35,13 +34,15 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["DeDRM.py"]
|
return [u"DeDRM.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
|
|
||||||
def add_cp65001_codec():
|
def add_cp65001_codec():
|
||||||
@@ -58,7 +59,7 @@ def set_utf8_default_encoding():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Regenerate setdefaultencoding.
|
# Regenerate setdefaultencoding.
|
||||||
importlib.reload(sys)
|
reload(sys)
|
||||||
sys.setdefaultencoding('utf-8')
|
sys.setdefaultencoding('utf-8')
|
||||||
|
|
||||||
for attr in dir(locale):
|
for attr in dir(locale):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
# and/or sell copies of the Software, and to permit persons to whom the
|
# and/or sell copies of the Software, and to permit persons to whom the
|
||||||
# Software is furnished to do so, subject to the following conditions:
|
# Software is furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# The above copyright notice and this permission notice shall be included in
|
||||||
# all copies or substantial portions of the Software.
|
# all copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
@@ -29,8 +29,6 @@
|
|||||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
# DEALINGS IN THE SOFTWARE.
|
# DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
# Adjusted for Python 3, September 2020
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AskFolder(...) -- Ask the user to select a folder Windows specific
|
AskFolder(...) -- Ask the user to select a folder Windows specific
|
||||||
"""
|
"""
|
||||||
@@ -166,15 +164,15 @@ def AskFolder(
|
|||||||
def BrowseCallback(hwnd, uMsg, lParam, lpData):
|
def BrowseCallback(hwnd, uMsg, lParam, lpData):
|
||||||
if uMsg == BFFM_INITIALIZED:
|
if uMsg == BFFM_INITIALIZED:
|
||||||
if actionButtonLabel:
|
if actionButtonLabel:
|
||||||
label = str(actionButtonLabel, errors='replace')
|
label = unicode(actionButtonLabel, errors='replace')
|
||||||
user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label)
|
user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label)
|
||||||
if cancelButtonLabel:
|
if cancelButtonLabel:
|
||||||
label = str(cancelButtonLabel, errors='replace')
|
label = unicode(cancelButtonLabel, errors='replace')
|
||||||
cancelButton = user32.GetDlgItem(hwnd, IDCANCEL)
|
cancelButton = user32.GetDlgItem(hwnd, IDCANCEL)
|
||||||
if cancelButton:
|
if cancelButton:
|
||||||
user32.SetWindowTextW(cancelButton, label)
|
user32.SetWindowTextW(cancelButton, label)
|
||||||
if windowTitle:
|
if windowTitle:
|
||||||
title = str(windowTitle, errors='replace')
|
title = unicode(windowTitle, erros='replace')
|
||||||
user32.SetWindowTextW(hwnd, title)
|
user32.SetWindowTextW(hwnd, title)
|
||||||
if defaultLocation:
|
if defaultLocation:
|
||||||
user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\'))
|
user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\'))
|
||||||
@@ -202,7 +200,7 @@ def AskFolder(
|
|||||||
if not pidl:
|
if not pidl:
|
||||||
result = None
|
result = None
|
||||||
else:
|
else:
|
||||||
path = LPCWSTR(" " * (MAX_PATH+1))
|
path = LPCWSTR(u" " * (MAX_PATH+1))
|
||||||
shell32.SHGetPathFromIDListW(pidl, path)
|
shell32.SHGetPathFromIDListW(pidl, path)
|
||||||
ole32.CoTaskMemFree(pidl)
|
ole32.CoTaskMemFree(pidl)
|
||||||
result = path.value
|
result = path.value
|
||||||
|
|||||||
423
DeDRM_plugin/config.py
Executable file → Normal file
423
DeDRM_plugin/config.py
Executable file → Normal file
@@ -1,18 +1,28 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
# Python 3, September 2020
|
|
||||||
|
|
||||||
# Standard Python modules.
|
# Standard Python modules.
|
||||||
import os, traceback, json, codecs
|
import os, traceback, json
|
||||||
|
|
||||||
from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
|
# PyQT4 modules (part of calibre).
|
||||||
|
try:
|
||||||
|
from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
|
||||||
QGroupBox, QPushButton, QListWidget, QListWidgetItem,
|
QGroupBox, QPushButton, QListWidget, QListWidgetItem,
|
||||||
QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
|
QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
|
||||||
|
except ImportError:
|
||||||
from PyQt5 import Qt as QtGui
|
from PyQt4.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
|
||||||
|
QGroupBox, QPushButton, QListWidget, QListWidgetItem,
|
||||||
|
QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
|
||||||
|
try:
|
||||||
|
from PyQt5 import Qt as QtGui
|
||||||
|
except ImportError:
|
||||||
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
# calibre modules and constants.
|
# calibre modules and constants.
|
||||||
@@ -73,32 +83,32 @@ class ConfigWidget(QWidget):
|
|||||||
button_layout = QVBoxLayout()
|
button_layout = QVBoxLayout()
|
||||||
keys_group_box_layout.addLayout(button_layout)
|
keys_group_box_layout.addLayout(button_layout)
|
||||||
self.bandn_button = QtGui.QPushButton(self)
|
self.bandn_button = QtGui.QPushButton(self)
|
||||||
self.bandn_button.setToolTip(_("Click to manage keys for Barnes and Noble ebooks"))
|
self.bandn_button.setToolTip(_(u"Click to manage keys for Barnes and Noble ebooks"))
|
||||||
self.bandn_button.setText("Barnes and Noble ebooks")
|
self.bandn_button.setText(u"Barnes and Noble ebooks")
|
||||||
self.bandn_button.clicked.connect(self.bandn_keys)
|
self.bandn_button.clicked.connect(self.bandn_keys)
|
||||||
self.kindle_android_button = QtGui.QPushButton(self)
|
self.kindle_android_button = QtGui.QPushButton(self)
|
||||||
self.kindle_android_button.setToolTip(_("Click to manage keys for Kindle for Android ebooks"))
|
self.kindle_android_button.setToolTip(_(u"Click to manage keys for Kindle for Android ebooks"))
|
||||||
self.kindle_android_button.setText("Kindle for Android ebooks")
|
self.kindle_android_button.setText(u"Kindle for Android ebooks")
|
||||||
self.kindle_android_button.clicked.connect(self.kindle_android)
|
self.kindle_android_button.clicked.connect(self.kindle_android)
|
||||||
self.kindle_serial_button = QtGui.QPushButton(self)
|
self.kindle_serial_button = QtGui.QPushButton(self)
|
||||||
self.kindle_serial_button.setToolTip(_("Click to manage eInk Kindle serial numbers for Kindle ebooks"))
|
self.kindle_serial_button.setToolTip(_(u"Click to manage eInk Kindle serial numbers for Kindle ebooks"))
|
||||||
self.kindle_serial_button.setText("eInk Kindle ebooks")
|
self.kindle_serial_button.setText(u"eInk Kindle ebooks")
|
||||||
self.kindle_serial_button.clicked.connect(self.kindle_serials)
|
self.kindle_serial_button.clicked.connect(self.kindle_serials)
|
||||||
self.kindle_key_button = QtGui.QPushButton(self)
|
self.kindle_key_button = QtGui.QPushButton(self)
|
||||||
self.kindle_key_button.setToolTip(_("Click to manage keys for Kindle for Mac/PC ebooks"))
|
self.kindle_key_button.setToolTip(_(u"Click to manage keys for Kindle for Mac/PC ebooks"))
|
||||||
self.kindle_key_button.setText("Kindle for Mac/PC ebooks")
|
self.kindle_key_button.setText(u"Kindle for Mac/PC ebooks")
|
||||||
self.kindle_key_button.clicked.connect(self.kindle_keys)
|
self.kindle_key_button.clicked.connect(self.kindle_keys)
|
||||||
self.adept_button = QtGui.QPushButton(self)
|
self.adept_button = QtGui.QPushButton(self)
|
||||||
self.adept_button.setToolTip(_("Click to manage keys for Adobe Digital Editions ebooks"))
|
self.adept_button.setToolTip(_(u"Click to manage keys for Adobe Digital Editions ebooks"))
|
||||||
self.adept_button.setText("Adobe Digital Editions ebooks")
|
self.adept_button.setText(u"Adobe Digital Editions ebooks")
|
||||||
self.adept_button.clicked.connect(self.adept_keys)
|
self.adept_button.clicked.connect(self.adept_keys)
|
||||||
self.mobi_button = QtGui.QPushButton(self)
|
self.mobi_button = QtGui.QPushButton(self)
|
||||||
self.mobi_button.setToolTip(_("Click to manage PIDs for Mobipocket ebooks"))
|
self.mobi_button.setToolTip(_(u"Click to manage PIDs for Mobipocket ebooks"))
|
||||||
self.mobi_button.setText("Mobipocket ebooks")
|
self.mobi_button.setText(u"Mobipocket ebooks")
|
||||||
self.mobi_button.clicked.connect(self.mobi_keys)
|
self.mobi_button.clicked.connect(self.mobi_keys)
|
||||||
self.ereader_button = QtGui.QPushButton(self)
|
self.ereader_button = QtGui.QPushButton(self)
|
||||||
self.ereader_button.setToolTip(_("Click to manage keys for eReader ebooks"))
|
self.ereader_button.setToolTip(_(u"Click to manage keys for eReader ebooks"))
|
||||||
self.ereader_button.setText("eReader ebooks")
|
self.ereader_button.setText(u"eReader ebooks")
|
||||||
self.ereader_button.clicked.connect(self.ereader_keys)
|
self.ereader_button.clicked.connect(self.ereader_keys)
|
||||||
button_layout.addWidget(self.kindle_serial_button)
|
button_layout.addWidget(self.kindle_serial_button)
|
||||||
button_layout.addWidget(self.kindle_android_button)
|
button_layout.addWidget(self.kindle_android_button)
|
||||||
@@ -111,48 +121,48 @@ class ConfigWidget(QWidget):
|
|||||||
self.resize(self.sizeHint())
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
def kindle_serials(self):
|
def kindle_serials(self):
|
||||||
d = ManageKeysDialog(self,"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog)
|
d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
def kindle_android(self):
|
def kindle_android(self):
|
||||||
d = ManageKeysDialog(self,"Kindle for Android Key",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a')
|
d = ManageKeysDialog(self,u"Kindle for Android Key",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a')
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
def kindle_keys(self):
|
def kindle_keys(self):
|
||||||
if isosx or iswindows:
|
if isosx or iswindows:
|
||||||
d = ManageKeysDialog(self,"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i')
|
d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i')
|
||||||
else:
|
else:
|
||||||
# linux
|
# linux
|
||||||
d = ManageKeysDialog(self,"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i', self.tempdedrmprefs['kindlewineprefix'])
|
d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i', self.tempdedrmprefs['kindlewineprefix'])
|
||||||
d.exec_()
|
d.exec_()
|
||||||
self.tempdedrmprefs['kindlewineprefix'] = d.getwineprefix()
|
self.tempdedrmprefs['kindlewineprefix'] = d.getwineprefix()
|
||||||
|
|
||||||
def adept_keys(self):
|
def adept_keys(self):
|
||||||
if isosx or iswindows:
|
if isosx or iswindows:
|
||||||
d = ManageKeysDialog(self,"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der')
|
d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der')
|
||||||
else:
|
else:
|
||||||
# linux
|
# linux
|
||||||
d = ManageKeysDialog(self,"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der', self.tempdedrmprefs['adobewineprefix'])
|
d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der', self.tempdedrmprefs['adobewineprefix'])
|
||||||
d.exec_()
|
d.exec_()
|
||||||
self.tempdedrmprefs['adobewineprefix'] = d.getwineprefix()
|
self.tempdedrmprefs['adobewineprefix'] = d.getwineprefix()
|
||||||
|
|
||||||
def mobi_keys(self):
|
def mobi_keys(self):
|
||||||
d = ManageKeysDialog(self,"Mobipocket PID",self.tempdedrmprefs['pids'], AddPIDDialog)
|
d = ManageKeysDialog(self,u"Mobipocket PID",self.tempdedrmprefs['pids'], AddPIDDialog)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
def bandn_keys(self):
|
def bandn_keys(self):
|
||||||
d = ManageKeysDialog(self,"Barnes and Noble Key",self.tempdedrmprefs['bandnkeys'], AddBandNKeyDialog, 'b64')
|
d = ManageKeysDialog(self,u"Barnes and Noble Key",self.tempdedrmprefs['bandnkeys'], AddBandNKeyDialog, 'b64')
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
def ereader_keys(self):
|
def ereader_keys(self):
|
||||||
d = ManageKeysDialog(self,"eReader Key",self.tempdedrmprefs['ereaderkeys'], AddEReaderDialog, 'b63')
|
d = ManageKeysDialog(self,u"eReader Key",self.tempdedrmprefs['ereaderkeys'], AddEReaderDialog, 'b63')
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
def help_link_activated(self, url):
|
def help_link_activated(self, url):
|
||||||
def get_help_file_resource():
|
def get_help_file_resource():
|
||||||
# Copy the HTML helpfile to the plugin directory each time the
|
# Copy the HTML helpfile to the plugin directory each time the
|
||||||
# link is clicked in case the helpfile is updated in newer plugins.
|
# link is clicked in case the helpfile is updated in newer plugins.
|
||||||
file_path = os.path.join(config_dir, "plugins", "DeDRM", "help", help_file_name)
|
file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name)
|
||||||
with open(file_path,'w') as f:
|
with open(file_path,'w') as f:
|
||||||
f.write(self.load_resource(help_file_name))
|
f.write(self.load_resource(help_file_name))
|
||||||
return file_path
|
return file_path
|
||||||
@@ -181,17 +191,17 @@ class ConfigWidget(QWidget):
|
|||||||
|
|
||||||
|
|
||||||
class ManageKeysDialog(QDialog):
|
class ManageKeysDialog(QDialog):
|
||||||
def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = "", wineprefix = None):
|
def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = u"", wineprefix = None):
|
||||||
QDialog.__init__(self,parent)
|
QDialog.__init__(self,parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.key_type_name = key_type_name
|
self.key_type_name = key_type_name
|
||||||
self.plugin_keys = plugin_keys
|
self.plugin_keys = plugin_keys
|
||||||
self.create_key = create_key
|
self.create_key = create_key
|
||||||
self.keyfile_ext = keyfile_ext
|
self.keyfile_ext = keyfile_ext
|
||||||
self.import_key = (keyfile_ext != "")
|
self.import_key = (keyfile_ext != u"")
|
||||||
self.binary_file = (keyfile_ext == "der")
|
self.binary_file = (keyfile_ext == u"der")
|
||||||
self.json_file = (keyfile_ext == "k4i")
|
self.json_file = (keyfile_ext == u"k4i")
|
||||||
self.android_file = (keyfile_ext == "k4a")
|
self.android_file = (keyfile_ext == u"k4a")
|
||||||
self.wineprefix = wineprefix
|
self.wineprefix = wineprefix
|
||||||
|
|
||||||
self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
|
self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
|
||||||
@@ -209,13 +219,13 @@ class ManageKeysDialog(QDialog):
|
|||||||
help_label.linkActivated.connect(self.help_link_activated)
|
help_label.linkActivated.connect(self.help_link_activated)
|
||||||
help_layout.addWidget(help_label)
|
help_layout.addWidget(help_label)
|
||||||
|
|
||||||
keys_group_box = QGroupBox(_("{0}s".format(self.key_type_name)), self)
|
keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self)
|
||||||
layout.addWidget(keys_group_box)
|
layout.addWidget(keys_group_box)
|
||||||
keys_group_box_layout = QHBoxLayout()
|
keys_group_box_layout = QHBoxLayout()
|
||||||
keys_group_box.setLayout(keys_group_box_layout)
|
keys_group_box.setLayout(keys_group_box_layout)
|
||||||
|
|
||||||
self.listy = QListWidget(self)
|
self.listy = QListWidget(self)
|
||||||
self.listy.setToolTip("{0}s that will be used to decrypt ebooks".format(self.key_type_name))
|
self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name))
|
||||||
self.listy.setSelectionMode(QAbstractItemView.SingleSelection)
|
self.listy.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||||
self.populate_list()
|
self.populate_list()
|
||||||
keys_group_box_layout.addWidget(self.listy)
|
keys_group_box_layout.addWidget(self.listy)
|
||||||
@@ -224,25 +234,25 @@ class ManageKeysDialog(QDialog):
|
|||||||
keys_group_box_layout.addLayout(button_layout)
|
keys_group_box_layout.addLayout(button_layout)
|
||||||
self._add_key_button = QtGui.QToolButton(self)
|
self._add_key_button = QtGui.QToolButton(self)
|
||||||
self._add_key_button.setIcon(QIcon(I('plus.png')))
|
self._add_key_button.setIcon(QIcon(I('plus.png')))
|
||||||
self._add_key_button.setToolTip("Create new {0}".format(self.key_type_name))
|
self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name))
|
||||||
self._add_key_button.clicked.connect(self.add_key)
|
self._add_key_button.clicked.connect(self.add_key)
|
||||||
button_layout.addWidget(self._add_key_button)
|
button_layout.addWidget(self._add_key_button)
|
||||||
|
|
||||||
self._delete_key_button = QtGui.QToolButton(self)
|
self._delete_key_button = QtGui.QToolButton(self)
|
||||||
self._delete_key_button.setToolTip(_("Delete highlighted key"))
|
self._delete_key_button.setToolTip(_(u"Delete highlighted key"))
|
||||||
self._delete_key_button.setIcon(QIcon(I('list_remove.png')))
|
self._delete_key_button.setIcon(QIcon(I('list_remove.png')))
|
||||||
self._delete_key_button.clicked.connect(self.delete_key)
|
self._delete_key_button.clicked.connect(self.delete_key)
|
||||||
button_layout.addWidget(self._delete_key_button)
|
button_layout.addWidget(self._delete_key_button)
|
||||||
|
|
||||||
if type(self.plugin_keys) == dict and self.import_key:
|
if type(self.plugin_keys) == dict and self.import_key:
|
||||||
self._rename_key_button = QtGui.QToolButton(self)
|
self._rename_key_button = QtGui.QToolButton(self)
|
||||||
self._rename_key_button.setToolTip(_("Rename highlighted key"))
|
self._rename_key_button.setToolTip(_(u"Rename highlighted key"))
|
||||||
self._rename_key_button.setIcon(QIcon(I('edit-select-all.png')))
|
self._rename_key_button.setIcon(QIcon(I('edit-select-all.png')))
|
||||||
self._rename_key_button.clicked.connect(self.rename_key)
|
self._rename_key_button.clicked.connect(self.rename_key)
|
||||||
button_layout.addWidget(self._rename_key_button)
|
button_layout.addWidget(self._rename_key_button)
|
||||||
|
|
||||||
self.export_key_button = QtGui.QToolButton(self)
|
self.export_key_button = QtGui.QToolButton(self)
|
||||||
self.export_key_button.setToolTip("Save highlighted key to a .{0} file".format(self.keyfile_ext))
|
self.export_key_button.setToolTip(u"Save highlighted key to a .{0} file".format(self.keyfile_ext))
|
||||||
self.export_key_button.setIcon(QIcon(I('save.png')))
|
self.export_key_button.setIcon(QIcon(I('save.png')))
|
||||||
self.export_key_button.clicked.connect(self.export_key)
|
self.export_key_button.clicked.connect(self.export_key)
|
||||||
button_layout.addWidget(self.export_key_button)
|
button_layout.addWidget(self.export_key_button)
|
||||||
@@ -254,7 +264,7 @@ class ManageKeysDialog(QDialog):
|
|||||||
wineprefix_layout = QHBoxLayout()
|
wineprefix_layout = QHBoxLayout()
|
||||||
layout.addLayout(wineprefix_layout)
|
layout.addLayout(wineprefix_layout)
|
||||||
wineprefix_layout.setAlignment(Qt.AlignCenter)
|
wineprefix_layout.setAlignment(Qt.AlignCenter)
|
||||||
self.wp_label = QLabel("WINEPREFIX:")
|
self.wp_label = QLabel(u"WINEPREFIX:")
|
||||||
wineprefix_layout.addWidget(self.wp_label)
|
wineprefix_layout.addWidget(self.wp_label)
|
||||||
self.wp_lineedit = QLineEdit(self)
|
self.wp_lineedit = QLineEdit(self)
|
||||||
wineprefix_layout.addWidget(self.wp_lineedit)
|
wineprefix_layout.addWidget(self.wp_lineedit)
|
||||||
@@ -266,8 +276,8 @@ class ManageKeysDialog(QDialog):
|
|||||||
layout.addLayout(migrate_layout)
|
layout.addLayout(migrate_layout)
|
||||||
if self.import_key:
|
if self.import_key:
|
||||||
migrate_layout.setAlignment(Qt.AlignJustify)
|
migrate_layout.setAlignment(Qt.AlignJustify)
|
||||||
self.migrate_btn = QPushButton("Import Existing Keyfiles", self)
|
self.migrate_btn = QPushButton(u"Import Existing Keyfiles", self)
|
||||||
self.migrate_btn.setToolTip("Import *.{0} files (created using other tools).".format(self.keyfile_ext))
|
self.migrate_btn.setToolTip(u"Import *.{0} files (created using other tools).".format(self.keyfile_ext))
|
||||||
self.migrate_btn.clicked.connect(self.migrate_wrapper)
|
self.migrate_btn.clicked.connect(self.migrate_wrapper)
|
||||||
migrate_layout.addWidget(self.migrate_btn)
|
migrate_layout.addWidget(self.migrate_btn)
|
||||||
migrate_layout.addStretch()
|
migrate_layout.addStretch()
|
||||||
@@ -279,8 +289,8 @@ class ManageKeysDialog(QDialog):
|
|||||||
|
|
||||||
def getwineprefix(self):
|
def getwineprefix(self):
|
||||||
if self.wineprefix is not None:
|
if self.wineprefix is not None:
|
||||||
return str(self.wp_lineedit.text()).strip()
|
return unicode(self.wp_lineedit.text()).strip()
|
||||||
return ""
|
return u""
|
||||||
|
|
||||||
def populate_list(self):
|
def populate_list(self):
|
||||||
if type(self.plugin_keys) == dict:
|
if type(self.plugin_keys) == dict:
|
||||||
@@ -300,15 +310,15 @@ class ManageKeysDialog(QDialog):
|
|||||||
new_key_value = d.key_value
|
new_key_value = d.key_value
|
||||||
if type(self.plugin_keys) == dict:
|
if type(self.plugin_keys) == dict:
|
||||||
if new_key_value in self.plugin_keys.values():
|
if new_key_value in self.plugin_keys.values():
|
||||||
old_key_name = [name for name, value in self.plugin_keys.items() if value == new_key_value][0]
|
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
|
||||||
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
||||||
"The new {1} is the same as the existing {1} named <strong>{0}</strong> and has not been added.".format(old_key_name,self.key_type_name), show=True)
|
u"The new {1} is the same as the existing {1} named <strong>{0}</strong> and has not been added.".format(old_key_name,self.key_type_name), show=True)
|
||||||
return
|
return
|
||||||
self.plugin_keys[d.key_name] = new_key_value
|
self.plugin_keys[d.key_name] = new_key_value
|
||||||
else:
|
else:
|
||||||
if new_key_value in self.plugin_keys:
|
if new_key_value in self.plugin_keys:
|
||||||
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
||||||
"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
|
u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.plugin_keys.append(d.key_value)
|
self.plugin_keys.append(d.key_value)
|
||||||
@@ -317,7 +327,7 @@ class ManageKeysDialog(QDialog):
|
|||||||
|
|
||||||
def rename_key(self):
|
def rename_key(self):
|
||||||
if not self.listy.currentItem():
|
if not self.listy.currentItem():
|
||||||
errmsg = "No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name)
|
errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name)
|
||||||
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||||
_(errmsg), show=True, show_copy_button=False)
|
_(errmsg), show=True, show_copy_button=False)
|
||||||
return
|
return
|
||||||
@@ -328,8 +338,8 @@ class ManageKeysDialog(QDialog):
|
|||||||
if d.result() != d.Accepted:
|
if d.result() != d.Accepted:
|
||||||
# rename cancelled or moot.
|
# rename cancelled or moot.
|
||||||
return
|
return
|
||||||
keyname = str(self.listy.currentItem().text())
|
keyname = unicode(self.listy.currentItem().text())
|
||||||
if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), "Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False):
|
if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named <strong>{0}</strong> to <strong>{1}</strong>?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False):
|
||||||
return
|
return
|
||||||
self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
|
self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
|
||||||
del self.plugin_keys[keyname]
|
del self.plugin_keys[keyname]
|
||||||
@@ -340,8 +350,8 @@ class ManageKeysDialog(QDialog):
|
|||||||
def delete_key(self):
|
def delete_key(self):
|
||||||
if not self.listy.currentItem():
|
if not self.listy.currentItem():
|
||||||
return
|
return
|
||||||
keyname = str(self.listy.currentItem().text())
|
keyname = unicode(self.listy.currentItem().text())
|
||||||
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), "Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
|
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
|
||||||
return
|
return
|
||||||
if type(self.plugin_keys) == dict:
|
if type(self.plugin_keys) == dict:
|
||||||
del self.plugin_keys[keyname]
|
del self.plugin_keys[keyname]
|
||||||
@@ -355,8 +365,8 @@ class ManageKeysDialog(QDialog):
|
|||||||
def get_help_file_resource():
|
def get_help_file_resource():
|
||||||
# Copy the HTML helpfile to the plugin directory each time the
|
# Copy the HTML helpfile to the plugin directory each time the
|
||||||
# link is clicked in case the helpfile is updated in newer plugins.
|
# link is clicked in case the helpfile is updated in newer plugins.
|
||||||
help_file_name = "{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name)
|
help_file_name = u"{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name)
|
||||||
file_path = os.path.join(config_dir, "plugins", "DeDRM", "help", help_file_name)
|
file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name)
|
||||||
with open(file_path,'w') as f:
|
with open(file_path,'w') as f:
|
||||||
f.write(self.parent.load_resource(help_file_name))
|
f.write(self.parent.load_resource(help_file_name))
|
||||||
return file_path
|
return file_path
|
||||||
@@ -364,9 +374,9 @@ class ManageKeysDialog(QDialog):
|
|||||||
open_url(QUrl(url))
|
open_url(QUrl(url))
|
||||||
|
|
||||||
def migrate_files(self):
|
def migrate_files(self):
|
||||||
unique_dlg_name = PLUGIN_NAME + "import {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
|
unique_dlg_name = PLUGIN_NAME + u"import {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
|
||||||
caption = "Select {0} files to import".format(self.key_type_name)
|
caption = u"Select {0} files to import".format(self.key_type_name)
|
||||||
filters = [("{0} files".format(self.key_type_name), [self.keyfile_ext])]
|
filters = [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])]
|
||||||
files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
|
files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
|
||||||
counter = 0
|
counter = 0
|
||||||
skipped = 0
|
skipped = 0
|
||||||
@@ -378,7 +388,7 @@ class ManageKeysDialog(QDialog):
|
|||||||
with open(fpath,'rb') as keyfile:
|
with open(fpath,'rb') as keyfile:
|
||||||
new_key_value = keyfile.read()
|
new_key_value = keyfile.read()
|
||||||
if self.binary_file:
|
if self.binary_file:
|
||||||
new_key_value = codecs.encode(new_key_value,'hex')
|
new_key_value = new_key_value.encode('hex')
|
||||||
elif self.json_file:
|
elif self.json_file:
|
||||||
new_key_value = json.loads(new_key_value)
|
new_key_value = json.loads(new_key_value)
|
||||||
elif self.android_file:
|
elif self.android_file:
|
||||||
@@ -388,27 +398,27 @@ class ManageKeysDialog(QDialog):
|
|||||||
for key in self.plugin_keys.keys():
|
for key in self.plugin_keys.keys():
|
||||||
if uStrCmp(new_key_name, key, True):
|
if uStrCmp(new_key_name, key, True):
|
||||||
skipped += 1
|
skipped += 1
|
||||||
msg = "A key with the name <strong>{0}</strong> already exists!\nSkipping key file <strong>{1}</strong>.\nRename the existing key and import again".format(new_key_name,filename)
|
msg = u"A key with the name <strong>{0}</strong> already exists!\nSkipping key file <strong>{1}</strong>.\nRename the existing key and import again".format(new_key_name,filename)
|
||||||
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||||
_(msg), show_copy_button=False, show=True)
|
_(msg), show_copy_button=False, show=True)
|
||||||
match = True
|
match = True
|
||||||
break
|
break
|
||||||
if not match:
|
if not match:
|
||||||
if new_key_value in self.plugin_keys.values():
|
if new_key_value in self.plugin_keys.values():
|
||||||
old_key_name = [name for name, value in self.plugin_keys.items() if value == new_key_value][0]
|
old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
|
||||||
skipped += 1
|
skipped += 1
|
||||||
info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||||
"The key in file {0} is the same as the existing key <strong>{1}</strong> and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True)
|
u"The key in file {0} is the same as the existing key <strong>{1}</strong> and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True)
|
||||||
else:
|
else:
|
||||||
counter += 1
|
counter += 1
|
||||||
self.plugin_keys[new_key_name] = new_key_value
|
self.plugin_keys[new_key_name] = new_key_value
|
||||||
|
|
||||||
msg = ""
|
msg = u""
|
||||||
if counter+skipped > 1:
|
if counter+skipped > 1:
|
||||||
if counter > 0:
|
if counter > 0:
|
||||||
msg += "Imported <strong>{0:d}</strong> key {1}. ".format(counter, "file" if counter == 1 else "files")
|
msg += u"Imported <strong>{0:d}</strong> key {1}. ".format(counter, u"file" if counter == 1 else u"files")
|
||||||
if skipped > 0:
|
if skipped > 0:
|
||||||
msg += "Skipped <strong>{0:d}</strong> key {1}.".format(skipped, "file" if counter == 1 else "files")
|
msg += u"Skipped <strong>{0:d}</strong> key {1}.".format(skipped, u"file" if counter == 1 else u"files")
|
||||||
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||||
_(msg), show_copy_button=False, show=True)
|
_(msg), show_copy_button=False, show=True)
|
||||||
return counter > 0
|
return counter > 0
|
||||||
@@ -420,30 +430,27 @@ class ManageKeysDialog(QDialog):
|
|||||||
|
|
||||||
def export_key(self):
|
def export_key(self):
|
||||||
if not self.listy.currentItem():
|
if not self.listy.currentItem():
|
||||||
errmsg = "No keyfile selected to export. Highlight a keyfile first."
|
errmsg = u"No keyfile selected to export. Highlight a keyfile first."
|
||||||
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||||
_(errmsg), show=True, show_copy_button=False)
|
_(errmsg), show=True, show_copy_button=False)
|
||||||
return
|
return
|
||||||
keyname = str(self.listy.currentItem().text())
|
keyname = unicode(self.listy.currentItem().text())
|
||||||
unique_dlg_name = PLUGIN_NAME + "export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
|
unique_dlg_name = PLUGIN_NAME + u"export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
|
||||||
caption = "Save {0} File as...".format(self.key_type_name)
|
caption = u"Save {0} File as...".format(self.key_type_name)
|
||||||
filters = [("{0} Files".format(self.key_type_name), ["{0}".format(self.keyfile_ext)])]
|
filters = [(u"{0} Files".format(self.key_type_name), [u"{0}".format(self.keyfile_ext)])]
|
||||||
defaultname = "{0}.{1}".format(keyname, self.keyfile_ext)
|
defaultname = u"{0}.{1}".format(keyname, self.keyfile_ext)
|
||||||
filename = choose_save_file(self, unique_dlg_name, caption, filters, all_files=False, initial_filename=defaultname)
|
filename = choose_save_file(self, unique_dlg_name, caption, filters, all_files=False, initial_filename=defaultname)
|
||||||
if filename:
|
if filename:
|
||||||
if self.binary_file:
|
with file(filename, 'wb') as fname:
|
||||||
with open(filename, 'wb') as fname:
|
if self.binary_file:
|
||||||
fname.write(codecs.decode(self.plugin_keys[keyname],'hex'))
|
fname.write(self.plugin_keys[keyname].decode('hex'))
|
||||||
elif self.json_file:
|
elif self.json_file:
|
||||||
with open(filename, 'w') as fname:
|
|
||||||
fname.write(json.dumps(self.plugin_keys[keyname]))
|
fname.write(json.dumps(self.plugin_keys[keyname]))
|
||||||
elif self.android_file:
|
elif self.android_file:
|
||||||
with open(filename, 'w') as fname:
|
|
||||||
for key in self.plugin_keys[keyname]:
|
for key in self.plugin_keys[keyname]:
|
||||||
fname.write(key)
|
fname.write(key)
|
||||||
fname.write('\n')
|
fname.write("\n")
|
||||||
else:
|
else:
|
||||||
with open(filename, 'w') as fname:
|
|
||||||
fname.write(self.plugin_keys[keyname])
|
fname.write(self.plugin_keys[keyname])
|
||||||
|
|
||||||
|
|
||||||
@@ -465,7 +472,7 @@ class RenameKeyDialog(QDialog):
|
|||||||
|
|
||||||
data_group_box_layout.addWidget(QLabel('New Key Name:', self))
|
data_group_box_layout.addWidget(QLabel('New Key Name:', self))
|
||||||
self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self)
|
self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self)
|
||||||
self.key_ledit.setToolTip("Enter a new name for this existing {0}.".format(parent.key_type_name))
|
self.key_ledit.setToolTip(u"Enter a new name for this existing {0}.".format(parent.key_type_name))
|
||||||
data_group_box_layout.addWidget(self.key_ledit)
|
data_group_box_layout.addWidget(self.key_ledit)
|
||||||
|
|
||||||
layout.addSpacing(20)
|
layout.addSpacing(20)
|
||||||
@@ -478,12 +485,12 @@ class RenameKeyDialog(QDialog):
|
|||||||
self.resize(self.sizeHint())
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
if not str(self.key_ledit.text()) or str(self.key_ledit.text()).isspace():
|
if not unicode(self.key_ledit.text()) or unicode(self.key_ledit.text()).isspace():
|
||||||
errmsg = "Key name field cannot be empty!"
|
errmsg = u"Key name field cannot be empty!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||||
_(errmsg), show=True, show_copy_button=False)
|
_(errmsg), show=True, show_copy_button=False)
|
||||||
if len(self.key_ledit.text()) < 4:
|
if len(self.key_ledit.text()) < 4:
|
||||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||||
_(errmsg), show=True, show_copy_button=False)
|
_(errmsg), show=True, show_copy_button=False)
|
||||||
if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()):
|
if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()):
|
||||||
@@ -492,14 +499,14 @@ class RenameKeyDialog(QDialog):
|
|||||||
for k in self.parent.plugin_keys.keys():
|
for k in self.parent.plugin_keys.keys():
|
||||||
if (uStrCmp(self.key_ledit.text(), k, True) and
|
if (uStrCmp(self.key_ledit.text(), k, True) and
|
||||||
not uStrCmp(k, self.parent.listy.currentItem().text(), True)):
|
not uStrCmp(k, self.parent.listy.currentItem().text(), True)):
|
||||||
errmsg = "The key name <strong>{0}</strong> is already being used.".format(self.key_ledit.text())
|
errmsg = u"The key name <strong>{0}</strong> is already being used.".format(self.key_ledit.text())
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
|
||||||
_(errmsg), show=True, show_copy_button=False)
|
_(errmsg), show=True, show_copy_button=False)
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key_name(self):
|
def key_name(self):
|
||||||
return str(self.key_ledit.text()).strip()
|
return unicode(self.key_ledit.text()).strip()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -512,48 +519,48 @@ class AddBandNKeyDialog(QDialog):
|
|||||||
def __init__(self, parent=None,):
|
def __init__(self, parent=None,):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.setWindowTitle("{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
data_group_box = QGroupBox("", self)
|
data_group_box = QGroupBox(u"", self)
|
||||||
layout.addWidget(data_group_box)
|
layout.addWidget(data_group_box)
|
||||||
data_group_box_layout = QVBoxLayout()
|
data_group_box_layout = QVBoxLayout()
|
||||||
data_group_box.setLayout(data_group_box_layout)
|
data_group_box.setLayout(data_group_box_layout)
|
||||||
|
|
||||||
key_group = QHBoxLayout()
|
key_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(key_group)
|
data_group_box_layout.addLayout(key_group)
|
||||||
key_group.addWidget(QLabel("Unique Key Name:", self))
|
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||||
self.key_ledit = QLineEdit("", self)
|
self.key_ledit = QLineEdit("", self)
|
||||||
self.key_ledit.setToolTip(_("<p>Enter an identifying name for this new key.</p>" +
|
self.key_ledit.setToolTip(_(u"<p>Enter an identifying name for this new key.</p>" +
|
||||||
"<p>It should be something that will help you remember " +
|
u"<p>It should be something that will help you remember " +
|
||||||
"what personal information was used to create it."))
|
u"what personal information was used to create it."))
|
||||||
key_group.addWidget(self.key_ledit)
|
key_group.addWidget(self.key_ledit)
|
||||||
|
|
||||||
name_group = QHBoxLayout()
|
name_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(name_group)
|
data_group_box_layout.addLayout(name_group)
|
||||||
name_group.addWidget(QLabel("B&N/nook account email address:", self))
|
name_group.addWidget(QLabel(u"B&N/nook account email address:", self))
|
||||||
self.name_ledit = QLineEdit("", self)
|
self.name_ledit = QLineEdit(u"", self)
|
||||||
self.name_ledit.setToolTip(_("<p>Enter your email address as it appears in your B&N " +
|
self.name_ledit.setToolTip(_(u"<p>Enter your email address as it appears in your B&N " +
|
||||||
"account.</p>" +
|
u"account.</p>" +
|
||||||
"<p>It will only be used to generate this " +
|
u"<p>It will only be used to generate this " +
|
||||||
"key and won\'t be stored anywhere " +
|
u"key and won\'t be stored anywhere " +
|
||||||
"in calibre or on your computer.</p>" +
|
u"in calibre or on your computer.</p>" +
|
||||||
"<p>eg: apprenticeharper@gmail.com</p>"))
|
u"<p>eg: apprenticeharper@gmail.com</p>"))
|
||||||
name_group.addWidget(self.name_ledit)
|
name_group.addWidget(self.name_ledit)
|
||||||
name_disclaimer_label = QLabel(_("(Will not be saved in configuration data)"), self)
|
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
|
||||||
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||||
data_group_box_layout.addWidget(name_disclaimer_label)
|
data_group_box_layout.addWidget(name_disclaimer_label)
|
||||||
|
|
||||||
ccn_group = QHBoxLayout()
|
ccn_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(ccn_group)
|
data_group_box_layout.addLayout(ccn_group)
|
||||||
ccn_group.addWidget(QLabel("B&N/nook account password:", self))
|
ccn_group.addWidget(QLabel(u"B&N/nook account password:", self))
|
||||||
self.cc_ledit = QLineEdit("", self)
|
self.cc_ledit = QLineEdit(u"", self)
|
||||||
self.cc_ledit.setToolTip(_("<p>Enter the password " +
|
self.cc_ledit.setToolTip(_(u"<p>Enter the password " +
|
||||||
"for your B&N account.</p>" +
|
u"for your B&N account.</p>" +
|
||||||
"<p>The password will only be used to generate this " +
|
u"<p>The password will only be used to generate this " +
|
||||||
"key and won\'t be stored anywhere in " +
|
u"key and won\'t be stored anywhere in " +
|
||||||
"calibre or on your computer."))
|
u"calibre or on your computer."))
|
||||||
ccn_group.addWidget(self.cc_ledit)
|
ccn_group.addWidget(self.cc_ledit)
|
||||||
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
||||||
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||||
@@ -562,13 +569,13 @@ class AddBandNKeyDialog(QDialog):
|
|||||||
|
|
||||||
key_group = QHBoxLayout()
|
key_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(key_group)
|
data_group_box_layout.addLayout(key_group)
|
||||||
key_group.addWidget(QLabel("Retrieved key:", self))
|
key_group.addWidget(QLabel(u"Retrieved key:", self))
|
||||||
self.key_display = QLabel("", self)
|
self.key_display = QLabel(u"", self)
|
||||||
self.key_display.setToolTip(_("Click the Retrieve Key button to fetch your B&N encryption key from the B&N servers"))
|
self.key_display.setToolTip(_(u"Click the Retrieve Key button to fetch your B&N encryption key from the B&N servers"))
|
||||||
key_group.addWidget(self.key_display)
|
key_group.addWidget(self.key_display)
|
||||||
self.retrieve_button = QtGui.QPushButton(self)
|
self.retrieve_button = QtGui.QPushButton(self)
|
||||||
self.retrieve_button.setToolTip(_("Click to retrieve your B&N encryption key from the B&N servers"))
|
self.retrieve_button.setToolTip(_(u"Click to retrieve your B&N encryption key from the B&N servers"))
|
||||||
self.retrieve_button.setText("Retrieve Key")
|
self.retrieve_button.setText(u"Retrieve Key")
|
||||||
self.retrieve_button.clicked.connect(self.retrieve_key)
|
self.retrieve_button.clicked.connect(self.retrieve_key)
|
||||||
key_group.addWidget(self.retrieve_button)
|
key_group.addWidget(self.retrieve_button)
|
||||||
layout.addSpacing(10)
|
layout.addSpacing(10)
|
||||||
@@ -582,35 +589,35 @@ class AddBandNKeyDialog(QDialog):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def key_name(self):
|
def key_name(self):
|
||||||
return str(self.key_ledit.text()).strip()
|
return unicode(self.key_ledit.text()).strip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key_value(self):
|
def key_value(self):
|
||||||
return str(self.key_display.text()).strip()
|
return unicode(self.key_display.text()).strip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_name(self):
|
def user_name(self):
|
||||||
return str(self.name_ledit.text()).strip().lower().replace(' ','')
|
return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cc_number(self):
|
def cc_number(self):
|
||||||
return str(self.cc_ledit.text()).strip()
|
return unicode(self.cc_ledit.text()).strip()
|
||||||
|
|
||||||
def retrieve_key(self):
|
def retrieve_key(self):
|
||||||
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key
|
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key
|
||||||
fetched_key = fetch_bandn_key(self.user_name,self.cc_number)
|
fetched_key = fetch_bandn_key(self.user_name,self.cc_number)
|
||||||
if fetched_key == "":
|
if fetched_key == "":
|
||||||
errmsg = "Could not retrieve key. Check username, password and intenet connectivity and try again."
|
errmsg = u"Could not retrieve key. Check username, password and intenet connectivity and try again."
|
||||||
error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
else:
|
else:
|
||||||
self.key_display.setText(fetched_key)
|
self.key_display.setText(fetched_key)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
||||||
errmsg = "All fields are required!"
|
errmsg = u"All fields are required!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
if len(self.key_name) < 4:
|
if len(self.key_name) < 4:
|
||||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
if len(self.key_value) == 0:
|
if len(self.key_value) == 0:
|
||||||
self.retrieve_key()
|
self.retrieve_key()
|
||||||
@@ -622,37 +629,37 @@ class AddEReaderDialog(QDialog):
|
|||||||
def __init__(self, parent=None,):
|
def __init__(self, parent=None,):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.setWindowTitle("{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
self.setWindowTitle(u"{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
data_group_box = QGroupBox("", self)
|
data_group_box = QGroupBox(u"", self)
|
||||||
layout.addWidget(data_group_box)
|
layout.addWidget(data_group_box)
|
||||||
data_group_box_layout = QVBoxLayout()
|
data_group_box_layout = QVBoxLayout()
|
||||||
data_group_box.setLayout(data_group_box_layout)
|
data_group_box.setLayout(data_group_box_layout)
|
||||||
|
|
||||||
key_group = QHBoxLayout()
|
key_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(key_group)
|
data_group_box_layout.addLayout(key_group)
|
||||||
key_group.addWidget(QLabel("Unique Key Name:", self))
|
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||||
self.key_ledit = QLineEdit("", self)
|
self.key_ledit = QLineEdit("", self)
|
||||||
self.key_ledit.setToolTip("<p>Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.")
|
self.key_ledit.setToolTip(u"<p>Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.")
|
||||||
key_group.addWidget(self.key_ledit)
|
key_group.addWidget(self.key_ledit)
|
||||||
|
|
||||||
name_group = QHBoxLayout()
|
name_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(name_group)
|
data_group_box_layout.addLayout(name_group)
|
||||||
name_group.addWidget(QLabel("Your Name:", self))
|
name_group.addWidget(QLabel(u"Your Name:", self))
|
||||||
self.name_ledit = QLineEdit("", self)
|
self.name_ledit = QLineEdit(u"", self)
|
||||||
self.name_ledit.setToolTip("Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)")
|
self.name_ledit.setToolTip(u"Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)")
|
||||||
name_group.addWidget(self.name_ledit)
|
name_group.addWidget(self.name_ledit)
|
||||||
name_disclaimer_label = QLabel(_("(Will not be saved in configuration data)"), self)
|
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
|
||||||
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||||
data_group_box_layout.addWidget(name_disclaimer_label)
|
data_group_box_layout.addWidget(name_disclaimer_label)
|
||||||
|
|
||||||
ccn_group = QHBoxLayout()
|
ccn_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(ccn_group)
|
data_group_box_layout.addLayout(ccn_group)
|
||||||
ccn_group.addWidget(QLabel("Credit Card#:", self))
|
ccn_group.addWidget(QLabel(u"Credit Card#:", self))
|
||||||
self.cc_ledit = QLineEdit("", self)
|
self.cc_ledit = QLineEdit(u"", self)
|
||||||
self.cc_ledit.setToolTip("<p>Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.")
|
self.cc_ledit.setToolTip(u"<p>Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.")
|
||||||
ccn_group.addWidget(self.cc_ledit)
|
ccn_group.addWidget(self.cc_ledit)
|
||||||
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
||||||
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||||
@@ -668,31 +675,31 @@ class AddEReaderDialog(QDialog):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def key_name(self):
|
def key_name(self):
|
||||||
return str(self.key_ledit.text()).strip()
|
return unicode(self.key_ledit.text()).strip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key_value(self):
|
def key_value(self):
|
||||||
from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key
|
from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key
|
||||||
return codecs.encode(generate_ereader_key(self.user_name, self.cc_number),'hex')
|
return generate_ereader_key(self.user_name,self.cc_number).encode('hex')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_name(self):
|
def user_name(self):
|
||||||
return str(self.name_ledit.text()).strip().lower().replace(' ','')
|
return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cc_number(self):
|
def cc_number(self):
|
||||||
return str(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
|
return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
|
||||||
|
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
||||||
errmsg = "All fields are required!"
|
errmsg = u"All fields are required!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
if not self.cc_number.isdigit():
|
if not self.cc_number.isdigit():
|
||||||
errmsg = "Numbers only in the credit card number field!"
|
errmsg = u"Numbers only in the credit card number field!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
if len(self.key_name) < 4:
|
if len(self.key_name) < 4:
|
||||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
@@ -701,7 +708,7 @@ class AddAdeptDialog(QDialog):
|
|||||||
def __init__(self, parent=None,):
|
def __init__(self, parent=None,):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.setWindowTitle("{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
self.setWindowTitle(u"{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
@@ -711,34 +718,34 @@ class AddAdeptDialog(QDialog):
|
|||||||
|
|
||||||
defaultkeys = adeptkeys()
|
defaultkeys = adeptkeys()
|
||||||
else: # linux
|
else: # linux
|
||||||
from .wineutils import WineGetKeys
|
from wineutils import WineGetKeys
|
||||||
|
|
||||||
scriptpath = os.path.join(parent.parent.alfdir,"adobekey.py")
|
scriptpath = os.path.join(parent.parent.alfdir,u"adobekey.py")
|
||||||
defaultkeys = WineGetKeys(scriptpath, ".der",parent.getwineprefix())
|
defaultkeys = WineGetKeys(scriptpath, u".der",parent.getwineprefix())
|
||||||
|
|
||||||
self.default_key = defaultkeys[0]
|
self.default_key = defaultkeys[0]
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.default_key = ""
|
self.default_key = u""
|
||||||
|
|
||||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
|
|
||||||
if len(self.default_key)>0:
|
if len(self.default_key)>0:
|
||||||
data_group_box = QGroupBox("", self)
|
data_group_box = QGroupBox(u"", self)
|
||||||
layout.addWidget(data_group_box)
|
layout.addWidget(data_group_box)
|
||||||
data_group_box_layout = QVBoxLayout()
|
data_group_box_layout = QVBoxLayout()
|
||||||
data_group_box.setLayout(data_group_box_layout)
|
data_group_box.setLayout(data_group_box_layout)
|
||||||
|
|
||||||
key_group = QHBoxLayout()
|
key_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(key_group)
|
data_group_box_layout.addLayout(key_group)
|
||||||
key_group.addWidget(QLabel("Unique Key Name:", self))
|
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||||
self.key_ledit = QLineEdit("default_key", self)
|
self.key_ledit = QLineEdit(u"default_key", self)
|
||||||
self.key_ledit.setToolTip("<p>Enter an identifying name for the current default Adobe Digital Editions key.")
|
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Adobe Digital Editions key.")
|
||||||
key_group.addWidget(self.key_ledit)
|
key_group.addWidget(self.key_ledit)
|
||||||
|
|
||||||
self.button_box.accepted.connect(self.accept)
|
self.button_box.accepted.connect(self.accept)
|
||||||
else:
|
else:
|
||||||
default_key_error = QLabel("The default encryption key for Adobe Digital Editions could not be found.", self)
|
default_key_error = QLabel(u"The default encryption key for Adobe Digital Editions could not be found.", self)
|
||||||
default_key_error.setAlignment(Qt.AlignHCenter)
|
default_key_error.setAlignment(Qt.AlignHCenter)
|
||||||
layout.addWidget(default_key_error)
|
layout.addWidget(default_key_error)
|
||||||
# if no default, bot buttons do the same
|
# if no default, bot buttons do the same
|
||||||
@@ -751,19 +758,19 @@ class AddAdeptDialog(QDialog):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def key_name(self):
|
def key_name(self):
|
||||||
return str(self.key_ledit.text()).strip()
|
return unicode(self.key_ledit.text()).strip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key_value(self):
|
def key_value(self):
|
||||||
return codecs.encode(self.default_key,'hex')
|
return self.default_key.encode('hex')
|
||||||
|
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||||
errmsg = "All fields are required!"
|
errmsg = u"All fields are required!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
if len(self.key_name) < 4:
|
if len(self.key_name) < 4:
|
||||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
@@ -772,7 +779,7 @@ class AddKindleDialog(QDialog):
|
|||||||
def __init__(self, parent=None,):
|
def __init__(self, parent=None,):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.setWindowTitle("{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
self.setWindowTitle(u"{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
@@ -782,37 +789,37 @@ class AddKindleDialog(QDialog):
|
|||||||
|
|
||||||
defaultkeys = kindlekeys()
|
defaultkeys = kindlekeys()
|
||||||
else: # linux
|
else: # linux
|
||||||
from .wineutils import WineGetKeys
|
from wineutils import WineGetKeys
|
||||||
|
|
||||||
scriptpath = os.path.join(parent.parent.alfdir,"kindlekey.py")
|
scriptpath = os.path.join(parent.parent.alfdir,u"kindlekey.py")
|
||||||
defaultkeys = WineGetKeys(scriptpath, ".k4i",parent.getwineprefix())
|
defaultkeys = WineGetKeys(scriptpath, u".k4i",parent.getwineprefix())
|
||||||
|
|
||||||
self.default_key = defaultkeys[0]
|
self.default_key = defaultkeys[0]
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.default_key = ""
|
self.default_key = u""
|
||||||
|
|
||||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
|
|
||||||
if len(self.default_key)>0:
|
if len(self.default_key)>0:
|
||||||
data_group_box = QGroupBox("", self)
|
data_group_box = QGroupBox(u"", self)
|
||||||
layout.addWidget(data_group_box)
|
layout.addWidget(data_group_box)
|
||||||
data_group_box_layout = QVBoxLayout()
|
data_group_box_layout = QVBoxLayout()
|
||||||
data_group_box.setLayout(data_group_box_layout)
|
data_group_box.setLayout(data_group_box_layout)
|
||||||
|
|
||||||
key_group = QHBoxLayout()
|
key_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(key_group)
|
data_group_box_layout.addLayout(key_group)
|
||||||
key_group.addWidget(QLabel("Unique Key Name:", self))
|
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||||
self.key_ledit = QLineEdit("default_key", self)
|
self.key_ledit = QLineEdit(u"default_key", self)
|
||||||
self.key_ledit.setToolTip("<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
|
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
|
||||||
key_group.addWidget(self.key_ledit)
|
key_group.addWidget(self.key_ledit)
|
||||||
|
|
||||||
self.button_box.accepted.connect(self.accept)
|
self.button_box.accepted.connect(self.accept)
|
||||||
else:
|
else:
|
||||||
default_key_error = QLabel("The default encryption key for Kindle for Mac/PC could not be found.", self)
|
default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self)
|
||||||
default_key_error.setAlignment(Qt.AlignHCenter)
|
default_key_error.setAlignment(Qt.AlignHCenter)
|
||||||
layout.addWidget(default_key_error)
|
layout.addWidget(default_key_error)
|
||||||
|
|
||||||
# if no default, both buttons do the same
|
# if no default, both buttons do the same
|
||||||
self.button_box.accepted.connect(self.reject)
|
self.button_box.accepted.connect(self.reject)
|
||||||
|
|
||||||
@@ -823,7 +830,7 @@ class AddKindleDialog(QDialog):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def key_name(self):
|
def key_name(self):
|
||||||
return str(self.key_ledit.text()).strip()
|
return unicode(self.key_ledit.text()).strip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key_value(self):
|
def key_value(self):
|
||||||
@@ -832,10 +839,10 @@ class AddKindleDialog(QDialog):
|
|||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||||
errmsg = "All fields are required!"
|
errmsg = u"All fields are required!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
if len(self.key_name) < 4:
|
if len(self.key_name) < 4:
|
||||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
@@ -844,20 +851,20 @@ class AddSerialDialog(QDialog):
|
|||||||
def __init__(self, parent=None,):
|
def __init__(self, parent=None,):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.setWindowTitle("{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
|
self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
data_group_box = QGroupBox("", self)
|
data_group_box = QGroupBox(u"", self)
|
||||||
layout.addWidget(data_group_box)
|
layout.addWidget(data_group_box)
|
||||||
data_group_box_layout = QVBoxLayout()
|
data_group_box_layout = QVBoxLayout()
|
||||||
data_group_box.setLayout(data_group_box_layout)
|
data_group_box.setLayout(data_group_box_layout)
|
||||||
|
|
||||||
key_group = QHBoxLayout()
|
key_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(key_group)
|
data_group_box_layout.addLayout(key_group)
|
||||||
key_group.addWidget(QLabel("EInk Kindle Serial Number:", self))
|
key_group.addWidget(QLabel(u"EInk Kindle Serial Number:", self))
|
||||||
self.key_ledit = QLineEdit("", self)
|
self.key_ledit = QLineEdit("", self)
|
||||||
self.key_ledit.setToolTip("Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
self.key_ledit.setToolTip(u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
||||||
key_group.addWidget(self.key_ledit)
|
key_group.addWidget(self.key_ledit)
|
||||||
|
|
||||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
@@ -869,18 +876,18 @@ class AddSerialDialog(QDialog):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def key_name(self):
|
def key_name(self):
|
||||||
return str(self.key_ledit.text()).strip()
|
return unicode(self.key_ledit.text()).strip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key_value(self):
|
def key_value(self):
|
||||||
return str(self.key_ledit.text()).replace(' ', '')
|
return unicode(self.key_ledit.text()).replace(' ', '')
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||||
errmsg = "Please enter an eInk Kindle Serial Number or click Cancel in the dialog."
|
errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog."
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
if len(self.key_name) != 16:
|
if len(self.key_name) != 16:
|
||||||
errmsg = "EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
@@ -890,36 +897,36 @@ class AddAndroidDialog(QDialog):
|
|||||||
|
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.setWindowTitle("{0} {1}: Add new Kindle for Android Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
self.setWindowTitle(u"{0} {1}: Add new Kindle for Android Key".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
|
|
||||||
data_group_box = QGroupBox("", self)
|
data_group_box = QGroupBox(u"", self)
|
||||||
layout.addWidget(data_group_box)
|
layout.addWidget(data_group_box)
|
||||||
data_group_box_layout = QVBoxLayout()
|
data_group_box_layout = QVBoxLayout()
|
||||||
data_group_box.setLayout(data_group_box_layout)
|
data_group_box.setLayout(data_group_box_layout)
|
||||||
|
|
||||||
file_group = QHBoxLayout()
|
file_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(file_group)
|
data_group_box_layout.addLayout(file_group)
|
||||||
add_btn = QPushButton("Choose Backup File", self)
|
add_btn = QPushButton(u"Choose Backup File", self)
|
||||||
add_btn.setToolTip("Import Kindle for Android backup file.")
|
add_btn.setToolTip(u"Import Kindle for Android backup file.")
|
||||||
add_btn.clicked.connect(self.get_android_file)
|
add_btn.clicked.connect(self.get_android_file)
|
||||||
file_group.addWidget(add_btn)
|
file_group.addWidget(add_btn)
|
||||||
self.selected_file_name = QLabel("",self)
|
self.selected_file_name = QLabel(u"",self)
|
||||||
self.selected_file_name.setAlignment(Qt.AlignHCenter)
|
self.selected_file_name.setAlignment(Qt.AlignHCenter)
|
||||||
file_group.addWidget(self.selected_file_name)
|
file_group.addWidget(self.selected_file_name)
|
||||||
|
|
||||||
key_group = QHBoxLayout()
|
key_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(key_group)
|
data_group_box_layout.addLayout(key_group)
|
||||||
key_group.addWidget(QLabel("Unique Key Name:", self))
|
key_group.addWidget(QLabel(u"Unique Key Name:", self))
|
||||||
self.key_ledit = QLineEdit("", self)
|
self.key_ledit = QLineEdit(u"", self)
|
||||||
self.key_ledit.setToolTip("<p>Enter an identifying name for the Android for Kindle key.")
|
self.key_ledit.setToolTip(u"<p>Enter an identifying name for the Android for Kindle key.")
|
||||||
key_group.addWidget(self.key_ledit)
|
key_group.addWidget(self.key_ledit)
|
||||||
#key_label = QLabel(_(''), self)
|
#key_label = QLabel(_(''), self)
|
||||||
#key_label.setAlignment(Qt.AlignHCenter)
|
#key_label.setAlignment(Qt.AlignHCenter)
|
||||||
#data_group_box_layout.addWidget(key_label)
|
#data_group_box_layout.addWidget(key_label)
|
||||||
|
|
||||||
self.button_box.accepted.connect(self.accept)
|
self.button_box.accepted.connect(self.accept)
|
||||||
self.button_box.rejected.connect(self.reject)
|
self.button_box.rejected.connect(self.reject)
|
||||||
layout.addWidget(self.button_box)
|
layout.addWidget(self.button_box)
|
||||||
@@ -927,23 +934,23 @@ class AddAndroidDialog(QDialog):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def key_name(self):
|
def key_name(self):
|
||||||
return str(self.key_ledit.text()).strip()
|
return unicode(self.key_ledit.text()).strip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def file_name(self):
|
def file_name(self):
|
||||||
return str(self.selected_file_name.text()).strip()
|
return unicode(self.selected_file_name.text()).strip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key_value(self):
|
def key_value(self):
|
||||||
return self.serials_from_file
|
return self.serials_from_file
|
||||||
|
|
||||||
def get_android_file(self):
|
def get_android_file(self):
|
||||||
unique_dlg_name = PLUGIN_NAME + "Import Kindle for Android backup file" #takes care of automatically remembering last directory
|
unique_dlg_name = PLUGIN_NAME + u"Import Kindle for Android backup file" #takes care of automatically remembering last directory
|
||||||
caption = "Select Kindle for Android backup file to add"
|
caption = u"Select Kindle for Android backup file to add"
|
||||||
filters = [("Kindle for Android backup files", ['db','ab','xml'])]
|
filters = [(u"Kindle for Android backup files", ['db','ab','xml'])]
|
||||||
files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
|
files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
|
||||||
self.serials_from_file = []
|
self.serials_from_file = []
|
||||||
file_name = ""
|
file_name = u""
|
||||||
if files:
|
if files:
|
||||||
# find the first selected file that yields some serial numbers
|
# find the first selected file that yields some serial numbers
|
||||||
for filename in files:
|
for filename in files:
|
||||||
@@ -954,17 +961,17 @@ class AddAndroidDialog(QDialog):
|
|||||||
file_name = os.path.basename(self.filename)
|
file_name = os.path.basename(self.filename)
|
||||||
self.serials_from_file.extend(file_serials)
|
self.serials_from_file.extend(file_serials)
|
||||||
self.selected_file_name.setText(file_name)
|
self.selected_file_name.setText(file_name)
|
||||||
|
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
if len(self.file_name) == 0 or len(self.key_value) == 0:
|
if len(self.file_name) == 0 or len(self.key_value) == 0:
|
||||||
errmsg = "Please choose a Kindle for Android backup file."
|
errmsg = u"Please choose a Kindle for Android backup file."
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||||
errmsg = "Please enter a key name."
|
errmsg = u"Please enter a key name."
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
if len(self.key_name) < 4:
|
if len(self.key_name) < 4:
|
||||||
errmsg = "Key name must be at <i>least</i> 4 characters long!"
|
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
@@ -972,20 +979,20 @@ class AddPIDDialog(QDialog):
|
|||||||
def __init__(self, parent=None,):
|
def __init__(self, parent=None,):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.setWindowTitle("{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION))
|
self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
data_group_box = QGroupBox("", self)
|
data_group_box = QGroupBox(u"", self)
|
||||||
layout.addWidget(data_group_box)
|
layout.addWidget(data_group_box)
|
||||||
data_group_box_layout = QVBoxLayout()
|
data_group_box_layout = QVBoxLayout()
|
||||||
data_group_box.setLayout(data_group_box_layout)
|
data_group_box.setLayout(data_group_box_layout)
|
||||||
|
|
||||||
key_group = QHBoxLayout()
|
key_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(key_group)
|
data_group_box_layout.addLayout(key_group)
|
||||||
key_group.addWidget(QLabel("PID:", self))
|
key_group.addWidget(QLabel(u"PID:", self))
|
||||||
self.key_ledit = QLineEdit("", self)
|
self.key_ledit = QLineEdit("", self)
|
||||||
self.key_ledit.setToolTip("Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
||||||
key_group.addWidget(self.key_ledit)
|
key_group.addWidget(self.key_ledit)
|
||||||
|
|
||||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
@@ -997,18 +1004,18 @@ class AddPIDDialog(QDialog):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def key_name(self):
|
def key_name(self):
|
||||||
return str(self.key_ledit.text()).strip()
|
return unicode(self.key_ledit.text()).strip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key_value(self):
|
def key_value(self):
|
||||||
return str(self.key_ledit.text()).strip()
|
return unicode(self.key_ledit.text()).strip()
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||||
errmsg = "Please enter a Mobipocket PID or click Cancel in the dialog."
|
errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog."
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
if len(self.key_name) != 8 and len(self.key_name) != 10:
|
if len(self.key_name) != 8 and len(self.key_name) != 10:
|
||||||
errmsg = "Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,20 @@
|
|||||||
#!/usr/bin/env python3
|
#! /usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
# For use with Topaz Scripts Version 2.6
|
# For use with Topaz Scripts Version 2.6
|
||||||
# Python 3, September 2020
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
from __future__ import print_function
|
||||||
# and also make sure that any unicode strings get
|
class Unbuffered:
|
||||||
# encoded using "replace" before writing them.
|
|
||||||
class SafeUnbuffered:
|
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
self.encoding = stream.encoding
|
|
||||||
if self.encoding == None:
|
|
||||||
self.encoding = "utf-8"
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
self.stream.write(data)
|
||||||
data = data.encode(self.encoding,"replace")
|
self.stream.flush()
|
||||||
self.stream.buffer.write(data)
|
|
||||||
self.stream.buffer.flush()
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
import os
|
import os
|
||||||
import getopt
|
import getopt
|
||||||
@@ -56,7 +47,7 @@ def readEncodedNumber(file):
|
|||||||
c = file.read(1)
|
c = file.read(1)
|
||||||
if (len(c) == 0):
|
if (len(c) == 0):
|
||||||
return None
|
return None
|
||||||
data = c[0]
|
data = ord(c)
|
||||||
datax = (datax <<7) + (data & 0x7F)
|
datax = (datax <<7) + (data & 0x7F)
|
||||||
data = datax
|
data = datax
|
||||||
|
|
||||||
@@ -116,7 +107,7 @@ def readString(file):
|
|||||||
def convert(i):
|
def convert(i):
|
||||||
result = ''
|
result = ''
|
||||||
val = encodeNumber(i)
|
val = encodeNumber(i)
|
||||||
for j in range(len(val)):
|
for j in xrange(len(val)):
|
||||||
c = ord(val[j:j+1])
|
c = ord(val[j:j+1])
|
||||||
result += '%02x' % c
|
result += '%02x' % c
|
||||||
return result
|
return result
|
||||||
@@ -130,10 +121,10 @@ class Dictionary(object):
|
|||||||
def __init__(self, dictFile):
|
def __init__(self, dictFile):
|
||||||
self.filename = dictFile
|
self.filename = dictFile
|
||||||
self.size = 0
|
self.size = 0
|
||||||
self.fo = open(dictFile,'rb')
|
self.fo = file(dictFile,'rb')
|
||||||
self.stable = []
|
self.stable = []
|
||||||
self.size = readEncodedNumber(self.fo)
|
self.size = readEncodedNumber(self.fo)
|
||||||
for i in range(self.size):
|
for i in xrange(self.size):
|
||||||
self.stable.append(self.escapestr(readString(self.fo)))
|
self.stable.append(self.escapestr(readString(self.fo)))
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
|
|
||||||
@@ -160,7 +151,7 @@ class Dictionary(object):
|
|||||||
return self.pos
|
return self.pos
|
||||||
|
|
||||||
def dumpDict(self):
|
def dumpDict(self):
|
||||||
for i in range(self.size):
|
for i in xrange(self.size):
|
||||||
print("%d %s %s" % (i, convert(i), self.stable[i]))
|
print("%d %s %s" % (i, convert(i), self.stable[i]))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -170,7 +161,7 @@ class Dictionary(object):
|
|||||||
|
|
||||||
class PageParser(object):
|
class PageParser(object):
|
||||||
def __init__(self, filename, dict, debug, flat_xml):
|
def __init__(self, filename, dict, debug, flat_xml):
|
||||||
self.fo = open(filename,'rb')
|
self.fo = file(filename,'rb')
|
||||||
self.id = os.path.basename(filename).replace('.dat','')
|
self.id = os.path.basename(filename).replace('.dat','')
|
||||||
self.dict = dict
|
self.dict = dict
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
@@ -188,232 +179,232 @@ class PageParser(object):
|
|||||||
# tag : (number of arguments, argument type, subtags present, special case of subtags presents when escaped)
|
# tag : (number of arguments, argument type, subtags present, special case of subtags presents when escaped)
|
||||||
|
|
||||||
token_tags = {
|
token_tags = {
|
||||||
b'x' : (1, 'scalar_number', 0, 0),
|
'x' : (1, 'scalar_number', 0, 0),
|
||||||
b'y' : (1, 'scalar_number', 0, 0),
|
'y' : (1, 'scalar_number', 0, 0),
|
||||||
b'h' : (1, 'scalar_number', 0, 0),
|
'h' : (1, 'scalar_number', 0, 0),
|
||||||
b'w' : (1, 'scalar_number', 0, 0),
|
'w' : (1, 'scalar_number', 0, 0),
|
||||||
b'firstWord' : (1, 'scalar_number', 0, 0),
|
'firstWord' : (1, 'scalar_number', 0, 0),
|
||||||
b'lastWord' : (1, 'scalar_number', 0, 0),
|
'lastWord' : (1, 'scalar_number', 0, 0),
|
||||||
b'rootID' : (1, 'scalar_number', 0, 0),
|
'rootID' : (1, 'scalar_number', 0, 0),
|
||||||
b'stemID' : (1, 'scalar_number', 0, 0),
|
'stemID' : (1, 'scalar_number', 0, 0),
|
||||||
b'type' : (1, 'scalar_text', 0, 0),
|
'type' : (1, 'scalar_text', 0, 0),
|
||||||
|
|
||||||
b'info' : (0, 'number', 1, 0),
|
'info' : (0, 'number', 1, 0),
|
||||||
|
|
||||||
b'info.word' : (0, 'number', 1, 1),
|
'info.word' : (0, 'number', 1, 1),
|
||||||
b'info.word.ocrText' : (1, 'text', 0, 0),
|
'info.word.ocrText' : (1, 'text', 0, 0),
|
||||||
b'info.word.firstGlyph' : (1, 'raw', 0, 0),
|
'info.word.firstGlyph' : (1, 'raw', 0, 0),
|
||||||
b'info.word.lastGlyph' : (1, 'raw', 0, 0),
|
'info.word.lastGlyph' : (1, 'raw', 0, 0),
|
||||||
b'info.word.bl' : (1, 'raw', 0, 0),
|
'info.word.bl' : (1, 'raw', 0, 0),
|
||||||
b'info.word.link_id' : (1, 'number', 0, 0),
|
'info.word.link_id' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
b'glyph' : (0, 'number', 1, 1),
|
'glyph' : (0, 'number', 1, 1),
|
||||||
b'glyph.x' : (1, 'number', 0, 0),
|
'glyph.x' : (1, 'number', 0, 0),
|
||||||
b'glyph.y' : (1, 'number', 0, 0),
|
'glyph.y' : (1, 'number', 0, 0),
|
||||||
b'glyph.glyphID' : (1, 'number', 0, 0),
|
'glyph.glyphID' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
b'dehyphen' : (0, 'number', 1, 1),
|
'dehyphen' : (0, 'number', 1, 1),
|
||||||
b'dehyphen.rootID' : (1, 'number', 0, 0),
|
'dehyphen.rootID' : (1, 'number', 0, 0),
|
||||||
b'dehyphen.stemID' : (1, 'number', 0, 0),
|
'dehyphen.stemID' : (1, 'number', 0, 0),
|
||||||
b'dehyphen.stemPage' : (1, 'number', 0, 0),
|
'dehyphen.stemPage' : (1, 'number', 0, 0),
|
||||||
b'dehyphen.sh' : (1, 'number', 0, 0),
|
'dehyphen.sh' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
b'links' : (0, 'number', 1, 1),
|
'links' : (0, 'number', 1, 1),
|
||||||
b'links.page' : (1, 'number', 0, 0),
|
'links.page' : (1, 'number', 0, 0),
|
||||||
b'links.rel' : (1, 'number', 0, 0),
|
'links.rel' : (1, 'number', 0, 0),
|
||||||
b'links.row' : (1, 'number', 0, 0),
|
'links.row' : (1, 'number', 0, 0),
|
||||||
b'links.title' : (1, 'text', 0, 0),
|
'links.title' : (1, 'text', 0, 0),
|
||||||
b'links.href' : (1, 'text', 0, 0),
|
'links.href' : (1, 'text', 0, 0),
|
||||||
b'links.type' : (1, 'text', 0, 0),
|
'links.type' : (1, 'text', 0, 0),
|
||||||
b'links.id' : (1, 'number', 0, 0),
|
'links.id' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
b'paraCont' : (0, 'number', 1, 1),
|
'paraCont' : (0, 'number', 1, 1),
|
||||||
b'paraCont.rootID' : (1, 'number', 0, 0),
|
'paraCont.rootID' : (1, 'number', 0, 0),
|
||||||
b'paraCont.stemID' : (1, 'number', 0, 0),
|
'paraCont.stemID' : (1, 'number', 0, 0),
|
||||||
b'paraCont.stemPage' : (1, 'number', 0, 0),
|
'paraCont.stemPage' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
b'paraStems' : (0, 'number', 1, 1),
|
'paraStems' : (0, 'number', 1, 1),
|
||||||
b'paraStems.stemID' : (1, 'number', 0, 0),
|
'paraStems.stemID' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
b'wordStems' : (0, 'number', 1, 1),
|
'wordStems' : (0, 'number', 1, 1),
|
||||||
b'wordStems.stemID' : (1, 'number', 0, 0),
|
'wordStems.stemID' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
b'empty' : (1, 'snippets', 1, 0),
|
'empty' : (1, 'snippets', 1, 0),
|
||||||
|
|
||||||
b'page' : (1, 'snippets', 1, 0),
|
'page' : (1, 'snippets', 1, 0),
|
||||||
b'page.class' : (1, 'scalar_text', 0, 0),
|
'page.class' : (1, 'scalar_text', 0, 0),
|
||||||
b'page.pageid' : (1, 'scalar_text', 0, 0),
|
'page.pageid' : (1, 'scalar_text', 0, 0),
|
||||||
b'page.pagelabel' : (1, 'scalar_text', 0, 0),
|
'page.pagelabel' : (1, 'scalar_text', 0, 0),
|
||||||
b'page.type' : (1, 'scalar_text', 0, 0),
|
'page.type' : (1, 'scalar_text', 0, 0),
|
||||||
b'page.h' : (1, 'scalar_number', 0, 0),
|
'page.h' : (1, 'scalar_number', 0, 0),
|
||||||
b'page.w' : (1, 'scalar_number', 0, 0),
|
'page.w' : (1, 'scalar_number', 0, 0),
|
||||||
b'page.startID' : (1, 'scalar_number', 0, 0),
|
'page.startID' : (1, 'scalar_number', 0, 0),
|
||||||
|
|
||||||
b'group' : (1, 'snippets', 1, 0),
|
'group' : (1, 'snippets', 1, 0),
|
||||||
b'group.class' : (1, 'scalar_text', 0, 0),
|
'group.class' : (1, 'scalar_text', 0, 0),
|
||||||
b'group.type' : (1, 'scalar_text', 0, 0),
|
'group.type' : (1, 'scalar_text', 0, 0),
|
||||||
b'group._tag' : (1, 'scalar_text', 0, 0),
|
'group._tag' : (1, 'scalar_text', 0, 0),
|
||||||
b'group.orientation': (1, 'scalar_text', 0, 0),
|
'group.orientation': (1, 'scalar_text', 0, 0),
|
||||||
|
|
||||||
b'region' : (1, 'snippets', 1, 0),
|
'region' : (1, 'snippets', 1, 0),
|
||||||
b'region.class' : (1, 'scalar_text', 0, 0),
|
'region.class' : (1, 'scalar_text', 0, 0),
|
||||||
b'region.type' : (1, 'scalar_text', 0, 0),
|
'region.type' : (1, 'scalar_text', 0, 0),
|
||||||
b'region.x' : (1, 'scalar_number', 0, 0),
|
'region.x' : (1, 'scalar_number', 0, 0),
|
||||||
b'region.y' : (1, 'scalar_number', 0, 0),
|
'region.y' : (1, 'scalar_number', 0, 0),
|
||||||
b'region.h' : (1, 'scalar_number', 0, 0),
|
'region.h' : (1, 'scalar_number', 0, 0),
|
||||||
b'region.w' : (1, 'scalar_number', 0, 0),
|
'region.w' : (1, 'scalar_number', 0, 0),
|
||||||
b'region.orientation' : (1, 'scalar_text', 0, 0),
|
'region.orientation' : (1, 'scalar_text', 0, 0),
|
||||||
|
|
||||||
b'empty_text_region' : (1, 'snippets', 1, 0),
|
'empty_text_region' : (1, 'snippets', 1, 0),
|
||||||
|
|
||||||
b'img' : (1, 'snippets', 1, 0),
|
'img' : (1, 'snippets', 1, 0),
|
||||||
b'img.x' : (1, 'scalar_number', 0, 0),
|
'img.x' : (1, 'scalar_number', 0, 0),
|
||||||
b'img.y' : (1, 'scalar_number', 0, 0),
|
'img.y' : (1, 'scalar_number', 0, 0),
|
||||||
b'img.h' : (1, 'scalar_number', 0, 0),
|
'img.h' : (1, 'scalar_number', 0, 0),
|
||||||
b'img.w' : (1, 'scalar_number', 0, 0),
|
'img.w' : (1, 'scalar_number', 0, 0),
|
||||||
b'img.src' : (1, 'scalar_number', 0, 0),
|
'img.src' : (1, 'scalar_number', 0, 0),
|
||||||
b'img.color_src' : (1, 'scalar_number', 0, 0),
|
'img.color_src' : (1, 'scalar_number', 0, 0),
|
||||||
b'img.gridSize' : (1, 'scalar_number', 0, 0),
|
'img.gridSize' : (1, 'scalar_number', 0, 0),
|
||||||
b'img.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
'img.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'img.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
'img.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'img.image_type' : (1, 'scalar_number', 0, 0),
|
'img.image_type' : (1, 'scalar_number', 0, 0),
|
||||||
|
|
||||||
b'paragraph' : (1, 'snippets', 1, 0),
|
'paragraph' : (1, 'snippets', 1, 0),
|
||||||
b'paragraph.class' : (1, 'scalar_text', 0, 0),
|
'paragraph.class' : (1, 'scalar_text', 0, 0),
|
||||||
b'paragraph.firstWord' : (1, 'scalar_number', 0, 0),
|
'paragraph.firstWord' : (1, 'scalar_number', 0, 0),
|
||||||
b'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
|
'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
|
||||||
b'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
|
'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
|
||||||
b'paragraph.gridSize' : (1, 'scalar_number', 0, 0),
|
'paragraph.gridSize' : (1, 'scalar_number', 0, 0),
|
||||||
b'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||||
|
|
||||||
|
|
||||||
b'word_semantic' : (1, 'snippets', 1, 1),
|
'word_semantic' : (1, 'snippets', 1, 1),
|
||||||
b'word_semantic.type' : (1, 'scalar_text', 0, 0),
|
'word_semantic.type' : (1, 'scalar_text', 0, 0),
|
||||||
b'word_semantic.class' : (1, 'scalar_text', 0, 0),
|
'word_semantic.class' : (1, 'scalar_text', 0, 0),
|
||||||
b'word_semantic.firstWord' : (1, 'scalar_number', 0, 0),
|
'word_semantic.firstWord' : (1, 'scalar_number', 0, 0),
|
||||||
b'word_semantic.lastWord' : (1, 'scalar_number', 0, 0),
|
'word_semantic.lastWord' : (1, 'scalar_number', 0, 0),
|
||||||
b'word_semantic.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
'word_semantic.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'word_semantic.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
'word_semantic.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'word_semantic.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
'word_semantic.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'word_semantic.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
'word_semantic.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||||
|
|
||||||
b'word' : (1, 'snippets', 1, 0),
|
'word' : (1, 'snippets', 1, 0),
|
||||||
b'word.type' : (1, 'scalar_text', 0, 0),
|
'word.type' : (1, 'scalar_text', 0, 0),
|
||||||
b'word.class' : (1, 'scalar_text', 0, 0),
|
'word.class' : (1, 'scalar_text', 0, 0),
|
||||||
b'word.firstGlyph' : (1, 'scalar_number', 0, 0),
|
'word.firstGlyph' : (1, 'scalar_number', 0, 0),
|
||||||
b'word.lastGlyph' : (1, 'scalar_number', 0, 0),
|
'word.lastGlyph' : (1, 'scalar_number', 0, 0),
|
||||||
|
|
||||||
b'_span' : (1, 'snippets', 1, 0),
|
'_span' : (1, 'snippets', 1, 0),
|
||||||
b'_span.class' : (1, 'scalar_text', 0, 0),
|
'_span.class' : (1, 'scalar_text', 0, 0),
|
||||||
b'_span.firstWord' : (1, 'scalar_number', 0, 0),
|
'_span.firstWord' : (1, 'scalar_number', 0, 0),
|
||||||
b'_span.lastWord' : (1, 'scalar_number', 0, 0),
|
'_span.lastWord' : (1, 'scalar_number', 0, 0),
|
||||||
b'_span.gridSize' : (1, 'scalar_number', 0, 0),
|
'_span.gridSize' : (1, 'scalar_number', 0, 0),
|
||||||
b'_span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
'_span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'_span.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
'_span.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'_span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
'_span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'_span.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
'_span.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||||
|
|
||||||
b'span' : (1, 'snippets', 1, 0),
|
'span' : (1, 'snippets', 1, 0),
|
||||||
b'span.firstWord' : (1, 'scalar_number', 0, 0),
|
'span.firstWord' : (1, 'scalar_number', 0, 0),
|
||||||
b'span.lastWord' : (1, 'scalar_number', 0, 0),
|
'span.lastWord' : (1, 'scalar_number', 0, 0),
|
||||||
b'span.gridSize' : (1, 'scalar_number', 0, 0),
|
'span.gridSize' : (1, 'scalar_number', 0, 0),
|
||||||
b'span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
'span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'span.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
'span.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
'span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||||
|
|
||||||
b'extratokens' : (1, 'snippets', 1, 0),
|
'extratokens' : (1, 'snippets', 1, 0),
|
||||||
b'extratokens.class' : (1, 'scalar_text', 0, 0),
|
'extratokens.class' : (1, 'scalar_text', 0, 0),
|
||||||
b'extratokens.type' : (1, 'scalar_text', 0, 0),
|
'extratokens.type' : (1, 'scalar_text', 0, 0),
|
||||||
b'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
|
'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
|
||||||
b'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0),
|
'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0),
|
||||||
b'extratokens.gridSize' : (1, 'scalar_number', 0, 0),
|
'extratokens.gridSize' : (1, 'scalar_number', 0, 0),
|
||||||
b'extratokens.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
'extratokens.gridBottomCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'extratokens.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
'extratokens.gridTopCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'extratokens.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
'extratokens.gridBeginCenter' : (1, 'scalar_number', 0, 0),
|
||||||
b'extratokens.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
'extratokens.gridEndCenter' : (1, 'scalar_number', 0, 0),
|
||||||
|
|
||||||
b'glyph.h' : (1, 'number', 0, 0),
|
'glyph.h' : (1, 'number', 0, 0),
|
||||||
b'glyph.w' : (1, 'number', 0, 0),
|
'glyph.w' : (1, 'number', 0, 0),
|
||||||
b'glyph.use' : (1, 'number', 0, 0),
|
'glyph.use' : (1, 'number', 0, 0),
|
||||||
b'glyph.vtx' : (1, 'number', 0, 1),
|
'glyph.vtx' : (1, 'number', 0, 1),
|
||||||
b'glyph.len' : (1, 'number', 0, 1),
|
'glyph.len' : (1, 'number', 0, 1),
|
||||||
b'glyph.dpi' : (1, 'number', 0, 0),
|
'glyph.dpi' : (1, 'number', 0, 0),
|
||||||
b'vtx' : (0, 'number', 1, 1),
|
'vtx' : (0, 'number', 1, 1),
|
||||||
b'vtx.x' : (1, 'number', 0, 0),
|
'vtx.x' : (1, 'number', 0, 0),
|
||||||
b'vtx.y' : (1, 'number', 0, 0),
|
'vtx.y' : (1, 'number', 0, 0),
|
||||||
b'len' : (0, 'number', 1, 1),
|
'len' : (0, 'number', 1, 1),
|
||||||
b'len.n' : (1, 'number', 0, 0),
|
'len.n' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
b'book' : (1, 'snippets', 1, 0),
|
'book' : (1, 'snippets', 1, 0),
|
||||||
b'version' : (1, 'snippets', 1, 0),
|
'version' : (1, 'snippets', 1, 0),
|
||||||
b'version.FlowEdit_1_id' : (1, 'scalar_text', 0, 0),
|
'version.FlowEdit_1_id' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.FlowEdit_1_version' : (1, 'scalar_text', 0, 0),
|
'version.FlowEdit_1_version' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.Schema_id' : (1, 'scalar_text', 0, 0),
|
'version.Schema_id' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.Schema_version' : (1, 'scalar_text', 0, 0),
|
'version.Schema_version' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.Topaz_version' : (1, 'scalar_text', 0, 0),
|
'version.Topaz_version' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.WordDetailEdit_1_id' : (1, 'scalar_text', 0, 0),
|
'version.WordDetailEdit_1_id' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.WordDetailEdit_1_version' : (1, 'scalar_text', 0, 0),
|
'version.WordDetailEdit_1_version' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.ZoneEdit_1_id' : (1, 'scalar_text', 0, 0),
|
'version.ZoneEdit_1_id' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.ZoneEdit_1_version' : (1, 'scalar_text', 0, 0),
|
'version.ZoneEdit_1_version' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.chapterheaders' : (1, 'scalar_text', 0, 0),
|
'version.chapterheaders' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.creation_date' : (1, 'scalar_text', 0, 0),
|
'version.creation_date' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.header_footer' : (1, 'scalar_text', 0, 0),
|
'version.header_footer' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.init_from_ocr' : (1, 'scalar_text', 0, 0),
|
'version.init_from_ocr' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.letter_insertion' : (1, 'scalar_text', 0, 0),
|
'version.letter_insertion' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.xmlinj_convert' : (1, 'scalar_text', 0, 0),
|
'version.xmlinj_convert' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.xmlinj_reflow' : (1, 'scalar_text', 0, 0),
|
'version.xmlinj_reflow' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.xmlinj_transform' : (1, 'scalar_text', 0, 0),
|
'version.xmlinj_transform' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.findlists' : (1, 'scalar_text', 0, 0),
|
'version.findlists' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.page_num' : (1, 'scalar_text', 0, 0),
|
'version.page_num' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.page_type' : (1, 'scalar_text', 0, 0),
|
'version.page_type' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.bad_text' : (1, 'scalar_text', 0, 0),
|
'version.bad_text' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.glyph_mismatch' : (1, 'scalar_text', 0, 0),
|
'version.glyph_mismatch' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.margins' : (1, 'scalar_text', 0, 0),
|
'version.margins' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.staggered_lines' : (1, 'scalar_text', 0, 0),
|
'version.staggered_lines' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.paragraph_continuation' : (1, 'scalar_text', 0, 0),
|
'version.paragraph_continuation' : (1, 'scalar_text', 0, 0),
|
||||||
b'version.toc' : (1, 'scalar_text', 0, 0),
|
'version.toc' : (1, 'scalar_text', 0, 0),
|
||||||
|
|
||||||
b'stylesheet' : (1, 'snippets', 1, 0),
|
'stylesheet' : (1, 'snippets', 1, 0),
|
||||||
b'style' : (1, 'snippets', 1, 0),
|
'style' : (1, 'snippets', 1, 0),
|
||||||
b'style._tag' : (1, 'scalar_text', 0, 0),
|
'style._tag' : (1, 'scalar_text', 0, 0),
|
||||||
b'style.type' : (1, 'scalar_text', 0, 0),
|
'style.type' : (1, 'scalar_text', 0, 0),
|
||||||
b'style._after_type' : (1, 'scalar_text', 0, 0),
|
'style._after_type' : (1, 'scalar_text', 0, 0),
|
||||||
b'style._parent_type' : (1, 'scalar_text', 0, 0),
|
'style._parent_type' : (1, 'scalar_text', 0, 0),
|
||||||
b'style._after_parent_type' : (1, 'scalar_text', 0, 0),
|
'style._after_parent_type' : (1, 'scalar_text', 0, 0),
|
||||||
b'style.class' : (1, 'scalar_text', 0, 0),
|
'style.class' : (1, 'scalar_text', 0, 0),
|
||||||
b'style._after_class' : (1, 'scalar_text', 0, 0),
|
'style._after_class' : (1, 'scalar_text', 0, 0),
|
||||||
b'rule' : (1, 'snippets', 1, 0),
|
'rule' : (1, 'snippets', 1, 0),
|
||||||
b'rule.attr' : (1, 'scalar_text', 0, 0),
|
'rule.attr' : (1, 'scalar_text', 0, 0),
|
||||||
b'rule.value' : (1, 'scalar_text', 0, 0),
|
'rule.value' : (1, 'scalar_text', 0, 0),
|
||||||
|
|
||||||
b'original' : (0, 'number', 1, 1),
|
'original' : (0, 'number', 1, 1),
|
||||||
b'original.pnum' : (1, 'number', 0, 0),
|
'original.pnum' : (1, 'number', 0, 0),
|
||||||
b'original.pid' : (1, 'text', 0, 0),
|
'original.pid' : (1, 'text', 0, 0),
|
||||||
b'pages' : (0, 'number', 1, 1),
|
'pages' : (0, 'number', 1, 1),
|
||||||
b'pages.ref' : (1, 'number', 0, 0),
|
'pages.ref' : (1, 'number', 0, 0),
|
||||||
b'pages.id' : (1, 'number', 0, 0),
|
'pages.id' : (1, 'number', 0, 0),
|
||||||
b'startID' : (0, 'number', 1, 1),
|
'startID' : (0, 'number', 1, 1),
|
||||||
b'startID.page' : (1, 'number', 0, 0),
|
'startID.page' : (1, 'number', 0, 0),
|
||||||
b'startID.id' : (1, 'number', 0, 0),
|
'startID.id' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
|
'median_d' : (1, 'number', 0, 0),
|
||||||
|
'median_h' : (1, 'number', 0, 0),
|
||||||
|
'median_firsty' : (1, 'number', 0, 0),
|
||||||
|
'median_lasty' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
b'median_d' : (1, 'number', 0, 0),
|
'num_footers_maybe' : (1, 'number', 0, 0),
|
||||||
b'median_h' : (1, 'number', 0, 0),
|
'num_footers_yes' : (1, 'number', 0, 0),
|
||||||
b'median_firsty' : (1, 'number', 0, 0),
|
'num_headers_maybe' : (1, 'number', 0, 0),
|
||||||
b'median_lasty' : (1, 'number', 0, 0),
|
'num_headers_yes' : (1, 'number', 0, 0),
|
||||||
|
|
||||||
b'num_footers_maybe' : (1, 'number', 0, 0),
|
'tracking' : (1, 'number', 0, 0),
|
||||||
b'num_footers_yes' : (1, 'number', 0, 0),
|
'src' : (1, 'text', 0, 0),
|
||||||
b'num_headers_maybe' : (1, 'number', 0, 0),
|
|
||||||
b'num_headers_yes' : (1, 'number', 0, 0),
|
|
||||||
|
|
||||||
b'tracking' : (1, 'number', 0, 0),
|
|
||||||
b'src' : (1, 'text', 0, 0),
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,8 +420,8 @@ class PageParser(object):
|
|||||||
def get_tagpath(self, i):
|
def get_tagpath(self, i):
|
||||||
cnt = len(self.tagpath)
|
cnt = len(self.tagpath)
|
||||||
if i < cnt : result = self.tagpath[i]
|
if i < cnt : result = self.tagpath[i]
|
||||||
for j in range(i+1, cnt) :
|
for j in xrange(i+1, cnt) :
|
||||||
result += b'.' + self.tagpath[j]
|
result += '.' + self.tagpath[j]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -481,7 +472,7 @@ class PageParser(object):
|
|||||||
|
|
||||||
if self.debug : print('Processing: ', self.get_tagpath(0))
|
if self.debug : print('Processing: ', self.get_tagpath(0))
|
||||||
cnt = self.tagpath_len()
|
cnt = self.tagpath_len()
|
||||||
for j in range(cnt):
|
for j in xrange(cnt):
|
||||||
tkn = self.get_tagpath(j)
|
tkn = self.get_tagpath(j)
|
||||||
if tkn in self.token_tags :
|
if tkn in self.token_tags :
|
||||||
num_args = self.token_tags[tkn][0]
|
num_args = self.token_tags[tkn][0]
|
||||||
@@ -505,8 +496,8 @@ class PageParser(object):
|
|||||||
|
|
||||||
if (subtags == 1):
|
if (subtags == 1):
|
||||||
ntags = readEncodedNumber(self.fo)
|
ntags = readEncodedNumber(self.fo)
|
||||||
if self.debug : print('subtags: ', token , ' has ' , str(ntags))
|
if self.debug : print('subtags: ' + token + ' has ' + str(ntags))
|
||||||
for j in range(ntags):
|
for j in xrange(ntags):
|
||||||
val = readEncodedNumber(self.fo)
|
val = readEncodedNumber(self.fo)
|
||||||
subtagres.append(self.procToken(self.dict.lookup(val)))
|
subtagres.append(self.procToken(self.dict.lookup(val)))
|
||||||
|
|
||||||
@@ -520,7 +511,7 @@ class PageParser(object):
|
|||||||
argres = self.decodeCMD(arg,argtype)
|
argres = self.decodeCMD(arg,argtype)
|
||||||
else :
|
else :
|
||||||
# num_arg scalar arguments
|
# num_arg scalar arguments
|
||||||
for i in range(num_args):
|
for i in xrange(num_args):
|
||||||
argres.append(self.formatArg(readEncodedNumber(self.fo), argtype))
|
argres.append(self.formatArg(readEncodedNumber(self.fo), argtype))
|
||||||
|
|
||||||
# build the return tag
|
# build the return tag
|
||||||
@@ -555,7 +546,7 @@ class PageParser(object):
|
|||||||
result += 'of the document is indicated by snippet number sets at the\n'
|
result += 'of the document is indicated by snippet number sets at the\n'
|
||||||
result += 'end of each snippet. \n'
|
result += 'end of each snippet. \n'
|
||||||
print(result)
|
print(result)
|
||||||
for i in range(cnt):
|
for i in xrange(cnt):
|
||||||
if self.debug: print('Snippet:',str(i))
|
if self.debug: print('Snippet:',str(i))
|
||||||
snippet = []
|
snippet = []
|
||||||
snippet.append(i)
|
snippet.append(i)
|
||||||
@@ -574,12 +565,12 @@ class PageParser(object):
|
|||||||
adj = readEncodedNumber(self.fo)
|
adj = readEncodedNumber(self.fo)
|
||||||
mode = mode >> 1
|
mode = mode >> 1
|
||||||
x = []
|
x = []
|
||||||
for i in range(cnt):
|
for i in xrange(cnt):
|
||||||
x.append(readEncodedNumber(self.fo) - adj)
|
x.append(readEncodedNumber(self.fo) - adj)
|
||||||
for i in range(mode):
|
for i in xrange(mode):
|
||||||
for j in range(1, cnt):
|
for j in xrange(1, cnt):
|
||||||
x[j] = x[j] + x[j - 1]
|
x[j] = x[j] + x[j - 1]
|
||||||
for i in range(cnt):
|
for i in xrange(cnt):
|
||||||
result.append(self.formatArg(x[i],argtype))
|
result.append(self.formatArg(x[i],argtype))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -613,7 +604,7 @@ class PageParser(object):
|
|||||||
subtagList = tag[1]
|
subtagList = tag[1]
|
||||||
argtype = tag[2]
|
argtype = tag[2]
|
||||||
argList = tag[3]
|
argList = tag[3]
|
||||||
nname = prefix + b'.' + name
|
nname = prefix + '.' + name
|
||||||
nsubtaglist = []
|
nsubtaglist = []
|
||||||
for j in subtagList:
|
for j in subtagList:
|
||||||
nsubtaglist.append(self.updateName(j,prefix))
|
nsubtaglist.append(self.updateName(j,prefix))
|
||||||
@@ -662,34 +653,34 @@ class PageParser(object):
|
|||||||
subtagList = node[1]
|
subtagList = node[1]
|
||||||
argtype = node[2]
|
argtype = node[2]
|
||||||
argList = node[3]
|
argList = node[3]
|
||||||
fullpathname = name.split(b'.')
|
fullpathname = name.split('.')
|
||||||
nodename = fullpathname.pop()
|
nodename = fullpathname.pop()
|
||||||
ilvl = len(fullpathname)
|
ilvl = len(fullpathname)
|
||||||
indent = b' ' * (3 * ilvl)
|
indent = ' ' * (3 * ilvl)
|
||||||
rlst = []
|
rlst = []
|
||||||
rlst.append(indent + b'<' + nodename + b'>')
|
rlst.append(indent + '<' + nodename + '>')
|
||||||
if len(argList) > 0:
|
if len(argList) > 0:
|
||||||
alst = []
|
alst = []
|
||||||
for j in argList:
|
for j in argList:
|
||||||
if (argtype == b'text') or (argtype == b'scalar_text') :
|
if (argtype == 'text') or (argtype == 'scalar_text') :
|
||||||
alst.append(j + b'|')
|
alst.append(j + '|')
|
||||||
else :
|
else :
|
||||||
alst.append(str(j).encode('utf-8') + b',')
|
alst.append(str(j) + ',')
|
||||||
argres = b"".join(alst)
|
argres = "".join(alst)
|
||||||
argres = argres[0:-1]
|
argres = argres[0:-1]
|
||||||
if argtype == b'snippets' :
|
if argtype == 'snippets' :
|
||||||
rlst.append(b'snippets:' + argres)
|
rlst.append('snippets:' + argres)
|
||||||
else :
|
else :
|
||||||
rlst.append(argres)
|
rlst.append(argres)
|
||||||
if len(subtagList) > 0 :
|
if len(subtagList) > 0 :
|
||||||
rlst.append(b'\n')
|
rlst.append('\n')
|
||||||
for j in subtagList:
|
for j in subtagList:
|
||||||
if len(j) > 0 :
|
if len(j) > 0 :
|
||||||
rlst.append(self.formatTag(j))
|
rlst.append(self.formatTag(j))
|
||||||
rlst.append(indent + b'</' + nodename + b'>\n')
|
rlst.append(indent + '</' + nodename + '>\n')
|
||||||
else:
|
else:
|
||||||
rlst.append(b'</' + nodename + b'>\n')
|
rlst.append('</' + nodename + '>\n')
|
||||||
return b"".join(rlst)
|
return "".join(rlst)
|
||||||
|
|
||||||
|
|
||||||
# flatten tag
|
# flatten tag
|
||||||
@@ -704,20 +695,20 @@ class PageParser(object):
|
|||||||
alst = []
|
alst = []
|
||||||
for j in argList:
|
for j in argList:
|
||||||
if (argtype == 'text') or (argtype == 'scalar_text') :
|
if (argtype == 'text') or (argtype == 'scalar_text') :
|
||||||
alst.append(j + b'|')
|
alst.append(j + '|')
|
||||||
else :
|
else :
|
||||||
alst.append(str(j).encode('utf-8') + b'|')
|
alst.append(str(j) + '|')
|
||||||
argres = b"".join(alst)
|
argres = "".join(alst)
|
||||||
argres = argres[0:-1]
|
argres = argres[0:-1]
|
||||||
if argtype == b'snippets' :
|
if argtype == 'snippets' :
|
||||||
rlst.append(b'.snippets=' + argres)
|
rlst.append('.snippets=' + argres)
|
||||||
else :
|
else :
|
||||||
rlst.append(b'=' + argres)
|
rlst.append('=' + argres)
|
||||||
rlst.append(b'\n')
|
rlst.append('\n')
|
||||||
for j in subtagList:
|
for j in subtagList:
|
||||||
if len(j) > 0 :
|
if len(j) > 0 :
|
||||||
rlst.append(self.flattenTag(j))
|
rlst.append(self.flattenTag(j))
|
||||||
return b"".join(rlst)
|
return "".join(rlst)
|
||||||
|
|
||||||
|
|
||||||
# reduce create xml output
|
# reduce create xml output
|
||||||
@@ -729,7 +720,7 @@ class PageParser(object):
|
|||||||
rlst.append(self.flattenTag(j))
|
rlst.append(self.flattenTag(j))
|
||||||
else:
|
else:
|
||||||
rlst.append(self.formatTag(j))
|
rlst.append(self.formatTag(j))
|
||||||
result = b"".join(rlst)
|
result = "".join(rlst)
|
||||||
if self.debug : print(result)
|
if self.debug : print(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -747,16 +738,16 @@ class PageParser(object):
|
|||||||
|
|
||||||
# peek at the first bytes to see what type of file it is
|
# peek at the first bytes to see what type of file it is
|
||||||
magic = self.fo.read(9)
|
magic = self.fo.read(9)
|
||||||
if (magic[0:1] == b'p') and (magic[2:9] == b'marker_'):
|
if (magic[0:1] == 'p') and (magic[2:9] == 'marker_'):
|
||||||
first_token = b'info'
|
first_token = 'info'
|
||||||
elif (magic[0:1] == b'p') and (magic[2:9] == b'__PAGE_'):
|
elif (magic[0:1] == 'p') and (magic[2:9] == '__PAGE_'):
|
||||||
skip = self.fo.read(2)
|
skip = self.fo.read(2)
|
||||||
first_token = b'info'
|
first_token = 'info'
|
||||||
elif (magic[0:1] == b'p') and (magic[2:8] == b'_PAGE_'):
|
elif (magic[0:1] == 'p') and (magic[2:8] == '_PAGE_'):
|
||||||
first_token = b'info'
|
first_token = 'info'
|
||||||
elif (magic[0:1] == b'g') and (magic[2:9] == b'__GLYPH'):
|
elif (magic[0:1] == 'g') and (magic[2:9] == '__GLYPH'):
|
||||||
skip = self.fo.read(3)
|
skip = self.fo.read(3)
|
||||||
first_token = b'info'
|
first_token = 'info'
|
||||||
else :
|
else :
|
||||||
# other0.dat file
|
# other0.dat file
|
||||||
first_token = None
|
first_token = None
|
||||||
@@ -778,7 +769,7 @@ class PageParser(object):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if (v == 0x72):
|
if (v == 0x72):
|
||||||
self.doLoop72(b'number')
|
self.doLoop72('number')
|
||||||
elif (v > 0) and (v < self.dict.getSize()) :
|
elif (v > 0) and (v < self.dict.getSize()) :
|
||||||
tag = self.procToken(self.dict.lookup(v))
|
tag = self.procToken(self.dict.lookup(v))
|
||||||
if len(tag) > 0 :
|
if len(tag) > 0 :
|
||||||
@@ -789,7 +780,7 @@ class PageParser(object):
|
|||||||
if (v == 0):
|
if (v == 0):
|
||||||
if (self.peek(1) == 0x5f):
|
if (self.peek(1) == 0x5f):
|
||||||
skip = self.fo.read(1)
|
skip = self.fo.read(1)
|
||||||
first_token = b'info'
|
first_token = 'info'
|
||||||
|
|
||||||
# now do snippet injection
|
# now do snippet injection
|
||||||
if len(self.snippetList) > 0 :
|
if len(self.snippetList) > 0 :
|
||||||
@@ -809,14 +800,14 @@ class PageParser(object):
|
|||||||
|
|
||||||
def fromData(dict, fname):
|
def fromData(dict, fname):
|
||||||
flat_xml = True
|
flat_xml = True
|
||||||
debug = True
|
debug = False
|
||||||
pp = PageParser(fname, dict, debug, flat_xml)
|
pp = PageParser(fname, dict, debug, flat_xml)
|
||||||
xmlpage = pp.process()
|
xmlpage = pp.process()
|
||||||
return xmlpage
|
return xmlpage
|
||||||
|
|
||||||
def getXML(dict, fname):
|
def getXML(dict, fname):
|
||||||
flat_xml = False
|
flat_xml = False
|
||||||
debug = True
|
debug = False
|
||||||
pp = PageParser(fname, dict, debug, flat_xml)
|
pp = PageParser(fname, dict, debug, flat_xml)
|
||||||
xmlpage = pp.process()
|
xmlpage = pp.process()
|
||||||
return xmlpage
|
return xmlpage
|
||||||
@@ -841,11 +832,9 @@ def usage():
|
|||||||
#
|
#
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
|
||||||
dictFile = ""
|
dictFile = ""
|
||||||
pageFile = ""
|
pageFile = ""
|
||||||
debug = True
|
debug = False
|
||||||
flat_xml = False
|
flat_xml = False
|
||||||
printOutput = False
|
printOutput = False
|
||||||
if len(argv) == 0:
|
if len(argv) == 0:
|
||||||
@@ -855,7 +844,7 @@ def main(argv):
|
|||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"])
|
opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"])
|
||||||
|
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError, err:
|
||||||
|
|
||||||
# print help information and exit:
|
# print help information and exit:
|
||||||
print(str(err)) # will print something like "option -a not recognized"
|
print(str(err)) # will print something like "option -a not recognized"
|
||||||
|
|||||||
46
DeDRM_plugin/encodebase64.py
Normal file
46
DeDRM_plugin/encodebase64.py
Normal file
@@ -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
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
__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())
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# 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.
|
||||||
@@ -11,7 +10,6 @@
|
|||||||
# Changelog epubtest
|
# Changelog epubtest
|
||||||
# 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf
|
# 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf
|
||||||
# 1.01 - Added routine for use by Windows DeDRM
|
# 1.01 - Added routine for use by Windows DeDRM
|
||||||
# 2.00 - Python 3, September 2020
|
|
||||||
#
|
#
|
||||||
# Written in 2011 by Paul Durrant
|
# Written in 2011 by Paul Durrant
|
||||||
# Released with unlicense. See http://unlicense.org/
|
# Released with unlicense. See http://unlicense.org/
|
||||||
@@ -46,7 +44,10 @@
|
|||||||
# It's still polite to give attribution if you do reuse this code.
|
# It's still polite to give attribution if you do reuse this code.
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = '2.0'
|
from __future__ import with_statement
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
__version__ = '1.01'
|
||||||
|
|
||||||
import sys, struct, os, traceback
|
import sys, struct, os, traceback
|
||||||
import zlib
|
import zlib
|
||||||
@@ -66,11 +67,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -108,13 +108,15 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["epubtest.py"]
|
return [u"epubtest.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
_FILENAME_LEN_OFFSET = 26
|
_FILENAME_LEN_OFFSET = 26
|
||||||
_EXTRA_LEN_OFFSET = 28
|
_EXTRA_LEN_OFFSET = 28
|
||||||
|
|||||||
133
DeDRM_plugin/erdr2pml.py
Executable file → Normal file
133
DeDRM_plugin/erdr2pml.py
Executable file → Normal file
@@ -1,9 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# erdr2pml.py
|
# erdr2pml.py
|
||||||
# Copyright © 2008-2020 The Dark Reverser, Apprentice Harper et al.
|
# Copyright © 2008 The Dark Reverser
|
||||||
#
|
#
|
||||||
|
# Modified 2008–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
|
||||||
|
# This is a python script. You need a Python interpreter to run it.
|
||||||
|
# For example, ActiveState Python, which exists for windows.
|
||||||
# Changelog
|
# Changelog
|
||||||
#
|
#
|
||||||
# Based on ereader2html version 0.08 plus some later small fixes
|
# Based on ereader2html version 0.08 plus some later small fixes
|
||||||
@@ -63,9 +67,8 @@
|
|||||||
# - Ignore sidebars for dictionaries (different format?)
|
# - Ignore sidebars for dictionaries (different format?)
|
||||||
# 0.22 - Unicode and plugin support, different image folders for PMLZ and source
|
# 0.22 - Unicode and plugin support, different image folders for PMLZ and source
|
||||||
# 0.23 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 0.23 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 1.00 - Added Python 3 compatibility for calibre 5.0
|
|
||||||
|
|
||||||
__version__='1.00'
|
__version__='0.23'
|
||||||
|
|
||||||
import sys, re
|
import sys, re
|
||||||
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile, traceback
|
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile, traceback
|
||||||
@@ -85,10 +88,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data,str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -123,13 +126,15 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["mobidedrm.py"]
|
return [u"mobidedrm.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
Des = None
|
Des = None
|
||||||
if iswindows:
|
if iswindows:
|
||||||
@@ -195,7 +200,7 @@ class Sectionizer(object):
|
|||||||
bkType = "Book"
|
bkType = "Book"
|
||||||
|
|
||||||
def __init__(self, filename, ident):
|
def __init__(self, filename, ident):
|
||||||
self.contents = open(filename, 'rb').read()
|
self.contents = file(filename, 'rb').read()
|
||||||
self.header = self.contents[0:72]
|
self.header = self.contents[0:72]
|
||||||
self.num_sections, = struct.unpack('>H', self.contents[76:78])
|
self.num_sections, = struct.unpack('>H', self.contents[76:78])
|
||||||
# Dictionary or normal content (TODO: Not hard-coded)
|
# Dictionary or normal content (TODO: Not hard-coded)
|
||||||
@@ -205,7 +210,7 @@ class Sectionizer(object):
|
|||||||
else:
|
else:
|
||||||
raise ValueError('Invalid file format')
|
raise ValueError('Invalid file format')
|
||||||
self.sections = []
|
self.sections = []
|
||||||
for i in range(self.num_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])
|
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
|
flags, val = a1, a2<<16|a3<<8|a4
|
||||||
self.sections.append( (offset, flags, val) )
|
self.sections.append( (offset, flags, val) )
|
||||||
@@ -224,16 +229,16 @@ class Sectionizer(object):
|
|||||||
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
|
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
|
||||||
def sanitizeFileName(name):
|
def sanitizeFileName(name):
|
||||||
# substitute filename unfriendly characters
|
# substitute filename unfriendly characters
|
||||||
name = name.replace("<","[").replace(">","]").replace(" : "," – ").replace(": "," – ").replace(":","—").replace("/","_").replace("\\","_").replace("|","_").replace("\"","\'")
|
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
|
||||||
# delete control characters
|
# delete control characters
|
||||||
name = "".join(char for char in name if ord(char)>=32)
|
name = u"".join(char for char in name if ord(char)>=32)
|
||||||
# white space to single space, delete leading and trailing while space
|
# white space to single space, delete leading and trailing while space
|
||||||
name = re.sub(r"\s", " ", name).strip()
|
name = re.sub(ur"\s", u" ", name).strip()
|
||||||
# remove leading dots
|
# remove leading dots
|
||||||
while len(name)>0 and name[0] == ".":
|
while len(name)>0 and name[0] == u".":
|
||||||
name = name[1:]
|
name = name[1:]
|
||||||
# remove trailing dots (Windows doesn't like them)
|
# remove trailing dots (Windows doesn't like them)
|
||||||
if name.endswith("."):
|
if name.endswith(u'.'):
|
||||||
name = name[:-1]
|
name = name[:-1]
|
||||||
return name
|
return name
|
||||||
|
|
||||||
@@ -245,7 +250,7 @@ def fixKey(key):
|
|||||||
def deXOR(text, sp, table):
|
def deXOR(text, sp, table):
|
||||||
r=''
|
r=''
|
||||||
j = sp
|
j = sp
|
||||||
for i in range(len(text)):
|
for i in xrange(len(text)):
|
||||||
r += chr(ord(table[j]) ^ ord(text[i]))
|
r += chr(ord(table[j]) ^ ord(text[i]))
|
||||||
j = j + 1
|
j = j + 1
|
||||||
if j == len(table):
|
if j == len(table):
|
||||||
@@ -271,7 +276,7 @@ class EreaderProcessor(object):
|
|||||||
def unshuff(data, shuf):
|
def unshuff(data, shuf):
|
||||||
r = [''] * len(data)
|
r = [''] * len(data)
|
||||||
j = 0
|
j = 0
|
||||||
for i in range(len(data)):
|
for i in xrange(len(data)):
|
||||||
j = (j + shuf) % len(data)
|
j = (j + shuf) % len(data)
|
||||||
r[j] = data[i]
|
r[j] = data[i]
|
||||||
assert len("".join(r)) == len(data)
|
assert len("".join(r)) == len(data)
|
||||||
@@ -325,7 +330,7 @@ class EreaderProcessor(object):
|
|||||||
self.flags = struct.unpack('>L', r[4:8])[0]
|
self.flags = struct.unpack('>L', r[4:8])[0]
|
||||||
reqd_flags = (1<<9) | (1<<7) | (1<<10)
|
reqd_flags = (1<<9) | (1<<7) | (1<<10)
|
||||||
if (self.flags & reqd_flags) != reqd_flags:
|
if (self.flags & reqd_flags) != reqd_flags:
|
||||||
print("Flags: 0x%X" % self.flags)
|
print "Flags: 0x%X" % self.flags
|
||||||
raise ValueError('incompatible eReader file')
|
raise ValueError('incompatible eReader file')
|
||||||
des = Des(fixKey(user_key))
|
des = Des(fixKey(user_key))
|
||||||
if version == 259:
|
if version == 259:
|
||||||
@@ -356,7 +361,7 @@ class EreaderProcessor(object):
|
|||||||
sect = self.section_reader(self.first_image_page + i)
|
sect = self.section_reader(self.first_image_page + i)
|
||||||
name = sect[4:4+32].strip('\0')
|
name = sect[4:4+32].strip('\0')
|
||||||
data = sect[62:]
|
data = sect[62:]
|
||||||
return sanitizeFileName(name.decode('windows-1252')), data
|
return sanitizeFileName(unicode(name,'windows-1252')), data
|
||||||
|
|
||||||
|
|
||||||
# def getChapterNamePMLOffsetData(self):
|
# def getChapterNamePMLOffsetData(self):
|
||||||
@@ -405,7 +410,7 @@ class EreaderProcessor(object):
|
|||||||
def getText(self):
|
def getText(self):
|
||||||
des = Des(fixKey(self.content_key))
|
des = Des(fixKey(self.content_key))
|
||||||
r = ''
|
r = ''
|
||||||
for i in range(self.num_text_pages):
|
for i in xrange(self.num_text_pages):
|
||||||
logging.debug('get page %d', i)
|
logging.debug('get page %d', i)
|
||||||
r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
|
r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
|
||||||
|
|
||||||
@@ -417,7 +422,7 @@ class EreaderProcessor(object):
|
|||||||
fnote_ids = deXOR(sect, 0, self.xortable)
|
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
|
# the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated
|
||||||
des = Des(fixKey(self.content_key))
|
des = Des(fixKey(self.content_key))
|
||||||
for i in range(1,self.num_footnote_pages):
|
for i in xrange(1,self.num_footnote_pages):
|
||||||
logging.debug('get footnotepage %d', i)
|
logging.debug('get footnotepage %d', i)
|
||||||
id_len = ord(fnote_ids[2])
|
id_len = ord(fnote_ids[2])
|
||||||
id = fnote_ids[3:3+id_len]
|
id = fnote_ids[3:3+id_len]
|
||||||
@@ -441,7 +446,7 @@ class EreaderProcessor(object):
|
|||||||
sbar_ids = deXOR(sect, 0, self.xortable)
|
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
|
# the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated
|
||||||
des = Des(fixKey(self.content_key))
|
des = Des(fixKey(self.content_key))
|
||||||
for i in range(1,self.num_sidebar_pages):
|
for i in xrange(1,self.num_sidebar_pages):
|
||||||
id_len = ord(sbar_ids[2])
|
id_len = ord(sbar_ids[2])
|
||||||
id = sbar_ids[3:3+id_len]
|
id = sbar_ids[3:3+id_len]
|
||||||
smarker = '<sidebar id="%s">\n' % id
|
smarker = '<sidebar id="%s">\n' % id
|
||||||
@@ -455,7 +460,7 @@ class EreaderProcessor(object):
|
|||||||
def cleanPML(pml):
|
def cleanPML(pml):
|
||||||
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
|
# Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
|
||||||
pml2 = pml
|
pml2 = pml
|
||||||
for k in range(128,256):
|
for k in xrange(128,256):
|
||||||
badChar = chr(k)
|
badChar = chr(k)
|
||||||
pml2 = pml2.replace(badChar, '\\a%03d' % k)
|
pml2 = pml2.replace(badChar, '\\a%03d' % k)
|
||||||
return pml2
|
return pml2
|
||||||
@@ -466,35 +471,35 @@ def decryptBook(infile, outpath, make_pmlz, user_key):
|
|||||||
# outpath is actually pmlz name
|
# outpath is actually pmlz name
|
||||||
pmlzname = outpath
|
pmlzname = outpath
|
||||||
outdir = tempfile.mkdtemp()
|
outdir = tempfile.mkdtemp()
|
||||||
imagedirpath = os.path.join(outdir,"images")
|
imagedirpath = os.path.join(outdir,u"images")
|
||||||
else:
|
else:
|
||||||
pmlzname = None
|
pmlzname = None
|
||||||
outdir = outpath
|
outdir = outpath
|
||||||
imagedirpath = os.path.join(outdir,bookname + "_img")
|
imagedirpath = os.path.join(outdir,bookname + u"_img")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not os.path.exists(outdir):
|
if not os.path.exists(outdir):
|
||||||
os.makedirs(outdir)
|
os.makedirs(outdir)
|
||||||
print("Decoding File")
|
print u"Decoding File"
|
||||||
sect =Sectionizer(infile, 'PNRdPPrs')
|
sect = Sectionizer(infile, 'PNRdPPrs')
|
||||||
er = EreaderProcessor(sect, user_key)
|
er = EreaderProcessor(sect, user_key)
|
||||||
|
|
||||||
if er.getNumImages() > 0:
|
if er.getNumImages() > 0:
|
||||||
print("Extracting images")
|
print u"Extracting images"
|
||||||
if not os.path.exists(imagedirpath):
|
if not os.path.exists(imagedirpath):
|
||||||
os.makedirs(imagedirpath)
|
os.makedirs(imagedirpath)
|
||||||
for i in range(er.getNumImages()):
|
for i in xrange(er.getNumImages()):
|
||||||
name, contents = er.getImage(i)
|
name, contents = er.getImage(i)
|
||||||
open(os.path.join(imagedirpath, name), 'wb').write(contents)
|
file(os.path.join(imagedirpath, name), 'wb').write(contents)
|
||||||
|
|
||||||
print("Extracting pml")
|
print u"Extracting pml"
|
||||||
pml_string = er.getText()
|
pml_string = er.getText()
|
||||||
pmlfilename = bookname + ".pml"
|
pmlfilename = bookname + ".pml"
|
||||||
open(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
|
file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
|
||||||
if pmlzname is not None:
|
if pmlzname is not None:
|
||||||
import zipfile
|
import zipfile
|
||||||
import shutil
|
import shutil
|
||||||
print("Creating PMLZ file {0}".format(os.path.basename(pmlzname)))
|
print u"Creating PMLZ file {0}".format(os.path.basename(pmlzname))
|
||||||
myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False)
|
myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False)
|
||||||
list = os.listdir(outdir)
|
list = os.listdir(outdir)
|
||||||
for filename in list:
|
for filename in list:
|
||||||
@@ -513,48 +518,48 @@ def decryptBook(infile, outpath, make_pmlz, user_key):
|
|||||||
myZipFile.close()
|
myZipFile.close()
|
||||||
# remove temporary directory
|
# remove temporary directory
|
||||||
shutil.rmtree(outdir, True)
|
shutil.rmtree(outdir, True)
|
||||||
print("Output is {0}".format(pmlzname))
|
print u"Output is {0}".format(pmlzname)
|
||||||
else:
|
else :
|
||||||
print("Output is in {0}".format(outdir))
|
print u"Output is in {0}".format(outdir)
|
||||||
print("done")
|
print "done"
|
||||||
except ValueError as e:
|
except ValueError, e:
|
||||||
print("Error: {0}".format(e))
|
print u"Error: {0}".format(e)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
print("Converts DRMed eReader books to PML Source")
|
print u"Converts DRMed eReader books to PML Source"
|
||||||
print("Usage:")
|
print u"Usage:"
|
||||||
print(" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number")
|
print u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number"
|
||||||
print(" ")
|
print u" "
|
||||||
print("Options: ")
|
print u"Options: "
|
||||||
print(" -h prints this message")
|
print u" -h prints this message"
|
||||||
print(" -p create PMLZ instead of source folder")
|
print u" -p create PMLZ instead of source folder"
|
||||||
print(" --make-pmlz create PMLZ instead of source folder")
|
print u" --make-pmlz create PMLZ instead of source folder"
|
||||||
print(" ")
|
print u" "
|
||||||
print("Note:")
|
print u"Note:"
|
||||||
print(" if outpath is ommitted, creates source in 'infile_Source' folder")
|
print u" if outpath is ommitted, creates source in 'infile_Source' folder"
|
||||||
print(" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'")
|
print u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'"
|
||||||
print(" if source folder created, images are in infile_img folder")
|
print u" if source folder created, images are in infile_img folder"
|
||||||
print(" if pmlz file created, images are in images folder")
|
print u" if pmlz file created, images are in images folder"
|
||||||
print(" It's enough to enter the last 8 digits of the credit card number")
|
print u" It's enough to enter the last 8 digits of the credit card number"
|
||||||
return
|
return
|
||||||
|
|
||||||
def getuser_key(name,cc):
|
def getuser_key(name,cc):
|
||||||
newname = "".join(c for c in name.lower() if c >= 'a' and c <= 'z' or c >= '0' and c <= '9')
|
newname = "".join(c for c in name.lower() if c >= 'a' and c <= 'z' or c >= '0' and c <= '9')
|
||||||
cc = cc.replace(" ","")
|
cc = cc.replace(" ","")
|
||||||
return struct.pack('>LL', binascii.crc32(bytes(newname.encode('utf-8'))) & 0xffffffff, binascii.crc32(bytes(cc[-8:].encode('utf-8'))) & 0xffffffff)
|
return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff)
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
print("eRdr2Pml v{0}. Copyright © 2009–2020 The Dark Reverser et al.".format(__version__))
|
print u"eRdr2Pml v{0}. Copyright © 2009–2012 The Dark Reverser et al.".format(__version__)
|
||||||
|
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"])
|
opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"])
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError, err:
|
||||||
print(err.args[0])
|
print err.args[0]
|
||||||
usage()
|
usage()
|
||||||
return 1
|
return 1
|
||||||
make_pmlz = False
|
make_pmlz = False
|
||||||
@@ -574,13 +579,13 @@ def cli_main():
|
|||||||
if len(args)==3:
|
if len(args)==3:
|
||||||
infile, name, cc = args
|
infile, name, cc = args
|
||||||
if make_pmlz:
|
if make_pmlz:
|
||||||
outpath = os.path.splitext(infile)[0] + ".pmlz"
|
outpath = os.path.splitext(infile)[0] + u".pmlz"
|
||||||
else:
|
else:
|
||||||
outpath = os.path.splitext(infile)[0] + "_Source"
|
outpath = os.path.splitext(infile)[0] + u"_Source"
|
||||||
elif len(args)==4:
|
elif len(args)==4:
|
||||||
infile, outpath, name, cc = args
|
infile, outpath, name, cc = args
|
||||||
|
|
||||||
print(binascii.b2a_hex(getuser_key(name,cc)))
|
print getuser_key(name,cc).encode('hex')
|
||||||
|
|
||||||
return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc))
|
return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc))
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
# For use with Topaz Scripts Version 2.6
|
# For use with Topaz Scripts Version 2.6
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
import csv
|
import csv
|
||||||
import os
|
import os
|
||||||
import math
|
import math
|
||||||
import getopt
|
import getopt
|
||||||
import functools
|
|
||||||
from struct import pack
|
from struct import pack
|
||||||
from struct import unpack
|
from struct import unpack
|
||||||
|
|
||||||
@@ -16,14 +16,14 @@ class DocParser(object):
|
|||||||
def __init__(self, flatxml, classlst, fileid, bookDir, gdict, fixedimage):
|
def __init__(self, flatxml, classlst, fileid, bookDir, gdict, fixedimage):
|
||||||
self.id = os.path.basename(fileid).replace('.dat','')
|
self.id = os.path.basename(fileid).replace('.dat','')
|
||||||
self.svgcount = 0
|
self.svgcount = 0
|
||||||
self.docList = flatxml.split(b'\n')
|
self.docList = flatxml.split('\n')
|
||||||
self.docSize = len(self.docList)
|
self.docSize = len(self.docList)
|
||||||
self.classList = {}
|
self.classList = {}
|
||||||
self.bookDir = bookDir
|
self.bookDir = bookDir
|
||||||
self.gdict = gdict
|
self.gdict = gdict
|
||||||
tmpList = classlst.split('\n')
|
tmpList = classlst.split('\n')
|
||||||
for pclass in tmpList:
|
for pclass in tmpList:
|
||||||
if pclass != b'':
|
if pclass != '':
|
||||||
# remove the leading period from the css name
|
# remove the leading period from the css name
|
||||||
cname = pclass[1:]
|
cname = pclass[1:]
|
||||||
self.classList[cname] = True
|
self.classList[cname] = True
|
||||||
@@ -58,9 +58,9 @@ class DocParser(object):
|
|||||||
imgfile = os.path.join(imgDir,imgname)
|
imgfile = os.path.join(imgDir,imgname)
|
||||||
|
|
||||||
# get glyph information
|
# get glyph information
|
||||||
gxList = self.getData(b'info.glyph.x',0,-1)
|
gxList = self.getData('info.glyph.x',0,-1)
|
||||||
gyList = self.getData(b'info.glyph.y',0,-1)
|
gyList = self.getData('info.glyph.y',0,-1)
|
||||||
gidList = self.getData(b'info.glyph.glyphID',0,-1)
|
gidList = self.getData('info.glyph.glyphID',0,-1)
|
||||||
|
|
||||||
gids = []
|
gids = []
|
||||||
maxws = []
|
maxws = []
|
||||||
@@ -95,7 +95,7 @@ class DocParser(object):
|
|||||||
# change the origin to minx, miny and calc max height and width
|
# change the origin to minx, miny and calc max height and width
|
||||||
maxw = maxws[0] + xs[0] - minx
|
maxw = maxws[0] + xs[0] - minx
|
||||||
maxh = maxhs[0] + ys[0] - miny
|
maxh = maxhs[0] + ys[0] - miny
|
||||||
for j in range(0, len(xs)):
|
for j in xrange(0, len(xs)):
|
||||||
xs[j] = xs[j] - minx
|
xs[j] = xs[j] - minx
|
||||||
ys[j] = ys[j] - miny
|
ys[j] = ys[j] - miny
|
||||||
maxw = max( maxw, (maxws[j] + xs[j]) )
|
maxw = max( maxw, (maxws[j] + xs[j]) )
|
||||||
@@ -107,10 +107,10 @@ class DocParser(object):
|
|||||||
ifile.write('<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
|
ifile.write('<!DOCTYPE svg PUBLIC "-//W3C/DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
|
||||||
ifile.write('<svg width="%dpx" height="%dpx" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">\n' % (math.floor(maxw/10), math.floor(maxh/10), maxw, maxh))
|
ifile.write('<svg width="%dpx" height="%dpx" viewBox="0 0 %d %d" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">\n' % (math.floor(maxw/10), math.floor(maxh/10), maxw, maxh))
|
||||||
ifile.write('<defs>\n')
|
ifile.write('<defs>\n')
|
||||||
for j in range(0,len(gdefs)):
|
for j in xrange(0,len(gdefs)):
|
||||||
ifile.write(gdefs[j])
|
ifile.write(gdefs[j])
|
||||||
ifile.write('</defs>\n')
|
ifile.write('</defs>\n')
|
||||||
for j in range(0,len(gids)):
|
for j in xrange(0,len(gids)):
|
||||||
ifile.write('<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (gids[j], xs[j], ys[j]))
|
ifile.write('<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (gids[j], xs[j], ys[j]))
|
||||||
ifile.write('</svg>')
|
ifile.write('</svg>')
|
||||||
ifile.close()
|
ifile.close()
|
||||||
@@ -123,11 +123,11 @@ class DocParser(object):
|
|||||||
def lineinDoc(self, pos) :
|
def lineinDoc(self, pos) :
|
||||||
if (pos >= 0) and (pos < self.docSize) :
|
if (pos >= 0) and (pos < self.docSize) :
|
||||||
item = self.docList[pos]
|
item = self.docList[pos]
|
||||||
if item.find(b'=') >= 0:
|
if item.find('=') >= 0:
|
||||||
(name, argres) = item.split(b'=',1)
|
(name, argres) = item.split('=',1)
|
||||||
else :
|
else :
|
||||||
name = item
|
name = item
|
||||||
argres = b''
|
argres = ''
|
||||||
return name, argres
|
return name, argres
|
||||||
|
|
||||||
|
|
||||||
@@ -139,15 +139,13 @@ class DocParser(object):
|
|||||||
else:
|
else:
|
||||||
end = min(self.docSize, end)
|
end = min(self.docSize, end)
|
||||||
foundat = -1
|
foundat = -1
|
||||||
for j in range(pos, end):
|
for j in xrange(pos, end):
|
||||||
item = self.docList[j]
|
item = self.docList[j]
|
||||||
if item.find(b'=') >= 0:
|
if item.find('=') >= 0:
|
||||||
(name, argres) = item.split(b'=',1)
|
(name, argres) = item.split('=',1)
|
||||||
else :
|
else :
|
||||||
name = item
|
name = item
|
||||||
argres = ''
|
argres = ''
|
||||||
if (isinstance(tagpath,str)):
|
|
||||||
tagpath = tagpath.encode('utf-8')
|
|
||||||
if name.endswith(tagpath) :
|
if name.endswith(tagpath) :
|
||||||
result = argres
|
result = argres
|
||||||
foundat = j
|
foundat = j
|
||||||
@@ -173,7 +171,7 @@ class DocParser(object):
|
|||||||
argres=[]
|
argres=[]
|
||||||
(foundat, argt) = self.findinDoc(tagpath, pos, end)
|
(foundat, argt) = self.findinDoc(tagpath, pos, end)
|
||||||
if (argt != None) and (len(argt) > 0) :
|
if (argt != None) and (len(argt) > 0) :
|
||||||
argList = argt.split(b'|')
|
argList = argt.split('|')
|
||||||
argres = [ int(strval) for strval in argList]
|
argres = [ int(strval) for strval in argList]
|
||||||
return argres
|
return argres
|
||||||
|
|
||||||
@@ -194,21 +192,21 @@ class DocParser(object):
|
|||||||
|
|
||||||
# also some class names have spaces in them so need to convert to dashes
|
# also some class names have spaces in them so need to convert to dashes
|
||||||
if nclass != None :
|
if nclass != None :
|
||||||
nclass = nclass.replace(b' ',b'-')
|
nclass = nclass.replace(' ','-')
|
||||||
classres = b''
|
classres = ''
|
||||||
nclass = nclass.lower()
|
nclass = nclass.lower()
|
||||||
nclass = b'cl-' + nclass
|
nclass = 'cl-' + nclass
|
||||||
baseclass = b''
|
baseclass = ''
|
||||||
# graphic is the base class for captions
|
# graphic is the base class for captions
|
||||||
if nclass.find(b'cl-cap-') >=0 :
|
if nclass.find('cl-cap-') >=0 :
|
||||||
classres = b'graphic' + b' '
|
classres = 'graphic' + ' '
|
||||||
else :
|
else :
|
||||||
# strip to find baseclass
|
# strip to find baseclass
|
||||||
p = nclass.find(b'_')
|
p = nclass.find('_')
|
||||||
if p > 0 :
|
if p > 0 :
|
||||||
baseclass = nclass[0:p]
|
baseclass = nclass[0:p]
|
||||||
if baseclass in self.classList:
|
if baseclass in self.classList:
|
||||||
classres += baseclass + b' '
|
classres += baseclass + ' '
|
||||||
classres += nclass
|
classres += nclass
|
||||||
nclass = classres
|
nclass = classres
|
||||||
return nclass
|
return nclass
|
||||||
@@ -228,11 +226,11 @@ class DocParser(object):
|
|||||||
return -1
|
return -1
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
(pos, pagetype) = self.findinDoc(b'page.type',0,-1)
|
(pos, pagetype) = self.findinDoc('page.type',0,-1)
|
||||||
|
|
||||||
groupList = self.posinDoc(b'page.group')
|
groupList = self.posinDoc('page.group')
|
||||||
groupregionList = self.posinDoc(b'page.group.region')
|
groupregionList = self.posinDoc('page.group.region')
|
||||||
pageregionList = self.posinDoc(b'page.region')
|
pageregionList = self.posinDoc('page.region')
|
||||||
# integrate into one list
|
# integrate into one list
|
||||||
for j in groupList:
|
for j in groupList:
|
||||||
result.append(('grpbeg',j))
|
result.append(('grpbeg',j))
|
||||||
@@ -240,7 +238,7 @@ class DocParser(object):
|
|||||||
result.append(('gregion',j))
|
result.append(('gregion',j))
|
||||||
for j in pageregionList:
|
for j in pageregionList:
|
||||||
result.append(('pregion',j))
|
result.append(('pregion',j))
|
||||||
result.sort(key=functools.cmp_to_key(compare))
|
result.sort(compare)
|
||||||
|
|
||||||
# insert group end and page end indicators
|
# insert group end and page end indicators
|
||||||
inGroup = False
|
inGroup = False
|
||||||
@@ -270,39 +268,39 @@ class DocParser(object):
|
|||||||
result = []
|
result = []
|
||||||
|
|
||||||
# paragraph
|
# paragraph
|
||||||
(pos, pclass) = self.findinDoc(b'paragraph.class',start,end)
|
(pos, pclass) = self.findinDoc('paragraph.class',start,end)
|
||||||
|
|
||||||
pclass = self.getClass(pclass)
|
pclass = self.getClass(pclass)
|
||||||
|
|
||||||
# if paragraph uses extratokens (extra glyphs) then make it fixed
|
# if paragraph uses extratokens (extra glyphs) then make it fixed
|
||||||
(pos, extraglyphs) = self.findinDoc(b'paragraph.extratokens',start,end)
|
(pos, extraglyphs) = self.findinDoc('paragraph.extratokens',start,end)
|
||||||
|
|
||||||
# build up a description of the paragraph in result and return it
|
# build up a description of the paragraph in result and return it
|
||||||
# first check for the basic - all words paragraph
|
# first check for the basic - all words paragraph
|
||||||
(pos, sfirst) = self.findinDoc(b'paragraph.firstWord',start,end)
|
(pos, sfirst) = self.findinDoc('paragraph.firstWord',start,end)
|
||||||
(pos, slast) = self.findinDoc(b'paragraph.lastWord',start,end)
|
(pos, slast) = self.findinDoc('paragraph.lastWord',start,end)
|
||||||
if (sfirst != None) and (slast != None) :
|
if (sfirst != None) and (slast != None) :
|
||||||
first = int(sfirst)
|
first = int(sfirst)
|
||||||
last = int(slast)
|
last = int(slast)
|
||||||
|
|
||||||
makeImage = (regtype == b'vertical') or (regtype == b'table')
|
makeImage = (regtype == 'vertical') or (regtype == 'table')
|
||||||
makeImage = makeImage or (extraglyphs != None)
|
makeImage = makeImage or (extraglyphs != None)
|
||||||
if self.fixedimage:
|
if self.fixedimage:
|
||||||
makeImage = makeImage or (regtype == b'fixed')
|
makeImage = makeImage or (regtype == 'fixed')
|
||||||
|
|
||||||
if (pclass != None):
|
if (pclass != None):
|
||||||
makeImage = makeImage or (pclass.find(b'.inverted') >= 0)
|
makeImage = makeImage or (pclass.find('.inverted') >= 0)
|
||||||
if self.fixedimage :
|
if self.fixedimage :
|
||||||
makeImage = makeImage or (pclass.find(b'cl-f-') >= 0)
|
makeImage = makeImage or (pclass.find('cl-f-') >= 0)
|
||||||
|
|
||||||
# before creating an image make sure glyph info exists
|
# before creating an image make sure glyph info exists
|
||||||
gidList = self.getData(b'info.glyph.glyphID',0,-1)
|
gidList = self.getData('info.glyph.glyphID',0,-1)
|
||||||
|
|
||||||
makeImage = makeImage & (len(gidList) > 0)
|
makeImage = makeImage & (len(gidList) > 0)
|
||||||
|
|
||||||
if not makeImage :
|
if not makeImage :
|
||||||
# standard all word paragraph
|
# standard all word paragraph
|
||||||
for wordnum in range(first, last):
|
for wordnum in xrange(first, last):
|
||||||
result.append(('ocr', wordnum))
|
result.append(('ocr', wordnum))
|
||||||
return pclass, result
|
return pclass, result
|
||||||
|
|
||||||
@@ -310,8 +308,8 @@ class DocParser(object):
|
|||||||
# translate first and last word into first and last glyphs
|
# translate first and last word into first and last glyphs
|
||||||
# and generate inline image and include it
|
# and generate inline image and include it
|
||||||
glyphList = []
|
glyphList = []
|
||||||
firstglyphList = self.getData(b'word.firstGlyph',0,-1)
|
firstglyphList = self.getData('word.firstGlyph',0,-1)
|
||||||
gidList = self.getData(b'info.glyph.glyphID',0,-1)
|
gidList = self.getData('info.glyph.glyphID',0,-1)
|
||||||
firstGlyph = firstglyphList[first]
|
firstGlyph = firstglyphList[first]
|
||||||
if last < len(firstglyphList):
|
if last < len(firstglyphList):
|
||||||
lastGlyph = firstglyphList[last]
|
lastGlyph = firstglyphList[last]
|
||||||
@@ -322,17 +320,17 @@ class DocParser(object):
|
|||||||
# by reverting to text based paragraph
|
# by reverting to text based paragraph
|
||||||
if firstGlyph >= lastGlyph:
|
if firstGlyph >= lastGlyph:
|
||||||
# revert to standard text based paragraph
|
# revert to standard text based paragraph
|
||||||
for wordnum in range(first, last):
|
for wordnum in xrange(first, last):
|
||||||
result.append(('ocr', wordnum))
|
result.append(('ocr', wordnum))
|
||||||
return pclass, result
|
return pclass, result
|
||||||
|
|
||||||
for glyphnum in range(firstGlyph, lastGlyph):
|
for glyphnum in xrange(firstGlyph, lastGlyph):
|
||||||
glyphList.append(glyphnum)
|
glyphList.append(glyphnum)
|
||||||
# include any extratokens if they exist
|
# include any extratokens if they exist
|
||||||
(pos, sfg) = self.findinDoc(b'extratokens.firstGlyph',start,end)
|
(pos, sfg) = self.findinDoc('extratokens.firstGlyph',start,end)
|
||||||
(pos, slg) = self.findinDoc(b'extratokens.lastGlyph',start,end)
|
(pos, slg) = self.findinDoc('extratokens.lastGlyph',start,end)
|
||||||
if (sfg != None) and (slg != None):
|
if (sfg != None) and (slg != None):
|
||||||
for glyphnum in range(int(sfg), int(slg)):
|
for glyphnum in xrange(int(sfg), int(slg)):
|
||||||
glyphList.append(glyphnum)
|
glyphList.append(glyphnum)
|
||||||
num = self.svgcount
|
num = self.svgcount
|
||||||
self.glyphs_to_image(glyphList)
|
self.glyphs_to_image(glyphList)
|
||||||
@@ -371,50 +369,50 @@ class DocParser(object):
|
|||||||
|
|
||||||
(name, argres) = self.lineinDoc(line)
|
(name, argres) = self.lineinDoc(line)
|
||||||
|
|
||||||
if name.endswith(b'span.firstWord') :
|
if name.endswith('span.firstWord') :
|
||||||
sp_first = int(argres)
|
sp_first = int(argres)
|
||||||
|
|
||||||
elif name.endswith(b'span.lastWord') :
|
elif name.endswith('span.lastWord') :
|
||||||
sp_last = int(argres)
|
sp_last = int(argres)
|
||||||
|
|
||||||
elif name.endswith(b'word.firstGlyph') :
|
elif name.endswith('word.firstGlyph') :
|
||||||
gl_first = int(argres)
|
gl_first = int(argres)
|
||||||
|
|
||||||
elif name.endswith(b'word.lastGlyph') :
|
elif name.endswith('word.lastGlyph') :
|
||||||
gl_last = int(argres)
|
gl_last = int(argres)
|
||||||
|
|
||||||
elif name.endswith(b'word_semantic.firstWord'):
|
elif name.endswith('word_semantic.firstWord'):
|
||||||
ws_first = int(argres)
|
ws_first = int(argres)
|
||||||
|
|
||||||
elif name.endswith(b'word_semantic.lastWord'):
|
elif name.endswith('word_semantic.lastWord'):
|
||||||
ws_last = int(argres)
|
ws_last = int(argres)
|
||||||
|
|
||||||
elif name.endswith(b'word.class'):
|
elif name.endswith('word.class'):
|
||||||
# we only handle spaceafter word class
|
# we only handle spaceafter word class
|
||||||
try:
|
try:
|
||||||
(cname, space) = argres.split(b'-',1)
|
(cname, space) = argres.split('-',1)
|
||||||
if space == b'' : space = b'0'
|
if space == '' : space = '0'
|
||||||
if (cname == b'spaceafter') and (int(space) > 0) :
|
if (cname == 'spaceafter') and (int(space) > 0) :
|
||||||
word_class = 'sa'
|
word_class = 'sa'
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif name.endswith(b'word.img.src'):
|
elif name.endswith('word.img.src'):
|
||||||
result.append(('img' + word_class, int(argres)))
|
result.append(('img' + word_class, int(argres)))
|
||||||
word_class = ''
|
word_class = ''
|
||||||
|
|
||||||
elif name.endswith(b'region.img.src'):
|
elif name.endswith('region.img.src'):
|
||||||
result.append(('img' + word_class, int(argres)))
|
result.append(('img' + word_class, int(argres)))
|
||||||
|
|
||||||
if (sp_first != -1) and (sp_last != -1):
|
if (sp_first != -1) and (sp_last != -1):
|
||||||
for wordnum in range(sp_first, sp_last):
|
for wordnum in xrange(sp_first, sp_last):
|
||||||
result.append(('ocr', wordnum))
|
result.append(('ocr', wordnum))
|
||||||
sp_first = -1
|
sp_first = -1
|
||||||
sp_last = -1
|
sp_last = -1
|
||||||
|
|
||||||
if (gl_first != -1) and (gl_last != -1):
|
if (gl_first != -1) and (gl_last != -1):
|
||||||
glyphList = []
|
glyphList = []
|
||||||
for glyphnum in range(gl_first, gl_last):
|
for glyphnum in xrange(gl_first, gl_last):
|
||||||
glyphList.append(glyphnum)
|
glyphList.append(glyphnum)
|
||||||
num = self.svgcount
|
num = self.svgcount
|
||||||
self.glyphs_to_image(glyphList)
|
self.glyphs_to_image(glyphList)
|
||||||
@@ -424,7 +422,7 @@ class DocParser(object):
|
|||||||
gl_last = -1
|
gl_last = -1
|
||||||
|
|
||||||
if (ws_first != -1) and (ws_last != -1):
|
if (ws_first != -1) and (ws_last != -1):
|
||||||
for wordnum in range(ws_first, ws_last):
|
for wordnum in xrange(ws_first, ws_last):
|
||||||
result.append(('ocr', wordnum))
|
result.append(('ocr', wordnum))
|
||||||
ws_first = -1
|
ws_first = -1
|
||||||
ws_last = -1
|
ws_last = -1
|
||||||
@@ -440,7 +438,7 @@ class DocParser(object):
|
|||||||
|
|
||||||
classres = ''
|
classres = ''
|
||||||
if pclass :
|
if pclass :
|
||||||
classres = ' class="' + pclass.decode('utf-8') + '"'
|
classres = ' class="' + pclass + '"'
|
||||||
|
|
||||||
br_lb = (regtype == 'fixed') or (regtype == 'chapterheading') or (regtype == 'vertical')
|
br_lb = (regtype == 'fixed') or (regtype == 'chapterheading') or (regtype == 'vertical')
|
||||||
|
|
||||||
@@ -456,7 +454,7 @@ class DocParser(object):
|
|||||||
|
|
||||||
cnt = len(pdesc)
|
cnt = len(pdesc)
|
||||||
|
|
||||||
for j in range( 0, cnt) :
|
for j in xrange( 0, cnt) :
|
||||||
|
|
||||||
(wtype, num) = pdesc[j]
|
(wtype, num) = pdesc[j]
|
||||||
|
|
||||||
@@ -473,8 +471,8 @@ class DocParser(object):
|
|||||||
if (link > 0):
|
if (link > 0):
|
||||||
linktype = self.link_type[link-1]
|
linktype = self.link_type[link-1]
|
||||||
title = self.link_title[link-1]
|
title = self.link_title[link-1]
|
||||||
if (title == b"") or (parares.rfind(title.decode('utf-8')) < 0):
|
if (title == "") or (parares.rfind(title) < 0):
|
||||||
title=parares[lstart:].encode('utf-8')
|
title=parares[lstart:]
|
||||||
if linktype == 'external' :
|
if linktype == 'external' :
|
||||||
linkhref = self.link_href[link-1]
|
linkhref = self.link_href[link-1]
|
||||||
linkhtml = '<a href="%s">' % linkhref
|
linkhtml = '<a href="%s">' % linkhref
|
||||||
@@ -485,34 +483,33 @@ class DocParser(object):
|
|||||||
else :
|
else :
|
||||||
# just link to the current page
|
# just link to the current page
|
||||||
linkhtml = '<a href="#' + self.id + '">'
|
linkhtml = '<a href="#' + self.id + '">'
|
||||||
linkhtml += title.decode('utf-8')
|
linkhtml += title + '</a>'
|
||||||
linkhtml += '</a>'
|
pos = parares.rfind(title)
|
||||||
pos = parares.rfind(title.decode('utf-8'))
|
|
||||||
if pos >= 0:
|
if pos >= 0:
|
||||||
parares = parares[0:pos] + linkhtml + parares[pos+len(title):]
|
parares = parares[0:pos] + linkhtml + parares[pos+len(title):]
|
||||||
else :
|
else :
|
||||||
parares += linkhtml
|
parares += linkhtml
|
||||||
lstart = len(parares)
|
lstart = len(parares)
|
||||||
if word == b'_link_' : word = b''
|
if word == '_link_' : word = ''
|
||||||
elif (link < 0) :
|
elif (link < 0) :
|
||||||
if word == b'_link_' : word = b''
|
if word == '_link_' : word = ''
|
||||||
|
|
||||||
if word == b'_lb_':
|
if word == '_lb_':
|
||||||
if ((num-1) in self.dehyphen_rootid ) or handle_links:
|
if ((num-1) in self.dehyphen_rootid ) or handle_links:
|
||||||
word = b''
|
word = ''
|
||||||
sep = ''
|
sep = ''
|
||||||
elif br_lb :
|
elif br_lb :
|
||||||
word = b'<br />\n'
|
word = '<br />\n'
|
||||||
sep = ''
|
sep = ''
|
||||||
else :
|
else :
|
||||||
word = b'\n'
|
word = '\n'
|
||||||
sep = ''
|
sep = ''
|
||||||
|
|
||||||
if num in self.dehyphen_rootid :
|
if num in self.dehyphen_rootid :
|
||||||
word = word[0:-1]
|
word = word[0:-1]
|
||||||
sep = ''
|
sep = ''
|
||||||
|
|
||||||
parares += word.decode('utf-8') + sep
|
parares += word + sep
|
||||||
|
|
||||||
elif wtype == 'img' :
|
elif wtype == 'img' :
|
||||||
sep = ''
|
sep = ''
|
||||||
@@ -526,9 +523,7 @@ class DocParser(object):
|
|||||||
|
|
||||||
elif wtype == 'svg' :
|
elif wtype == 'svg' :
|
||||||
sep = ''
|
sep = ''
|
||||||
parares += '<img src="img/'
|
parares += '<img src="img/' + self.id + '_%04d.svg" alt="" />' % num
|
||||||
parares += self.id
|
|
||||||
parares += '_%04d.svg" alt="" />' % num
|
|
||||||
parares += sep
|
parares += sep
|
||||||
|
|
||||||
if len(sep) > 0 : parares = parares[0:-1]
|
if len(sep) > 0 : parares = parares[0:-1]
|
||||||
@@ -546,12 +541,12 @@ class DocParser(object):
|
|||||||
lstart = 0
|
lstart = 0
|
||||||
|
|
||||||
cnt = len(pdesc)
|
cnt = len(pdesc)
|
||||||
for j in range( 0, cnt) :
|
for j in xrange( 0, cnt) :
|
||||||
|
|
||||||
(wtype, num) = pdesc[j]
|
(wtype, num) = pdesc[j]
|
||||||
|
|
||||||
if wtype == 'ocr' :
|
if wtype == 'ocr' :
|
||||||
word = self.ocrtext[num].decode('utf-8')
|
word = self.ocrtext[num]
|
||||||
sep = ' '
|
sep = ' '
|
||||||
|
|
||||||
if handle_links:
|
if handle_links:
|
||||||
@@ -559,7 +554,7 @@ class DocParser(object):
|
|||||||
if (link > 0):
|
if (link > 0):
|
||||||
linktype = self.link_type[link-1]
|
linktype = self.link_type[link-1]
|
||||||
title = self.link_title[link-1]
|
title = self.link_title[link-1]
|
||||||
title = title.rstrip(b'. ')
|
title = title.rstrip('. ')
|
||||||
alt_title = parares[lstart:]
|
alt_title = parares[lstart:]
|
||||||
alt_title = alt_title.strip()
|
alt_title = alt_title.strip()
|
||||||
# now strip off the actual printed page number
|
# now strip off the actual printed page number
|
||||||
@@ -613,38 +608,38 @@ class DocParser(object):
|
|||||||
hlst = []
|
hlst = []
|
||||||
|
|
||||||
# get the ocr text
|
# get the ocr text
|
||||||
(pos, argres) = self.findinDoc(b'info.word.ocrText',0,-1)
|
(pos, argres) = self.findinDoc('info.word.ocrText',0,-1)
|
||||||
if argres : self.ocrtext = argres.split(b'|')
|
if argres : self.ocrtext = argres.split('|')
|
||||||
|
|
||||||
# get information to dehyphenate the text
|
# get information to dehyphenate the text
|
||||||
self.dehyphen_rootid = self.getData(b'info.dehyphen.rootID',0,-1)
|
self.dehyphen_rootid = self.getData('info.dehyphen.rootID',0,-1)
|
||||||
|
|
||||||
# determine if first paragraph is continued from previous page
|
# determine if first paragraph is continued from previous page
|
||||||
(pos, self.parastems_stemid) = self.findinDoc(b'info.paraStems.stemID',0,-1)
|
(pos, self.parastems_stemid) = self.findinDoc('info.paraStems.stemID',0,-1)
|
||||||
first_para_continued = (self.parastems_stemid != None)
|
first_para_continued = (self.parastems_stemid != None)
|
||||||
|
|
||||||
# determine if last paragraph is continued onto the next page
|
# determine if last paragraph is continued onto the next page
|
||||||
(pos, self.paracont_stemid) = self.findinDoc(b'info.paraCont.stemID',0,-1)
|
(pos, self.paracont_stemid) = self.findinDoc('info.paraCont.stemID',0,-1)
|
||||||
last_para_continued = (self.paracont_stemid != None)
|
last_para_continued = (self.paracont_stemid != None)
|
||||||
|
|
||||||
# collect link ids
|
# collect link ids
|
||||||
self.link_id = self.getData(b'info.word.link_id',0,-1)
|
self.link_id = self.getData('info.word.link_id',0,-1)
|
||||||
|
|
||||||
# collect link destination page numbers
|
# collect link destination page numbers
|
||||||
self.link_page = self.getData(b'info.links.page',0,-1)
|
self.link_page = self.getData('info.links.page',0,-1)
|
||||||
|
|
||||||
# collect link types (container versus external)
|
# collect link types (container versus external)
|
||||||
(pos, argres) = self.findinDoc(b'info.links.type',0,-1)
|
(pos, argres) = self.findinDoc('info.links.type',0,-1)
|
||||||
if argres : self.link_type = argres.split(b'|')
|
if argres : self.link_type = argres.split('|')
|
||||||
|
|
||||||
# collect link destinations
|
# collect link destinations
|
||||||
(pos, argres) = self.findinDoc(b'info.links.href',0,-1)
|
(pos, argres) = self.findinDoc('info.links.href',0,-1)
|
||||||
if argres : self.link_href = argres.split(b'|')
|
if argres : self.link_href = argres.split('|')
|
||||||
|
|
||||||
# collect link titles
|
# collect link titles
|
||||||
(pos, argres) = self.findinDoc(b'info.links.title',0,-1)
|
(pos, argres) = self.findinDoc('info.links.title',0,-1)
|
||||||
if argres :
|
if argres :
|
||||||
self.link_title = argres.split(b'|')
|
self.link_title = argres.split('|')
|
||||||
else:
|
else:
|
||||||
self.link_title.append('')
|
self.link_title.append('')
|
||||||
|
|
||||||
@@ -659,7 +654,7 @@ class DocParser(object):
|
|||||||
|
|
||||||
# process each region on the page and convert what you can to html
|
# process each region on the page and convert what you can to html
|
||||||
|
|
||||||
for j in range(regcnt):
|
for j in xrange(regcnt):
|
||||||
|
|
||||||
(etype, start) = pageDesc[j]
|
(etype, start) = pageDesc[j]
|
||||||
(ntype, end) = pageDesc[j+1]
|
(ntype, end) = pageDesc[j+1]
|
||||||
@@ -668,51 +663,51 @@ class DocParser(object):
|
|||||||
# set anchor for link target on this page
|
# set anchor for link target on this page
|
||||||
if not anchorSet and not first_para_continued:
|
if not anchorSet and not first_para_continued:
|
||||||
hlst.append('<div style="visibility: hidden; height: 0; width: 0;" id="')
|
hlst.append('<div style="visibility: hidden; height: 0; width: 0;" id="')
|
||||||
hlst.append(self.id + '" title="pagetype_' + pagetype.decode('utf-8') + '"></div>\n')
|
hlst.append(self.id + '" title="pagetype_' + pagetype + '"></div>\n')
|
||||||
anchorSet = True
|
anchorSet = True
|
||||||
|
|
||||||
# handle groups of graphics with text captions
|
# handle groups of graphics with text captions
|
||||||
if (etype == b'grpbeg'):
|
if (etype == 'grpbeg'):
|
||||||
(pos, grptype) = self.findinDoc(b'group.type', start, end)
|
(pos, grptype) = self.findinDoc('group.type', start, end)
|
||||||
if grptype != None:
|
if grptype != None:
|
||||||
if grptype == b'graphic':
|
if grptype == 'graphic':
|
||||||
gcstr = ' class="' + grptype.decode('utf-8') + '"'
|
gcstr = ' class="' + grptype + '"'
|
||||||
hlst.append('<div' + gcstr + '>')
|
hlst.append('<div' + gcstr + '>')
|
||||||
inGroup = True
|
inGroup = True
|
||||||
|
|
||||||
elif (etype == b'grpend'):
|
elif (etype == 'grpend'):
|
||||||
if inGroup:
|
if inGroup:
|
||||||
hlst.append('</div>\n')
|
hlst.append('</div>\n')
|
||||||
inGroup = False
|
inGroup = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
(pos, regtype) = self.findinDoc(b'region.type',start,end)
|
(pos, regtype) = self.findinDoc('region.type',start,end)
|
||||||
|
|
||||||
if regtype == b'graphic' :
|
if regtype == 'graphic' :
|
||||||
(pos, simgsrc) = self.findinDoc(b'img.src',start,end)
|
(pos, simgsrc) = self.findinDoc('img.src',start,end)
|
||||||
if simgsrc:
|
if simgsrc:
|
||||||
if inGroup:
|
if inGroup:
|
||||||
hlst.append('<img src="img/img%04d.jpg" alt="" />' % int(simgsrc))
|
hlst.append('<img src="img/img%04d.jpg" alt="" />' % int(simgsrc))
|
||||||
else:
|
else:
|
||||||
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
||||||
|
|
||||||
elif regtype == b'chapterheading' :
|
elif regtype == 'chapterheading' :
|
||||||
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
||||||
if not breakSet:
|
if not breakSet:
|
||||||
hlst.append('<div style="page-break-after: always;"> </div>\n')
|
hlst.append('<div style="page-break-after: always;"> </div>\n')
|
||||||
breakSet = True
|
breakSet = True
|
||||||
tag = 'h1'
|
tag = 'h1'
|
||||||
if pclass and (len(pclass) >= 7):
|
if pclass and (len(pclass) >= 7):
|
||||||
if pclass[3:7] == b'ch1-' : tag = 'h1'
|
if pclass[3:7] == 'ch1-' : tag = 'h1'
|
||||||
if pclass[3:7] == b'ch2-' : tag = 'h2'
|
if pclass[3:7] == 'ch2-' : tag = 'h2'
|
||||||
if pclass[3:7] == b'ch3-' : tag = 'h3'
|
if pclass[3:7] == 'ch3-' : tag = 'h3'
|
||||||
hlst.append('<' + tag + ' class="' + pclass.decode('utf-8') + '">')
|
hlst.append('<' + tag + ' class="' + pclass + '">')
|
||||||
else:
|
else:
|
||||||
hlst.append('<' + tag + '>')
|
hlst.append('<' + tag + '>')
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
||||||
hlst.append('</' + tag + '>')
|
hlst.append('</' + tag + '>')
|
||||||
|
|
||||||
elif (regtype == b'text') or (regtype == b'fixed') or (regtype == b'insert') or (regtype == b'listitem'):
|
elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'):
|
||||||
ptype = 'full'
|
ptype = 'full'
|
||||||
# check to see if this is a continution from the previous page
|
# check to see if this is a continution from the previous page
|
||||||
if first_para_continued :
|
if first_para_continued :
|
||||||
@@ -721,16 +716,16 @@ class DocParser(object):
|
|||||||
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
||||||
if pclass and (len(pclass) >= 6) and (ptype == 'full'):
|
if pclass and (len(pclass) >= 6) and (ptype == 'full'):
|
||||||
tag = 'p'
|
tag = 'p'
|
||||||
if pclass[3:6] == b'h1-' : tag = 'h4'
|
if pclass[3:6] == 'h1-' : tag = 'h4'
|
||||||
if pclass[3:6] == b'h2-' : tag = 'h5'
|
if pclass[3:6] == 'h2-' : tag = 'h5'
|
||||||
if pclass[3:6] == b'h3-' : tag = 'h6'
|
if pclass[3:6] == 'h3-' : tag = 'h6'
|
||||||
hlst.append('<' + tag + ' class="' + pclass.decode('utf-8') + '">')
|
hlst.append('<' + tag + ' class="' + pclass + '">')
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
||||||
hlst.append('</' + tag + '>')
|
hlst.append('</' + tag + '>')
|
||||||
else :
|
else :
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
||||||
|
|
||||||
elif (regtype == b'tocentry') :
|
elif (regtype == 'tocentry') :
|
||||||
ptype = 'full'
|
ptype = 'full'
|
||||||
if first_para_continued :
|
if first_para_continued :
|
||||||
ptype = 'end'
|
ptype = 'end'
|
||||||
@@ -739,7 +734,7 @@ class DocParser(object):
|
|||||||
tocinfo += self.buildTOCEntry(pdesc)
|
tocinfo += self.buildTOCEntry(pdesc)
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
||||||
|
|
||||||
elif (regtype == b'vertical') or (regtype == b'table') :
|
elif (regtype == 'vertical') or (regtype == 'table') :
|
||||||
ptype = 'full'
|
ptype = 'full'
|
||||||
if inGroup:
|
if inGroup:
|
||||||
ptype = 'middle'
|
ptype = 'middle'
|
||||||
@@ -750,19 +745,19 @@ class DocParser(object):
|
|||||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
||||||
|
|
||||||
|
|
||||||
elif (regtype == b'synth_fcvr.center'):
|
elif (regtype == 'synth_fcvr.center'):
|
||||||
(pos, simgsrc) = self.findinDoc(b'img.src',start,end)
|
(pos, simgsrc) = self.findinDoc('img.src',start,end)
|
||||||
if simgsrc:
|
if simgsrc:
|
||||||
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
||||||
|
|
||||||
else :
|
else :
|
||||||
print(' Making region type', regtype, end=' ')
|
print(' Making region type', regtype, end=' ')
|
||||||
(pos, temp) = self.findinDoc(b'paragraph',start,end)
|
(pos, temp) = self.findinDoc('paragraph',start,end)
|
||||||
(pos2, temp) = self.findinDoc(b'span',start,end)
|
(pos2, temp) = self.findinDoc('span',start,end)
|
||||||
if pos != -1 or pos2 != -1:
|
if pos != -1 or pos2 != -1:
|
||||||
print(' a "text" region')
|
print(' a "text" region')
|
||||||
orig_regtype = regtype
|
orig_regtype = regtype
|
||||||
regtype = b'fixed'
|
regtype = 'fixed'
|
||||||
ptype = 'full'
|
ptype = 'full'
|
||||||
# check to see if this is a continution from the previous page
|
# check to see if this is a continution from the previous page
|
||||||
if first_para_continued :
|
if first_para_continued :
|
||||||
@@ -770,23 +765,23 @@ class DocParser(object):
|
|||||||
first_para_continued = False
|
first_para_continued = False
|
||||||
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
(pclass, pdesc) = self.getParaDescription(start,end, regtype)
|
||||||
if not pclass:
|
if not pclass:
|
||||||
if orig_regtype.endswith(b'.right') : pclass = 'cl-right'
|
if orig_regtype.endswith('.right') : pclass = 'cl-right'
|
||||||
elif orig_regtype.endswith(b'.center') : pclass = 'cl-center'
|
elif orig_regtype.endswith('.center') : pclass = 'cl-center'
|
||||||
elif orig_regtype.endswith(b'.left') : pclass = 'cl-left'
|
elif orig_regtype.endswith('.left') : pclass = 'cl-left'
|
||||||
elif orig_regtype.endswith(b'.justify') : pclass = 'cl-justify'
|
elif orig_regtype.endswith('.justify') : pclass = 'cl-justify'
|
||||||
if pclass and (ptype == 'full') and (len(pclass) >= 6):
|
if pclass and (ptype == 'full') and (len(pclass) >= 6):
|
||||||
tag = 'p'
|
tag = 'p'
|
||||||
if pclass[3:6] == b'h1-' : tag = 'h4'
|
if pclass[3:6] == 'h1-' : tag = 'h4'
|
||||||
if pclass[3:6] == b'h2-' : tag = 'h5'
|
if pclass[3:6] == 'h2-' : tag = 'h5'
|
||||||
if pclass[3:6] == b'h3-' : tag = 'h6'
|
if pclass[3:6] == 'h3-' : tag = 'h6'
|
||||||
hlst.append('<' + tag + ' class="' + pclass.decode('utf-8') + '">')
|
hlst.append('<' + tag + ' class="' + pclass + '">')
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
|
||||||
hlst.append('</' + tag + '>')
|
hlst.append('</' + tag + '>')
|
||||||
else :
|
else :
|
||||||
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
|
||||||
else :
|
else :
|
||||||
print(' a "graphic" region')
|
print(' a "graphic" region')
|
||||||
(pos, simgsrc) = self.findinDoc(b'img.src',start,end)
|
(pos, simgsrc) = self.findinDoc('img.src',start,end)
|
||||||
if simgsrc:
|
if simgsrc:
|
||||||
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
hlst.append('<div class="graphic"><img src="img/img%04d.jpg" alt="" /></div>' % int(simgsrc))
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from struct import unpack
|
|||||||
class PParser(object):
|
class PParser(object):
|
||||||
def __init__(self, gd, flatxml, meta_array):
|
def __init__(self, gd, flatxml, meta_array):
|
||||||
self.gd = gd
|
self.gd = gd
|
||||||
self.flatdoc = flatxml.split(b'\n')
|
self.flatdoc = flatxml.split('\n')
|
||||||
self.docSize = len(self.flatdoc)
|
self.docSize = len(self.flatdoc)
|
||||||
self.temp = []
|
self.temp = []
|
||||||
|
|
||||||
@@ -58,11 +58,11 @@ class PParser(object):
|
|||||||
def lineinDoc(self, pos) :
|
def lineinDoc(self, pos) :
|
||||||
if (pos >= 0) and (pos < self.docSize) :
|
if (pos >= 0) and (pos < self.docSize) :
|
||||||
item = self.flatdoc[pos]
|
item = self.flatdoc[pos]
|
||||||
if item.find(b'=') >= 0:
|
if item.find('=') >= 0:
|
||||||
(name, argres) = item.split(b'=',1)
|
(name, argres) = item.split('=',1)
|
||||||
else :
|
else :
|
||||||
name = item
|
name = item
|
||||||
argres = b''
|
argres = ''
|
||||||
return name, argres
|
return name, argres
|
||||||
|
|
||||||
# find tag in doc if within pos to end inclusive
|
# find tag in doc if within pos to end inclusive
|
||||||
@@ -73,15 +73,13 @@ class PParser(object):
|
|||||||
else:
|
else:
|
||||||
end = min(self.docSize, end)
|
end = min(self.docSize, end)
|
||||||
foundat = -1
|
foundat = -1
|
||||||
for j in range(pos, end):
|
for j in xrange(pos, end):
|
||||||
item = self.flatdoc[j]
|
item = self.flatdoc[j]
|
||||||
if item.find(b'=') >= 0:
|
if item.find('=') >= 0:
|
||||||
(name, argres) = item.split(b'=',1)
|
(name, argres) = item.split('=',1)
|
||||||
else :
|
else :
|
||||||
name = item
|
name = item
|
||||||
argres = b''
|
argres = ''
|
||||||
if (isinstance(tagpath,str)):
|
|
||||||
tagpath = tagpath.encode('utf-8')
|
|
||||||
if name.endswith(tagpath) :
|
if name.endswith(tagpath) :
|
||||||
result = argres
|
result = argres
|
||||||
foundat = j
|
foundat = j
|
||||||
@@ -103,11 +101,11 @@ class PParser(object):
|
|||||||
def getData(self, path):
|
def getData(self, path):
|
||||||
result = None
|
result = None
|
||||||
cnt = len(self.flatdoc)
|
cnt = len(self.flatdoc)
|
||||||
for j in range(cnt):
|
for j in xrange(cnt):
|
||||||
item = self.flatdoc[j]
|
item = self.flatdoc[j]
|
||||||
if item.find(b'=') >= 0:
|
if item.find('=') >= 0:
|
||||||
(name, argt) = item.split(b'=')
|
(name, argt) = item.split('=')
|
||||||
argres = argt.split(b'|')
|
argres = argt.split('|')
|
||||||
else:
|
else:
|
||||||
name = item
|
name = item
|
||||||
argres = []
|
argres = []
|
||||||
@@ -115,24 +113,22 @@ class PParser(object):
|
|||||||
result = argres
|
result = argres
|
||||||
break
|
break
|
||||||
if (len(argres) > 0) :
|
if (len(argres) > 0) :
|
||||||
for j in range(0,len(argres)):
|
for j in xrange(0,len(argres)):
|
||||||
argres[j] = int(argres[j])
|
argres[j] = int(argres[j])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def getDataatPos(self, path, pos):
|
def getDataatPos(self, path, pos):
|
||||||
result = None
|
result = None
|
||||||
item = self.flatdoc[pos]
|
item = self.flatdoc[pos]
|
||||||
if item.find(b'=') >= 0:
|
if item.find('=') >= 0:
|
||||||
(name, argt) = item.split(b'=')
|
(name, argt) = item.split('=')
|
||||||
argres = argt.split(b'|')
|
argres = argt.split('|')
|
||||||
else:
|
else:
|
||||||
name = item
|
name = item
|
||||||
argres = []
|
argres = []
|
||||||
if (len(argres) > 0) :
|
if (len(argres) > 0) :
|
||||||
for j in range(0,len(argres)):
|
for j in xrange(0,len(argres)):
|
||||||
argres[j] = int(argres[j])
|
argres[j] = int(argres[j])
|
||||||
if (isinstance(path,str)):
|
|
||||||
path = path.encode('utf-8')
|
|
||||||
if (name.endswith(path)):
|
if (name.endswith(path)):
|
||||||
result = argres
|
result = argres
|
||||||
return result
|
return result
|
||||||
@@ -140,22 +136,20 @@ class PParser(object):
|
|||||||
def getDataTemp(self, path):
|
def getDataTemp(self, path):
|
||||||
result = None
|
result = None
|
||||||
cnt = len(self.temp)
|
cnt = len(self.temp)
|
||||||
for j in range(cnt):
|
for j in xrange(cnt):
|
||||||
item = self.temp[j]
|
item = self.temp[j]
|
||||||
if item.find(b'=') >= 0:
|
if item.find('=') >= 0:
|
||||||
(name, argt) = item.split(b'=')
|
(name, argt) = item.split('=')
|
||||||
argres = argt.split(b'|')
|
argres = argt.split('|')
|
||||||
else:
|
else:
|
||||||
name = item
|
name = item
|
||||||
argres = []
|
argres = []
|
||||||
if (isinstance(path,str)):
|
|
||||||
path = path.encode('utf-8')
|
|
||||||
if (name.endswith(path)):
|
if (name.endswith(path)):
|
||||||
result = argres
|
result = argres
|
||||||
self.temp.pop(j)
|
self.temp.pop(j)
|
||||||
break
|
break
|
||||||
if (len(argres) > 0) :
|
if (len(argres) > 0) :
|
||||||
for j in range(0,len(argres)):
|
for j in xrange(0,len(argres)):
|
||||||
argres[j] = int(argres[j])
|
argres[j] = int(argres[j])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -226,15 +220,15 @@ def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array
|
|||||||
if (pp.gid != None):
|
if (pp.gid != None):
|
||||||
mlst.append('<defs>\n')
|
mlst.append('<defs>\n')
|
||||||
gdefs = pp.getGlyphs()
|
gdefs = pp.getGlyphs()
|
||||||
for j in range(0,len(gdefs)):
|
for j in xrange(0,len(gdefs)):
|
||||||
mlst.append(gdefs[j])
|
mlst.append(gdefs[j])
|
||||||
mlst.append('</defs>\n')
|
mlst.append('</defs>\n')
|
||||||
img = pp.getImages()
|
img = pp.getImages()
|
||||||
if (img != None):
|
if (img != None):
|
||||||
for j in range(0,len(img)):
|
for j in xrange(0,len(img)):
|
||||||
mlst.append(img[j])
|
mlst.append(img[j])
|
||||||
if (pp.gid != None):
|
if (pp.gid != None):
|
||||||
for j in range(0,len(pp.gid)):
|
for j in xrange(0,len(pp.gid)):
|
||||||
mlst.append('<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (pp.gid[j], pp.gx[j], pp.gy[j]))
|
mlst.append('<use xlink:href="#gl%d" x="%d" y="%d" />\n' % (pp.gid[j], pp.gx[j], pp.gy[j]))
|
||||||
if (img == None or len(img) == 0) and (pp.gid == None or len(pp.gid) == 0):
|
if (img == None or len(img) == 0) and (pp.gid == None or len(pp.gid) == 0):
|
||||||
xpos = "%d" % (pp.pw // 3)
|
xpos = "%d" % (pp.pw // 3)
|
||||||
|
|||||||
@@ -1,28 +1,21 @@
|
|||||||
#!/usr/bin/env python3
|
#! /usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
# Python 3 for calibre 5.0
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
from __future__ import print_function
|
||||||
# and also make sure that any unicode strings get
|
from .convert2xml import encodeNumber
|
||||||
# encoded using "replace" before writing them.
|
|
||||||
class SafeUnbuffered:
|
class Unbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
self.encoding = stream.encoding
|
|
||||||
if self.encoding == None:
|
|
||||||
self.encoding = "utf-8"
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
self.stream.write(data)
|
||||||
data = data.encode(self.encoding,"replace")
|
self.stream.flush()
|
||||||
self.stream.buffer.write(data)
|
|
||||||
self.stream.buffer.flush()
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
sys.stdout=Unbuffered(sys.stdout)
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
import os
|
import os
|
||||||
import getopt
|
import getopt
|
||||||
@@ -94,13 +87,13 @@ def readString(file):
|
|||||||
def getMetaArray(metaFile):
|
def getMetaArray(metaFile):
|
||||||
# parse the meta file
|
# parse the meta file
|
||||||
result = {}
|
result = {}
|
||||||
fo = open(metaFile,'rb')
|
fo = file(metaFile,'rb')
|
||||||
size = readEncodedNumber(fo)
|
size = readEncodedNumber(fo)
|
||||||
for i in range(size):
|
for i in xrange(size):
|
||||||
tag = readString(fo)
|
tag = readString(fo)
|
||||||
value = readString(fo)
|
value = readString(fo)
|
||||||
result[tag] = value
|
result[tag] = value
|
||||||
# print(tag, value)
|
# print tag, value
|
||||||
fo.close()
|
fo.close()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -110,17 +103,17 @@ class Dictionary(object):
|
|||||||
def __init__(self, dictFile):
|
def __init__(self, dictFile):
|
||||||
self.filename = dictFile
|
self.filename = dictFile
|
||||||
self.size = 0
|
self.size = 0
|
||||||
self.fo = open(dictFile,'rb')
|
self.fo = file(dictFile,'rb')
|
||||||
self.stable = []
|
self.stable = []
|
||||||
self.size = readEncodedNumber(self.fo)
|
self.size = readEncodedNumber(self.fo)
|
||||||
for i in range(self.size):
|
for i in xrange(self.size):
|
||||||
self.stable.append(self.escapestr(readString(self.fo)))
|
self.stable.append(self.escapestr(readString(self.fo)))
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
def escapestr(self, str):
|
def escapestr(self, str):
|
||||||
str = str.replace(b'&',b'&')
|
str = str.replace('&','&')
|
||||||
str = str.replace(b'<',b'<')
|
str = str.replace('<','<')
|
||||||
str = str.replace(b'>',b'>')
|
str = str.replace('>','>')
|
||||||
str = str.replace(b'=',b'=')
|
str = str.replace('=','=')
|
||||||
return str
|
return str
|
||||||
def lookup(self,val):
|
def lookup(self,val):
|
||||||
if ((val >= 0) and (val < self.size)) :
|
if ((val >= 0) and (val < self.size)) :
|
||||||
@@ -138,7 +131,7 @@ class Dictionary(object):
|
|||||||
|
|
||||||
class PageDimParser(object):
|
class PageDimParser(object):
|
||||||
def __init__(self, flatxml):
|
def __init__(self, flatxml):
|
||||||
self.flatdoc = flatxml.split(b'\n')
|
self.flatdoc = flatxml.split('\n')
|
||||||
# find tag if within pos to end inclusive
|
# find tag if within pos to end inclusive
|
||||||
def findinDoc(self, tagpath, pos, end) :
|
def findinDoc(self, tagpath, pos, end) :
|
||||||
result = None
|
result = None
|
||||||
@@ -149,10 +142,10 @@ class PageDimParser(object):
|
|||||||
else:
|
else:
|
||||||
end = min(cnt,end)
|
end = min(cnt,end)
|
||||||
foundat = -1
|
foundat = -1
|
||||||
for j in range(pos, end):
|
for j in xrange(pos, end):
|
||||||
item = docList[j]
|
item = docList[j]
|
||||||
if item.find(b'=') >= 0:
|
if item.find('=') >= 0:
|
||||||
(name, argres) = item.split(b'=')
|
(name, argres) = item.split('=')
|
||||||
else :
|
else :
|
||||||
name = item
|
name = item
|
||||||
argres = ''
|
argres = ''
|
||||||
@@ -162,8 +155,8 @@ class PageDimParser(object):
|
|||||||
break
|
break
|
||||||
return foundat, result
|
return foundat, result
|
||||||
def process(self):
|
def process(self):
|
||||||
(pos, sph) = self.findinDoc(b'page.h',0,-1)
|
(pos, sph) = self.findinDoc('page.h',0,-1)
|
||||||
(pos, spw) = self.findinDoc(b'page.w',0,-1)
|
(pos, spw) = self.findinDoc('page.w',0,-1)
|
||||||
if (sph == None): sph = '-1'
|
if (sph == None): sph = '-1'
|
||||||
if (spw == None): spw = '-1'
|
if (spw == None): spw = '-1'
|
||||||
return sph, spw
|
return sph, spw
|
||||||
@@ -176,21 +169,21 @@ def getPageDim(flatxml):
|
|||||||
|
|
||||||
class GParser(object):
|
class GParser(object):
|
||||||
def __init__(self, flatxml):
|
def __init__(self, flatxml):
|
||||||
self.flatdoc = flatxml.split(b'\n')
|
self.flatdoc = flatxml.split('\n')
|
||||||
self.dpi = 1440
|
self.dpi = 1440
|
||||||
self.gh = self.getData(b'info.glyph.h')
|
self.gh = self.getData('info.glyph.h')
|
||||||
self.gw = self.getData(b'info.glyph.w')
|
self.gw = self.getData('info.glyph.w')
|
||||||
self.guse = self.getData(b'info.glyph.use')
|
self.guse = self.getData('info.glyph.use')
|
||||||
if self.guse :
|
if self.guse :
|
||||||
self.count = len(self.guse)
|
self.count = len(self.guse)
|
||||||
else :
|
else :
|
||||||
self.count = 0
|
self.count = 0
|
||||||
self.gvtx = self.getData(b'info.glyph.vtx')
|
self.gvtx = self.getData('info.glyph.vtx')
|
||||||
self.glen = self.getData(b'info.glyph.len')
|
self.glen = self.getData('info.glyph.len')
|
||||||
self.gdpi = self.getData(b'info.glyph.dpi')
|
self.gdpi = self.getData('info.glyph.dpi')
|
||||||
self.vx = self.getData(b'info.vtx.x')
|
self.vx = self.getData('info.vtx.x')
|
||||||
self.vy = self.getData(b'info.vtx.y')
|
self.vy = self.getData('info.vtx.y')
|
||||||
self.vlen = self.getData(b'info.len.n')
|
self.vlen = self.getData('info.len.n')
|
||||||
if self.vlen :
|
if self.vlen :
|
||||||
self.glen.append(len(self.vlen))
|
self.glen.append(len(self.vlen))
|
||||||
elif self.glen:
|
elif self.glen:
|
||||||
@@ -202,11 +195,11 @@ class GParser(object):
|
|||||||
def getData(self, path):
|
def getData(self, path):
|
||||||
result = None
|
result = None
|
||||||
cnt = len(self.flatdoc)
|
cnt = len(self.flatdoc)
|
||||||
for j in range(cnt):
|
for j in xrange(cnt):
|
||||||
item = self.flatdoc[j]
|
item = self.flatdoc[j]
|
||||||
if item.find(b'=') >= 0:
|
if item.find('=') >= 0:
|
||||||
(name, argt) = item.split(b'=')
|
(name, argt) = item.split('=')
|
||||||
argres = argt.split(b'|')
|
argres = argt.split('|')
|
||||||
else:
|
else:
|
||||||
name = item
|
name = item
|
||||||
argres = []
|
argres = []
|
||||||
@@ -214,7 +207,7 @@ class GParser(object):
|
|||||||
result = argres
|
result = argres
|
||||||
break
|
break
|
||||||
if (len(argres) > 0) :
|
if (len(argres) > 0) :
|
||||||
for j in range(0,len(argres)):
|
for j in xrange(0,len(argres)):
|
||||||
argres[j] = int(argres[j])
|
argres[j] = int(argres[j])
|
||||||
return result
|
return result
|
||||||
def getGlyphDim(self, gly):
|
def getGlyphDim(self, gly):
|
||||||
@@ -230,7 +223,7 @@ class GParser(object):
|
|||||||
tx = self.vx[self.gvtx[gly]:self.gvtx[gly+1]]
|
tx = self.vx[self.gvtx[gly]:self.gvtx[gly+1]]
|
||||||
ty = self.vy[self.gvtx[gly]:self.gvtx[gly+1]]
|
ty = self.vy[self.gvtx[gly]:self.gvtx[gly+1]]
|
||||||
p = 0
|
p = 0
|
||||||
for k in range(self.glen[gly], self.glen[gly+1]):
|
for k in xrange(self.glen[gly], self.glen[gly+1]):
|
||||||
if (p == 0):
|
if (p == 0):
|
||||||
zx = tx[0:self.vlen[k]+1]
|
zx = tx[0:self.vlen[k]+1]
|
||||||
zy = ty[0:self.vlen[k]+1]
|
zy = ty[0:self.vlen[k]+1]
|
||||||
@@ -329,17 +322,17 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
imgname = filename.replace('color','img')
|
imgname = filename.replace('color','img')
|
||||||
sfile = os.path.join(spath,filename)
|
sfile = os.path.join(spath,filename)
|
||||||
dfile = os.path.join(dpath,imgname)
|
dfile = os.path.join(dpath,imgname)
|
||||||
imgdata = open(sfile,'rb').read()
|
imgdata = file(sfile,'rb').read()
|
||||||
open(dfile,'wb').write(imgdata)
|
file(dfile,'wb').write(imgdata)
|
||||||
|
|
||||||
print("Creating cover.jpg")
|
print("Creating cover.jpg")
|
||||||
isCover = False
|
isCover = False
|
||||||
cpath = os.path.join(bookDir,'img')
|
cpath = os.path.join(bookDir,'img')
|
||||||
cpath = os.path.join(cpath,'img0000.jpg')
|
cpath = os.path.join(cpath,'img0000.jpg')
|
||||||
if os.path.isfile(cpath):
|
if os.path.isfile(cpath):
|
||||||
cover = open(cpath, 'rb').read()
|
cover = file(cpath, 'rb').read()
|
||||||
cpath = os.path.join(bookDir,'cover.jpg')
|
cpath = os.path.join(bookDir,'cover.jpg')
|
||||||
open(cpath, 'wb').write(cover)
|
file(cpath, 'wb').write(cover)
|
||||||
isCover = True
|
isCover = True
|
||||||
|
|
||||||
|
|
||||||
@@ -368,7 +361,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
mlst.append('<meta name="' + key + '" content="' + meta_array[key] + '" />\n')
|
mlst.append('<meta name="' + key + '" content="' + meta_array[key] + '" />\n')
|
||||||
metastr = "".join(mlst)
|
metastr = "".join(mlst)
|
||||||
mlst = None
|
mlst = None
|
||||||
open(xname, 'wb').write(metastr)
|
file(xname, 'wb').write(metastr)
|
||||||
|
|
||||||
print('Processing StyleSheet')
|
print('Processing StyleSheet')
|
||||||
|
|
||||||
@@ -431,10 +424,10 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
|
|
||||||
# now get the css info
|
# now get the css info
|
||||||
cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw)
|
cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw)
|
||||||
open(xname, 'w').write(cssstr)
|
file(xname, 'wb').write(cssstr)
|
||||||
if buildXML:
|
if buildXML:
|
||||||
xname = os.path.join(xmlDir, 'other0000.xml')
|
xname = os.path.join(xmlDir, 'other0000.xml')
|
||||||
open(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
|
file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
|
||||||
|
|
||||||
print('Processing Glyphs')
|
print('Processing Glyphs')
|
||||||
gd = GlyphDict()
|
gd = GlyphDict()
|
||||||
@@ -456,10 +449,10 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
|
|
||||||
if buildXML:
|
if buildXML:
|
||||||
xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
|
xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
|
||||||
open(xname, 'wb').write(convert2xml.getXML(dict, fname))
|
file(xname, 'wb').write(convert2xml.getXML(dict, fname))
|
||||||
|
|
||||||
gp = GParser(flat_xml)
|
gp = GParser(flat_xml)
|
||||||
for i in range(0, gp.count):
|
for i in xrange(0, gp.count):
|
||||||
path = gp.getPath(i)
|
path = gp.getPath(i)
|
||||||
maxh, maxw = gp.getGlyphDim(i)
|
maxh, maxw = gp.getGlyphDim(i)
|
||||||
fullpath = '<path id="gl%d" d="%s" fill="black" /><!-- width=%d height=%d -->\n' % (counter * 256 + i, path, maxw, maxh)
|
fullpath = '<path id="gl%d" d="%s" fill="black" /><!-- width=%d height=%d -->\n' % (counter * 256 + i, path, maxw, maxh)
|
||||||
@@ -514,7 +507,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
|
|
||||||
if buildXML:
|
if buildXML:
|
||||||
xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
|
xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
|
||||||
open(xname, 'wb').write(convert2xml.getXML(dict, fname))
|
file(xname, 'wb').write(convert2xml.getXML(dict, fname))
|
||||||
|
|
||||||
# first get the html
|
# first get the html
|
||||||
pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage)
|
pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage)
|
||||||
@@ -525,7 +518,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
hlst.append('</body>\n</html>\n')
|
hlst.append('</body>\n</html>\n')
|
||||||
htmlstr = "".join(hlst)
|
htmlstr = "".join(hlst)
|
||||||
hlst = None
|
hlst = None
|
||||||
open(os.path.join(bookDir, htmlFileName), 'w').write(htmlstr)
|
file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr)
|
||||||
|
|
||||||
print(" ")
|
print(" ")
|
||||||
print('Extracting Table of Contents from Amazon OCR')
|
print('Extracting Table of Contents from Amazon OCR')
|
||||||
@@ -571,7 +564,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
tlst.append('</body>\n')
|
tlst.append('</body>\n')
|
||||||
tlst.append('</html>\n')
|
tlst.append('</html>\n')
|
||||||
tochtml = "".join(tlst)
|
tochtml = "".join(tlst)
|
||||||
open(os.path.join(svgDir, 'toc.xhtml'), 'w').write(tochtml)
|
file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml)
|
||||||
|
|
||||||
|
|
||||||
# now create index_svg.xhtml that points to all required files
|
# now create index_svg.xhtml that points to all required files
|
||||||
@@ -608,7 +601,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
flst = []
|
flst = []
|
||||||
for page in pagelst:
|
for page in pagelst:
|
||||||
flst.append(xmllst[page])
|
flst.append(xmllst[page])
|
||||||
flat_svg = b"".join(flst)
|
flat_svg = "".join(flst)
|
||||||
flst=None
|
flst=None
|
||||||
svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi)
|
svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi)
|
||||||
if (raw) :
|
if (raw) :
|
||||||
@@ -626,7 +619,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
slst.append('</body>\n</html>\n')
|
slst.append('</body>\n</html>\n')
|
||||||
svgindex = "".join(slst)
|
svgindex = "".join(slst)
|
||||||
slst = None
|
slst = None
|
||||||
open(os.path.join(bookDir, 'index_svg.xhtml'), 'w').write(svgindex)
|
file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex)
|
||||||
|
|
||||||
print(" ")
|
print(" ")
|
||||||
|
|
||||||
@@ -637,16 +630,16 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
olst.append('<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n')
|
olst.append('<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="guid_id">\n')
|
||||||
# adding metadata
|
# adding metadata
|
||||||
olst.append(' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n')
|
olst.append(' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n')
|
||||||
if b'GUID' in meta_array:
|
if 'GUID' in meta_array:
|
||||||
olst.append(' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array[b'GUID'].decode('utf-8') + '</dc:identifier>\n')
|
olst.append(' <dc:identifier opf:scheme="GUID" id="guid_id">' + meta_array['GUID'] + '</dc:identifier>\n')
|
||||||
if b'ASIN' in meta_array:
|
if 'ASIN' in meta_array:
|
||||||
olst.append(' <dc:identifier opf:scheme="ASIN">' + meta_array[b'ASIN'].decode('utf-8') + '</dc:identifier>\n')
|
olst.append(' <dc:identifier opf:scheme="ASIN">' + meta_array['ASIN'] + '</dc:identifier>\n')
|
||||||
if b'oASIN' in meta_array:
|
if 'oASIN' in meta_array:
|
||||||
olst.append(' <dc:identifier opf:scheme="oASIN">' + meta_array[b'oASIN'].decode('utf-8') + '</dc:identifier>\n')
|
olst.append(' <dc:identifier opf:scheme="oASIN">' + meta_array['oASIN'] + '</dc:identifier>\n')
|
||||||
olst.append(' <dc:title>' + meta_array[b'Title'].decode('utf-8') + '</dc:title>\n')
|
olst.append(' <dc:title>' + meta_array['Title'] + '</dc:title>\n')
|
||||||
olst.append(' <dc:creator opf:role="aut">' + meta_array[b'Authors'].decode('utf-8') + '</dc:creator>\n')
|
olst.append(' <dc:creator opf:role="aut">' + meta_array['Authors'] + '</dc:creator>\n')
|
||||||
olst.append(' <dc:language>en</dc:language>\n')
|
olst.append(' <dc:language>en</dc:language>\n')
|
||||||
olst.append(' <dc:date>' + meta_array[b'UpdateTime'].decode('utf-8') + '</dc:date>\n')
|
olst.append(' <dc:date>' + meta_array['UpdateTime'] + '</dc:date>\n')
|
||||||
if isCover:
|
if isCover:
|
||||||
olst.append(' <meta name="cover" content="bookcover"/>\n')
|
olst.append(' <meta name="cover" content="bookcover"/>\n')
|
||||||
olst.append(' </metadata>\n')
|
olst.append(' </metadata>\n')
|
||||||
@@ -675,7 +668,7 @@ def generateBook(bookDir, raw, fixedimage):
|
|||||||
olst.append('</package>\n')
|
olst.append('</package>\n')
|
||||||
opfstr = "".join(olst)
|
opfstr = "".join(olst)
|
||||||
olst = None
|
olst = None
|
||||||
open(opfname, 'w').write(opfstr)
|
file(opfname, 'wb').write(opfstr)
|
||||||
|
|
||||||
print('Processing Complete')
|
print('Processing Complete')
|
||||||
|
|
||||||
@@ -694,8 +687,6 @@ def usage():
|
|||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
|
||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
|
||||||
bookDir = ''
|
bookDir = ''
|
||||||
if len(argv) == 0:
|
if len(argv) == 0:
|
||||||
argv = sys.argv
|
argv = sys.argv
|
||||||
@@ -703,7 +694,7 @@ def main(argv):
|
|||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"])
|
opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"])
|
||||||
|
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError, err:
|
||||||
print(str(err))
|
print(str(err))
|
||||||
usage()
|
usage()
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -1,13 +1,27 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# ignobleepub.py
|
from __future__ import with_statement
|
||||||
# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
|
|
||||||
|
# ignobleepub.pyw, version 4.1
|
||||||
|
# Copyright © 2009-2010 by i♥cabbages
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
# Modified 2015–2017 by Apprentice Harper
|
||||||
|
|
||||||
|
# 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:
|
# Revision history:
|
||||||
# 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
|
||||||
@@ -23,14 +37,14 @@
|
|||||||
# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 4.0 - Work if TkInter is missing
|
# 4.0 - Work if TkInter is missing
|
||||||
# 4.1 - Import tkFileDialog, don't assume something else will import it.
|
# 4.1 - Import tkFileDialog, don't assume something else will import it.
|
||||||
# 5.0 - Python 3 for calibre 5.0
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt Barnes & Noble encrypted ePub books.
|
Decrypt Barnes & Noble encrypted ePub books.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "5.0"
|
__version__ = "4.1"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -51,10 +65,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data,str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -92,11 +106,13 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
return ["ineptepub.py"]
|
return [u"ineptepub.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
|
|
||||||
class IGNOBLEError(Exception):
|
class IGNOBLEError(Exception):
|
||||||
@@ -240,14 +256,14 @@ def ignobleBook(inpath):
|
|||||||
|
|
||||||
def decryptBook(keyb64, inpath, outpath):
|
def decryptBook(keyb64, inpath, outpath):
|
||||||
if AES is None:
|
if AES is None:
|
||||||
raise IGNOBLEError("PyCrypto or OpenSSL must be installed.")
|
raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.")
|
||||||
key = keyb64.decode('base64')[:16]
|
key = keyb64.decode('base64')[:16]
|
||||||
aes = AES(key)
|
aes = AES(key)
|
||||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||||
namelist = set(inf.namelist())
|
namelist = set(inf.namelist())
|
||||||
if 'META-INF/rights.xml' not in namelist or \
|
if 'META-INF/rights.xml' not in namelist or \
|
||||||
'META-INF/encryption.xml' not in namelist:
|
'META-INF/encryption.xml' not in namelist:
|
||||||
print("{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
print(u"{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
||||||
return 1
|
return 1
|
||||||
for name in META_NAMES:
|
for name in META_NAMES:
|
||||||
namelist.remove(name)
|
namelist.remove(name)
|
||||||
@@ -257,7 +273,7 @@ def decryptBook(keyb64, inpath, outpath):
|
|||||||
expr = './/%s' % (adept('encryptedKey'),)
|
expr = './/%s' % (adept('encryptedKey'),)
|
||||||
bookkey = ''.join(rights.findtext(expr))
|
bookkey = ''.join(rights.findtext(expr))
|
||||||
if len(bookkey) != 64:
|
if len(bookkey) != 64:
|
||||||
print("{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath)))
|
print(u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath)))
|
||||||
return 1
|
return 1
|
||||||
bookkey = aes.decrypt(bookkey.decode('base64'))
|
bookkey = aes.decrypt(bookkey.decode('base64'))
|
||||||
bookkey = bookkey[:-ord(bookkey[-1])]
|
bookkey = bookkey[:-ord(bookkey[-1])]
|
||||||
@@ -300,7 +316,7 @@ def decryptBook(keyb64, inpath, outpath):
|
|||||||
pass
|
pass
|
||||||
outf.writestr(zi, decryptor.decrypt(path, data))
|
outf.writestr(zi, decryptor.decrypt(path, data))
|
||||||
except:
|
except:
|
||||||
print("Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
print(u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
||||||
return 2
|
return 2
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -311,90 +327,90 @@ def cli_main():
|
|||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if len(argv) != 4:
|
if len(argv) != 4:
|
||||||
print("usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname))
|
print(u"usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname))
|
||||||
return 1
|
return 1
|
||||||
keypath, inpath, outpath = argv[1:]
|
keypath, inpath, outpath = argv[1:]
|
||||||
userkey = open(keypath,'rb').read()
|
userkey = open(keypath,'rb').read()
|
||||||
result = decryptBook(userkey, inpath, outpath)
|
result = decryptBook(userkey, inpath, outpath)
|
||||||
if result == 0:
|
if result == 0:
|
||||||
print("Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
print(u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import Tkinter
|
||||||
import tkinter.constants
|
import Tkconstants
|
||||||
import tkinter.filedialog
|
import tkFileDialog
|
||||||
import tkinter.messagebox
|
import tkMessageBox
|
||||||
import traceback
|
import traceback
|
||||||
except:
|
except:
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class DecryptionDialog(tkinter.Frame):
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
tkinter.Frame.__init__(self, root, border=5)
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
self.status = tkinter.Label(self, text="Select files for decryption")
|
self.status = Tkinter.Label(self, text=u"Select files for decryption")
|
||||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||||
body = tkinter.Frame(self)
|
body = Tkinter.Frame(self)
|
||||||
body.pack(fill=tkinter.constants.X, expand=1)
|
body.pack(fill=Tkconstants.X, expand=1)
|
||||||
sticky = tkinter.constants.E + tkinter.constants.W
|
sticky = Tkconstants.E + Tkconstants.W
|
||||||
body.grid_columnconfigure(1, weight=2)
|
body.grid_columnconfigure(1, weight=2)
|
||||||
tkinter.Label(body, text="Key file").grid(row=0)
|
Tkinter.Label(body, text=u"Key file").grid(row=0)
|
||||||
self.keypath = tkinter.Entry(body, width=30)
|
self.keypath = Tkinter.Entry(body, width=30)
|
||||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||||
if os.path.exists("bnepubkey.b64"):
|
if os.path.exists(u"bnepubkey.b64"):
|
||||||
self.keypath.insert(0, "bnepubkey.b64")
|
self.keypath.insert(0, u"bnepubkey.b64")
|
||||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||||
button.grid(row=0, column=2)
|
button.grid(row=0, column=2)
|
||||||
tkinter.Label(body, text="Input file").grid(row=1)
|
Tkinter.Label(body, text=u"Input file").grid(row=1)
|
||||||
self.inpath = tkinter.Entry(body, width=30)
|
self.inpath = Tkinter.Entry(body, width=30)
|
||||||
self.inpath.grid(row=1, column=1, sticky=sticky)
|
self.inpath.grid(row=1, column=1, sticky=sticky)
|
||||||
button = tkinter.Button(body, text="...", command=self.get_inpath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
|
||||||
button.grid(row=1, column=2)
|
button.grid(row=1, column=2)
|
||||||
tkinter.Label(body, text="Output file").grid(row=2)
|
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||||
self.outpath = tkinter.Entry(body, width=30)
|
self.outpath = Tkinter.Entry(body, width=30)
|
||||||
self.outpath.grid(row=2, column=1, sticky=sticky)
|
self.outpath.grid(row=2, column=1, sticky=sticky)
|
||||||
button = tkinter.Button(body, text="...", command=self.get_outpath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
|
||||||
button.grid(row=2, column=2)
|
button.grid(row=2, column=2)
|
||||||
buttons = tkinter.Frame(self)
|
buttons = Tkinter.Frame(self)
|
||||||
buttons.pack()
|
buttons.pack()
|
||||||
botton = tkinter.Button(
|
botton = Tkinter.Button(
|
||||||
buttons, text="Decrypt", width=10, command=self.decrypt)
|
buttons, text=u"Decrypt", width=10, command=self.decrypt)
|
||||||
botton.pack(side=tkinter.constants.LEFT)
|
botton.pack(side=Tkconstants.LEFT)
|
||||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||||
button = tkinter.Button(
|
button = Tkinter.Button(
|
||||||
buttons, text="Quit", width=10, command=self.quit)
|
buttons, text=u"Quit", width=10, command=self.quit)
|
||||||
button.pack(side=tkinter.constants.RIGHT)
|
button.pack(side=Tkconstants.RIGHT)
|
||||||
|
|
||||||
def get_keypath(self):
|
def get_keypath(self):
|
||||||
keypath = tkinter.filedialog.askopenfilename(
|
keypath = tkFileDialog.askopenfilename(
|
||||||
parent=None, title="Select Barnes & Noble \'.b64\' key file",
|
parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
|
||||||
defaultextension=".b64",
|
defaultextension=u".b64",
|
||||||
filetypes=[('base64-encoded files', '.b64'),
|
filetypes=[('base64-encoded files', '.b64'),
|
||||||
('All Files', '.*')])
|
('All Files', '.*')])
|
||||||
if keypath:
|
if keypath:
|
||||||
keypath = os.path.normpath(keypath)
|
keypath = os.path.normpath(keypath)
|
||||||
self.keypath.delete(0, tkinter.constants.END)
|
self.keypath.delete(0, Tkconstants.END)
|
||||||
self.keypath.insert(0, keypath)
|
self.keypath.insert(0, keypath)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_inpath(self):
|
def get_inpath(self):
|
||||||
inpath = tkinter.filedialog.askopenfilename(
|
inpath = tkFileDialog.askopenfilename(
|
||||||
parent=None, title="Select B&N-encrypted ePub file to decrypt",
|
parent=None, title=u"Select B&N-encrypted ePub file to decrypt",
|
||||||
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
|
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
||||||
if inpath:
|
if inpath:
|
||||||
inpath = os.path.normpath(inpath)
|
inpath = os.path.normpath(inpath)
|
||||||
self.inpath.delete(0, tkinter.constants.END)
|
self.inpath.delete(0, Tkconstants.END)
|
||||||
self.inpath.insert(0, inpath)
|
self.inpath.insert(0, inpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_outpath(self):
|
def get_outpath(self):
|
||||||
outpath = tkinter.filedialog.asksaveasfilename(
|
outpath = tkFileDialog.asksaveasfilename(
|
||||||
parent=None, title="Select unencrypted ePub file to produce",
|
parent=None, title=u"Select unencrypted ePub file to produce",
|
||||||
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
|
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
||||||
if outpath:
|
if outpath:
|
||||||
outpath = os.path.normpath(outpath)
|
outpath = os.path.normpath(outpath)
|
||||||
self.outpath.delete(0, tkinter.constants.END)
|
self.outpath.delete(0, Tkconstants.END)
|
||||||
self.outpath.insert(0, outpath)
|
self.outpath.insert(0, outpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -403,34 +419,34 @@ def gui_main():
|
|||||||
inpath = self.inpath.get()
|
inpath = self.inpath.get()
|
||||||
outpath = self.outpath.get()
|
outpath = self.outpath.get()
|
||||||
if not keypath or not os.path.exists(keypath):
|
if not keypath or not os.path.exists(keypath):
|
||||||
self.status['text'] = "Specified key file does not exist"
|
self.status['text'] = u"Specified key file does not exist"
|
||||||
return
|
return
|
||||||
if not inpath or not os.path.exists(inpath):
|
if not inpath or not os.path.exists(inpath):
|
||||||
self.status['text'] = "Specified input file does not exist"
|
self.status['text'] = u"Specified input file does not exist"
|
||||||
return
|
return
|
||||||
if not outpath:
|
if not outpath:
|
||||||
self.status['text'] = "Output file not specified"
|
self.status['text'] = u"Output file not specified"
|
||||||
return
|
return
|
||||||
if inpath == outpath:
|
if inpath == outpath:
|
||||||
self.status['text'] = "Must have different input and output files"
|
self.status['text'] = u"Must have different input and output files"
|
||||||
return
|
return
|
||||||
userkey = open(keypath,'rb').read()
|
userkey = open(keypath,'rb').read()
|
||||||
self.status['text'] = "Decrypting..."
|
self.status['text'] = u"Decrypting..."
|
||||||
try:
|
try:
|
||||||
decrypt_status = decryptBook(userkey, inpath, outpath)
|
decrypt_status = decryptBook(userkey, inpath, outpath)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
self.status['text'] = "Error: {0}".format(e.args[0])
|
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||||
return
|
return
|
||||||
if decrypt_status == 0:
|
if decrypt_status == 0:
|
||||||
self.status['text'] = "File successfully decrypted"
|
self.status['text'] = u"File successfully decrypted"
|
||||||
else:
|
else:
|
||||||
self.status['text'] = "The was an error decrypting the file."
|
self.status['text'] = u"The was an error decrypting the file."
|
||||||
|
|
||||||
root = tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
root.title("Barnes & Noble ePub Decrypter v.{0}".format(__version__))
|
root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))
|
||||||
root.resizable(True, False)
|
root.resizable(True, False)
|
||||||
root.minsize(300, 0)
|
root.minsize(300, 0)
|
||||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ignoblekey.py
|
# ignoblekey.py
|
||||||
# Copyright © 2015-2020 Apprentice Alf, Apprentice Harper et al.
|
# Copyright © 2015 Apprentice Alf and Apprentice Harper
|
||||||
|
|
||||||
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||||
|
|
||||||
@@ -12,14 +14,14 @@
|
|||||||
# Revision history:
|
# Revision history:
|
||||||
# 1.0 - Initial release
|
# 1.0 - Initial release
|
||||||
# 1.1 - remove duplicates and return last key as single key
|
# 1.1 - remove duplicates and return last key as single key
|
||||||
# 2.0 - Python 3 for calibre 5.0
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Get Barnes & Noble EPUB user key from nook Studio log file
|
Get Barnes & Noble EPUB user key from nook Studio log file
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "2.0"
|
__version__ = "1.1"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -37,11 +39,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -79,13 +80,15 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["ignoblekey.py"]
|
return [u"ignoblekey.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -95,22 +98,22 @@ def getNookLogFiles():
|
|||||||
logFiles = []
|
logFiles = []
|
||||||
found = False
|
found = False
|
||||||
if iswindows:
|
if iswindows:
|
||||||
import winreg
|
import _winreg as winreg
|
||||||
|
|
||||||
# some 64 bit machines do not have the proper registry key for some reason
|
# some 64 bit machines do not have the proper registry key for some reason
|
||||||
# or the python interface to the 32 vs 64 bit registry is broken
|
# or the python interface to the 32 vs 64 bit registry is broken
|
||||||
paths = set()
|
paths = set()
|
||||||
if 'LOCALAPPDATA' in os.environ.keys():
|
if 'LOCALAPPDATA' in os.environ.keys():
|
||||||
# Python 2.x does not return unicode env. Use Python 3.x
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
paths.add(path)
|
paths.add(path)
|
||||||
if 'USERPROFILE' in os.environ.keys():
|
if 'USERPROFILE' in os.environ.keys():
|
||||||
# Python 2.x does not return unicode env. Use Python 3.x
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Local"
|
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Local"
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
paths.add(path)
|
paths.add(path)
|
||||||
path = winreg.ExpandEnvironmentStrings("%USERPROFILE%")+"\\AppData\\Roaming"
|
path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming"
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
paths.add(path)
|
paths.add(path)
|
||||||
# User Shell Folders show take precedent over Shell Folders if present
|
# User Shell Folders show take precedent over Shell Folders if present
|
||||||
@@ -196,7 +199,7 @@ def nookkeys(files = []):
|
|||||||
for file in files:
|
for file in files:
|
||||||
fileKeys = getKeysFromLog(file)
|
fileKeys = getKeysFromLog(file)
|
||||||
if fileKeys:
|
if fileKeys:
|
||||||
print("Found {0} keys in the Nook Study log files".format(len(fileKeys)))
|
print(u"Found {0} keys in the Nook Study log files".format(len(fileKeys)))
|
||||||
keys.extend(fileKeys)
|
keys.extend(fileKeys)
|
||||||
return list(set(keys))
|
return list(set(keys))
|
||||||
|
|
||||||
@@ -207,29 +210,29 @@ def getkey(outpath, files=[]):
|
|||||||
if len(keys) > 0:
|
if len(keys) > 0:
|
||||||
if not os.path.isdir(outpath):
|
if not os.path.isdir(outpath):
|
||||||
outfile = outpath
|
outfile = outpath
|
||||||
with open(outfile, 'w') as keyfileout:
|
with file(outfile, 'w') as keyfileout:
|
||||||
keyfileout.write(keys[-1])
|
keyfileout.write(keys[-1])
|
||||||
print("Saved a key to {0}".format(outfile))
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
else:
|
else:
|
||||||
keycount = 0
|
keycount = 0
|
||||||
for key in keys:
|
for key in keys:
|
||||||
while True:
|
while True:
|
||||||
keycount += 1
|
keycount += 1
|
||||||
outfile = os.path.join(outpath,"nookkey{0:d}.b64".format(keycount))
|
outfile = os.path.join(outpath,u"nookkey{0:d}.b64".format(keycount))
|
||||||
if not os.path.exists(outfile):
|
if not os.path.exists(outfile):
|
||||||
break
|
break
|
||||||
with open(outfile, 'w') as keyfileout:
|
with file(outfile, 'w') as keyfileout:
|
||||||
keyfileout.write(key)
|
keyfileout.write(key)
|
||||||
print("Saved a key to {0}".format(outfile))
|
print(u"Saved a key to {0}".format(outfile))
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def usage(progname):
|
def usage(progname):
|
||||||
print("Finds the nook Study encryption keys.")
|
print(u"Finds the nook Study encryption keys.")
|
||||||
print("Keys are saved to the current directory, or a specified output directory.")
|
print(u"Keys are saved to the current directory, or a specified output directory.")
|
||||||
print("If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
print(u"If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
||||||
print("Usage:")
|
print(u"Usage:")
|
||||||
print(" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname))
|
print(u" {0:s} [-h] [-k <logFile>] [<outpath>]".format(progname))
|
||||||
|
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
@@ -237,12 +240,12 @@ def cli_main():
|
|||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
print("{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__))
|
print(u"{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "hk:")
|
opts, args = getopt.getopt(argv[1:], "hk:")
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError, err:
|
||||||
print("Error in options or arguments: {0}".format(err.args[0]))
|
print(u"Error in options or arguments: {0}".format(err.args[0]))
|
||||||
usage(progname)
|
usage(progname)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
@@ -271,33 +274,33 @@ def cli_main():
|
|||||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
outpath = os.path.realpath(os.path.normpath(outpath))
|
||||||
|
|
||||||
if not getkey(outpath, files):
|
if not getkey(outpath, files):
|
||||||
print("Could not retrieve nook Study key.")
|
print(u"Could not retrieve nook Study key.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import Tkinter
|
||||||
import tkinter.constants
|
import Tkconstants
|
||||||
import tkinter.messagebox
|
import tkMessageBox
|
||||||
import traceback
|
import traceback
|
||||||
except:
|
except:
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class ExceptionDialog(tkinter.Frame):
|
class ExceptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root, text):
|
def __init__(self, root, text):
|
||||||
tkinter.Frame.__init__(self, root, border=5)
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
label = tkinter.Label(self, text="Unexpected error:",
|
label = Tkinter.Label(self, text=u"Unexpected error:",
|
||||||
anchor=tkinter.constants.W, justify=tkinter.constants.LEFT)
|
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
|
||||||
label.pack(fill=tkinter.constants.X, expand=0)
|
label.pack(fill=Tkconstants.X, expand=0)
|
||||||
self.text = tkinter.Text(self)
|
self.text = Tkinter.Text(self)
|
||||||
self.text.pack(fill=tkinter.constants.BOTH, expand=1)
|
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||||
|
|
||||||
self.text.insert(tkinter.constants.END, text)
|
self.text.insert(Tkconstants.END, text)
|
||||||
|
|
||||||
|
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
root = tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
progpath, progname = os.path.split(argv[0])
|
progpath, progname = os.path.split(argv[0])
|
||||||
success = False
|
success = False
|
||||||
@@ -308,21 +311,21 @@ def gui_main():
|
|||||||
print(key)
|
print(key)
|
||||||
while True:
|
while True:
|
||||||
keycount += 1
|
keycount += 1
|
||||||
outfile = os.path.join(progpath,"nookkey{0:d}.b64".format(keycount))
|
outfile = os.path.join(progpath,u"nookkey{0:d}.b64".format(keycount))
|
||||||
if not os.path.exists(outfile):
|
if not os.path.exists(outfile):
|
||||||
break
|
break
|
||||||
|
|
||||||
with open(outfile, 'w') as keyfileout:
|
with file(outfile, 'w') as keyfileout:
|
||||||
keyfileout.write(key)
|
keyfileout.write(key)
|
||||||
success = True
|
success = True
|
||||||
tkinter.messagebox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
|
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||||
except DrmException as e:
|
except DrmException, e:
|
||||||
tkinter.messagebox.showerror(progname, "Error: {0}".format(str(e)))
|
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||||
except Exception:
|
except Exception:
|
||||||
root.wm_state('normal')
|
root.wm_state('normal')
|
||||||
root.title(progname)
|
root.title(progname)
|
||||||
text = traceback.format_exc()
|
text = traceback.format_exc()
|
||||||
ExceptionDialog(root, text).pack(fill=tkinter.constants.BOTH, expand=1)
|
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
if not success:
|
if not success:
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# ignoblekeyfetch.py
|
from __future__ import with_statement
|
||||||
# Copyright © 2015-2020 Apprentice Harper et al.
|
|
||||||
|
# ignoblekeyfetch.pyw, version 1.1
|
||||||
|
# Copyright © 2015 Apprentice Harper
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
@@ -22,14 +24,14 @@
|
|||||||
# Revision history:
|
# Revision history:
|
||||||
# 1.0 - Initial version
|
# 1.0 - Initial version
|
||||||
# 1.1 - Try second URL if first one fails
|
# 1.1 - Try second URL if first one fails
|
||||||
# 2.0 - Python 3 for calibre 5.0
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Fetch Barnes & Noble EPUB user key from B&N servers using email and password
|
Fetch Barnes & Noble EPUB user key from B&N servers using email and password
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "2.0"
|
__version__ = "1.1"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -44,11 +46,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -86,13 +87,15 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["ignoblekeyfetch.py"]
|
return [u"ignoblekeyfetch.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
|
|
||||||
class IGNOBLEError(Exception):
|
class IGNOBLEError(Exception):
|
||||||
@@ -100,25 +103,26 @@ class IGNOBLEError(Exception):
|
|||||||
|
|
||||||
def fetch_key(email, password):
|
def fetch_key(email, password):
|
||||||
# change email and password to utf-8 if unicode
|
# change email and password to utf-8 if unicode
|
||||||
if type(email)==str:
|
if type(email)==unicode:
|
||||||
email = email.encode('utf-8')
|
email = email.encode('utf-8')
|
||||||
if type(password)==str:
|
if type(password)==unicode:
|
||||||
password = password.encode('utf-8')
|
password = password.encode('utf-8')
|
||||||
|
|
||||||
import random
|
import random
|
||||||
random = "%030x" % random.randrange(16**30)
|
random = "%030x" % random.randrange(16**30)
|
||||||
|
|
||||||
import urllib.parse, urllib.request, re
|
import urllib, urllib2, re
|
||||||
|
|
||||||
# try the URL from nook for PC
|
# try the URL from nook for PC
|
||||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||||
fetch_url += urllib.parse.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
|
fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
|
||||||
fetch_url += urllib.parse.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||||
#print fetch_url
|
#print fetch_url
|
||||||
|
|
||||||
found = ''
|
found = ''
|
||||||
try:
|
try:
|
||||||
response = urllib.request.urlopen(fetch_url)
|
req = urllib2.Request(fetch_url)
|
||||||
|
response = urllib2.urlopen(req)
|
||||||
the_page = response.read()
|
the_page = response.read()
|
||||||
#print the_page
|
#print the_page
|
||||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||||
@@ -127,13 +131,14 @@ def fetch_key(email, password):
|
|||||||
if len(found)!=28:
|
if len(found)!=28:
|
||||||
# try the URL from android devices
|
# try the URL from android devices
|
||||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||||
fetch_url += urllib.parse.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
|
fetch_url += urllib.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
|
||||||
fetch_url += urllib.parse.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||||
#print fetch_url
|
#print fetch_url
|
||||||
|
|
||||||
found = ''
|
found = ''
|
||||||
try:
|
try:
|
||||||
response = urllib.request.urlopen(fetch_url)
|
req = urllib2.Request(fetch_url)
|
||||||
|
response = urllib2.urlopen(req)
|
||||||
the_page = response.read()
|
the_page = response.read()
|
||||||
#print the_page
|
#print the_page
|
||||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||||
@@ -151,67 +156,67 @@ def cli_main():
|
|||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if len(argv) != 4:
|
if len(argv) != 4:
|
||||||
print("usage: {0} <email> <password> <keyfileout.b64>".format(progname))
|
print(u"usage: {0} <email> <password> <keyfileout.b64>".format(progname))
|
||||||
return 1
|
return 1
|
||||||
email, password, keypath = argv[1:]
|
email, password, keypath = argv[1:]
|
||||||
userkey = fetch_key(email, password)
|
userkey = fetch_key(email, password)
|
||||||
if len(userkey) == 28:
|
if len(userkey) == 28:
|
||||||
open(keypath,'wb').write(userkey)
|
open(keypath,'wb').write(userkey)
|
||||||
return 0
|
return 0
|
||||||
print("Failed to fetch key.")
|
print(u"Failed to fetch key.")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import Tkinter
|
||||||
import tkinter.filedialog
|
import tkFileDialog
|
||||||
import tkinter.constants
|
import Tkconstants
|
||||||
import tkinter.messagebox
|
import tkMessageBox
|
||||||
import traceback
|
import traceback
|
||||||
except:
|
except:
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class DecryptionDialog(tkinter.Frame):
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
tkinter.Frame.__init__(self, root, border=5)
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
self.status = tkinter.Label(self, text="Enter parameters")
|
self.status = Tkinter.Label(self, text=u"Enter parameters")
|
||||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||||
body = tkinter.Frame(self)
|
body = Tkinter.Frame(self)
|
||||||
body.pack(fill=tkinter.constants.X, expand=1)
|
body.pack(fill=Tkconstants.X, expand=1)
|
||||||
sticky = tkinter.constants.E + tkinter.constants.W
|
sticky = Tkconstants.E + Tkconstants.W
|
||||||
body.grid_columnconfigure(1, weight=2)
|
body.grid_columnconfigure(1, weight=2)
|
||||||
tkinter.Label(body, text="Account email address").grid(row=0)
|
Tkinter.Label(body, text=u"Account email address").grid(row=0)
|
||||||
self.name = tkinter.Entry(body, width=40)
|
self.name = Tkinter.Entry(body, width=40)
|
||||||
self.name.grid(row=0, column=1, sticky=sticky)
|
self.name.grid(row=0, column=1, sticky=sticky)
|
||||||
tkinter.Label(body, text="Account password").grid(row=1)
|
Tkinter.Label(body, text=u"Account password").grid(row=1)
|
||||||
self.ccn = tkinter.Entry(body, width=40)
|
self.ccn = Tkinter.Entry(body, width=40)
|
||||||
self.ccn.grid(row=1, column=1, sticky=sticky)
|
self.ccn.grid(row=1, column=1, sticky=sticky)
|
||||||
tkinter.Label(body, text="Output file").grid(row=2)
|
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||||
self.keypath = tkinter.Entry(body, width=40)
|
self.keypath = Tkinter.Entry(body, width=40)
|
||||||
self.keypath.grid(row=2, column=1, sticky=sticky)
|
self.keypath.grid(row=2, column=1, sticky=sticky)
|
||||||
self.keypath.insert(2, "bnepubkey.b64")
|
self.keypath.insert(2, u"bnepubkey.b64")
|
||||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||||
button.grid(row=2, column=2)
|
button.grid(row=2, column=2)
|
||||||
buttons = tkinter.Frame(self)
|
buttons = Tkinter.Frame(self)
|
||||||
buttons.pack()
|
buttons.pack()
|
||||||
botton = tkinter.Button(
|
botton = Tkinter.Button(
|
||||||
buttons, text="Fetch", width=10, command=self.generate)
|
buttons, text=u"Fetch", width=10, command=self.generate)
|
||||||
botton.pack(side=tkinter.constants.LEFT)
|
botton.pack(side=Tkconstants.LEFT)
|
||||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||||
button = tkinter.Button(
|
button = Tkinter.Button(
|
||||||
buttons, text="Quit", width=10, command=self.quit)
|
buttons, text=u"Quit", width=10, command=self.quit)
|
||||||
button.pack(side=tkinter.constants.RIGHT)
|
button.pack(side=Tkconstants.RIGHT)
|
||||||
|
|
||||||
def get_keypath(self):
|
def get_keypath(self):
|
||||||
keypath = tkinter.filedialog.asksaveasfilename(
|
keypath = tkFileDialog.asksaveasfilename(
|
||||||
parent=None, title="Select B&N ePub key file to produce",
|
parent=None, title=u"Select B&N ePub key file to produce",
|
||||||
defaultextension=".b64",
|
defaultextension=u".b64",
|
||||||
filetypes=[('base64-encoded files', '.b64'),
|
filetypes=[('base64-encoded files', '.b64'),
|
||||||
('All Files', '.*')])
|
('All Files', '.*')])
|
||||||
if keypath:
|
if keypath:
|
||||||
keypath = os.path.normpath(keypath)
|
keypath = os.path.normpath(keypath)
|
||||||
self.keypath.delete(0, tkinter.constants.END)
|
self.keypath.delete(0, Tkconstants.END)
|
||||||
self.keypath.insert(0, keypath)
|
self.keypath.insert(0, keypath)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -220,31 +225,31 @@ def gui_main():
|
|||||||
password = self.ccn.get()
|
password = self.ccn.get()
|
||||||
keypath = self.keypath.get()
|
keypath = self.keypath.get()
|
||||||
if not email:
|
if not email:
|
||||||
self.status['text'] = "Email address not given"
|
self.status['text'] = u"Email address not given"
|
||||||
return
|
return
|
||||||
if not password:
|
if not password:
|
||||||
self.status['text'] = "Account password not given"
|
self.status['text'] = u"Account password not given"
|
||||||
return
|
return
|
||||||
if not keypath:
|
if not keypath:
|
||||||
self.status['text'] = "Output keyfile path not set"
|
self.status['text'] = u"Output keyfile path not set"
|
||||||
return
|
return
|
||||||
self.status['text'] = "Fetching..."
|
self.status['text'] = u"Fetching..."
|
||||||
try:
|
try:
|
||||||
userkey = fetch_key(email, password)
|
userkey = fetch_key(email, password)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
self.status['text'] = "Error: {0}".format(e.args[0])
|
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||||
return
|
return
|
||||||
if len(userkey) == 28:
|
if len(userkey) == 28:
|
||||||
open(keypath,'wb').write(userkey)
|
open(keypath,'wb').write(userkey)
|
||||||
self.status['text'] = "Keyfile fetched successfully"
|
self.status['text'] = u"Keyfile fetched successfully"
|
||||||
else:
|
else:
|
||||||
self.status['text'] = "Keyfile fetch failed."
|
self.status['text'] = u"Keyfile fetch failed."
|
||||||
|
|
||||||
root = tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
root.title("Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
|
root.title(u"Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
|
||||||
root.resizable(True, False)
|
root.resizable(True, False)
|
||||||
root.minsize(300, 0)
|
root.minsize(300, 0)
|
||||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# ignoblekeygen.py
|
from __future__ import with_statement
|
||||||
# Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al.
|
|
||||||
|
# ignoblekeygen.pyw, version 2.5
|
||||||
|
# Copyright © 2009-2010 i♥cabbages
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
|
||||||
# Windows users: Before running this program, you must first install Python.
|
# Windows users: Before running this program, you must first install Python.
|
||||||
# We recommend ActiveState Python 2.7.X for Windows (x86) from
|
# We recommend ActiveState Python 2.7.X for Windows (x86) from
|
||||||
# http://www.activestate.com/activepython/downloads.
|
# http://www.activestate.com/activepython/downloads.
|
||||||
@@ -30,19 +34,18 @@
|
|||||||
# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 2.7 - Work if TkInter is missing
|
# 2.7 - Work if TkInter is missing
|
||||||
# 2.8 - Fix bug in stand-alone use (import tkFileDialog)
|
# 2.8 - Fix bug in stand-alone use (import tkFileDialog)
|
||||||
# 3.0 - Added Python 3 compatibility for calibre 5.0
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "3.0"
|
__version__ = "2.8"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
import base64
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
# and also make sure that any unicode strings get
|
# and also make sure that any unicode strings get
|
||||||
@@ -54,11 +57,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -96,13 +98,15 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["ignoblekeygen.py"]
|
return [u"ignoblekeygen.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
|
|
||||||
class IGNOBLEError(Exception):
|
class IGNOBLEError(Exception):
|
||||||
@@ -195,24 +199,23 @@ def normalize_name(name):
|
|||||||
|
|
||||||
def generate_key(name, ccn):
|
def generate_key(name, ccn):
|
||||||
# remove spaces and case from name and CC numbers.
|
# remove spaces and case from name and CC numbers.
|
||||||
name = normalize_name(name)
|
if type(name)==unicode:
|
||||||
ccn = normalize_name(ccn)
|
|
||||||
|
|
||||||
if type(name)==str:
|
|
||||||
name = name.encode('utf-8')
|
name = name.encode('utf-8')
|
||||||
if type(ccn)==str:
|
if type(ccn)==unicode:
|
||||||
ccn = ccn.encode('utf-8')
|
ccn = ccn.encode('utf-8')
|
||||||
|
|
||||||
name = name + b'\x00'
|
name = normalize_name(name) + '\x00'
|
||||||
ccn = ccn + b'\x00'
|
ccn = normalize_name(ccn) + '\x00'
|
||||||
|
|
||||||
name_sha = hashlib.sha1(name).digest()[:16]
|
name_sha = hashlib.sha1(name).digest()[:16]
|
||||||
ccn_sha = hashlib.sha1(ccn).digest()[:16]
|
ccn_sha = hashlib.sha1(ccn).digest()[:16]
|
||||||
both_sha = hashlib.sha1(name + ccn).digest()
|
both_sha = hashlib.sha1(name + ccn).digest()
|
||||||
aes = AES(ccn_sha, name_sha)
|
aes = AES(ccn_sha, name_sha)
|
||||||
crypt = aes.encrypt(both_sha + (b'\x0c' * 0x0c))
|
crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
|
||||||
userkey = hashlib.sha1(crypt).digest()
|
userkey = hashlib.sha1(crypt).digest()
|
||||||
return base64.b64encode(userkey)
|
return userkey.encode('base64')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
@@ -226,7 +229,7 @@ def cli_main():
|
|||||||
(progname,))
|
(progname,))
|
||||||
return 1
|
return 1
|
||||||
if len(argv) != 4:
|
if len(argv) != 4:
|
||||||
print("usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname))
|
print(u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname))
|
||||||
return 1
|
return 1
|
||||||
name, ccn, keypath = argv[1:]
|
name, ccn, keypath = argv[1:]
|
||||||
userkey = generate_key(name, ccn)
|
userkey = generate_key(name, ccn)
|
||||||
@@ -236,54 +239,54 @@ def cli_main():
|
|||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import Tkinter
|
||||||
import tkinter.constants
|
import Tkconstants
|
||||||
import tkinter.messagebox
|
import tkMessageBox
|
||||||
import tkinter.filedialog
|
import tkFileDialog
|
||||||
import traceback
|
import traceback
|
||||||
except:
|
except:
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class DecryptionDialog(tkinter.Frame):
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
tkinter.Frame.__init__(self, root, border=5)
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
self.status = tkinter.Label(self, text="Enter parameters")
|
self.status = Tkinter.Label(self, text=u"Enter parameters")
|
||||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||||
body = tkinter.Frame(self)
|
body = Tkinter.Frame(self)
|
||||||
body.pack(fill=tkinter.constants.X, expand=1)
|
body.pack(fill=Tkconstants.X, expand=1)
|
||||||
sticky = tkinter.constants.E + tkinter.constants.W
|
sticky = Tkconstants.E + Tkconstants.W
|
||||||
body.grid_columnconfigure(1, weight=2)
|
body.grid_columnconfigure(1, weight=2)
|
||||||
tkinter.Label(body, text="Account Name").grid(row=0)
|
Tkinter.Label(body, text=u"Account Name").grid(row=0)
|
||||||
self.name = tkinter.Entry(body, width=40)
|
self.name = Tkinter.Entry(body, width=40)
|
||||||
self.name.grid(row=0, column=1, sticky=sticky)
|
self.name.grid(row=0, column=1, sticky=sticky)
|
||||||
tkinter.Label(body, text="CC#").grid(row=1)
|
Tkinter.Label(body, text=u"CC#").grid(row=1)
|
||||||
self.ccn = tkinter.Entry(body, width=40)
|
self.ccn = Tkinter.Entry(body, width=40)
|
||||||
self.ccn.grid(row=1, column=1, sticky=sticky)
|
self.ccn.grid(row=1, column=1, sticky=sticky)
|
||||||
tkinter.Label(body, text="Output file").grid(row=2)
|
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||||
self.keypath = tkinter.Entry(body, width=40)
|
self.keypath = Tkinter.Entry(body, width=40)
|
||||||
self.keypath.grid(row=2, column=1, sticky=sticky)
|
self.keypath.grid(row=2, column=1, sticky=sticky)
|
||||||
self.keypath.insert(2, "bnepubkey.b64")
|
self.keypath.insert(2, u"bnepubkey.b64")
|
||||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||||
button.grid(row=2, column=2)
|
button.grid(row=2, column=2)
|
||||||
buttons = tkinter.Frame(self)
|
buttons = Tkinter.Frame(self)
|
||||||
buttons.pack()
|
buttons.pack()
|
||||||
botton = tkinter.Button(
|
botton = Tkinter.Button(
|
||||||
buttons, text="Generate", width=10, command=self.generate)
|
buttons, text=u"Generate", width=10, command=self.generate)
|
||||||
botton.pack(side=tkinter.constants.LEFT)
|
botton.pack(side=Tkconstants.LEFT)
|
||||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||||
button = tkinter.Button(
|
button = Tkinter.Button(
|
||||||
buttons, text="Quit", width=10, command=self.quit)
|
buttons, text=u"Quit", width=10, command=self.quit)
|
||||||
button.pack(side=tkinter.constants.RIGHT)
|
button.pack(side=Tkconstants.RIGHT)
|
||||||
|
|
||||||
def get_keypath(self):
|
def get_keypath(self):
|
||||||
keypath = tkinter.filedialog.asksaveasfilename(
|
keypath = tkFileDialog.asksaveasfilename(
|
||||||
parent=None, title="Select B&N ePub key file to produce",
|
parent=None, title=u"Select B&N ePub key file to produce",
|
||||||
defaultextension=".b64",
|
defaultextension=u".b64",
|
||||||
filetypes=[('base64-encoded files', '.b64'),
|
filetypes=[('base64-encoded files', '.b64'),
|
||||||
('All Files', '.*')])
|
('All Files', '.*')])
|
||||||
if keypath:
|
if keypath:
|
||||||
keypath = os.path.normpath(keypath)
|
keypath = os.path.normpath(keypath)
|
||||||
self.keypath.delete(0, tkinter.constants.END)
|
self.keypath.delete(0, Tkconstants.END)
|
||||||
self.keypath.insert(0, keypath)
|
self.keypath.insert(0, keypath)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -292,35 +295,35 @@ def gui_main():
|
|||||||
ccn = self.ccn.get()
|
ccn = self.ccn.get()
|
||||||
keypath = self.keypath.get()
|
keypath = self.keypath.get()
|
||||||
if not name:
|
if not name:
|
||||||
self.status['text'] = "Name not specified"
|
self.status['text'] = u"Name not specified"
|
||||||
return
|
return
|
||||||
if not ccn:
|
if not ccn:
|
||||||
self.status['text'] = "Credit card number not specified"
|
self.status['text'] = u"Credit card number not specified"
|
||||||
return
|
return
|
||||||
if not keypath:
|
if not keypath:
|
||||||
self.status['text'] = "Output keyfile path not specified"
|
self.status['text'] = u"Output keyfile path not specified"
|
||||||
return
|
return
|
||||||
self.status['text'] = "Generating..."
|
self.status['text'] = u"Generating..."
|
||||||
try:
|
try:
|
||||||
userkey = generate_key(name, ccn)
|
userkey = generate_key(name, ccn)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
self.status['text'] = "Error: (0}".format(e.args[0])
|
self.status['text'] = u"Error: (0}".format(e.args[0])
|
||||||
return
|
return
|
||||||
open(keypath,'wb').write(userkey)
|
open(keypath,'wb').write(userkey)
|
||||||
self.status['text'] = "Keyfile successfully generated"
|
self.status['text'] = u"Keyfile successfully generated"
|
||||||
|
|
||||||
root = tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
if AES is None:
|
if AES is None:
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
tkinter.messagebox.showerror(
|
tkMessageBox.showerror(
|
||||||
"Ignoble EPUB Keyfile Generator",
|
"Ignoble EPUB Keyfile Generator",
|
||||||
"This script requires OpenSSL or PyCrypto, which must be installed "
|
"This script requires OpenSSL or PyCrypto, which must be installed "
|
||||||
"separately. Read the top-of-script comment for details.")
|
"separately. Read the top-of-script comment for details.")
|
||||||
return 1
|
return 1
|
||||||
root.title("Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
|
root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
|
||||||
root.resizable(True, False)
|
root.resizable(True, False)
|
||||||
root.minsize(300, 0)
|
root.minsize(300, 0)
|
||||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#! /usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ignoblepdf.py
|
# ignoblepdf.py
|
||||||
# Copyright © 2009-2020 by Apprentice Harper et al.
|
# Copyright © 2009-2020 by Apprentice Harper et al.
|
||||||
@@ -13,7 +14,6 @@
|
|||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 0.1 - Initial alpha testing release 2020 by Pu D. Pud
|
# 0.1 - Initial alpha testing release 2020 by Pu D. Pud
|
||||||
# 0.2 - Python 3 for calibre 5.0 (in testing)
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -21,7 +21,7 @@ Decrypts Barnes & Noble encrypted PDF files.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "0.2"
|
__version__ = "0.1"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -43,11 +43,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -82,11 +81,13 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
return ["ignoblepdf.py"]
|
return [u"ignoblepdf.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
|
|
||||||
class IGNOBLEError(Exception):
|
class IGNOBLEError(Exception):
|
||||||
@@ -236,7 +237,10 @@ def _load_crypto():
|
|||||||
ARC4, AES = _load_crypto()
|
ARC4, AES = _load_crypto()
|
||||||
|
|
||||||
|
|
||||||
from io import BytesIO
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
|
||||||
# Do we generate cross reference streams on output?
|
# Do we generate cross reference streams on output?
|
||||||
@@ -424,7 +428,7 @@ class PSBaseParser(object):
|
|||||||
if not pos:
|
if not pos:
|
||||||
pos = self.bufpos+self.charpos
|
pos = self.bufpos+self.charpos
|
||||||
self.fp.seek(pos)
|
self.fp.seek(pos)
|
||||||
# print('poll(%d): %r' % (pos, self.fp.read(n)), file=sys.stderr)
|
##print >>sys.stderr, 'poll(%d): %r' % (pos, self.fp.read(n))
|
||||||
self.fp.seek(pos0)
|
self.fp.seek(pos0)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -541,7 +545,7 @@ class PSBaseParser(object):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return (self.parse_main, j)
|
return (self.parse_main, j)
|
||||||
|
|
||||||
def parse_decimal(self, s, i):
|
def parse_decimal(self, s, i):
|
||||||
m = END_NUMBER.search(s, i)
|
m = END_NUMBER.search(s, i)
|
||||||
if not m:
|
if not m:
|
||||||
@@ -749,7 +753,7 @@ class PSStackParser(PSBaseParser):
|
|||||||
'''
|
'''
|
||||||
while not self.results:
|
while not self.results:
|
||||||
(pos, token) = self.nexttoken()
|
(pos, token) = self.nexttoken()
|
||||||
# print((pos, token), (self.curtype, self.curstack))
|
##print (pos,token), (self.curtype, self.curstack)
|
||||||
if (isinstance(token, int) or
|
if (isinstance(token, int) or
|
||||||
isinstance(token, Decimal) or
|
isinstance(token, Decimal) or
|
||||||
isinstance(token, bool) or
|
isinstance(token, bool) or
|
||||||
@@ -774,7 +778,7 @@ class PSStackParser(PSBaseParser):
|
|||||||
try:
|
try:
|
||||||
(pos, objs) = self.end_type('d')
|
(pos, objs) = self.end_type('d')
|
||||||
if len(objs) % 2 != 0:
|
if len(objs) % 2 != 0:
|
||||||
print("Incomplete dictionary construct")
|
print "Incomplete dictionary construct"
|
||||||
objs.append("") # this isn't necessary.
|
objs.append("") # this isn't necessary.
|
||||||
# temporary fix. is this due to rental books?
|
# temporary fix. is this due to rental books?
|
||||||
# raise PSSyntaxError(
|
# raise PSSyntaxError(
|
||||||
@@ -999,7 +1003,7 @@ class PDFStream(PDFObject):
|
|||||||
if 'Filter' not in self.dic:
|
if 'Filter' not in self.dic:
|
||||||
self.data = data
|
self.data = data
|
||||||
self.rawdata = None
|
self.rawdata = None
|
||||||
##print(self.dict)
|
##print self.dict
|
||||||
return
|
return
|
||||||
filters = self.dic['Filter']
|
filters = self.dic['Filter']
|
||||||
if not isinstance(filters, list):
|
if not isinstance(filters, list):
|
||||||
@@ -1009,7 +1013,7 @@ class PDFStream(PDFObject):
|
|||||||
# will get errors if the document is encrypted.
|
# will get errors if the document is encrypted.
|
||||||
data = zlib.decompress(data)
|
data = zlib.decompress(data)
|
||||||
elif f in LITERALS_LZW_DECODE:
|
elif f in LITERALS_LZW_DECODE:
|
||||||
data = ''.join(LZWDecoder(BytesIO(data)).run())
|
data = ''.join(LZWDecoder(StringIO(data)).run())
|
||||||
elif f in LITERALS_ASCII85_DECODE:
|
elif f in LITERALS_ASCII85_DECODE:
|
||||||
data = ascii85decode(data)
|
data = ascii85decode(data)
|
||||||
elif f == LITERAL_CRYPT:
|
elif f == LITERAL_CRYPT:
|
||||||
@@ -1033,7 +1037,7 @@ class PDFStream(PDFObject):
|
|||||||
columns = int_value(params['Columns'])
|
columns = int_value(params['Columns'])
|
||||||
buf = ''
|
buf = ''
|
||||||
ent0 = '\x00' * columns
|
ent0 = '\x00' * columns
|
||||||
for i in range(0, len(data), columns+1):
|
for i in xrange(0, len(data), columns+1):
|
||||||
pred = data[i]
|
pred = data[i]
|
||||||
ent1 = data[i+1:i+1+columns]
|
ent1 = data[i+1:i+1+columns]
|
||||||
if pred == '\x02':
|
if pred == '\x02':
|
||||||
@@ -1115,7 +1119,7 @@ class PDFXRef(object):
|
|||||||
(start, nobjs) = map(int, f)
|
(start, nobjs) = map(int, f)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
|
raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
|
||||||
for objid in range(start, start+nobjs):
|
for objid in xrange(start, start+nobjs):
|
||||||
try:
|
try:
|
||||||
(_, line) = parser.nextline()
|
(_, line) = parser.nextline()
|
||||||
except PSEOF:
|
except PSEOF:
|
||||||
@@ -1167,7 +1171,7 @@ class PDFXRefStream(object):
|
|||||||
|
|
||||||
def objids(self):
|
def objids(self):
|
||||||
for first, size in self.index:
|
for first, size in self.index:
|
||||||
for objid in range(first, first + size):
|
for objid in xrange(first, first + size):
|
||||||
yield objid
|
yield objid
|
||||||
|
|
||||||
def load(self, parser, debug=0):
|
def load(self, parser, debug=0):
|
||||||
@@ -1376,7 +1380,7 @@ class PDFDocument(object):
|
|||||||
hash.update('ffffffff'.decode('hex'))
|
hash.update('ffffffff'.decode('hex'))
|
||||||
if 5 <= R:
|
if 5 <= R:
|
||||||
# 8
|
# 8
|
||||||
for _ in range(50):
|
for _ in xrange(50):
|
||||||
hash = hashlib.md5(hash.digest()[:length/8])
|
hash = hashlib.md5(hash.digest()[:length/8])
|
||||||
key = hash.digest()[:length/8]
|
key = hash.digest()[:length/8]
|
||||||
if R == 2:
|
if R == 2:
|
||||||
@@ -1387,7 +1391,7 @@ class PDFDocument(object):
|
|||||||
hash = hashlib.md5(self.PASSWORD_PADDING) # 2
|
hash = hashlib.md5(self.PASSWORD_PADDING) # 2
|
||||||
hash.update(docid[0]) # 3
|
hash.update(docid[0]) # 3
|
||||||
x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
|
x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
|
||||||
for i in range(1,19+1):
|
for i in xrange(1,19+1):
|
||||||
k = ''.join( chr(ord(c) ^ i) for c in key )
|
k = ''.join( chr(ord(c) ^ i) for c in key )
|
||||||
x = ARC4.new(k).decrypt(x)
|
x = ARC4.new(k).decrypt(x)
|
||||||
u1 = x+x # 32bytes total
|
u1 = x+x # 32bytes total
|
||||||
@@ -1443,15 +1447,15 @@ class PDFDocument(object):
|
|||||||
V = ord(bookkey[0])
|
V = ord(bookkey[0])
|
||||||
bookkey = bookkey[1:]
|
bookkey = bookkey[1:]
|
||||||
else:
|
else:
|
||||||
print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
|
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
|
||||||
print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
|
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
|
||||||
print("bookkey[0] is %d" % ord(bookkey[0]))
|
print "bookkey[0] is %d" % ord(bookkey[0])
|
||||||
raise IGNOBLEError('error decrypting book session key - mismatched length')
|
raise IGNOBLEError('error decrypting book session key - mismatched length')
|
||||||
else:
|
else:
|
||||||
# proper length unknown try with whatever you have
|
# proper length unknown try with whatever you have
|
||||||
print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
|
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
|
||||||
print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
|
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
|
||||||
print("bookkey[0] is %d" % ord(bookkey[0]))
|
print "bookkey[0] is %d" % ord(bookkey[0])
|
||||||
if ebx_V == 3:
|
if ebx_V == 3:
|
||||||
V = 3
|
V = 3
|
||||||
else:
|
else:
|
||||||
@@ -1496,7 +1500,7 @@ class PDFDocument(object):
|
|||||||
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
||||||
# remove pkcs#5 aes padding
|
# remove pkcs#5 aes padding
|
||||||
cutter = -1 * ord(plaintext[-1])
|
cutter = -1 * ord(plaintext[-1])
|
||||||
#print(cutter)
|
#print cutter
|
||||||
plaintext = plaintext[:cutter]
|
plaintext = plaintext[:cutter]
|
||||||
return plaintext
|
return plaintext
|
||||||
|
|
||||||
@@ -1507,7 +1511,7 @@ class PDFDocument(object):
|
|||||||
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
||||||
# remove pkcs#5 aes padding
|
# remove pkcs#5 aes padding
|
||||||
cutter = -1 * ord(plaintext[-1])
|
cutter = -1 * ord(plaintext[-1])
|
||||||
#print(cutter)
|
#print cutter
|
||||||
plaintext = plaintext[:cutter]
|
plaintext = plaintext[:cutter]
|
||||||
return plaintext
|
return plaintext
|
||||||
|
|
||||||
@@ -1775,7 +1779,7 @@ class PDFParser(PSStackParser):
|
|||||||
class PDFObjStrmParser(PDFParser):
|
class PDFObjStrmParser(PDFParser):
|
||||||
|
|
||||||
def __init__(self, data, doc):
|
def __init__(self, data, doc):
|
||||||
PSStackParser.__init__(self, BytesIO(data))
|
PSStackParser.__init__(self, StringIO(data))
|
||||||
self.doc = doc
|
self.doc = doc
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1850,7 +1854,7 @@ class PDFSerializer(object):
|
|||||||
if not gen_xref_stm:
|
if not gen_xref_stm:
|
||||||
self.write('xref\n')
|
self.write('xref\n')
|
||||||
self.write('0 %d\n' % (maxobj + 1,))
|
self.write('0 %d\n' % (maxobj + 1,))
|
||||||
for objid in range(0, maxobj + 1):
|
for objid in xrange(0, maxobj + 1):
|
||||||
if objid in xrefs:
|
if objid in xrefs:
|
||||||
# force the genno to be 0
|
# force the genno to be 0
|
||||||
self.write("%010d 00000 n \n" % xrefs[objid][0])
|
self.write("%010d 00000 n \n" % xrefs[objid][0])
|
||||||
@@ -2001,20 +2005,20 @@ class PDFSerializer(object):
|
|||||||
|
|
||||||
def decryptBook(userkey, inpath, outpath):
|
def decryptBook(userkey, inpath, outpath):
|
||||||
if AES is None:
|
if AES is None:
|
||||||
raise IGNOBLEError("PyCrypto or OpenSSL must be installed.")
|
raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.")
|
||||||
with open(inpath, 'rb') as inf:
|
with open(inpath, 'rb') as inf:
|
||||||
#try:
|
#try:
|
||||||
serializer = PDFSerializer(inf, userkey)
|
serializer = PDFSerializer(inf, userkey)
|
||||||
#except:
|
#except:
|
||||||
# print("Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath)))
|
# print u"Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath))
|
||||||
# return 2
|
# return 2
|
||||||
# hope this will fix the 'bad file descriptor' problem
|
# hope this will fix the 'bad file descriptor' problem
|
||||||
with open(outpath, 'wb') as outf:
|
with open(outpath, 'wb') as outf:
|
||||||
# help construct to make sure the method runs to the end
|
# help construct to make sure the method runs to the end
|
||||||
try:
|
try:
|
||||||
serializer.dump(outf)
|
serializer.dump(outf)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
print("error writing pdf: {0}".format(e.args[0]))
|
print u"error writing pdf: {0}".format(e.args[0])
|
||||||
return 2
|
return 2
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -2025,91 +2029,91 @@ def cli_main():
|
|||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if len(argv) != 4:
|
if len(argv) != 4:
|
||||||
print("usage: {0} <keyfile.b64> <inbook.pdf> <outbook.pdf>".format(progname))
|
print u"usage: {0} <keyfile.b64> <inbook.pdf> <outbook.pdf>".format(progname)
|
||||||
return 1
|
return 1
|
||||||
keypath, inpath, outpath = argv[1:]
|
keypath, inpath, outpath = argv[1:]
|
||||||
userkey = open(keypath,'rb').read()
|
userkey = open(keypath,'rb').read()
|
||||||
result = decryptBook(userkey, inpath, outpath)
|
result = decryptBook(userkey, inpath, outpath)
|
||||||
if result == 0:
|
if result == 0:
|
||||||
print("Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import Tkinter
|
||||||
import tkinter.constants
|
import Tkconstants
|
||||||
import tkinter.filedialog
|
import tkFileDialog
|
||||||
import tkinter.messagebox
|
import tkMessageBox
|
||||||
import traceback
|
import traceback
|
||||||
except:
|
except:
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class DecryptionDialog(tkinter.Frame):
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
tkinter.Frame.__init__(self, root, border=5)
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
self.status = tkinter.Label(self, text="Select files for decryption")
|
self.status = Tkinter.Label(self, text=u"Select files for decryption")
|
||||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||||
body = tkinter.Frame(self)
|
body = Tkinter.Frame(self)
|
||||||
body.pack(fill=tkinter.constants.X, expand=1)
|
body.pack(fill=Tkconstants.X, expand=1)
|
||||||
sticky = tkinter.constants.E + tkinter.constants.W
|
sticky = Tkconstants.E + Tkconstants.W
|
||||||
body.grid_columnconfigure(1, weight=2)
|
body.grid_columnconfigure(1, weight=2)
|
||||||
tkinter.Label(body, text="Key file").grid(row=0)
|
Tkinter.Label(body, text=u"Key file").grid(row=0)
|
||||||
self.keypath = tkinter.Entry(body, width=30)
|
self.keypath = Tkinter.Entry(body, width=30)
|
||||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||||
if os.path.exists("bnpdfkey.b64"):
|
if os.path.exists(u"bnpdfkey.b64"):
|
||||||
self.keypath.insert(0, "bnpdfkey.b64")
|
self.keypath.insert(0, u"bnpdfkey.b64")
|
||||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||||
button.grid(row=0, column=2)
|
button.grid(row=0, column=2)
|
||||||
tkinter.Label(body, text="Input file").grid(row=1)
|
Tkinter.Label(body, text=u"Input file").grid(row=1)
|
||||||
self.inpath = tkinter.Entry(body, width=30)
|
self.inpath = Tkinter.Entry(body, width=30)
|
||||||
self.inpath.grid(row=1, column=1, sticky=sticky)
|
self.inpath.grid(row=1, column=1, sticky=sticky)
|
||||||
button = tkinter.Button(body, text="...", command=self.get_inpath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
|
||||||
button.grid(row=1, column=2)
|
button.grid(row=1, column=2)
|
||||||
tkinter.Label(body, text="Output file").grid(row=2)
|
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||||
self.outpath = tkinter.Entry(body, width=30)
|
self.outpath = Tkinter.Entry(body, width=30)
|
||||||
self.outpath.grid(row=2, column=1, sticky=sticky)
|
self.outpath.grid(row=2, column=1, sticky=sticky)
|
||||||
button = tkinter.Button(body, text="...", command=self.get_outpath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
|
||||||
button.grid(row=2, column=2)
|
button.grid(row=2, column=2)
|
||||||
buttons = tkinter.Frame(self)
|
buttons = Tkinter.Frame(self)
|
||||||
buttons.pack()
|
buttons.pack()
|
||||||
botton = tkinter.Button(
|
botton = Tkinter.Button(
|
||||||
buttons, text="Decrypt", width=10, command=self.decrypt)
|
buttons, text=u"Decrypt", width=10, command=self.decrypt)
|
||||||
botton.pack(side=tkinter.constants.LEFT)
|
botton.pack(side=Tkconstants.LEFT)
|
||||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||||
button = tkinter.Button(
|
button = Tkinter.Button(
|
||||||
buttons, text="Quit", width=10, command=self.quit)
|
buttons, text=u"Quit", width=10, command=self.quit)
|
||||||
button.pack(side=tkinter.constants.RIGHT)
|
button.pack(side=Tkconstants.RIGHT)
|
||||||
|
|
||||||
def get_keypath(self):
|
def get_keypath(self):
|
||||||
keypath = tkinter.filedialog.askopenfilename(
|
keypath = tkFileDialog.askopenfilename(
|
||||||
parent=None, title="Select Barnes & Noble \'.b64\' key file",
|
parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
|
||||||
defaultextension=".b64",
|
defaultextension=u".b64",
|
||||||
filetypes=[('base64-encoded files', '.b64'),
|
filetypes=[('base64-encoded files', '.b64'),
|
||||||
('All Files', '.*')])
|
('All Files', '.*')])
|
||||||
if keypath:
|
if keypath:
|
||||||
keypath = os.path.normpath(keypath)
|
keypath = os.path.normpath(keypath)
|
||||||
self.keypath.delete(0, tkinter.constants.END)
|
self.keypath.delete(0, Tkconstants.END)
|
||||||
self.keypath.insert(0, keypath)
|
self.keypath.insert(0, keypath)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_inpath(self):
|
def get_inpath(self):
|
||||||
inpath = tkinter.filedialog.askopenfilename(
|
inpath = tkFileDialog.askopenfilename(
|
||||||
parent=None, title="Select B&N-encrypted PDF file to decrypt",
|
parent=None, title=u"Select B&N-encrypted PDF file to decrypt",
|
||||||
defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
|
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
|
||||||
if inpath:
|
if inpath:
|
||||||
inpath = os.path.normpath(inpath)
|
inpath = os.path.normpath(inpath)
|
||||||
self.inpath.delete(0, tkinter.constants.END)
|
self.inpath.delete(0, Tkconstants.END)
|
||||||
self.inpath.insert(0, inpath)
|
self.inpath.insert(0, inpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_outpath(self):
|
def get_outpath(self):
|
||||||
outpath = tkinter.filedialog.asksaveasfilename(
|
outpath = tkFileDialog.asksaveasfilename(
|
||||||
parent=None, title="Select unencrypted PDF file to produce",
|
parent=None, title=u"Select unencrypted PDF file to produce",
|
||||||
defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
|
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
|
||||||
if outpath:
|
if outpath:
|
||||||
outpath = os.path.normpath(outpath)
|
outpath = os.path.normpath(outpath)
|
||||||
self.outpath.delete(0, tkinter.constants.END)
|
self.outpath.delete(0, Tkconstants.END)
|
||||||
self.outpath.insert(0, outpath)
|
self.outpath.insert(0, outpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2118,42 +2122,42 @@ def gui_main():
|
|||||||
inpath = self.inpath.get()
|
inpath = self.inpath.get()
|
||||||
outpath = self.outpath.get()
|
outpath = self.outpath.get()
|
||||||
if not keypath or not os.path.exists(keypath):
|
if not keypath or not os.path.exists(keypath):
|
||||||
self.status['text'] = "Specified key file does not exist"
|
self.status['text'] = u"Specified key file does not exist"
|
||||||
return
|
return
|
||||||
if not inpath or not os.path.exists(inpath):
|
if not inpath or not os.path.exists(inpath):
|
||||||
self.status['text'] = "Specified input file does not exist"
|
self.status['text'] = u"Specified input file does not exist"
|
||||||
return
|
return
|
||||||
if not outpath:
|
if not outpath:
|
||||||
self.status['text'] = "Output file not specified"
|
self.status['text'] = u"Output file not specified"
|
||||||
return
|
return
|
||||||
if inpath == outpath:
|
if inpath == outpath:
|
||||||
self.status['text'] = "Must have different input and output files"
|
self.status['text'] = u"Must have different input and output files"
|
||||||
return
|
return
|
||||||
userkey = open(keypath,'rb').read()
|
userkey = open(keypath,'rb').read()
|
||||||
self.status['text'] = "Decrypting..."
|
self.status['text'] = u"Decrypting..."
|
||||||
try:
|
try:
|
||||||
decrypt_status = decryptBook(userkey, inpath, outpath)
|
decrypt_status = decryptBook(userkey, inpath, outpath)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
self.status['text'] = "Error; {0}".format(e.args[0])
|
self.status['text'] = u"Error; {0}".format(e.args[0])
|
||||||
return
|
return
|
||||||
if decrypt_status == 0:
|
if decrypt_status == 0:
|
||||||
self.status['text'] = "File successfully decrypted"
|
self.status['text'] = u"File successfully decrypted"
|
||||||
else:
|
else:
|
||||||
self.status['text'] = "The was an error decrypting the file."
|
self.status['text'] = u"The was an error decrypting the file."
|
||||||
|
|
||||||
|
|
||||||
root = tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
if AES is None:
|
if AES is None:
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
tkinter.messagebox.showerror(
|
tkMessageBox.showerror(
|
||||||
"IGNOBLE PDF",
|
"IGNOBLE PDF",
|
||||||
"This script requires OpenSSL or PyCrypto, which must be installed "
|
"This script requires OpenSSL or PyCrypto, which must be installed "
|
||||||
"separately. Read the top-of-script comment for details.")
|
"separately. Read the top-of-script comment for details.")
|
||||||
return 1
|
return 1
|
||||||
root.title("Barnes & Noble PDF Decrypter v.{0}".format(__version__))
|
root.title(u"Barnes & Noble PDF Decrypter v.{0}".format(__version__))
|
||||||
root.resizable(True, False)
|
root.resizable(True, False)
|
||||||
root.minsize(370, 0)
|
root.minsize(370, 0)
|
||||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# ineptepub.py
|
from __future__ import with_statement
|
||||||
# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
# ineptepub.pyw, version 6.6
|
||||||
|
# Copyright © 2009-2020 by Apprentice Harper et al.
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
# Original script by i♥cabbages
|
||||||
|
# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
# Modified 2015–2020 by Apprentice Harper et al.
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 1 - Initial release
|
# 1 - Initial release
|
||||||
@@ -29,16 +36,17 @@
|
|||||||
# 6.4 - Remove erroneous check on DER file sanity
|
# 6.4 - Remove erroneous check on DER file sanity
|
||||||
# 6.5 - Completely remove erroneous check on DER file sanity
|
# 6.5 - Completely remove erroneous check on DER file sanity
|
||||||
# 6.6 - Import tkFileDialog, don't assume something else will import it.
|
# 6.6 - Import tkFileDialog, don't assume something else will import it.
|
||||||
# 7.0 - Add Python 3 compatibility for calibre 5.0
|
# 6.7 - Add Python 3 compatibility while still working with Python 2.7
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "7.0"
|
__version__ = "6.7"
|
||||||
|
|
||||||
import codecs
|
import six
|
||||||
|
from six.moves import range
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
@@ -47,6 +55,7 @@ import zipfile
|
|||||||
from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
|
from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
import base64
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
# and also make sure that any unicode strings get
|
# and also make sure that any unicode strings get
|
||||||
@@ -58,11 +67,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,six.text_type):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -101,10 +109,12 @@ def unicode_argv():
|
|||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
range(start, argc.value)]
|
||||||
return ["ineptepub.py"]
|
return [u"ineptepub.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == six.text_type) else six.text_type(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
|
|
||||||
class ADEPTError(Exception):
|
class ADEPTError(Exception):
|
||||||
@@ -203,7 +213,6 @@ def _load_crypto_libcrypto():
|
|||||||
def _load_crypto_pycrypto():
|
def _load_crypto_pycrypto():
|
||||||
from Crypto.Cipher import AES as _AES
|
from Crypto.Cipher import AES as _AES
|
||||||
from Crypto.PublicKey import RSA as _RSA
|
from Crypto.PublicKey import RSA as _RSA
|
||||||
from Crypto.Cipher import PKCS1_v1_5 as _PKCS1_v1_5
|
|
||||||
|
|
||||||
# ASN.1 parsing code from tlslite
|
# ASN.1 parsing code from tlslite
|
||||||
class ASN1Error(Exception):
|
class ASN1Error(Exception):
|
||||||
@@ -295,14 +304,14 @@ def _load_crypto_pycrypto():
|
|||||||
|
|
||||||
class AES(object):
|
class AES(object):
|
||||||
def __init__(self, key):
|
def __init__(self, key):
|
||||||
self._aes = _AES.new(key, _AES.MODE_CBC, b'\x00'*16)
|
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
|
||||||
|
|
||||||
def decrypt(self, data):
|
def decrypt(self, data):
|
||||||
return self._aes.decrypt(data)
|
return self._aes.decrypt(data)
|
||||||
|
|
||||||
class RSA(object):
|
class RSA(object):
|
||||||
def __init__(self, der):
|
def __init__(self, der):
|
||||||
key = ASN1Parser([x for x in der])
|
key = ASN1Parser([ord(x) for x in der])
|
||||||
key = [key.getChild(x).value for x in range(1, 4)]
|
key = [key.getChild(x).value for x in range(1, 4)]
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
key = [self.bytesToNumber(v) for v in key]
|
||||||
self._rsa = _RSA.construct(key)
|
self._rsa = _RSA.construct(key)
|
||||||
@@ -314,7 +323,7 @@ def _load_crypto_pycrypto():
|
|||||||
return total
|
return total
|
||||||
|
|
||||||
def decrypt(self, data):
|
def decrypt(self, data):
|
||||||
return _PKCS1_v1_5.new(self._rsa).decrypt(data, 172)
|
return self._rsa.decrypt(data)
|
||||||
|
|
||||||
return (AES, RSA)
|
return (AES, RSA)
|
||||||
|
|
||||||
@@ -391,13 +400,13 @@ def adeptBook(inpath):
|
|||||||
|
|
||||||
def decryptBook(userkey, inpath, outpath):
|
def decryptBook(userkey, inpath, outpath):
|
||||||
if AES is None:
|
if AES is None:
|
||||||
raise ADEPTError("PyCrypto or OpenSSL must be installed.")
|
raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
|
||||||
rsa = RSA(userkey)
|
rsa = RSA(userkey)
|
||||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||||
namelist = set(inf.namelist())
|
namelist = set(inf.namelist())
|
||||||
if 'META-INF/rights.xml' not in namelist or \
|
if 'META-INF/rights.xml' not in namelist or \
|
||||||
'META-INF/encryption.xml' not in namelist:
|
'META-INF/encryption.xml' not in namelist:
|
||||||
print("{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
print(u"{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
||||||
return 1
|
return 1
|
||||||
for name in META_NAMES:
|
for name in META_NAMES:
|
||||||
namelist.remove(name)
|
namelist.remove(name)
|
||||||
@@ -407,18 +416,17 @@ def decryptBook(userkey, inpath, outpath):
|
|||||||
expr = './/%s' % (adept('encryptedKey'),)
|
expr = './/%s' % (adept('encryptedKey'),)
|
||||||
bookkey = ''.join(rights.findtext(expr))
|
bookkey = ''.join(rights.findtext(expr))
|
||||||
if len(bookkey) != 172:
|
if len(bookkey) != 172:
|
||||||
print("{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
|
print(u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
|
||||||
return 1
|
return 1
|
||||||
bookkey = rsa.decrypt(codecs.decode(bookkey.encode('ascii'), 'base64'))
|
bookkey = bookkey.encode('ascii')
|
||||||
|
bookkey = base64.b64decode(bookkey)
|
||||||
|
bookkey = rsa.decrypt(bookkey)
|
||||||
# Padded as per RSAES-PKCS1-v1_5
|
# Padded as per RSAES-PKCS1-v1_5
|
||||||
if len(bookkey) != 16:
|
if bookkey[-17] != '\x00' and bookkey[-17] != 0:
|
||||||
if bookkey[-17] != '\x00' and bookkey[-17] != 0:
|
print(u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
|
||||||
print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
|
return 2
|
||||||
return 2
|
|
||||||
else:
|
|
||||||
bookkey = bookkey[-16:]
|
|
||||||
encryption = inf.read('META-INF/encryption.xml')
|
encryption = inf.read('META-INF/encryption.xml')
|
||||||
decryptor = Decryptor(bookkey, encryption)
|
decryptor = Decryptor(bookkey[-16:], encryption)
|
||||||
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
||||||
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
|
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
|
||||||
zi = ZipInfo('mimetype')
|
zi = ZipInfo('mimetype')
|
||||||
@@ -456,7 +464,7 @@ def decryptBook(userkey, inpath, outpath):
|
|||||||
pass
|
pass
|
||||||
outf.writestr(zi, decryptor.decrypt(path, data))
|
outf.writestr(zi, decryptor.decrypt(path, data))
|
||||||
except:
|
except:
|
||||||
print("Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
print(u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
||||||
return 2
|
return 2
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -467,90 +475,90 @@ def cli_main():
|
|||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if len(argv) != 4:
|
if len(argv) != 4:
|
||||||
print("usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname))
|
print(u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname))
|
||||||
return 1
|
return 1
|
||||||
keypath, inpath, outpath = argv[1:]
|
keypath, inpath, outpath = argv[1:]
|
||||||
userkey = open(keypath,'rb').read()
|
userkey = open(keypath,'rb').read()
|
||||||
result = decryptBook(userkey, inpath, outpath)
|
result = decryptBook(userkey, inpath, outpath)
|
||||||
if result == 0:
|
if result == 0:
|
||||||
print("Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
print(u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import six.moves.tkinter
|
||||||
import tkinter.constants
|
import six.moves.tkinter_constants
|
||||||
import tkinter.filedialog
|
import six.moves.tkinter_filedialog
|
||||||
import tkinter.messagebox
|
import six.moves.tkinter_messagebox
|
||||||
import traceback
|
import traceback
|
||||||
except:
|
except:
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class DecryptionDialog(tkinter.Frame):
|
class DecryptionDialog(six.moves.tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
tkinter.Frame.__init__(self, root, border=5)
|
six.moves.tkinter.Frame.__init__(self, root, border=5)
|
||||||
self.status = tkinter.Label(self, text="Select files for decryption")
|
self.status = six.moves.tkinter.Label(self, text=u"Select files for decryption")
|
||||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
self.status.pack(fill=six.moves.tkinter_constants.X, expand=1)
|
||||||
body = tkinter.Frame(self)
|
body = six.moves.tkinter.Frame(self)
|
||||||
body.pack(fill=tkinter.constants.X, expand=1)
|
body.pack(fill=six.moves.tkinter_constants.X, expand=1)
|
||||||
sticky = tkinter.constants.E + tkinter.constants.W
|
sticky = six.moves.tkinter_constants.E + six.moves.tkinter_constants.W
|
||||||
body.grid_columnconfigure(1, weight=2)
|
body.grid_columnconfigure(1, weight=2)
|
||||||
tkinter.Label(body, text="Key file").grid(row=0)
|
six.moves.tkinter.Label(body, text=u"Key file").grid(row=0)
|
||||||
self.keypath = tkinter.Entry(body, width=30)
|
self.keypath = six.moves.tkinter.Entry(body, width=30)
|
||||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||||
if os.path.exists("adeptkey.der"):
|
if os.path.exists(u"adeptkey.der"):
|
||||||
self.keypath.insert(0, "adeptkey.der")
|
self.keypath.insert(0, u"adeptkey.der")
|
||||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||||
button.grid(row=0, column=2)
|
button.grid(row=0, column=2)
|
||||||
tkinter.Label(body, text="Input file").grid(row=1)
|
six.moves.tkinter.Label(body, text=u"Input file").grid(row=1)
|
||||||
self.inpath = tkinter.Entry(body, width=30)
|
self.inpath = six.moves.tkinter.Entry(body, width=30)
|
||||||
self.inpath.grid(row=1, column=1, sticky=sticky)
|
self.inpath.grid(row=1, column=1, sticky=sticky)
|
||||||
button = tkinter.Button(body, text="...", command=self.get_inpath)
|
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_inpath)
|
||||||
button.grid(row=1, column=2)
|
button.grid(row=1, column=2)
|
||||||
tkinter.Label(body, text="Output file").grid(row=2)
|
six.moves.tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||||
self.outpath = tkinter.Entry(body, width=30)
|
self.outpath = six.moves.tkinter.Entry(body, width=30)
|
||||||
self.outpath.grid(row=2, column=1, sticky=sticky)
|
self.outpath.grid(row=2, column=1, sticky=sticky)
|
||||||
button = tkinter.Button(body, text="...", command=self.get_outpath)
|
button = six.moves.tkinter.Button(body, text=u"...", command=self.get_outpath)
|
||||||
button.grid(row=2, column=2)
|
button.grid(row=2, column=2)
|
||||||
buttons = tkinter.Frame(self)
|
buttons = six.moves.tkinter.Frame(self)
|
||||||
buttons.pack()
|
buttons.pack()
|
||||||
botton = tkinter.Button(
|
botton = six.moves.tkinter.Button(
|
||||||
buttons, text="Decrypt", width=10, command=self.decrypt)
|
buttons, text=u"Decrypt", width=10, command=self.decrypt)
|
||||||
botton.pack(side=tkinter.constants.LEFT)
|
botton.pack(side=six.moves.tkinter_constants.LEFT)
|
||||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
six.moves.tkinter.Frame(buttons, width=10).pack(side=six.moves.tkinter_constants.LEFT)
|
||||||
button = tkinter.Button(
|
button = six.moves.tkinter.Button(
|
||||||
buttons, text="Quit", width=10, command=self.quit)
|
buttons, text=u"Quit", width=10, command=self.quit)
|
||||||
button.pack(side=tkinter.constants.RIGHT)
|
button.pack(side=six.moves.tkinter_constants.RIGHT)
|
||||||
|
|
||||||
def get_keypath(self):
|
def get_keypath(self):
|
||||||
keypath = tkinter.filedialog.askopenfilename(
|
keypath = six.moves.tkinter_filedialog.askopenfilename(
|
||||||
parent=None, title="Select Adobe Adept \'.der\' key file",
|
parent=None, title=u"Select Adobe Adept \'.der\' key file",
|
||||||
defaultextension=".der",
|
defaultextension=u".der",
|
||||||
filetypes=[('Adobe Adept DER-encoded files', '.der'),
|
filetypes=[('Adobe Adept DER-encoded files', '.der'),
|
||||||
('All Files', '.*')])
|
('All Files', '.*')])
|
||||||
if keypath:
|
if keypath:
|
||||||
keypath = os.path.normpath(keypath)
|
keypath = os.path.normpath(keypath)
|
||||||
self.keypath.delete(0, tkinter.constants.END)
|
self.keypath.delete(0, six.moves.tkinter_constants.END)
|
||||||
self.keypath.insert(0, keypath)
|
self.keypath.insert(0, keypath)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_inpath(self):
|
def get_inpath(self):
|
||||||
inpath = tkinter.filedialog.askopenfilename(
|
inpath = six.moves.tkinter_filedialog.askopenfilename(
|
||||||
parent=None, title="Select ADEPT-encrypted ePub file to decrypt",
|
parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt",
|
||||||
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
|
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
||||||
if inpath:
|
if inpath:
|
||||||
inpath = os.path.normpath(inpath)
|
inpath = os.path.normpath(inpath)
|
||||||
self.inpath.delete(0, tkinter.constants.END)
|
self.inpath.delete(0, six.moves.tkinter_constants.END)
|
||||||
self.inpath.insert(0, inpath)
|
self.inpath.insert(0, inpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_outpath(self):
|
def get_outpath(self):
|
||||||
outpath = tkinter.filedialog.asksaveasfilename(
|
outpath = six.moves.tkinter_filedialog.asksaveasfilename(
|
||||||
parent=None, title="Select unencrypted ePub file to produce",
|
parent=None, title=u"Select unencrypted ePub file to produce",
|
||||||
defaultextension=".epub", filetypes=[('ePub files', '.epub')])
|
defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
|
||||||
if outpath:
|
if outpath:
|
||||||
outpath = os.path.normpath(outpath)
|
outpath = os.path.normpath(outpath)
|
||||||
self.outpath.delete(0, tkinter.constants.END)
|
self.outpath.delete(0, six.moves.tkinter_constants.END)
|
||||||
self.outpath.insert(0, outpath)
|
self.outpath.insert(0, outpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -559,34 +567,34 @@ def gui_main():
|
|||||||
inpath = self.inpath.get()
|
inpath = self.inpath.get()
|
||||||
outpath = self.outpath.get()
|
outpath = self.outpath.get()
|
||||||
if not keypath or not os.path.exists(keypath):
|
if not keypath or not os.path.exists(keypath):
|
||||||
self.status['text'] = "Specified key file does not exist"
|
self.status['text'] = u"Specified key file does not exist"
|
||||||
return
|
return
|
||||||
if not inpath or not os.path.exists(inpath):
|
if not inpath or not os.path.exists(inpath):
|
||||||
self.status['text'] = "Specified input file does not exist"
|
self.status['text'] = u"Specified input file does not exist"
|
||||||
return
|
return
|
||||||
if not outpath:
|
if not outpath:
|
||||||
self.status['text'] = "Output file not specified"
|
self.status['text'] = u"Output file not specified"
|
||||||
return
|
return
|
||||||
if inpath == outpath:
|
if inpath == outpath:
|
||||||
self.status['text'] = "Must have different input and output files"
|
self.status['text'] = u"Must have different input and output files"
|
||||||
return
|
return
|
||||||
userkey = open(keypath,'rb').read()
|
userkey = open(keypath,'rb').read()
|
||||||
self.status['text'] = "Decrypting..."
|
self.status['text'] = u"Decrypting..."
|
||||||
try:
|
try:
|
||||||
decrypt_status = decryptBook(userkey, inpath, outpath)
|
decrypt_status = decryptBook(userkey, inpath, outpath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.status['text'] = "Error: {0}".format(e.args[0])
|
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||||
return
|
return
|
||||||
if decrypt_status == 0:
|
if decrypt_status == 0:
|
||||||
self.status['text'] = "File successfully decrypted"
|
self.status['text'] = u"File successfully decrypted"
|
||||||
else:
|
else:
|
||||||
self.status['text'] = "There was an error decrypting the file."
|
self.status['text'] = u"The was an error decrypting the file."
|
||||||
|
|
||||||
root = tkinter.Tk()
|
root = six.moves.tkinter.Tk()
|
||||||
root.title("Adobe Adept ePub Decrypter v.{0}".format(__version__))
|
root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__))
|
||||||
root.resizable(True, False)
|
root.resizable(True, False)
|
||||||
root.minsize(300, 0)
|
root.minsize(300, 0)
|
||||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
DecryptionDialog(root).pack(fill=six.moves.tkinter_constants.X, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
#! /usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ineptpdf.py
|
# ineptpdf.py
|
||||||
# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
|
# Copyright © 2009-2017 by Apprentice Harper et al.
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
|
|
||||||
|
# Modified 2010–2012 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
# Modified 2015-2017 by Apprentice Harper et al.
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 1 - Initial release
|
# 1 - Initial release
|
||||||
@@ -45,14 +49,14 @@
|
|||||||
# 8.0.4 - Completely remove erroneous check on DER file sanity
|
# 8.0.4 - Completely remove erroneous check on DER file sanity
|
||||||
# 8.0.5 - Do not process DRM-free documents
|
# 8.0.5 - Do not process DRM-free documents
|
||||||
# 8.0.6 - Replace use of float by Decimal for greater precision, and import tkFileDialog
|
# 8.0.6 - Replace use of float by Decimal for greater precision, and import tkFileDialog
|
||||||
# 9.0.0 - Add Python 3 compatibility for calibre 5
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "9.0.0"
|
__version__ = "8.0.6"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -60,8 +64,8 @@ import re
|
|||||||
import zlib
|
import zlib
|
||||||
import struct
|
import struct
|
||||||
import hashlib
|
import hashlib
|
||||||
from decimal import Decimal
|
from decimal import *
|
||||||
import itertools
|
from itertools import chain, islice
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
@@ -74,11 +78,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -113,11 +116,13 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
return ["ineptpdf.py"]
|
return [u"ineptpdf.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
|
|
||||||
class ADEPTError(Exception):
|
class ADEPTError(Exception):
|
||||||
@@ -373,12 +378,12 @@ def _load_crypto_pycrypto():
|
|||||||
class RSA(object):
|
class RSA(object):
|
||||||
def __init__(self, der):
|
def __init__(self, der):
|
||||||
key = ASN1Parser([ord(x) for x in der])
|
key = ASN1Parser([ord(x) for x in der])
|
||||||
key = [key.getChild(x).value for x in range(1, 4)]
|
key = [key.getChild(x).value for x in xrange(1, 4)]
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
key = [self.bytesToNumber(v) for v in key]
|
||||||
self._rsa = _RSA.construct(key)
|
self._rsa = _RSA.construct(key)
|
||||||
|
|
||||||
def bytesToNumber(self, bytes):
|
def bytesToNumber(self, bytes):
|
||||||
total = 0
|
total = 0L
|
||||||
for byte in bytes:
|
for byte in bytes:
|
||||||
total = (total << 8) + byte
|
total = (total << 8) + byte
|
||||||
return total
|
return total
|
||||||
@@ -403,7 +408,10 @@ def _load_crypto():
|
|||||||
ARC4, RSA, AES = _load_crypto()
|
ARC4, RSA, AES = _load_crypto()
|
||||||
|
|
||||||
|
|
||||||
from io import BytesIO
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
|
||||||
# Do we generate cross reference streams on output?
|
# Do we generate cross reference streams on output?
|
||||||
@@ -479,9 +487,9 @@ class PSLiteral(PSObject):
|
|||||||
name = []
|
name = []
|
||||||
for char in self.name:
|
for char in self.name:
|
||||||
if not char.isalnum():
|
if not char.isalnum():
|
||||||
char = b'#%02x' % ord(char)
|
char = '#%02x' % ord(char)
|
||||||
name.append(char)
|
name.append(char)
|
||||||
return b'/%s' % ''.join(name)
|
return '/%s' % ''.join(name)
|
||||||
|
|
||||||
# PSKeyword
|
# PSKeyword
|
||||||
class PSKeyword(PSObject):
|
class PSKeyword(PSObject):
|
||||||
@@ -708,7 +716,7 @@ class PSBaseParser(object):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return (self.parse_main, j)
|
return (self.parse_main, j)
|
||||||
|
|
||||||
def parse_decimal(self, s, i):
|
def parse_decimal(self, s, i):
|
||||||
m = END_NUMBER.search(s, i)
|
m = END_NUMBER.search(s, i)
|
||||||
if not m:
|
if not m:
|
||||||
@@ -941,7 +949,7 @@ class PSStackParser(PSBaseParser):
|
|||||||
try:
|
try:
|
||||||
(pos, objs) = self.end_type('d')
|
(pos, objs) = self.end_type('d')
|
||||||
if len(objs) % 2 != 0:
|
if len(objs) % 2 != 0:
|
||||||
print("Incomplete dictionary construct")
|
print "Incomplete dictionary construct"
|
||||||
objs.append("") # this isn't necessary.
|
objs.append("") # this isn't necessary.
|
||||||
# temporary fix. is this due to rental books?
|
# temporary fix. is this due to rental books?
|
||||||
# raise PSSyntaxError(
|
# raise PSSyntaxError(
|
||||||
@@ -1098,18 +1106,18 @@ def stream_value(x):
|
|||||||
# ascii85decode(data)
|
# ascii85decode(data)
|
||||||
def ascii85decode(data):
|
def ascii85decode(data):
|
||||||
n = b = 0
|
n = b = 0
|
||||||
out = b''
|
out = ''
|
||||||
for c in data:
|
for c in data:
|
||||||
if b'!' <= c and c <= b'u':
|
if '!' <= c and c <= 'u':
|
||||||
n += 1
|
n += 1
|
||||||
b = b*85+(c-33)
|
b = b*85+(ord(c)-33)
|
||||||
if n == 5:
|
if n == 5:
|
||||||
out += struct.pack('>L',b)
|
out += struct.pack('>L',b)
|
||||||
n = b = 0
|
n = b = 0
|
||||||
elif c == b'z':
|
elif c == 'z':
|
||||||
assert n == 0
|
assert n == 0
|
||||||
out += b'\0\0\0\0'
|
out += '\0\0\0\0'
|
||||||
elif c == b'~':
|
elif c == '~':
|
||||||
if n:
|
if n:
|
||||||
for _ in range(5-n):
|
for _ in range(5-n):
|
||||||
b = b*85+84
|
b = b*85+84
|
||||||
@@ -1130,7 +1138,7 @@ class PDFStream(PDFObject):
|
|||||||
cutdiv = len(rawdata) // 16
|
cutdiv = len(rawdata) // 16
|
||||||
rawdata = rawdata[:16*cutdiv]
|
rawdata = rawdata[:16*cutdiv]
|
||||||
else:
|
else:
|
||||||
if eol in (b'\r', b'\n', b'\r\n'):
|
if eol in ('\r', '\n', '\r\n'):
|
||||||
rawdata = rawdata[:length]
|
rawdata = rawdata[:length]
|
||||||
|
|
||||||
self.dic = dic
|
self.dic = dic
|
||||||
@@ -1176,7 +1184,7 @@ class PDFStream(PDFObject):
|
|||||||
# will get errors if the document is encrypted.
|
# will get errors if the document is encrypted.
|
||||||
data = zlib.decompress(data)
|
data = zlib.decompress(data)
|
||||||
elif f in LITERALS_LZW_DECODE:
|
elif f in LITERALS_LZW_DECODE:
|
||||||
data = ''.join(LZWDecoder(BytesIO(data)).run())
|
data = ''.join(LZWDecoder(StringIO(data)).run())
|
||||||
elif f in LITERALS_ASCII85_DECODE:
|
elif f in LITERALS_ASCII85_DECODE:
|
||||||
data = ascii85decode(data)
|
data = ascii85decode(data)
|
||||||
elif f == LITERAL_CRYPT:
|
elif f == LITERAL_CRYPT:
|
||||||
@@ -1198,14 +1206,14 @@ class PDFStream(PDFObject):
|
|||||||
raise PDFValueError(
|
raise PDFValueError(
|
||||||
'Columns undefined for predictor=12')
|
'Columns undefined for predictor=12')
|
||||||
columns = int_value(params['Columns'])
|
columns = int_value(params['Columns'])
|
||||||
buf = b''
|
buf = ''
|
||||||
ent0 = b'\x00' * columns
|
ent0 = '\x00' * columns
|
||||||
for i in range(0, len(data), columns+1):
|
for i in xrange(0, len(data), columns+1):
|
||||||
pred = data[i]
|
pred = data[i]
|
||||||
ent1 = data[i+1:i+1+columns]
|
ent1 = data[i+1:i+1+columns]
|
||||||
if pred == b'\x02':
|
if pred == '\x02':
|
||||||
ent1 = ''.join(bytes([(a+b) & 255]) \
|
ent1 = ''.join(chr((ord(a)+ord(b)) & 255) \
|
||||||
for (a,b) in zip(ent0,ent1))
|
for (a,b) in zip(ent0,ent1))
|
||||||
buf += ent1
|
buf += ent1
|
||||||
ent0 = ent1
|
ent0 = ent1
|
||||||
data = buf
|
data = buf
|
||||||
@@ -1282,7 +1290,7 @@ class PDFXRef(object):
|
|||||||
(start, nobjs) = map(int, f)
|
(start, nobjs) = map(int, f)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
|
raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
|
||||||
for objid in range(start, start+nobjs):
|
for objid in xrange(start, start+nobjs):
|
||||||
try:
|
try:
|
||||||
(_, line) = parser.nextline()
|
(_, line) = parser.nextline()
|
||||||
except PSEOF:
|
except PSEOF:
|
||||||
@@ -1334,7 +1342,7 @@ class PDFXRefStream(object):
|
|||||||
|
|
||||||
def objids(self):
|
def objids(self):
|
||||||
for first, size in self.index:
|
for first, size in self.index:
|
||||||
for objid in range(first, first + size):
|
for objid in xrange(first, first + size):
|
||||||
yield objid
|
yield objid
|
||||||
|
|
||||||
def load(self, parser, debug=0):
|
def load(self, parser, debug=0):
|
||||||
@@ -1347,8 +1355,8 @@ class PDFXRefStream(object):
|
|||||||
raise PDFNoValidXRef('Invalid PDF stream spec.')
|
raise PDFNoValidXRef('Invalid PDF stream spec.')
|
||||||
size = stream.dic['Size']
|
size = stream.dic['Size']
|
||||||
index = stream.dic.get('Index', (0,size))
|
index = stream.dic.get('Index', (0,size))
|
||||||
self.index = zip(itertools.islice(index, 0, None, 2),
|
self.index = zip(islice(index, 0, None, 2),
|
||||||
itertools.islice(index, 1, None, 2))
|
islice(index, 1, None, 2))
|
||||||
(self.fl1, self.fl2, self.fl3) = stream.dic['W']
|
(self.fl1, self.fl2, self.fl3) = stream.dic['W']
|
||||||
self.data = stream.get_data()
|
self.data = stream.get_data()
|
||||||
self.entlen = self.fl1+self.fl2+self.fl3
|
self.entlen = self.fl1+self.fl2+self.fl3
|
||||||
@@ -1498,10 +1506,10 @@ class PDFDocument(object):
|
|||||||
if plaintext[-16:] != 16 * chr(16):
|
if plaintext[-16:] != 16 * chr(16):
|
||||||
raise ADEPTError('Offlinekey cannot be decrypted, aborting ...')
|
raise ADEPTError('Offlinekey cannot be decrypted, aborting ...')
|
||||||
pdrlpol = AES.new(plaintext[16:32],AES.MODE_CBC,edclist[2].decode('base64')).decrypt(pdrlpol)
|
pdrlpol = AES.new(plaintext[16:32],AES.MODE_CBC,edclist[2].decode('base64')).decrypt(pdrlpol)
|
||||||
if pdrlpol[-1] < 1 or pdrlpol[-1] > 16:
|
if ord(pdrlpol[-1]) < 1 or ord(pdrlpol[-1]) > 16:
|
||||||
raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
|
raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
|
||||||
else:
|
else:
|
||||||
cutter = -1 * pdrlpol[-1]
|
cutter = -1 * ord(pdrlpol[-1])
|
||||||
pdrlpol = pdrlpol[:cutter]
|
pdrlpol = pdrlpol[:cutter]
|
||||||
return plaintext[:16]
|
return plaintext[:16]
|
||||||
|
|
||||||
@@ -1543,7 +1551,7 @@ class PDFDocument(object):
|
|||||||
hash.update('ffffffff'.decode('hex'))
|
hash.update('ffffffff'.decode('hex'))
|
||||||
if 5 <= R:
|
if 5 <= R:
|
||||||
# 8
|
# 8
|
||||||
for _ in range(50):
|
for _ in xrange(50):
|
||||||
hash = hashlib.md5(hash.digest()[:length/8])
|
hash = hashlib.md5(hash.digest()[:length/8])
|
||||||
key = hash.digest()[:length/8]
|
key = hash.digest()[:length/8]
|
||||||
if R == 2:
|
if R == 2:
|
||||||
@@ -1554,8 +1562,8 @@ class PDFDocument(object):
|
|||||||
hash = hashlib.md5(self.PASSWORD_PADDING) # 2
|
hash = hashlib.md5(self.PASSWORD_PADDING) # 2
|
||||||
hash.update(docid[0]) # 3
|
hash.update(docid[0]) # 3
|
||||||
x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
|
x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
|
||||||
for i in range(1,19+1):
|
for i in xrange(1,19+1):
|
||||||
k = ''.join(bytes([c ^ i]) for c in key )
|
k = ''.join( chr(ord(c) ^ i) for c in key )
|
||||||
x = ARC4.new(k).decrypt(x)
|
x = ARC4.new(k).decrypt(x)
|
||||||
u1 = x+x # 32bytes total
|
u1 = x+x # 32bytes total
|
||||||
if R == 2:
|
if R == 2:
|
||||||
@@ -1577,9 +1585,9 @@ class PDFDocument(object):
|
|||||||
if V != 4:
|
if V != 4:
|
||||||
self.decipher = self.decipher_rc4 # XXX may be AES
|
self.decipher = self.decipher_rc4 # XXX may be AES
|
||||||
# aes
|
# aes
|
||||||
elif V == 4 and length == 128:
|
elif V == 4 and Length == 128:
|
||||||
self.decipher = self.decipher_aes
|
elf.decipher = self.decipher_aes
|
||||||
elif V == 4 and length == 256:
|
elif V == 4 and Length == 256:
|
||||||
raise PDFNotImplementedError('AES256 encryption is currently unsupported')
|
raise PDFNotImplementedError('AES256 encryption is currently unsupported')
|
||||||
self.ready = True
|
self.ready = True
|
||||||
return
|
return
|
||||||
@@ -1608,18 +1616,18 @@ class PDFDocument(object):
|
|||||||
else:
|
else:
|
||||||
V = 2
|
V = 2
|
||||||
elif len(bookkey) == length + 1:
|
elif len(bookkey) == length + 1:
|
||||||
V = bookkey[0]
|
V = ord(bookkey[0])
|
||||||
bookkey = bookkey[1:]
|
bookkey = bookkey[1:]
|
||||||
else:
|
else:
|
||||||
print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
|
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
|
||||||
print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
|
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
|
||||||
print("bookkey[0] is %d" % bookkey[0])
|
print "bookkey[0] is %d" % ord(bookkey[0])
|
||||||
raise ADEPTError('error decrypting book session key - mismatched length')
|
raise ADEPTError('error decrypting book session key - mismatched length')
|
||||||
else:
|
else:
|
||||||
# proper length unknown try with whatever you have
|
# proper length unknown try with whatever you have
|
||||||
print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type))
|
print "ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)
|
||||||
print("length is %d and len(bookkey) is %d" % (length, len(bookkey)))
|
print "length is %d and len(bookkey) is %d" % (length, len(bookkey))
|
||||||
print("bookkey[0] is %d" % bookkey[0])
|
print "bookkey[0] is %d" % ord(bookkey[0])
|
||||||
if ebx_V == 3:
|
if ebx_V == 3:
|
||||||
V = 3
|
V = 3
|
||||||
else:
|
else:
|
||||||
@@ -1663,7 +1671,7 @@ class PDFDocument(object):
|
|||||||
data = data[16:]
|
data = data[16:]
|
||||||
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
||||||
# remove pkcs#5 aes padding
|
# remove pkcs#5 aes padding
|
||||||
cutter = -1 * plaintext[-1]
|
cutter = -1 * ord(plaintext[-1])
|
||||||
#print cutter
|
#print cutter
|
||||||
plaintext = plaintext[:cutter]
|
plaintext = plaintext[:cutter]
|
||||||
return plaintext
|
return plaintext
|
||||||
@@ -1674,7 +1682,7 @@ class PDFDocument(object):
|
|||||||
data = data[16:]
|
data = data[16:]
|
||||||
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
plaintext = AES.new(key,AES.MODE_CBC,ivector).decrypt(data)
|
||||||
# remove pkcs#5 aes padding
|
# remove pkcs#5 aes padding
|
||||||
cutter = -1 * plaintext[-1]
|
cutter = -1 * ord(plaintext[-1])
|
||||||
#print cutter
|
#print cutter
|
||||||
plaintext = plaintext[:cutter]
|
plaintext = plaintext[:cutter]
|
||||||
return plaintext
|
return plaintext
|
||||||
@@ -1943,7 +1951,7 @@ class PDFParser(PSStackParser):
|
|||||||
class PDFObjStrmParser(PDFParser):
|
class PDFObjStrmParser(PDFParser):
|
||||||
|
|
||||||
def __init__(self, data, doc):
|
def __init__(self, data, doc):
|
||||||
PSStackParser.__init__(self, BytesIO(data))
|
PSStackParser.__init__(self, StringIO(data))
|
||||||
self.doc = doc
|
self.doc = doc
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2018,7 +2026,7 @@ class PDFSerializer(object):
|
|||||||
if not gen_xref_stm:
|
if not gen_xref_stm:
|
||||||
self.write('xref\n')
|
self.write('xref\n')
|
||||||
self.write('0 %d\n' % (maxobj + 1,))
|
self.write('0 %d\n' % (maxobj + 1,))
|
||||||
for objid in range(0, maxobj + 1):
|
for objid in xrange(0, maxobj + 1):
|
||||||
if objid in xrefs:
|
if objid in xrefs:
|
||||||
# force the genno to be 0
|
# force the genno to be 0
|
||||||
self.write("%010d 00000 n \n" % xrefs[objid][0])
|
self.write("%010d 00000 n \n" % xrefs[objid][0])
|
||||||
@@ -2127,7 +2135,7 @@ class PDFSerializer(object):
|
|||||||
if self.last.isalnum():
|
if self.last.isalnum():
|
||||||
self.write(' ')
|
self.write(' ')
|
||||||
self.write(str(obj).lower())
|
self.write(str(obj).lower())
|
||||||
elif isinstance(obj, int):
|
elif isinstance(obj, (int, long)):
|
||||||
if self.last.isalnum():
|
if self.last.isalnum():
|
||||||
self.write(' ')
|
self.write(' ')
|
||||||
self.write(str(obj))
|
self.write(str(obj))
|
||||||
@@ -2169,20 +2177,20 @@ class PDFSerializer(object):
|
|||||||
|
|
||||||
def decryptBook(userkey, inpath, outpath):
|
def decryptBook(userkey, inpath, outpath):
|
||||||
if RSA is None:
|
if RSA is None:
|
||||||
raise ADEPTError("PyCrypto or OpenSSL must be installed.")
|
raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
|
||||||
with open(inpath, 'rb') as inf:
|
with open(inpath, 'rb') as inf:
|
||||||
#try:
|
#try:
|
||||||
serializer = PDFSerializer(inf, userkey)
|
serializer = PDFSerializer(inf, userkey)
|
||||||
#except:
|
#except:
|
||||||
# print "Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath))
|
# print u"Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath))
|
||||||
# return 2
|
# return 2
|
||||||
# hope this will fix the 'bad file descriptor' problem
|
# hope this will fix the 'bad file descriptor' problem
|
||||||
with open(outpath, 'wb') as outf:
|
with open(outpath, 'wb') as outf:
|
||||||
# help construct to make sure the method runs to the end
|
# help construct to make sure the method runs to the end
|
||||||
try:
|
try:
|
||||||
serializer.dump(outf)
|
serializer.dump(outf)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
print("error writing pdf: {0}".format(e.args[0]))
|
print u"error writing pdf: {0}".format(e.args[0])
|
||||||
return 2
|
return 2
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -2193,91 +2201,91 @@ def cli_main():
|
|||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if len(argv) != 4:
|
if len(argv) != 4:
|
||||||
print("usage: {0} <keyfile.der> <inbook.pdf> <outbook.pdf>".format(progname))
|
print u"usage: {0} <keyfile.der> <inbook.pdf> <outbook.pdf>".format(progname)
|
||||||
return 1
|
return 1
|
||||||
keypath, inpath, outpath = argv[1:]
|
keypath, inpath, outpath = argv[1:]
|
||||||
userkey = open(keypath,'rb').read()
|
userkey = open(keypath,'rb').read()
|
||||||
result = decryptBook(userkey, inpath, outpath)
|
result = decryptBook(userkey, inpath, outpath)
|
||||||
if result == 0:
|
if result == 0:
|
||||||
print("Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath)))
|
print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import Tkinter
|
||||||
import tkinter.constants
|
import Tkconstants
|
||||||
import tkinter.filedialog
|
import tkFileDialog
|
||||||
import tkinter.messagebox
|
import tkMessageBox
|
||||||
import traceback
|
import traceback
|
||||||
except:
|
except:
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class DecryptionDialog(tkinter.Frame):
|
class DecryptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
tkinter.Frame.__init__(self, root, border=5)
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
self.status = tkinter.Label(self, text="Select files for decryption")
|
self.status = Tkinter.Label(self, text=u"Select files for decryption")
|
||||||
self.status.pack(fill=tkinter.constants.X, expand=1)
|
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||||
body = tkinter.Frame(self)
|
body = Tkinter.Frame(self)
|
||||||
body.pack(fill=tkinter.constants.X, expand=1)
|
body.pack(fill=Tkconstants.X, expand=1)
|
||||||
sticky = tkinter.constants.E + tkinter.constants.W
|
sticky = Tkconstants.E + Tkconstants.W
|
||||||
body.grid_columnconfigure(1, weight=2)
|
body.grid_columnconfigure(1, weight=2)
|
||||||
tkinter.Label(body, text="Key file").grid(row=0)
|
Tkinter.Label(body, text=u"Key file").grid(row=0)
|
||||||
self.keypath = tkinter.Entry(body, width=30)
|
self.keypath = Tkinter.Entry(body, width=30)
|
||||||
self.keypath.grid(row=0, column=1, sticky=sticky)
|
self.keypath.grid(row=0, column=1, sticky=sticky)
|
||||||
if os.path.exists("adeptkey.der"):
|
if os.path.exists(u"adeptkey.der"):
|
||||||
self.keypath.insert(0, "adeptkey.der")
|
self.keypath.insert(0, u"adeptkey.der")
|
||||||
button = tkinter.Button(body, text="...", command=self.get_keypath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
|
||||||
button.grid(row=0, column=2)
|
button.grid(row=0, column=2)
|
||||||
tkinter.Label(body, text="Input file").grid(row=1)
|
Tkinter.Label(body, text=u"Input file").grid(row=1)
|
||||||
self.inpath = tkinter.Entry(body, width=30)
|
self.inpath = Tkinter.Entry(body, width=30)
|
||||||
self.inpath.grid(row=1, column=1, sticky=sticky)
|
self.inpath.grid(row=1, column=1, sticky=sticky)
|
||||||
button = tkinter.Button(body, text="...", command=self.get_inpath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
|
||||||
button.grid(row=1, column=2)
|
button.grid(row=1, column=2)
|
||||||
tkinter.Label(body, text="Output file").grid(row=2)
|
Tkinter.Label(body, text=u"Output file").grid(row=2)
|
||||||
self.outpath = tkinter.Entry(body, width=30)
|
self.outpath = Tkinter.Entry(body, width=30)
|
||||||
self.outpath.grid(row=2, column=1, sticky=sticky)
|
self.outpath.grid(row=2, column=1, sticky=sticky)
|
||||||
button = tkinter.Button(body, text="...", command=self.get_outpath)
|
button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
|
||||||
button.grid(row=2, column=2)
|
button.grid(row=2, column=2)
|
||||||
buttons = tkinter.Frame(self)
|
buttons = Tkinter.Frame(self)
|
||||||
buttons.pack()
|
buttons.pack()
|
||||||
botton = tkinter.Button(
|
botton = Tkinter.Button(
|
||||||
buttons, text="Decrypt", width=10, command=self.decrypt)
|
buttons, text=u"Decrypt", width=10, command=self.decrypt)
|
||||||
botton.pack(side=tkinter.constants.LEFT)
|
botton.pack(side=Tkconstants.LEFT)
|
||||||
tkinter.Frame(buttons, width=10).pack(side=tkinter.constants.LEFT)
|
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
|
||||||
button = tkinter.Button(
|
button = Tkinter.Button(
|
||||||
buttons, text="Quit", width=10, command=self.quit)
|
buttons, text=u"Quit", width=10, command=self.quit)
|
||||||
button.pack(side=tkinter.constants.RIGHT)
|
button.pack(side=Tkconstants.RIGHT)
|
||||||
|
|
||||||
def get_keypath(self):
|
def get_keypath(self):
|
||||||
keypath = tkinter.filedialog.askopenfilename(
|
keypath = tkFileDialog.askopenfilename(
|
||||||
parent=None, title="Select Adobe Adept \'.der\' key file",
|
parent=None, title=u"Select Adobe Adept \'.der\' key file",
|
||||||
defaultextension=".der",
|
defaultextension=u".der",
|
||||||
filetypes=[('Adobe Adept DER-encoded files', '.der'),
|
filetypes=[('Adobe Adept DER-encoded files', '.der'),
|
||||||
('All Files', '.*')])
|
('All Files', '.*')])
|
||||||
if keypath:
|
if keypath:
|
||||||
keypath = os.path.normpath(keypath)
|
keypath = os.path.normpath(keypath)
|
||||||
self.keypath.delete(0, tkinter.constants.END)
|
self.keypath.delete(0, Tkconstants.END)
|
||||||
self.keypath.insert(0, keypath)
|
self.keypath.insert(0, keypath)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_inpath(self):
|
def get_inpath(self):
|
||||||
inpath = tkinter.filedialog.askopenfilename(
|
inpath = tkFileDialog.askopenfilename(
|
||||||
parent=None, title="Select ADEPT-encrypted PDF file to decrypt",
|
parent=None, title=u"Select ADEPT-encrypted PDF file to decrypt",
|
||||||
defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
|
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
|
||||||
if inpath:
|
if inpath:
|
||||||
inpath = os.path.normpath(inpath)
|
inpath = os.path.normpath(inpath)
|
||||||
self.inpath.delete(0, tkinter.constants.END)
|
self.inpath.delete(0, Tkconstants.END)
|
||||||
self.inpath.insert(0, inpath)
|
self.inpath.insert(0, inpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_outpath(self):
|
def get_outpath(self):
|
||||||
outpath = tkinter.filedialog.asksaveasfilename(
|
outpath = tkFileDialog.asksaveasfilename(
|
||||||
parent=None, title="Select unencrypted PDF file to produce",
|
parent=None, title=u"Select unencrypted PDF file to produce",
|
||||||
defaultextension=".pdf", filetypes=[('PDF files', '.pdf')])
|
defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
|
||||||
if outpath:
|
if outpath:
|
||||||
outpath = os.path.normpath(outpath)
|
outpath = os.path.normpath(outpath)
|
||||||
self.outpath.delete(0, tkinter.constants.END)
|
self.outpath.delete(0, Tkconstants.END)
|
||||||
self.outpath.insert(0, outpath)
|
self.outpath.insert(0, outpath)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2286,42 +2294,42 @@ def gui_main():
|
|||||||
inpath = self.inpath.get()
|
inpath = self.inpath.get()
|
||||||
outpath = self.outpath.get()
|
outpath = self.outpath.get()
|
||||||
if not keypath or not os.path.exists(keypath):
|
if not keypath or not os.path.exists(keypath):
|
||||||
self.status['text'] = "Specified key file does not exist"
|
self.status['text'] = u"Specified key file does not exist"
|
||||||
return
|
return
|
||||||
if not inpath or not os.path.exists(inpath):
|
if not inpath or not os.path.exists(inpath):
|
||||||
self.status['text'] = "Specified input file does not exist"
|
self.status['text'] = u"Specified input file does not exist"
|
||||||
return
|
return
|
||||||
if not outpath:
|
if not outpath:
|
||||||
self.status['text'] = "Output file not specified"
|
self.status['text'] = u"Output file not specified"
|
||||||
return
|
return
|
||||||
if inpath == outpath:
|
if inpath == outpath:
|
||||||
self.status['text'] = "Must have different input and output files"
|
self.status['text'] = u"Must have different input and output files"
|
||||||
return
|
return
|
||||||
userkey = open(keypath,'rb').read()
|
userkey = open(keypath,'rb').read()
|
||||||
self.status['text'] = "Decrypting..."
|
self.status['text'] = u"Decrypting..."
|
||||||
try:
|
try:
|
||||||
decrypt_status = decryptBook(userkey, inpath, outpath)
|
decrypt_status = decryptBook(userkey, inpath, outpath)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
self.status['text'] = "Error; {0}".format(e.args[0])
|
self.status['text'] = u"Error; {0}".format(e.args[0])
|
||||||
return
|
return
|
||||||
if decrypt_status == 0:
|
if decrypt_status == 0:
|
||||||
self.status['text'] = "File successfully decrypted"
|
self.status['text'] = u"File successfully decrypted"
|
||||||
else:
|
else:
|
||||||
self.status['text'] = "The was an error decrypting the file."
|
self.status['text'] = u"The was an error decrypting the file."
|
||||||
|
|
||||||
|
|
||||||
root = tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
if RSA is None:
|
if RSA is None:
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
tkinter.messagebox.showerror(
|
tkMessageBox.showerror(
|
||||||
"INEPT PDF",
|
"INEPT PDF",
|
||||||
"This script requires OpenSSL or PyCrypto, which must be installed "
|
"This script requires OpenSSL or PyCrypto, which must be installed "
|
||||||
"separately. Read the top-of-script comment for details.")
|
"separately. Read the top-of-script comment for details.")
|
||||||
return 1
|
return 1
|
||||||
root.title("Adobe Adept PDF Decrypter v.{0}".format(__version__))
|
root.title(u"Adobe Adept PDF Decrypter v.{0}".format(__version__))
|
||||||
root.resizable(True, False)
|
root.resizable(True, False)
|
||||||
root.minsize(370, 0)
|
root.minsize(370, 0)
|
||||||
DecryptionDialog(root).pack(fill=tkinter.constants.X, expand=1)
|
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ion.py
|
# ion.py
|
||||||
# Copyright © 2013-2020 Apprentice Harper et al.
|
# Copyright © 2013-2020 Apprentice Harper et al.
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '3.0'
|
__version__ = '2.0'
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# Pascal implementation by lulzkabulz.
|
# Pascal implementation by lulzkabulz.
|
||||||
@@ -15,7 +17,7 @@ __version__ = '3.0'
|
|||||||
# 1.2 - Added pylzma import fallback
|
# 1.2 - Added pylzma import fallback
|
||||||
# 1.3 - Fixed lzma support for calibre 4.6+
|
# 1.3 - Fixed lzma support for calibre 4.6+
|
||||||
# 2.0 - VoucherEnvelope v2/v3 support by apprenticesakuya.
|
# 2.0 - VoucherEnvelope v2/v3 support by apprenticesakuya.
|
||||||
# 3.0 - Added Python 3 compatibility for calibre 5.0
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt Kindle KFX files.
|
Decrypt Kindle KFX files.
|
||||||
@@ -28,7 +30,10 @@ import os
|
|||||||
import os.path
|
import os.path
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from io import BytesIO
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
from Crypto.Util.py3compat import bchr, bord
|
from Crypto.Util.py3compat import bchr, bord
|
||||||
@@ -832,7 +837,7 @@ class DrmIonVoucher(object):
|
|||||||
b = aes.decrypt(self.ciphertext)
|
b = aes.decrypt(self.ciphertext)
|
||||||
b = pkcs7unpad(b, 16)
|
b = pkcs7unpad(b, 16)
|
||||||
|
|
||||||
self.drmkey = BinaryIonParser(BytesIO(b))
|
self.drmkey = BinaryIonParser(StringIO(b))
|
||||||
addprottable(self.drmkey)
|
addprottable(self.drmkey)
|
||||||
|
|
||||||
_assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
|
_assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
|
||||||
@@ -871,7 +876,7 @@ class DrmIonVoucher(object):
|
|||||||
self.envelope.next()
|
self.envelope.next()
|
||||||
field = self.envelope.getfieldname()
|
field = self.envelope.getfieldname()
|
||||||
if field == "voucher":
|
if field == "voucher":
|
||||||
self.voucher = BinaryIonParser(BytesIO(self.envelope.lobvalue()))
|
self.voucher = BinaryIonParser(StringIO(self.envelope.lobvalue()))
|
||||||
addprottable(self.voucher)
|
addprottable(self.voucher)
|
||||||
continue
|
continue
|
||||||
elif field != "strategy":
|
elif field != "strategy":
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
# k4mobidedrm.py
|
# k4mobidedrm.py
|
||||||
# Copyright © 2008-2020 by Apprentice Harper et al.
|
# Copyright © 2008-2019 by Apprentice Harper et al.
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '6.0'
|
__version__ = '5.7'
|
||||||
|
|
||||||
# Engine to remove drm from Kindle and Mobipocket ebooks
|
# Engine to remove drm from Kindle and Mobipocket ebooks
|
||||||
# for personal use for archiving and converting your ebooks
|
# for personal use for archiving and converting your ebooks
|
||||||
@@ -60,8 +62,6 @@ __version__ = '6.0'
|
|||||||
# 5.5 - Added GPL v3 licence explicitly.
|
# 5.5 - Added GPL v3 licence explicitly.
|
||||||
# 5.6 - Invoke KFXZipBook to handle zipped KFX files
|
# 5.6 - Invoke KFXZipBook to handle zipped KFX files
|
||||||
# 5.7 - Revamp cleanup_name
|
# 5.7 - Revamp cleanup_name
|
||||||
# 6.0 - Added Python 3 compatibility for calibre 5.0
|
|
||||||
|
|
||||||
|
|
||||||
import sys, os, re
|
import sys, os, re
|
||||||
import csv
|
import csv
|
||||||
@@ -69,7 +69,7 @@ import getopt
|
|||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
import time
|
import time
|
||||||
import html.entities
|
import htmlentitydefs
|
||||||
import json
|
import json
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
@@ -103,11 +103,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -142,13 +141,15 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["mobidedrm.py"]
|
return [u"mobidedrm.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
# cleanup unicode filenames
|
# cleanup unicode filenames
|
||||||
# borrowed from calibre from calibre/src/calibre/__init__.py
|
# borrowed from calibre from calibre/src/calibre/__init__.py
|
||||||
@@ -158,60 +159,60 @@ def unicode_argv():
|
|||||||
# and some improvements suggested by jhaisley
|
# and some improvements suggested by jhaisley
|
||||||
def cleanup_name(name):
|
def cleanup_name(name):
|
||||||
# substitute filename unfriendly characters
|
# substitute filename unfriendly characters
|
||||||
name = name.replace("<","[").replace(">","]").replace(" : "," – ").replace(": "," – ").replace(":","—").replace("/","_").replace("\\","_").replace("|","_").replace("\"","\'").replace("*","_").replace("?","")
|
name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"")
|
||||||
# white space to single space, delete leading and trailing while space
|
# white space to single space, delete leading and trailing while space
|
||||||
name = re.sub(r"\s", " ", name).strip()
|
name = re.sub(ur"\s", u" ", name).strip()
|
||||||
# delete control characters
|
# delete control characters
|
||||||
name = "".join(char for char in name if ord(char)>=32)
|
name = u"".join(char for char in name if ord(char)>=32)
|
||||||
# delete non-ascii characters
|
# delete non-ascii characters
|
||||||
name = "".join(char for char in name if ord(char)<=126)
|
name = u"".join(char for char in name if ord(char)<=126)
|
||||||
# remove leading dots
|
# remove leading dots
|
||||||
while len(name)>0 and name[0] == ".":
|
while len(name)>0 and name[0] == u".":
|
||||||
name = name[1:]
|
name = name[1:]
|
||||||
# remove trailing dots (Windows doesn't like them)
|
# remove trailing dots (Windows doesn't like them)
|
||||||
while name.endswith("."):
|
while name.endswith(u'.'):
|
||||||
name = name[:-1]
|
name = name[:-1]
|
||||||
if len(name)==0:
|
if len(name)==0:
|
||||||
name="DecryptedBook"
|
name=u"DecryptedBook"
|
||||||
return name
|
return name
|
||||||
|
|
||||||
# must be passed unicode
|
# must be passed unicode
|
||||||
def unescape(text):
|
def unescape(text):
|
||||||
def fixup(m):
|
def fixup(m):
|
||||||
text = m.group(0)
|
text = m.group(0)
|
||||||
if text[:2] == "&#":
|
if text[:2] == u"&#":
|
||||||
# character reference
|
# character reference
|
||||||
try:
|
try:
|
||||||
if text[:3] == "&#x":
|
if text[:3] == u"&#x":
|
||||||
return chr(int(text[3:-1], 16))
|
return unichr(int(text[3:-1], 16))
|
||||||
else:
|
else:
|
||||||
return chr(int(text[2:-1]))
|
return unichr(int(text[2:-1]))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# named entity
|
# named entity
|
||||||
try:
|
try:
|
||||||
text = chr(htmlentitydefs.name2codepoint[text[1:-1]])
|
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return text # leave as is
|
return text # leave as is
|
||||||
return re.sub("&#?\\w+;", fixup, text)
|
return re.sub(u"&#?\w+;", fixup, text)
|
||||||
|
|
||||||
def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime = time.time()):
|
def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime = time.time()):
|
||||||
# handle the obvious cases at the beginning
|
# handle the obvious cases at the beginning
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException("Input file does not exist.")
|
raise DrmException(u"Input file does not exist.")
|
||||||
|
|
||||||
mobi = True
|
mobi = True
|
||||||
magic8 = open(infile,'rb').read(8)
|
magic8 = open(infile,'rb').read(8)
|
||||||
if magic8 == b'\xeaDRMION\xee':
|
if magic8 == '\xeaDRMION\xee':
|
||||||
raise DrmException("The .kfx DRMION file cannot be decrypted by itself. A .kfx-zip archive containing a DRM voucher is required.")
|
raise DrmException(u"The .kfx DRMION file cannot be decrypted by itself. A .kfx-zip archive containing a DRM voucher is required.")
|
||||||
|
|
||||||
magic3 = magic8[:3]
|
magic3 = magic8[:3]
|
||||||
if magic3 == b'TPZ':
|
if magic3 == 'TPZ':
|
||||||
mobi = False
|
mobi = False
|
||||||
|
|
||||||
if magic8[:4] == b'PK\x03\x04':
|
if magic8[:4] == 'PK\x03\x04':
|
||||||
mb = kfxdedrm.KFXZipBook(infile)
|
mb = kfxdedrm.KFXZipBook(infile)
|
||||||
elif mobi:
|
elif mobi:
|
||||||
mb = mobidedrm.MobiBook(infile)
|
mb = mobidedrm.MobiBook(infile)
|
||||||
@@ -219,7 +220,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
|
|||||||
mb = topazextract.TopazBook(infile)
|
mb = topazextract.TopazBook(infile)
|
||||||
|
|
||||||
bookname = unescape(mb.getBookTitle())
|
bookname = unescape(mb.getBookTitle())
|
||||||
print("Decrypting {1} ebook: {0}".format(bookname, mb.getBookType()))
|
print u"Decrypting {1} ebook: {0}".format(bookname, mb.getBookType())
|
||||||
|
|
||||||
# copy list of pids
|
# copy list of pids
|
||||||
totalpids = list(pids)
|
totalpids = list(pids)
|
||||||
@@ -231,7 +232,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
|
|||||||
totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases))
|
totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases))
|
||||||
# remove any duplicates
|
# remove any duplicates
|
||||||
totalpids = list(set(totalpids))
|
totalpids = list(set(totalpids))
|
||||||
print("Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids)))
|
print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids))
|
||||||
#print totalpids
|
#print totalpids
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -240,7 +241,7 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
|
|||||||
mb.cleanup
|
mb.cleanup
|
||||||
raise
|
raise
|
||||||
|
|
||||||
print("Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime))
|
print u"Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime)
|
||||||
return mb
|
return mb
|
||||||
|
|
||||||
|
|
||||||
@@ -254,16 +255,16 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
|
|||||||
with open(dbfile, 'r') as keyfilein:
|
with open(dbfile, 'r') as keyfilein:
|
||||||
kindleDatabase = json.loads(keyfilein.read())
|
kindleDatabase = json.loads(keyfilein.read())
|
||||||
kDatabases.append([dbfile,kindleDatabase])
|
kDatabases.append([dbfile,kindleDatabase])
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
print("Error getting database from file {0:s}: {1:s}".format(dbfile,e))
|
print u"Error getting database from file {0:s}: {1:s}".format(dbfile,e)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime)
|
book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
print("Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime))
|
print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
@@ -274,7 +275,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
|
|||||||
re.match('^{0-9A-F-}{36}$', orig_fn_root)
|
re.match('^{0-9A-F-}{36}$', orig_fn_root)
|
||||||
): # Kindle for PC / Mac / Android / Fire / iOS
|
): # Kindle for PC / Mac / Android / Fire / iOS
|
||||||
clean_title = cleanup_name(book.getBookTitle())
|
clean_title = cleanup_name(book.getBookTitle())
|
||||||
outfilename = "{}_{}".format(orig_fn_root, clean_title)
|
outfilename = u'{}_{}'.format(orig_fn_root, clean_title)
|
||||||
else: # E Ink Kindle, which already uses a reasonable name
|
else: # E Ink Kindle, which already uses a reasonable name
|
||||||
outfilename = orig_fn_root
|
outfilename = orig_fn_root
|
||||||
|
|
||||||
@@ -282,16 +283,16 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
|
|||||||
if len(outfilename)>150:
|
if len(outfilename)>150:
|
||||||
outfilename = outfilename[:99]+"--"+outfilename[-49:]
|
outfilename = outfilename[:99]+"--"+outfilename[-49:]
|
||||||
|
|
||||||
outfilename = outfilename+"_nodrm"
|
outfilename = outfilename+u"_nodrm"
|
||||||
outfile = os.path.join(outdir, outfilename + book.getBookExtension())
|
outfile = os.path.join(outdir, outfilename + book.getBookExtension())
|
||||||
|
|
||||||
book.getFile(outfile)
|
book.getFile(outfile)
|
||||||
print("Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename))
|
print u"Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
|
||||||
|
|
||||||
if book.getBookType()=="Topaz":
|
if book.getBookType()==u"Topaz":
|
||||||
zipname = os.path.join(outdir, outfilename + "_SVG.zip")
|
zipname = os.path.join(outdir, outfilename + u"_SVG.zip")
|
||||||
book.getSVGZip(zipname)
|
book.getSVGZip(zipname)
|
||||||
print("Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename))
|
print u"Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
|
||||||
|
|
||||||
# remove internal temporary directory of Topaz pieces
|
# remove internal temporary directory of Topaz pieces
|
||||||
book.cleanup()
|
book.cleanup()
|
||||||
@@ -299,9 +300,9 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
|
|||||||
|
|
||||||
|
|
||||||
def usage(progname):
|
def usage(progname):
|
||||||
print("Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks")
|
print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks"
|
||||||
print("Usage:")
|
print u"Usage:"
|
||||||
print(" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml|backup.ab> ] <infile> <outdir>".format(progname))
|
print u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] [ -a <AmazonSecureStorage.xml|backup.ab> ] <infile> <outdir>".format(progname)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Main
|
# Main
|
||||||
@@ -309,12 +310,12 @@ def usage(progname):
|
|||||||
def cli_main():
|
def cli_main():
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
print("K4MobiDeDrm v{0}.\nCopyright © 2008-2020 Apprentice Harper et al.".format(__version__))
|
print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2017 Apprentice Harper et al.".format(__version__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "k:p:s:a:h")
|
opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError, err:
|
||||||
print("Error in options or arguments: {0}".format(err.args[0]))
|
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||||
usage(progname)
|
usage(progname)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
if len(args)<2:
|
if len(args)<2:
|
||||||
@@ -329,9 +330,6 @@ def cli_main():
|
|||||||
pids = []
|
pids = []
|
||||||
|
|
||||||
for o, a in opts:
|
for o, a in opts:
|
||||||
if o == "-h":
|
|
||||||
usage(progname)
|
|
||||||
sys.exit(0)
|
|
||||||
if o == "-k":
|
if o == "-k":
|
||||||
if a == None :
|
if a == None :
|
||||||
raise DrmException("Invalid parameter for -k")
|
raise DrmException("Invalid parameter for -k")
|
||||||
@@ -339,7 +337,7 @@ def cli_main():
|
|||||||
if o == "-p":
|
if o == "-p":
|
||||||
if a == None :
|
if a == None :
|
||||||
raise DrmException("Invalid parameter for -p")
|
raise DrmException("Invalid parameter for -p")
|
||||||
pids = a.encode('utf-8').split(b',')
|
pids = a.split(',')
|
||||||
if o == "-s":
|
if o == "-s":
|
||||||
if a == None :
|
if a == None :
|
||||||
raise DrmException("Invalid parameter for -s")
|
raise DrmException("Invalid parameter for -s")
|
||||||
@@ -349,6 +347,9 @@ def cli_main():
|
|||||||
raise DrmException("Invalid parameter for -a")
|
raise DrmException("Invalid parameter for -a")
|
||||||
androidFiles.append(a)
|
androidFiles.append(a)
|
||||||
|
|
||||||
|
# try with built in Kindle Info files if not on Linux
|
||||||
|
k4 = not sys.platform.startswith('linux')
|
||||||
|
|
||||||
return decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids)
|
return decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
# Engine to remove drm from Kindle KFX ebooks
|
# Engine to remove drm from Kindle KFX ebooks
|
||||||
|
|
||||||
# 2.0 - Python 3 for calibre 5.0
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from io import BytesIO
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
try:
|
||||||
|
from calibre_plugins.dedrm import ion
|
||||||
|
except ImportError:
|
||||||
|
import ion
|
||||||
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '2.0'
|
__version__ = '1.0'
|
||||||
|
|
||||||
|
|
||||||
class KFXZipBook:
|
class KFXZipBook:
|
||||||
@@ -27,10 +35,6 @@ class KFXZipBook:
|
|||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
def processBook(self, totalpids):
|
def processBook(self, totalpids):
|
||||||
try:
|
|
||||||
import ion
|
|
||||||
except:
|
|
||||||
from calibre_plugins.dedrm import ion
|
|
||||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
with zipfile.ZipFile(self.infile, 'r') as zf:
|
||||||
for filename in zf.namelist():
|
for filename in zf.namelist():
|
||||||
with zf.open(filename) as fh:
|
with zf.open(filename) as fh:
|
||||||
@@ -40,13 +44,13 @@ class KFXZipBook:
|
|||||||
data += fh.read()
|
data += fh.read()
|
||||||
if self.voucher is None:
|
if self.voucher is None:
|
||||||
self.decrypt_voucher(totalpids)
|
self.decrypt_voucher(totalpids)
|
||||||
print("Decrypting KFX DRMION: {0}".format(filename))
|
print(u'Decrypting KFX DRMION: {0}'.format(filename))
|
||||||
outfile = BytesIO()
|
outfile = StringIO()
|
||||||
ion.DrmIon(BytesIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
|
ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
|
||||||
self.decrypted[filename] = outfile.getvalue()
|
self.decrypted[filename] = outfile.getvalue()
|
||||||
|
|
||||||
if not self.decrypted:
|
if not self.decrypted:
|
||||||
print("The .kfx-zip archive does not contain an encrypted DRMION file")
|
print(u'The .kfx-zip archive does not contain an encrypted DRMION file')
|
||||||
|
|
||||||
def decrypt_voucher(self, totalpids):
|
def decrypt_voucher(self, totalpids):
|
||||||
with zipfile.ZipFile(self.infile, 'r') as zf:
|
with zipfile.ZipFile(self.infile, 'r') as zf:
|
||||||
@@ -60,9 +64,9 @@ class KFXZipBook:
|
|||||||
if 'ProtectedData' in data:
|
if 'ProtectedData' in data:
|
||||||
break # found DRM voucher
|
break # found DRM voucher
|
||||||
else:
|
else:
|
||||||
raise Exception("The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher")
|
raise Exception(u'The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher')
|
||||||
|
|
||||||
print("Decrypting KFX DRM voucher: {0}".format(info.filename))
|
print(u'Decrypting KFX DRM voucher: {0}'.format(info.filename))
|
||||||
|
|
||||||
for pid in [''] + totalpids:
|
for pid in [''] + totalpids:
|
||||||
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,0), (40,40)]:
|
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,0), (40,40)]:
|
||||||
@@ -72,20 +76,20 @@ class KFXZipBook:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
voucher = ion.DrmIonVoucher(BytesIO(data), pid[:dsn_len], pid[dsn_len:])
|
voucher = ion.DrmIonVoucher(StringIO(data), pid[:dsn_len], pid[dsn_len:])
|
||||||
voucher.parse()
|
voucher.parse()
|
||||||
voucher.decryptvoucher()
|
voucher.decryptvoucher()
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise Exception("Failed to decrypt KFX DRM voucher with any key")
|
raise Exception(u'Failed to decrypt KFX DRM voucher with any key')
|
||||||
|
|
||||||
print("KFX DRM voucher successfully decrypted")
|
print(u'KFX DRM voucher successfully decrypted')
|
||||||
|
|
||||||
license_type = voucher.getlicensetype()
|
license_type = voucher.getlicensetype()
|
||||||
if license_type != "Purchase":
|
if license_type != "Purchase":
|
||||||
raise Exception(("This book is licensed as {0}. "
|
raise Exception((u'This book is licensed as {0}. '
|
||||||
'These tools are intended for use on purchased books.').format(license_type))
|
'These tools are intended for use on purchased books.').format(license_type))
|
||||||
|
|
||||||
self.voucher = voucher
|
self.voucher = voucher
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
# kgenpids.py
|
# kgenpids.py
|
||||||
# Copyright © 2008-2020 Apprentice Harper et al.
|
# Copyright © 2008-2017 Apprentice Harper et al.
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '3.0'
|
__version__ = '2.1'
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 2.0 - Fix for non-ascii Windows user names
|
# 2.0 - Fix for non-ascii Windows user names
|
||||||
# 2.1 - Actual fix for non-ascii WIndows user names.
|
# 2.1 - Actual fix for non-ascii WIndows user names.
|
||||||
# 2.2 - Return information needed for KFX decryption
|
# x.x - Return information needed for KFX decryption
|
||||||
# 3.0 - Python 3 for calibre 5.0
|
|
||||||
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os, csv
|
import os, csv
|
||||||
@@ -30,9 +31,9 @@ global charMap3
|
|||||||
global charMap4
|
global charMap4
|
||||||
|
|
||||||
|
|
||||||
charMap1 = b'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
||||||
charMap3 = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
charMap3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||||
charMap4 = b'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
charMap4 = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||||
|
|
||||||
# crypto digestroutines
|
# crypto digestroutines
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -49,15 +50,14 @@ def SHA1(message):
|
|||||||
|
|
||||||
|
|
||||||
# Encode the bytes in data with the characters in map
|
# Encode the bytes in data with the characters in map
|
||||||
# data and map should be byte arrays
|
|
||||||
def encode(data, map):
|
def encode(data, map):
|
||||||
result = b''
|
result = ''
|
||||||
for char in data:
|
for char in data:
|
||||||
value = char
|
value = ord(char)
|
||||||
Q = (value ^ 0x80) // len(map)
|
Q = (value ^ 0x80) // len(map)
|
||||||
R = value % len(map)
|
R = value % len(map)
|
||||||
result += bytes([map[Q]])
|
result += map[Q]
|
||||||
result += bytes([map[R]])
|
result += map[R]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Hash the bytes in data and then encode the digest with the characters in map
|
# Hash the bytes in data and then encode the digest with the characters in map
|
||||||
@@ -84,7 +84,7 @@ def decode(data,map):
|
|||||||
def getTwoBitsFromBitField(bitField,offset):
|
def getTwoBitsFromBitField(bitField,offset):
|
||||||
byteNumber = offset // 4
|
byteNumber = offset // 4
|
||||||
bitPosition = 6 - 2*(offset % 4)
|
bitPosition = 6 - 2*(offset % 4)
|
||||||
return bitField[byteNumber] >> bitPosition & 3
|
return ord(bitField[byteNumber]) >> bitPosition & 3
|
||||||
|
|
||||||
# Returns the six bits at offset from a bit field
|
# Returns the six bits at offset from a bit field
|
||||||
def getSixBitsFromBitField(bitField,offset):
|
def getSixBitsFromBitField(bitField,offset):
|
||||||
@@ -95,9 +95,9 @@ def getSixBitsFromBitField(bitField,offset):
|
|||||||
# 8 bits to six bits encoding from hash to generate PID string
|
# 8 bits to six bits encoding from hash to generate PID string
|
||||||
def encodePID(hash):
|
def encodePID(hash):
|
||||||
global charMap3
|
global charMap3
|
||||||
PID = b''
|
PID = ''
|
||||||
for position in range (0,8):
|
for position in range (0,8):
|
||||||
PID += bytes([charMap3[getSixBitsFromBitField(hash,position)]])
|
PID += charMap3[getSixBitsFromBitField(hash,position)]
|
||||||
return PID
|
return PID
|
||||||
|
|
||||||
# Encryption table used to generate the device PID
|
# Encryption table used to generate the device PID
|
||||||
@@ -118,7 +118,7 @@ def generatePidEncryptionTable() :
|
|||||||
def generatePidSeed(table,dsn) :
|
def generatePidSeed(table,dsn) :
|
||||||
value = 0
|
value = 0
|
||||||
for counter in range (0,4) :
|
for counter in range (0,4) :
|
||||||
index = (dsn[counter] ^ value) & 0xFF
|
index = (ord(dsn[counter]) ^ value) &0xFF
|
||||||
value = (value >> 8) ^ table[index]
|
value = (value >> 8) ^ table[index]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -126,15 +126,15 @@ def generatePidSeed(table,dsn) :
|
|||||||
def generateDevicePID(table,dsn,nbRoll):
|
def generateDevicePID(table,dsn,nbRoll):
|
||||||
global charMap4
|
global charMap4
|
||||||
seed = generatePidSeed(table,dsn)
|
seed = generatePidSeed(table,dsn)
|
||||||
pidAscii = b''
|
pidAscii = ''
|
||||||
pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
|
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
|
index = 0
|
||||||
for counter in range (0,nbRoll):
|
for counter in range (0,nbRoll):
|
||||||
pid[index] = pid[index] ^ dsn[counter]
|
pid[index] = pid[index] ^ ord(dsn[counter])
|
||||||
index = (index+1) %8
|
index = (index+1) %8
|
||||||
for counter in range (0,8):
|
for counter in range (0,8):
|
||||||
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
|
||||||
pidAscii += bytes([charMap4[index]])
|
pidAscii += charMap4[index]
|
||||||
return pidAscii
|
return pidAscii
|
||||||
|
|
||||||
def crc32(s):
|
def crc32(s):
|
||||||
@@ -150,7 +150,7 @@ def checksumPid(s):
|
|||||||
for i in (0,1):
|
for i in (0,1):
|
||||||
b = crc & 0xff
|
b = crc & 0xff
|
||||||
pos = (b // l) ^ (b % l)
|
pos = (b // l) ^ (b % l)
|
||||||
res += bytes([charMap4[pos%l]])
|
res += charMap4[pos%l]
|
||||||
crc >>= 8
|
crc >>= 8
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@@ -160,15 +160,15 @@ def pidFromSerial(s, l):
|
|||||||
global charMap4
|
global charMap4
|
||||||
crc = crc32(s)
|
crc = crc32(s)
|
||||||
arr1 = [0]*l
|
arr1 = [0]*l
|
||||||
for i in range(len(s)):
|
for i in xrange(len(s)):
|
||||||
arr1[i%l] ^= s[i]
|
arr1[i%l] ^= ord(s[i])
|
||||||
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
||||||
for i in range(l):
|
for i in xrange(l):
|
||||||
arr1[i] ^= crc_bytes[i&3]
|
arr1[i] ^= crc_bytes[i&3]
|
||||||
pid = b""
|
pid = ""
|
||||||
for i in range(l):
|
for i in xrange(l):
|
||||||
b = arr1[i] & 0xff
|
b = arr1[i] & 0xff
|
||||||
pid += bytes([charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]])
|
pid+=charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
|
||||||
return pid
|
return pid
|
||||||
|
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ def getKindlePids(rec209, token, serialnum):
|
|||||||
|
|
||||||
pids=[]
|
pids=[]
|
||||||
|
|
||||||
if isinstance(serialnum,str):
|
if isinstance(serialnum,unicode):
|
||||||
serialnum = serialnum.encode('utf-8')
|
serialnum = serialnum.encode('utf-8')
|
||||||
|
|
||||||
# Compute book PID
|
# Compute book PID
|
||||||
@@ -189,7 +189,7 @@ def getKindlePids(rec209, token, serialnum):
|
|||||||
pids.append(bookPID)
|
pids.append(bookPID)
|
||||||
|
|
||||||
# compute fixed pid for old pre 2.5 firmware update pid as well
|
# compute fixed pid for old pre 2.5 firmware update pid as well
|
||||||
kindlePID = pidFromSerial(serialnum, 7) + b"*"
|
kindlePID = pidFromSerial(serialnum, 7) + "*"
|
||||||
kindlePID = checksumPid(kindlePID)
|
kindlePID = checksumPid(kindlePID)
|
||||||
pids.append(kindlePID)
|
pids.append(kindlePID)
|
||||||
|
|
||||||
@@ -206,7 +206,7 @@ def getK4Pids(rec209, token, kindleDatabase):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the kindle account token, if present
|
# Get the kindle account token, if present
|
||||||
kindleAccountToken = bytearray.fromhex((kindleDatabase[1])['kindle.account.tokens'])
|
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
kindleAccountToken=""
|
kindleAccountToken=""
|
||||||
@@ -214,44 +214,44 @@ def getK4Pids(rec209, token, kindleDatabase):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the DSN token, if present
|
# Get the DSN token, if present
|
||||||
DSN = bytearray.fromhex((kindleDatabase[1])['DSN'])
|
DSN = (kindleDatabase[1])['DSN'].decode('hex')
|
||||||
print("Got DSN key from database {0}".format(kindleDatabase[0]))
|
print(u"Got DSN key from database {0}".format(kindleDatabase[0]))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# See if we have the info to generate the DSN
|
# See if we have the info to generate the DSN
|
||||||
try:
|
try:
|
||||||
# Get the Mazama Random number
|
# Get the Mazama Random number
|
||||||
MazamaRandomNumber = bytearray.fromhex((kindleDatabase[1])['MazamaRandomNumber'])
|
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||||
#print "Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
|
#print u"Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the SerialNumber token, if present
|
# Get the SerialNumber token, if present
|
||||||
IDString = bytearray.fromhex((kindleDatabase[1])['SerialNumber'])
|
IDString = (kindleDatabase[1])['SerialNumber'].decode('hex')
|
||||||
print("Got SerialNumber from database {0}".format(kindleDatabase[0]))
|
print(u"Got SerialNumber from database {0}".format(kindleDatabase[0]))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Get the IDString we added
|
# Get the IDString we added
|
||||||
IDString = bytearray.fromhex((kindleDatabase[1])['IDString'])
|
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get the UsernameHash token, if present
|
# Get the UsernameHash token, if present
|
||||||
encodedUsername = bytearray.fromhex((kindleDatabase[1])['UsernameHash'])
|
encodedUsername = (kindleDatabase[1])['UsernameHash'].decode('hex')
|
||||||
print("Got UsernameHash from database {0}".format(kindleDatabase[0]))
|
print(u"Got UsernameHash from database {0}".format(kindleDatabase[0]))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Get the UserName we added
|
# Get the UserName we added
|
||||||
UserName = bytearray.fromhex((kindleDatabase[1])['UserName'])
|
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||||
# encode it
|
# encode it
|
||||||
encodedUsername = encodeHash(UserName,charMap1)
|
encodedUsername = encodeHash(UserName,charMap1)
|
||||||
#print "encodedUsername",encodedUsername.encode('hex')
|
#print u"encodedUsername",encodedUsername.encode('hex')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print("Keys not found in the database {0}.".format(kindleDatabase[0]))
|
print(u"Keys not found in the database {0}.".format(kindleDatabase[0]))
|
||||||
return pids
|
return pids
|
||||||
|
|
||||||
# Get the ID string used
|
# Get the ID string used
|
||||||
encodedIDString = encodeHash(IDString,charMap1)
|
encodedIDString = encodeHash(IDString,charMap1)
|
||||||
#print "encodedIDString",encodedIDString.encode('hex')
|
#print u"encodedIDString",encodedIDString.encode('hex')
|
||||||
|
|
||||||
# concat, hash and encode to calculate the DSN
|
# concat, hash and encode to calculate the DSN
|
||||||
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
|
||||||
#print "DSN",DSN.encode('hex')
|
#print u"DSN",DSN.encode('hex')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if rec209 is None:
|
if rec209 is None:
|
||||||
@@ -297,15 +297,15 @@ def getPidList(md1, md2, serials=[], kDatabases=[]):
|
|||||||
for kDatabase in kDatabases:
|
for kDatabase in kDatabases:
|
||||||
try:
|
try:
|
||||||
pidlst.extend(getK4Pids(md1, md2, kDatabase))
|
pidlst.extend(getK4Pids(md1, md2, kDatabase))
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
print("Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0]))
|
print(u"Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0]))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
for serialnum in serials:
|
for serialnum in serials:
|
||||||
try:
|
try:
|
||||||
pidlst.extend(getKindlePids(md1, md2, serialnum))
|
pidlst.extend(getKindlePids(md1, md2, serialnum))
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
print("Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0]))
|
print(u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0]))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
return pidlst
|
return pidlst
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
# kindlekey.py
|
# kindlekey.py
|
||||||
# Copyright © 2008-2020 Apprentice Harper et al.
|
# Copyright © 2008-2020 Apprentice Harper et al.
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '3.0'
|
__version__ = '2.8'
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||||
@@ -28,7 +30,7 @@ __version__ = '3.0'
|
|||||||
# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
|
# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
|
||||||
# 2.6 - Start adding support for Kindle 1.25+ .kinf2018 file
|
# 2.6 - Start adding support for Kindle 1.25+ .kinf2018 file
|
||||||
# 2.7 - Finish .kinf2018 support, PC & Mac by Apprentice Sakuya
|
# 2.7 - Finish .kinf2018 support, PC & Mac by Apprentice Sakuya
|
||||||
# 3.0 - Python 3 for calibre 5.0
|
# 2.8 - Fix for Mac OS X Big Sur
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -36,11 +38,9 @@ Retrieve Kindle for PC/Mac user key.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, os, re
|
import sys, os, re
|
||||||
import codecs
|
from struct import pack, unpack
|
||||||
from struct import pack, unpack, unpack_from
|
|
||||||
import json
|
import json
|
||||||
import getopt
|
import getopt
|
||||||
import traceback
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
RegError
|
RegError
|
||||||
@@ -60,11 +60,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -102,13 +101,15 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["kindlekey.py"]
|
return [u"kindlekey.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -155,15 +156,14 @@ def primes(n):
|
|||||||
return primeList
|
return primeList
|
||||||
|
|
||||||
# Encode the bytes in data with the characters in map
|
# Encode the bytes in data with the characters in map
|
||||||
# data and map should be byte arrays
|
|
||||||
def encode(data, map):
|
def encode(data, map):
|
||||||
result = b''
|
result = ''
|
||||||
for char in data:
|
for char in data:
|
||||||
value = char
|
value = ord(char)
|
||||||
Q = (value ^ 0x80) // len(map)
|
Q = (value ^ 0x80) // len(map)
|
||||||
R = value % len(map)
|
R = value % len(map)
|
||||||
result += bytes([map[Q]])
|
result += map[Q]
|
||||||
result += bytes([map[R]])
|
result += map[R]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Hash the bytes in data and then encode the digest with the characters in map
|
# Hash the bytes in data and then encode the digest with the characters in map
|
||||||
@@ -172,7 +172,7 @@ def encodeHash(data,map):
|
|||||||
|
|
||||||
# Decode the string in data with the characters in map. Returns the decoded bytes
|
# Decode the string in data with the characters in map. Returns the decoded bytes
|
||||||
def decode(data,map):
|
def decode(data,map):
|
||||||
result = b''
|
result = ''
|
||||||
for i in range (0,len(data)-1,2):
|
for i in range (0,len(data)-1,2):
|
||||||
high = map.find(data[i])
|
high = map.find(data[i])
|
||||||
low = map.find(data[i+1])
|
low = map.find(data[i+1])
|
||||||
@@ -188,7 +188,7 @@ if iswindows:
|
|||||||
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
||||||
string_at, Structure, c_void_p, cast
|
string_at, Structure, c_void_p, cast
|
||||||
|
|
||||||
import winreg
|
import _winreg as winreg
|
||||||
MAX_PATH = 255
|
MAX_PATH = 255
|
||||||
kernel32 = windll.kernel32
|
kernel32 = windll.kernel32
|
||||||
advapi32 = windll.advapi32
|
advapi32 = windll.advapi32
|
||||||
@@ -232,12 +232,20 @@ if iswindows:
|
|||||||
class DecryptNotBlockAlignedError(DecryptError):
|
class DecryptNotBlockAlignedError(DecryptError):
|
||||||
""" Error in decryption processing """
|
""" Error in decryption processing """
|
||||||
|
|
||||||
|
def xorS(a,b):
|
||||||
|
""" XOR two strings """
|
||||||
|
assert len(a)==len(b)
|
||||||
|
x = []
|
||||||
|
for i in range(len(a)):
|
||||||
|
x.append( chr(ord(a[i])^ord(b[i])))
|
||||||
|
return ''.join(x)
|
||||||
|
|
||||||
def xor(a,b):
|
def xor(a,b):
|
||||||
""" XOR two byte arrays, to lesser length """
|
""" XOR two strings """
|
||||||
x = []
|
x = []
|
||||||
for i in range(min(len(a),len(b))):
|
for i in range(min(len(a),len(b))):
|
||||||
x.append( a[i] ^ b[i])
|
x.append( chr(ord(a[i])^ord(b[i])))
|
||||||
return bytes(x)
|
return ''.join(x)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Base 'BlockCipher' and Pad classes for cipher instances.
|
Base 'BlockCipher' and Pad classes for cipher instances.
|
||||||
@@ -256,10 +264,10 @@ if iswindows:
|
|||||||
self.resetDecrypt()
|
self.resetDecrypt()
|
||||||
def resetEncrypt(self):
|
def resetEncrypt(self):
|
||||||
self.encryptBlockCount = 0
|
self.encryptBlockCount = 0
|
||||||
self.bytesToEncrypt = b''
|
self.bytesToEncrypt = ''
|
||||||
def resetDecrypt(self):
|
def resetDecrypt(self):
|
||||||
self.decryptBlockCount = 0
|
self.decryptBlockCount = 0
|
||||||
self.bytesToDecrypt = b''
|
self.bytesToDecrypt = ''
|
||||||
|
|
||||||
def encrypt(self, plainText, more = None):
|
def encrypt(self, plainText, more = None):
|
||||||
""" Encrypt a string and return a binary string """
|
""" Encrypt a string and return a binary string """
|
||||||
@@ -292,14 +300,14 @@ if iswindows:
|
|||||||
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
|
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
|
||||||
if more == None: # no more calls to decrypt, should have all the data
|
if more == None: # no more calls to decrypt, should have all the data
|
||||||
if numExtraBytes != 0:
|
if numExtraBytes != 0:
|
||||||
raise DecryptNotBlockAlignedError('Data not block aligned on decrypt')
|
raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt'
|
||||||
|
|
||||||
# hold back some bytes in case last decrypt has zero len
|
# hold back some bytes in case last decrypt has zero len
|
||||||
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
|
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
|
||||||
numBlocks -= 1
|
numBlocks -= 1
|
||||||
numExtraBytes = self.blockSize
|
numExtraBytes = self.blockSize
|
||||||
|
|
||||||
plainText = b''
|
plainText = ''
|
||||||
for i in range(numBlocks):
|
for i in range(numBlocks):
|
||||||
bStart = i*self.blockSize
|
bStart = i*self.blockSize
|
||||||
ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize])
|
ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize])
|
||||||
@@ -334,7 +342,7 @@ if iswindows:
|
|||||||
def removePad(self, paddedBinaryString, blockSize):
|
def removePad(self, paddedBinaryString, blockSize):
|
||||||
""" Remove padding from a binary string """
|
""" Remove padding from a binary string """
|
||||||
if not(0<len(paddedBinaryString)):
|
if not(0<len(paddedBinaryString)):
|
||||||
raise DecryptNotBlockAlignedError('Expected More Data')
|
raise DecryptNotBlockAlignedError, 'Expected More Data'
|
||||||
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
|
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
|
||||||
|
|
||||||
class noPadding(Pad):
|
class noPadding(Pad):
|
||||||
@@ -364,11 +372,11 @@ if iswindows:
|
|||||||
self.blockSize = blockSize # blockSize is in bytes
|
self.blockSize = blockSize # blockSize is in bytes
|
||||||
self.padding = padding # change default to noPadding() to get normal ECB behavior
|
self.padding = padding # change default to noPadding() to get normal ECB behavior
|
||||||
|
|
||||||
assert( keySize%4==0 and (keySize//4) in NrTable[4]),'key size must be 16,20,24,29 or 32 bytes'
|
assert( keySize%4==0 and NrTable[4].has_key(keySize/4)),'key size must be 16,20,24,29 or 32 bytes'
|
||||||
assert( blockSize%4==0 and (blockSize//4) in NrTable), 'block size must be 16,20,24,29 or 32 bytes'
|
assert( blockSize%4==0 and NrTable.has_key(blockSize/4)), 'block size must be 16,20,24,29 or 32 bytes'
|
||||||
|
|
||||||
self.Nb = self.blockSize//4 # Nb is number of columns of 32 bit words
|
self.Nb = self.blockSize/4 # Nb is number of columns of 32 bit words
|
||||||
self.Nk = keySize//4 # Nk is the key length in 32-bit words
|
self.Nk = keySize/4 # Nk is the key length in 32-bit words
|
||||||
self.Nr = NrTable[self.Nb][self.Nk] # The number of rounds (Nr) is a function of
|
self.Nr = NrTable[self.Nb][self.Nk] # The number of rounds (Nr) is a function of
|
||||||
# the block (Nb) and key (Nk) sizes.
|
# the block (Nb) and key (Nk) sizes.
|
||||||
if key != None:
|
if key != None:
|
||||||
@@ -412,15 +420,15 @@ if iswindows:
|
|||||||
def _toBlock(self, bs):
|
def _toBlock(self, bs):
|
||||||
""" Convert binary string to array of bytes, state[col][row]"""
|
""" Convert binary string to array of bytes, state[col][row]"""
|
||||||
assert ( len(bs) == 4*self.Nb ), 'Rijndarl blocks must be of size blockSize'
|
assert ( len(bs) == 4*self.Nb ), 'Rijndarl blocks must be of size blockSize'
|
||||||
return [[bs[4*i],bs[4*i+1],bs[4*i+2],bs[4*i+3]] for i in range(self.Nb)]
|
return [[ord(bs[4*i]),ord(bs[4*i+1]),ord(bs[4*i+2]),ord(bs[4*i+3])] for i in range(self.Nb)]
|
||||||
|
|
||||||
def _toBString(self, block):
|
def _toBString(self, block):
|
||||||
""" Convert block (array of bytes) to binary string """
|
""" Convert block (array of bytes) to binary string """
|
||||||
l = []
|
l = []
|
||||||
for col in block:
|
for col in block:
|
||||||
for rowElement in col:
|
for rowElement in col:
|
||||||
l.append(rowElement)
|
l.append(chr(rowElement))
|
||||||
return bytes(l)
|
return ''.join(l)
|
||||||
#-------------------------------------
|
#-------------------------------------
|
||||||
""" Number of rounds Nr = NrTable[Nb][Nk]
|
""" Number of rounds Nr = NrTable[Nb][Nk]
|
||||||
|
|
||||||
@@ -432,16 +440,17 @@ if iswindows:
|
|||||||
7: {4:13, 5:13, 6:13, 7:13, 8:14},
|
7: {4:13, 5:13, 6:13, 7:13, 8:14},
|
||||||
8: {4:14, 5:14, 6:14, 7:14, 8:14}}
|
8: {4:14, 5:14, 6:14, 7:14, 8:14}}
|
||||||
#-------------------------------------
|
#-------------------------------------
|
||||||
def keyExpansion(algInstance, keyArray):
|
def keyExpansion(algInstance, keyString):
|
||||||
""" Expand a byte array of size keySize into a larger array """
|
""" Expand a string of size keySize into a larger array """
|
||||||
Nk, Nb, Nr = algInstance.Nk, algInstance.Nb, algInstance.Nr # for readability
|
Nk, Nb, Nr = algInstance.Nk, algInstance.Nb, algInstance.Nr # for readability
|
||||||
w = [[keyArray[4*i],keyArray[4*i+1],keyArray[4*i+2],keyArray[4*i+3]] for i in range(Nk)]
|
key = [ord(byte) for byte in keyString] # convert string to list
|
||||||
|
w = [[key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]] for i in range(Nk)]
|
||||||
for i in range(Nk,Nb*(Nr+1)):
|
for i in range(Nk,Nb*(Nr+1)):
|
||||||
temp = w[i-1] # a four byte column
|
temp = w[i-1] # a four byte column
|
||||||
if (i%Nk) == 0 :
|
if (i%Nk) == 0 :
|
||||||
temp = temp[1:]+[temp[0]] # RotWord(temp)
|
temp = temp[1:]+[temp[0]] # RotWord(temp)
|
||||||
temp = [ Sbox[byte] for byte in temp ]
|
temp = [ Sbox[byte] for byte in temp ]
|
||||||
temp[0] ^= Rcon[i//Nk]
|
temp[0] ^= Rcon[i/Nk]
|
||||||
elif Nk > 6 and i%Nk == 4 :
|
elif Nk > 6 and i%Nk == 4 :
|
||||||
temp = [ Sbox[byte] for byte in temp ] # SubWord(temp)
|
temp = [ Sbox[byte] for byte in temp ] # SubWord(temp)
|
||||||
w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] )
|
w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] )
|
||||||
@@ -641,7 +650,7 @@ if iswindows:
|
|||||||
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
|
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
|
||||||
""" Initialize AES, keySize is in bytes """
|
""" Initialize AES, keySize is in bytes """
|
||||||
if not (keySize == 16 or keySize == 24 or keySize == 32) :
|
if not (keySize == 16 or keySize == 24 or keySize == 32) :
|
||||||
raise BadKeySizeError('Illegal AES key size, must be 16, 24, or 32 bytes')
|
raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes'
|
||||||
|
|
||||||
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
|
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
|
||||||
|
|
||||||
@@ -733,7 +742,7 @@ if iswindows:
|
|||||||
if self.decryptBlockCount == 0: # first call, process IV
|
if self.decryptBlockCount == 0: # first call, process IV
|
||||||
if self.iv == None: # auto decrypt IV?
|
if self.iv == None: # auto decrypt IV?
|
||||||
self.prior_CT_block = encryptedBlock
|
self.prior_CT_block = encryptedBlock
|
||||||
return b''
|
return ''
|
||||||
else:
|
else:
|
||||||
assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption"
|
assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption"
|
||||||
self.prior_CT_block = self.iv
|
self.prior_CT_block = self.iv
|
||||||
@@ -781,10 +790,10 @@ if iswindows:
|
|||||||
# [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
# [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
||||||
def pbkdf2(self, passwd, salt, iter, keylen):
|
def pbkdf2(self, passwd, salt, iter, keylen):
|
||||||
|
|
||||||
def xorbytes( a, b ):
|
def xorstr( a, b ):
|
||||||
if len(a) != len(b):
|
if len(a) != len(b):
|
||||||
raise Exception("xorbytes(): lengths differ")
|
raise Exception("xorstr(): lengths differ")
|
||||||
return bytes([x ^ y for x, y in zip(a, b)])
|
return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
|
||||||
|
|
||||||
def prf( h, data ):
|
def prf( h, data ):
|
||||||
hm = h.copy()
|
hm = h.copy()
|
||||||
@@ -796,24 +805,24 @@ if iswindows:
|
|||||||
T = U
|
T = U
|
||||||
for i in range(2, itercount+1):
|
for i in range(2, itercount+1):
|
||||||
U = prf( h, U )
|
U = prf( h, U )
|
||||||
T = xorbytes( T, U )
|
T = xorstr( T, U )
|
||||||
return T
|
return T
|
||||||
|
|
||||||
sha = hashlib.sha1
|
sha = hashlib.sha1
|
||||||
digest_size = sha().digest_size
|
digest_size = sha().digest_size
|
||||||
# l - number of output blocks to produce
|
# l - number of output blocks to produce
|
||||||
l = keylen // digest_size
|
l = keylen / digest_size
|
||||||
if keylen % digest_size != 0:
|
if keylen % digest_size != 0:
|
||||||
l += 1
|
l += 1
|
||||||
h = hmac.new( passwd, None, sha )
|
h = hmac.new( passwd, None, sha )
|
||||||
T = b""
|
T = ""
|
||||||
for i in range(1, l+1):
|
for i in range(1, l+1):
|
||||||
T += pbkdf2_F( h, salt, iter, i )
|
T += pbkdf2_F( h, salt, iter, i )
|
||||||
return T[0: keylen]
|
return T[0: keylen]
|
||||||
|
|
||||||
def UnprotectHeaderData(encryptedData):
|
def UnprotectHeaderData(encryptedData):
|
||||||
passwdData = b'header_key_data'
|
passwdData = 'header_key_data'
|
||||||
salt = b'HEADER.2011'
|
salt = 'HEADER.2011'
|
||||||
iter = 0x80
|
iter = 0x80
|
||||||
keylen = 0x100
|
keylen = 0x100
|
||||||
key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen)
|
key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen)
|
||||||
@@ -826,12 +835,12 @@ if iswindows:
|
|||||||
|
|
||||||
# Various character maps used to decrypt kindle info values.
|
# Various character maps used to decrypt kindle info values.
|
||||||
# Probably supposed to act as obfuscation
|
# Probably supposed to act as obfuscation
|
||||||
charMap2 = b"AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
||||||
charMap5 = b"AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
|
||||||
# New maps in K4PC 1.9.0
|
# New maps in K4PC 1.9.0
|
||||||
testMap1 = b"n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
|
||||||
testMap6 = b"9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
|
testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
|
||||||
testMap8 = b"YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
|
testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
|
||||||
|
|
||||||
# interface with Windows OS Routines
|
# interface with Windows OS Routines
|
||||||
class DataBlob(Structure):
|
class DataBlob(Structure):
|
||||||
@@ -895,12 +904,12 @@ if iswindows:
|
|||||||
size.value = len(buffer)
|
size.value = len(buffer)
|
||||||
|
|
||||||
# replace any non-ASCII values with 0xfffd
|
# replace any non-ASCII values with 0xfffd
|
||||||
for i in range(0,len(buffer)):
|
for i in xrange(0,len(buffer)):
|
||||||
if buffer[i]>"\u007f":
|
if buffer[i]>u"\u007f":
|
||||||
#print "swapping char "+str(i)+" ("+buffer[i]+")"
|
#print u"swapping char "+str(i)+" ("+buffer[i]+")"
|
||||||
buffer[i] = "\ufffd"
|
buffer[i] = u"\ufffd"
|
||||||
# return utf-8 encoding of modified username
|
# return utf-8 encoding of modified username
|
||||||
#print "modified username:"+buffer.value
|
#print u"modified username:"+buffer.value
|
||||||
return buffer.value.encode('utf-8')
|
return buffer.value.encode('utf-8')
|
||||||
return GetUserName
|
return GetUserName
|
||||||
GetUserName = GetUserName()
|
GetUserName = GetUserName()
|
||||||
@@ -919,19 +928,19 @@ if iswindows:
|
|||||||
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
|
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
|
||||||
None, None, flags, byref(outdata)):
|
None, None, flags, byref(outdata)):
|
||||||
# raise DrmException("Failed to Unprotect Data")
|
# raise DrmException("Failed to Unprotect Data")
|
||||||
return b'failed'
|
return 'failed'
|
||||||
return string_at(outdata.pbData, outdata.cbData)
|
return string_at(outdata.pbData, outdata.cbData)
|
||||||
return CryptUnprotectData
|
return CryptUnprotectData
|
||||||
CryptUnprotectData = CryptUnprotectData()
|
CryptUnprotectData = CryptUnprotectData()
|
||||||
|
|
||||||
# Returns Environmental Variables that contain unicode
|
# Returns Environmental Variables that contain unicode
|
||||||
# name must be unicode string, not byte string.
|
|
||||||
def getEnvironmentVariable(name):
|
def getEnvironmentVariable(name):
|
||||||
import ctypes
|
import ctypes
|
||||||
|
name = unicode(name) # make sure string argument is unicode
|
||||||
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
|
n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
|
||||||
if n == 0:
|
if n == 0:
|
||||||
return None
|
return None
|
||||||
buf = ctypes.create_unicode_buffer("\0"*n)
|
buf = ctypes.create_unicode_buffer(u'\0'*n)
|
||||||
ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n)
|
ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n)
|
||||||
return buf.value
|
return buf.value
|
||||||
|
|
||||||
@@ -943,7 +952,7 @@ if iswindows:
|
|||||||
path = ""
|
path = ""
|
||||||
if 'LOCALAPPDATA' in os.environ.keys():
|
if 'LOCALAPPDATA' in os.environ.keys():
|
||||||
# Python 2.x does not return unicode env. Use Python 3.x
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
path = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||||
# this is just another alternative.
|
# this is just another alternative.
|
||||||
# path = getEnvironmentVariable('LOCALAPPDATA')
|
# path = getEnvironmentVariable('LOCALAPPDATA')
|
||||||
if not os.path.isdir(path):
|
if not os.path.isdir(path):
|
||||||
@@ -971,20 +980,20 @@ if iswindows:
|
|||||||
print ('Could not find the folder in which to look for kinfoFiles.')
|
print ('Could not find the folder in which to look for kinfoFiles.')
|
||||||
else:
|
else:
|
||||||
# Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
|
# Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
|
||||||
print("searching for kinfoFiles in " + path)
|
print(u'searching for kinfoFiles in ' + path.encode('ascii', 'ignore'))
|
||||||
|
|
||||||
# look for (K4PC 1.25.1 and later) .kinf2018 file
|
# look for (K4PC 1.25.1 and later) .kinf2018 file
|
||||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2018'
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2018'
|
||||||
if os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
found = True
|
found = True
|
||||||
print('Found K4PC 1.25+ kinf2018 file: ' + kinfopath)
|
print('Found K4PC 1.25+ kinf2018 file: ' + kinfopath.encode('ascii','ignore'))
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# look for (K4PC 1.9.0 and later) .kinf2011 file
|
# look for (K4PC 1.9.0 and later) .kinf2011 file
|
||||||
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
|
||||||
if os.path.isfile(kinfopath):
|
if os.path.isfile(kinfopath):
|
||||||
found = True
|
found = True
|
||||||
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath)
|
print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath.encode('ascii','ignore'))
|
||||||
kInfoFiles.append(kinfopath)
|
kInfoFiles.append(kinfopath)
|
||||||
|
|
||||||
# look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
# look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
|
||||||
@@ -1017,31 +1026,29 @@ if iswindows:
|
|||||||
# database of keynames and values
|
# database of keynames and values
|
||||||
def getDBfromFile(kInfoFile):
|
def getDBfromFile(kInfoFile):
|
||||||
names = [\
|
names = [\
|
||||||
b'kindle.account.tokens',\
|
'kindle.account.tokens',\
|
||||||
b'kindle.cookie.item',\
|
'kindle.cookie.item',\
|
||||||
b'eulaVersionAccepted',\
|
'eulaVersionAccepted',\
|
||||||
b'login_date',\
|
'login_date',\
|
||||||
b'kindle.token.item',\
|
'kindle.token.item',\
|
||||||
b'login',\
|
'login',\
|
||||||
b'kindle.key.item',\
|
'kindle.key.item',\
|
||||||
b'kindle.name.info',\
|
'kindle.name.info',\
|
||||||
b'kindle.device.info',\
|
'kindle.device.info',\
|
||||||
b'MazamaRandomNumber',\
|
'MazamaRandomNumber',\
|
||||||
b'max_date',\
|
'max_date',\
|
||||||
b'SIGVERIF',\
|
'SIGVERIF',\
|
||||||
b'build_version',\
|
'build_version',\
|
||||||
b'SerialNumber',\
|
'SerialNumber',\
|
||||||
b'UsernameHash',\
|
'UsernameHash',\
|
||||||
b'kindle.directedid.info',\
|
'kindle.directedid.info',\
|
||||||
b'DSN',\
|
'DSN',\
|
||||||
b'kindle.accounttype.info',\
|
'kindle.accounttype.info',\
|
||||||
b'krx.flashcardsplugin.data.encryption_key',\
|
'krx.flashcardsplugin.data.encryption_key',\
|
||||||
b'krx.notebookexportplugin.data.encryption_key',\
|
'krx.notebookexportplugin.data.encryption_key',\
|
||||||
b'proxy.http.password',\
|
'proxy.http.password',\
|
||||||
b'proxy.http.username'
|
'proxy.http.username'
|
||||||
]
|
]
|
||||||
namehashmap = {encodeHash(n,testMap8):n for n in names}
|
|
||||||
# print(namehashmap)
|
|
||||||
DB = {}
|
DB = {}
|
||||||
with open(kInfoFile, 'rb') as infoReader:
|
with open(kInfoFile, 'rb') as infoReader:
|
||||||
data = infoReader.read()
|
data = infoReader.read()
|
||||||
@@ -1049,7 +1056,7 @@ if iswindows:
|
|||||||
# the .kinf file uses "/" to separate it into records
|
# the .kinf file uses "/" to separate it into records
|
||||||
# so remove the trailing "/" to make it easy to use split
|
# so remove the trailing "/" to make it easy to use split
|
||||||
data = data[:-1]
|
data = data[:-1]
|
||||||
items = data.split(b'/')
|
items = data.split('/')
|
||||||
|
|
||||||
# starts with an encoded and encrypted header blob
|
# starts with an encoded and encrypted header blob
|
||||||
headerblob = items.pop(0)
|
headerblob = items.pop(0)
|
||||||
@@ -1057,7 +1064,7 @@ if iswindows:
|
|||||||
cleartext = UnprotectHeaderData(encryptedValue)
|
cleartext = UnprotectHeaderData(encryptedValue)
|
||||||
#print "header cleartext:",cleartext
|
#print "header cleartext:",cleartext
|
||||||
# now extract the pieces that form the added entropy
|
# now extract the pieces that form the added entropy
|
||||||
pattern = re.compile(br'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
||||||
for m in re.finditer(pattern, cleartext):
|
for m in re.finditer(pattern, cleartext):
|
||||||
version = int(m.group(1))
|
version = int(m.group(1))
|
||||||
build = m.group(2)
|
build = m.group(2)
|
||||||
@@ -1066,8 +1073,8 @@ if iswindows:
|
|||||||
if version == 5: # .kinf2011
|
if version == 5: # .kinf2011
|
||||||
added_entropy = build + guid
|
added_entropy = build + guid
|
||||||
elif version == 6: # .kinf2018
|
elif version == 6: # .kinf2018
|
||||||
salt = str(0x6d8 * int(build)).encode('utf-8') + guid
|
salt = str(0x6d8 * int(build)) + guid
|
||||||
sp = GetUserName() + b'+@#$%+' + GetIDString().encode('utf-8')
|
sp = GetUserName() + '+@#$%+' + GetIDString()
|
||||||
passwd = encode(SHA256(sp), charMap5)
|
passwd = encode(SHA256(sp), charMap5)
|
||||||
key = KeyIVGen().pbkdf2(passwd, salt, 10000, 0x400)[:32] # this is very slow
|
key = KeyIVGen().pbkdf2(passwd, salt, 10000, 0x400)[:32] # this is very slow
|
||||||
|
|
||||||
@@ -1091,15 +1098,18 @@ if iswindows:
|
|||||||
# read and store in rcnt records of data
|
# read and store in rcnt records of data
|
||||||
# that make up the contents value
|
# that make up the contents value
|
||||||
edlst = []
|
edlst = []
|
||||||
for i in range(rcnt):
|
for i in xrange(rcnt):
|
||||||
item = items.pop(0)
|
item = items.pop(0)
|
||||||
edlst.append(item)
|
edlst.append(item)
|
||||||
|
|
||||||
# key names now use the new testMap8 encoding
|
# key names now use the new testMap8 encoding
|
||||||
if keyhash in namehashmap:
|
keyname = "unknown"
|
||||||
keyname=namehashmap[keyhash]
|
for name in names:
|
||||||
#print "keyname found from hash:",keyname
|
if encodeHash(name,testMap8) == keyhash:
|
||||||
else:
|
keyname = name
|
||||||
|
#print "keyname found from hash:",keyname
|
||||||
|
break
|
||||||
|
if keyname == "unknown":
|
||||||
keyname = keyhash
|
keyname = keyhash
|
||||||
#print "keyname not found, hash is:",keyname
|
#print "keyname not found, hash is:",keyname
|
||||||
|
|
||||||
@@ -1116,7 +1126,7 @@ if iswindows:
|
|||||||
# move first offsets chars to end to align for decode by testMap8
|
# move first offsets chars to end to align for decode by testMap8
|
||||||
# by moving noffset chars from the start of the
|
# by moving noffset chars from the start of the
|
||||||
# string to the end of the string
|
# string to the end of the string
|
||||||
encdata = b"".join(edlst)
|
encdata = "".join(edlst)
|
||||||
#print "encrypted data:",encdata
|
#print "encrypted data:",encdata
|
||||||
contlen = len(encdata)
|
contlen = len(encdata)
|
||||||
noffset = contlen - primes(int(contlen/3))[-1]
|
noffset = contlen - primes(int(contlen/3))[-1]
|
||||||
@@ -1155,11 +1165,11 @@ if iswindows:
|
|||||||
|
|
||||||
if len(DB)>6:
|
if len(DB)>6:
|
||||||
# store values used in decryption
|
# store values used in decryption
|
||||||
DB[b'IDString'] = GetIDString().encode('utf-8')
|
DB['IDString'] = GetIDString()
|
||||||
DB[b'UserName'] = GetUserName()
|
DB['UserName'] = GetUserName()
|
||||||
print("Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode('utf-8')))
|
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))
|
||||||
else:
|
else:
|
||||||
print("Couldn't decrypt file.")
|
print u"Couldn't decrypt file."
|
||||||
DB = {}
|
DB = {}
|
||||||
return DB
|
return DB
|
||||||
elif isosx:
|
elif isosx:
|
||||||
@@ -1174,8 +1184,12 @@ elif isosx:
|
|||||||
|
|
||||||
libcrypto = find_library('crypto')
|
libcrypto = find_library('crypto')
|
||||||
if libcrypto is None:
|
if libcrypto is None:
|
||||||
raise DrmException("libcrypto not found")
|
libcrypto = '/usr/lib/libcrypto.dylib'
|
||||||
libcrypto = CDLL(libcrypto)
|
try:
|
||||||
|
libcrypto = CDLL(libcrypto)
|
||||||
|
except Exception as e:
|
||||||
|
raise DrmException(u"libcrypto not found: " % e)
|
||||||
|
|
||||||
|
|
||||||
# From OpenSSL's crypto aes header
|
# From OpenSSL's crypto aes header
|
||||||
#
|
#
|
||||||
@@ -1232,14 +1246,14 @@ elif isosx:
|
|||||||
def set_decrypt_key(self, userkey, iv):
|
def set_decrypt_key(self, userkey, iv):
|
||||||
self._blocksize = len(userkey)
|
self._blocksize = len(userkey)
|
||||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
||||||
raise DrmException("AES improper key used")
|
raise DrmException(u"AES improper key used")
|
||||||
return
|
return
|
||||||
keyctx = self._keyctx = AES_KEY()
|
keyctx = self._keyctx = AES_KEY()
|
||||||
self._iv = iv
|
self._iv = iv
|
||||||
self._userkey = userkey
|
self._userkey = userkey
|
||||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
|
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
|
||||||
if rv < 0:
|
if rv < 0:
|
||||||
raise DrmException("Failed to initialize AES key")
|
raise DrmException(u"Failed to initialize AES key")
|
||||||
|
|
||||||
def decrypt(self, data):
|
def decrypt(self, data):
|
||||||
out = create_string_buffer(len(data))
|
out = create_string_buffer(len(data))
|
||||||
@@ -1247,7 +1261,7 @@ elif isosx:
|
|||||||
keyctx = self._keyctx
|
keyctx = self._keyctx
|
||||||
rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
|
rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
raise DrmException("AES decryption failed")
|
raise DrmException(u"AES decryption failed")
|
||||||
return out.raw
|
return out.raw
|
||||||
|
|
||||||
def keyivgen(self, passwd, salt, iter, keylen):
|
def keyivgen(self, passwd, salt, iter, keylen):
|
||||||
@@ -1269,8 +1283,8 @@ elif isosx:
|
|||||||
LibCrypto = _load_crypto()
|
LibCrypto = _load_crypto()
|
||||||
|
|
||||||
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||||||
charMap1 = b'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
||||||
charMap2 = b'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
|
charMap2 = 'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
|
||||||
|
|
||||||
# For kinf approach of K4Mac 1.6.X or later
|
# For kinf approach of K4Mac 1.6.X or later
|
||||||
# On K4PC charMap5 = 'AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE'
|
# On K4PC charMap5 = 'AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE'
|
||||||
@@ -1278,7 +1292,7 @@ elif isosx:
|
|||||||
charMap5 = charMap2
|
charMap5 = charMap2
|
||||||
|
|
||||||
# new in K4M 1.9.X
|
# new in K4M 1.9.X
|
||||||
testMap8 = b'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD'
|
testMap8 = 'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD'
|
||||||
|
|
||||||
# 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 serial numbers of all internal hard drive drives
|
# returns serial numbers of all internal hard drive drives
|
||||||
@@ -1292,11 +1306,11 @@ elif isosx:
|
|||||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
out1, out2 = p.communicate()
|
out1, out2 = p.communicate()
|
||||||
#print out1
|
#print out1
|
||||||
reslst = out1.split(b'\n')
|
reslst = out1.split('\n')
|
||||||
cnt = len(reslst)
|
cnt = len(reslst)
|
||||||
for j in range(cnt):
|
for j in xrange(cnt):
|
||||||
resline = reslst[j]
|
resline = reslst[j]
|
||||||
pp = resline.find(b'\"Serial Number\" = \"')
|
pp = resline.find('\"Serial Number\" = \"')
|
||||||
if pp >= 0:
|
if pp >= 0:
|
||||||
sernum = resline[pp+19:-1]
|
sernum = resline[pp+19:-1]
|
||||||
sernums.append(sernum.strip())
|
sernums.append(sernum.strip())
|
||||||
@@ -1308,12 +1322,12 @@ elif isosx:
|
|||||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
out1, out2 = p.communicate()
|
out1, out2 = p.communicate()
|
||||||
reslst = out1.split(b'\n')
|
reslst = out1.split('\n')
|
||||||
cnt = len(reslst)
|
cnt = len(reslst)
|
||||||
for j in range(cnt):
|
for j in xrange(cnt):
|
||||||
resline = reslst[j]
|
resline = reslst[j]
|
||||||
if resline.startswith(b'/dev'):
|
if resline.startswith('/dev'):
|
||||||
(devpart, mpath) = resline.split(b' on ')[:2]
|
(devpart, mpath) = resline.split(' on ')[:2]
|
||||||
dpart = devpart[5:]
|
dpart = devpart[5:]
|
||||||
names.append(dpart)
|
names.append(dpart)
|
||||||
return names
|
return names
|
||||||
@@ -1329,11 +1343,11 @@ elif isosx:
|
|||||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
out1, out2 = p.communicate()
|
out1, out2 = p.communicate()
|
||||||
#print out1
|
#print out1
|
||||||
reslst = out1.split(b'\n')
|
reslst = out1.split('\n')
|
||||||
cnt = len(reslst)
|
cnt = len(reslst)
|
||||||
for j in range(cnt):
|
for j in xrange(cnt):
|
||||||
resline = reslst[j]
|
resline = reslst[j]
|
||||||
pp = resline.find(b'\"UUID\" = \"')
|
pp = resline.find('\"UUID\" = \"')
|
||||||
if pp >= 0:
|
if pp >= 0:
|
||||||
uuidnum = resline[pp+10:-1]
|
uuidnum = resline[pp+10:-1]
|
||||||
uuidnum = uuidnum.strip()
|
uuidnum = uuidnum.strip()
|
||||||
@@ -1349,16 +1363,16 @@ elif isosx:
|
|||||||
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
|
||||||
out1, out2 = p.communicate()
|
out1, out2 = p.communicate()
|
||||||
reslst = out1.split(b'\n')
|
reslst = out1.split('\n')
|
||||||
cnt = len(reslst)
|
cnt = len(reslst)
|
||||||
for j in range(cnt):
|
for j in xrange(cnt):
|
||||||
resline = reslst[j]
|
resline = reslst[j]
|
||||||
pp = resline.find(b'Ethernet Address: ')
|
pp = resline.find('Ethernet Address: ')
|
||||||
if pp >= 0:
|
if pp >= 0:
|
||||||
#print resline
|
#print resline
|
||||||
macnum = resline[pp+18:]
|
macnum = resline[pp+18:]
|
||||||
macnum = macnum.strip()
|
macnum = macnum.strip()
|
||||||
maclst = macnum.split(b':')
|
maclst = macnum.split(':')
|
||||||
n = len(maclst)
|
n = len(maclst)
|
||||||
if n != 6:
|
if n != 6:
|
||||||
continue
|
continue
|
||||||
@@ -1366,7 +1380,7 @@ elif isosx:
|
|||||||
# now munge it up the way Kindle app does
|
# now munge it up the way Kindle app does
|
||||||
# by xoring it with 0xa5 and swapping elements 3 and 4
|
# by xoring it with 0xa5 and swapping elements 3 and 4
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
maclst[i] = int(b'0x' + maclst[i], 0)
|
maclst[i] = int('0x' + maclst[i], 0)
|
||||||
mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||||
mlst[5] = maclst[5] ^ 0xa5
|
mlst[5] = maclst[5] ^ 0xa5
|
||||||
mlst[4] = maclst[3] ^ 0xa5
|
mlst[4] = maclst[3] ^ 0xa5
|
||||||
@@ -1374,7 +1388,7 @@ elif isosx:
|
|||||||
mlst[2] = maclst[2] ^ 0xa5
|
mlst[2] = maclst[2] ^ 0xa5
|
||||||
mlst[1] = maclst[1] ^ 0xa5
|
mlst[1] = maclst[1] ^ 0xa5
|
||||||
mlst[0] = maclst[0] ^ 0xa5
|
mlst[0] = maclst[0] ^ 0xa5
|
||||||
macnum = b'%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
|
macnum = '%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
|
||||||
#print 'munged mac', macnum
|
#print 'munged mac', macnum
|
||||||
macnums.append(macnum)
|
macnums.append(macnum)
|
||||||
return macnums
|
return macnums
|
||||||
@@ -1384,7 +1398,7 @@ elif isosx:
|
|||||||
def GetUserName():
|
def GetUserName():
|
||||||
username = os.getenv('USER')
|
username = os.getenv('USER')
|
||||||
#print "Username:",username
|
#print "Username:",username
|
||||||
return username.encode('utf-8')
|
return username
|
||||||
|
|
||||||
def GetIDStrings():
|
def GetIDStrings():
|
||||||
# Return all possible ID Strings
|
# Return all possible ID Strings
|
||||||
@@ -1393,7 +1407,7 @@ elif isosx:
|
|||||||
strings.extend(GetVolumesSerialNumbers())
|
strings.extend(GetVolumesSerialNumbers())
|
||||||
strings.extend(GetDiskPartitionNames())
|
strings.extend(GetDiskPartitionNames())
|
||||||
strings.extend(GetDiskPartitionUUIDs())
|
strings.extend(GetDiskPartitionUUIDs())
|
||||||
strings.append(b'9999999999')
|
strings.append('9999999999')
|
||||||
#print "ID Strings:\n",strings
|
#print "ID Strings:\n",strings
|
||||||
return strings
|
return strings
|
||||||
|
|
||||||
@@ -1401,8 +1415,8 @@ elif isosx:
|
|||||||
# unprotect the new header blob in .kinf2011
|
# unprotect the new header blob in .kinf2011
|
||||||
# used in Kindle for Mac Version >= 1.9.0
|
# used in Kindle for Mac Version >= 1.9.0
|
||||||
def UnprotectHeaderData(encryptedData):
|
def UnprotectHeaderData(encryptedData):
|
||||||
passwdData = b'header_key_data'
|
passwdData = 'header_key_data'
|
||||||
salt = b'HEADER.2011'
|
salt = 'HEADER.2011'
|
||||||
iter = 0x80
|
iter = 0x80
|
||||||
keylen = 0x100
|
keylen = 0x100
|
||||||
crp = LibCrypto()
|
crp = LibCrypto()
|
||||||
@@ -1417,7 +1431,7 @@ elif isosx:
|
|||||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||||
class CryptUnprotectData(object):
|
class CryptUnprotectData(object):
|
||||||
def __init__(self, entropy, IDString):
|
def __init__(self, entropy, IDString):
|
||||||
sp = GetUserName() + b'+@#$%+' + IDString
|
sp = GetUserName() + '+@#$%+' + IDString
|
||||||
passwdData = encode(SHA256(sp),charMap2)
|
passwdData = encode(SHA256(sp),charMap2)
|
||||||
salt = entropy
|
salt = entropy
|
||||||
self.crp = LibCrypto()
|
self.crp = LibCrypto()
|
||||||
@@ -1496,79 +1510,59 @@ elif isosx:
|
|||||||
# database of keynames and values
|
# database of keynames and values
|
||||||
def getDBfromFile(kInfoFile):
|
def getDBfromFile(kInfoFile):
|
||||||
names = [\
|
names = [\
|
||||||
b'kindle.account.tokens',\
|
'kindle.account.tokens',\
|
||||||
b'kindle.cookie.item',\
|
'kindle.cookie.item',\
|
||||||
b'eulaVersionAccepted',\
|
'eulaVersionAccepted',\
|
||||||
b'login_date',\
|
'login_date',\
|
||||||
b'kindle.token.item',\
|
'kindle.token.item',\
|
||||||
b'login',\
|
'login',\
|
||||||
b'kindle.key.item',\
|
'kindle.key.item',\
|
||||||
b'kindle.name.info',\
|
'kindle.name.info',\
|
||||||
b'kindle.device.info',\
|
'kindle.device.info',\
|
||||||
b'MazamaRandomNumber',\
|
'MazamaRandomNumber',\
|
||||||
b'max_date',\
|
'max_date',\
|
||||||
b'SIGVERIF',\
|
'SIGVERIF',\
|
||||||
b'build_version',\
|
'build_version',\
|
||||||
b'SerialNumber',\
|
'SerialNumber',\
|
||||||
b'UsernameHash',\
|
'UsernameHash',\
|
||||||
b'kindle.directedid.info',\
|
'kindle.directedid.info',\
|
||||||
b'DSN'
|
'DSN'
|
||||||
b'kindle.accounttype.info',\
|
|
||||||
b'krx.flashcardsplugin.data.encryption_key',\
|
|
||||||
b'krx.notebookexportplugin.data.encryption_key',\
|
|
||||||
b'proxy.http.password',\
|
|
||||||
b'proxy.http.username'
|
|
||||||
]
|
]
|
||||||
with open(kInfoFile, 'rb') as infoReader:
|
with open(kInfoFile, 'rb') as infoReader:
|
||||||
filedata = infoReader.read()
|
filedata = infoReader.read()
|
||||||
|
|
||||||
data = filedata[:-1]
|
data = filedata[:-1]
|
||||||
items = data.split(b'/')
|
items = data.split('/')
|
||||||
IDStrings = GetIDStrings()
|
IDStrings = GetIDStrings()
|
||||||
print ("trying username ", GetUserName(), " on file ", kInfoFile)
|
|
||||||
for IDString in IDStrings:
|
for IDString in IDStrings:
|
||||||
print ("trying IDString:",IDString)
|
#print "trying IDString:",IDString
|
||||||
try:
|
try:
|
||||||
DB = {}
|
DB = {}
|
||||||
items = data.split(b'/')
|
items = data.split('/')
|
||||||
|
|
||||||
# the headerblob is the encrypted information needed to build the entropy string
|
# the headerblob is the encrypted information needed to build the entropy string
|
||||||
headerblob = items.pop(0)
|
headerblob = items.pop(0)
|
||||||
#print ("headerblob: ",headerblob)
|
|
||||||
encryptedValue = decode(headerblob, charMap1)
|
encryptedValue = decode(headerblob, charMap1)
|
||||||
#print ("encryptedvalue: ",encryptedValue)
|
|
||||||
cleartext = UnprotectHeaderData(encryptedValue)
|
cleartext = UnprotectHeaderData(encryptedValue)
|
||||||
#print ("cleartext: ",cleartext)
|
|
||||||
|
|
||||||
# now extract the pieces in the same way
|
# now extract the pieces in the same way
|
||||||
pattern = re.compile(br'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
|
||||||
for m in re.finditer(pattern, cleartext):
|
for m in re.finditer(pattern, cleartext):
|
||||||
version = int(m.group(1))
|
version = int(m.group(1))
|
||||||
build = m.group(2)
|
build = m.group(2)
|
||||||
guid = m.group(4)
|
guid = m.group(4)
|
||||||
|
|
||||||
#print ("version",version)
|
|
||||||
#print ("build",build)
|
|
||||||
#print ("guid",guid,"\n")
|
|
||||||
|
|
||||||
if version == 5: # .kinf2011: identical to K4PC, except the build number gets multiplied
|
if version == 5: # .kinf2011: identical to K4PC, except the build number gets multiplied
|
||||||
entropy = str(0x2df * int(build)).encode('utf-8') + guid
|
entropy = str(0x2df * int(build)) + guid
|
||||||
cud = CryptUnprotectData(entropy,IDString)
|
cud = CryptUnprotectData(entropy,IDString)
|
||||||
#print ("entropy",entropy)
|
|
||||||
#print ("cud",cud)
|
|
||||||
|
|
||||||
elif version == 6: # .kinf2018: identical to K4PC
|
elif version == 6: # .kinf2018: identical to K4PC
|
||||||
salt = str(0x6d8 * int(build)).encode('utf-8') + guid
|
salt = str(0x6d8 * int(build)) + guid
|
||||||
sp = GetUserName() + b'+@#$%+' + IDString
|
sp = GetUserName() + '+@#$%+' + IDString
|
||||||
passwd = encode(SHA256(sp), charMap5)
|
passwd = encode(SHA256(sp), charMap5)
|
||||||
key = LibCrypto().keyivgen(passwd, salt, 10000, 0x400)[:32]
|
key = LibCrypto().keyivgen(passwd, salt, 10000, 0x400)[:32]
|
||||||
|
|
||||||
#print ("salt",salt)
|
# loop through the item records until all are processed
|
||||||
#print ("sp",sp)
|
|
||||||
#print ("passwd",passwd)
|
|
||||||
#print ("key",key)
|
|
||||||
|
|
||||||
# loop through the item records until all are processed
|
|
||||||
while len(items) > 0:
|
while len(items) > 0:
|
||||||
|
|
||||||
# get the first item record
|
# get the first item record
|
||||||
@@ -1577,7 +1571,7 @@ elif isosx:
|
|||||||
# the first 32 chars of the first record of a group
|
# the first 32 chars of the first record of a group
|
||||||
# is the MD5 hash of the key name encoded by charMap5
|
# is the MD5 hash of the key name encoded by charMap5
|
||||||
keyhash = item[0:32]
|
keyhash = item[0:32]
|
||||||
keyname = b'unknown'
|
keyname = 'unknown'
|
||||||
|
|
||||||
# unlike K4PC the keyhash is not used in generating entropy
|
# unlike K4PC the keyhash is not used in generating entropy
|
||||||
# entropy = SHA1(keyhash) + added_entropy
|
# entropy = SHA1(keyhash) + added_entropy
|
||||||
@@ -1593,16 +1587,16 @@ elif isosx:
|
|||||||
# read and store in rcnt records of data
|
# read and store in rcnt records of data
|
||||||
# that make up the contents value
|
# that make up the contents value
|
||||||
edlst = []
|
edlst = []
|
||||||
for i in range(rcnt):
|
for i in xrange(rcnt):
|
||||||
item = items.pop(0)
|
item = items.pop(0)
|
||||||
edlst.append(item)
|
edlst.append(item)
|
||||||
|
|
||||||
keyname = b'unknown'
|
keyname = 'unknown'
|
||||||
for name in names:
|
for name in names:
|
||||||
if encodeHash(name,testMap8) == keyhash:
|
if encodeHash(name,testMap8) == keyhash:
|
||||||
keyname = name
|
keyname = name
|
||||||
break
|
break
|
||||||
if keyname == b'unknown':
|
if keyname == 'unknown':
|
||||||
keyname = keyhash
|
keyname = keyhash
|
||||||
|
|
||||||
# the testMap8 encoded contents data has had a length
|
# the testMap8 encoded contents data has had a length
|
||||||
@@ -1616,7 +1610,7 @@ elif isosx:
|
|||||||
# (in other words split 'about' 2/3rds of the way through)
|
# (in other words split 'about' 2/3rds of the way through)
|
||||||
|
|
||||||
# move first offsets chars to end to align for decode by testMap8
|
# move first offsets chars to end to align for decode by testMap8
|
||||||
encdata = b''.join(edlst)
|
encdata = ''.join(edlst)
|
||||||
contlen = len(encdata)
|
contlen = len(encdata)
|
||||||
|
|
||||||
# now properly split and recombine
|
# now properly split and recombine
|
||||||
@@ -1656,22 +1650,20 @@ elif isosx:
|
|||||||
|
|
||||||
if len(DB)>6:
|
if len(DB)>6:
|
||||||
break
|
break
|
||||||
|
except:
|
||||||
except Exception:
|
|
||||||
print (traceback.format_exc())
|
|
||||||
pass
|
pass
|
||||||
if len(DB)>6:
|
if len(DB)>6:
|
||||||
# store values used in decryption
|
# store values used in decryption
|
||||||
print("Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString.decode('utf-8'), GetUserName().decode('utf-8')))
|
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())
|
||||||
DB[b'IDString'] = IDString
|
DB['IDString'] = IDString
|
||||||
DB[b'UserName'] = GetUserName()
|
DB['UserName'] = GetUserName()
|
||||||
else:
|
else:
|
||||||
print("Couldn't decrypt file.")
|
print u"Couldn't decrypt file."
|
||||||
DB = {}
|
DB = {}
|
||||||
return DB
|
return DB
|
||||||
else:
|
else:
|
||||||
def getDBfromFile(kInfoFile):
|
def getDBfromFile(kInfoFile):
|
||||||
raise DrmException("This script only runs under Windows or Mac OS X.")
|
raise DrmException(u"This script only runs under Windows or Mac OS X.")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def kindlekeys(files = []):
|
def kindlekeys(files = []):
|
||||||
@@ -1682,11 +1674,9 @@ def kindlekeys(files = []):
|
|||||||
key = getDBfromFile(file)
|
key = getDBfromFile(file)
|
||||||
if key:
|
if key:
|
||||||
# convert all values to hex, just in case.
|
# convert all values to hex, just in case.
|
||||||
n_key = {}
|
for keyname in key:
|
||||||
for k,v in key.items():
|
key[keyname]=key[keyname].encode('hex')
|
||||||
n_key[k.decode()]=codecs.encode(v, 'hex_codec').decode()
|
keys.append(key)
|
||||||
# key = {k.decode():v.decode() for k,v in key.items()}
|
|
||||||
keys.append(n_key)
|
|
||||||
return keys
|
return keys
|
||||||
|
|
||||||
# interface for Python DeDRM
|
# interface for Python DeDRM
|
||||||
@@ -1696,29 +1686,29 @@ def getkey(outpath, files=[]):
|
|||||||
if len(keys) > 0:
|
if len(keys) > 0:
|
||||||
if not os.path.isdir(outpath):
|
if not os.path.isdir(outpath):
|
||||||
outfile = outpath
|
outfile = outpath
|
||||||
with open(outfile, 'w') as keyfileout:
|
with file(outfile, 'w') as keyfileout:
|
||||||
keyfileout.write(json.dumps(keys[0]))
|
keyfileout.write(json.dumps(keys[0]))
|
||||||
print("Saved a key to {0}".format(outfile))
|
print u"Saved a key to {0}".format(outfile)
|
||||||
else:
|
else:
|
||||||
keycount = 0
|
keycount = 0
|
||||||
for key in keys:
|
for key in keys:
|
||||||
while True:
|
while True:
|
||||||
keycount += 1
|
keycount += 1
|
||||||
outfile = os.path.join(outpath,"kindlekey{0:d}.k4i".format(keycount))
|
outfile = os.path.join(outpath,u"kindlekey{0:d}.k4i".format(keycount))
|
||||||
if not os.path.exists(outfile):
|
if not os.path.exists(outfile):
|
||||||
break
|
break
|
||||||
with open(outfile, 'w') as keyfileout:
|
with file(outfile, 'w') as keyfileout:
|
||||||
keyfileout.write(json.dumps(key))
|
keyfileout.write(json.dumps(key))
|
||||||
print("Saved a key to {0}".format(outfile))
|
print u"Saved a key to {0}".format(outfile)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def usage(progname):
|
def usage(progname):
|
||||||
print("Finds, decrypts and saves the default Kindle For Mac/PC encryption keys.")
|
print u"Finds, decrypts and saves the default Kindle For Mac/PC encryption keys."
|
||||||
print("Keys are saved to the current directory, or a specified output directory.")
|
print u"Keys are saved to the current directory, or a specified output directory."
|
||||||
print("If a file name is passed instead of a directory, only the first key is saved, in that file.")
|
print u"If a file name is passed instead of a directory, only the first key is saved, in that file."
|
||||||
print("Usage:")
|
print u"Usage:"
|
||||||
print(" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname))
|
print u" {0:s} [-h] [-k <kindle.info>] [<outpath>]".format(progname)
|
||||||
|
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
@@ -1726,12 +1716,12 @@ def cli_main():
|
|||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
print("{0} v{1}\nCopyright © 2010-2020 by some_updates, Apprentice Harper et al.".format(progname,__version__))
|
print u"{0} v{1}\nCopyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "hk:")
|
opts, args = getopt.getopt(argv[1:], "hk:")
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError, err:
|
||||||
print("Error in options or arguments: {0}".format(err.args[0]))
|
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||||
usage(progname)
|
usage(progname)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
@@ -1760,33 +1750,33 @@ def cli_main():
|
|||||||
outpath = os.path.realpath(os.path.normpath(outpath))
|
outpath = os.path.realpath(os.path.normpath(outpath))
|
||||||
|
|
||||||
if not getkey(outpath, files):
|
if not getkey(outpath, files):
|
||||||
print("Could not retrieve Kindle for Mac/PC key.")
|
print u"Could not retrieve Kindle for Mac/PC key."
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def gui_main():
|
def gui_main():
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import Tkinter
|
||||||
import tkinter.constants
|
import Tkconstants
|
||||||
import tkinter.messagebox
|
import tkMessageBox
|
||||||
import traceback
|
import traceback
|
||||||
except:
|
except:
|
||||||
return cli_main()
|
return cli_main()
|
||||||
|
|
||||||
class ExceptionDialog(tkinter.Frame):
|
class ExceptionDialog(Tkinter.Frame):
|
||||||
def __init__(self, root, text):
|
def __init__(self, root, text):
|
||||||
tkinter.Frame.__init__(self, root, border=5)
|
Tkinter.Frame.__init__(self, root, border=5)
|
||||||
label = tkinter.Label(self, text="Unexpected error:",
|
label = Tkinter.Label(self, text=u"Unexpected error:",
|
||||||
anchor=tkinter.constants.W, justify=tkinter.constants.LEFT)
|
anchor=Tkconstants.W, justify=Tkconstants.LEFT)
|
||||||
label.pack(fill=tkinter.constants.X, expand=0)
|
label.pack(fill=Tkconstants.X, expand=0)
|
||||||
self.text = tkinter.Text(self)
|
self.text = Tkinter.Text(self)
|
||||||
self.text.pack(fill=tkinter.constants.BOTH, expand=1)
|
self.text.pack(fill=Tkconstants.BOTH, expand=1)
|
||||||
|
|
||||||
self.text.insert(tkinter.constants.END, text)
|
self.text.insert(Tkconstants.END, text)
|
||||||
|
|
||||||
|
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
root = tkinter.Tk()
|
root = Tkinter.Tk()
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
progpath, progname = os.path.split(argv[0])
|
progpath, progname = os.path.split(argv[0])
|
||||||
success = False
|
success = False
|
||||||
@@ -1796,21 +1786,21 @@ def gui_main():
|
|||||||
for key in keys:
|
for key in keys:
|
||||||
while True:
|
while True:
|
||||||
keycount += 1
|
keycount += 1
|
||||||
outfile = os.path.join(progpath,"kindlekey{0:d}.k4i".format(keycount))
|
outfile = os.path.join(progpath,u"kindlekey{0:d}.k4i".format(keycount))
|
||||||
if not os.path.exists(outfile):
|
if not os.path.exists(outfile):
|
||||||
break
|
break
|
||||||
|
|
||||||
with open(outfile, 'w') as keyfileout:
|
with file(outfile, 'w') as keyfileout:
|
||||||
keyfileout.write(json.dumps(key))
|
keyfileout.write(json.dumps(key))
|
||||||
success = True
|
success = True
|
||||||
tkinter.messagebox.showinfo(progname, "Key successfully retrieved to {0}".format(outfile))
|
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||||
except DrmException as e:
|
except DrmException, e:
|
||||||
tkinter.messagebox.showerror(progname, "Error: {0}".format(str(e)))
|
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||||
except Exception:
|
except Exception:
|
||||||
root.wm_state('normal')
|
root.wm_state('normal')
|
||||||
root.title(progname)
|
root.title(progname)
|
||||||
text = traceback.format_exc()
|
text = traceback.format_exc()
|
||||||
ExceptionDialog(root, text).pack(fill=tkinter.constants.BOTH, expand=1)
|
ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
if not success:
|
if not success:
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Mobipocket PID calculator v0.4 for Amazon Kindle.
|
# Mobipocket PID calculator v0.4 for Amazon Kindle.
|
||||||
@@ -10,9 +10,8 @@
|
|||||||
# 0.3 updated for unicode
|
# 0.3 updated for unicode
|
||||||
# 0.4 Added support for serial numbers starting with '9', fixed unicode bugs.
|
# 0.4 Added support for serial numbers starting with '9', fixed unicode bugs.
|
||||||
# 0.5 moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 0.5 moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 1.0 Python 3 for calibre 5.0
|
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
import binascii
|
import binascii
|
||||||
|
|
||||||
@@ -26,11 +25,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -65,13 +63,19 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["kindlepid.py"]
|
return [u"kindlepid.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = "utf-8"
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
|
if sys.hexversion >= 0x3000000:
|
||||||
|
print('This script is incompatible with Python 3.x. Please install Python 2.7.x.')
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||||
|
|
||||||
@@ -79,7 +83,7 @@ def crc32(s):
|
|||||||
return (~binascii.crc32(s,-1))&0xFFFFFFFF
|
return (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||||
|
|
||||||
def checksumPid(s):
|
def checksumPid(s):
|
||||||
crc = crc32(s.encode('ascii'))
|
crc = crc32(s)
|
||||||
crc = crc ^ (crc >> 16)
|
crc = crc ^ (crc >> 16)
|
||||||
res = s
|
res = s
|
||||||
l = len(letters)
|
l = len(letters)
|
||||||
@@ -95,43 +99,43 @@ def pidFromSerial(s, l):
|
|||||||
crc = crc32(s)
|
crc = crc32(s)
|
||||||
|
|
||||||
arr1 = [0]*l
|
arr1 = [0]*l
|
||||||
for i in range(len(s)):
|
for i in xrange(len(s)):
|
||||||
arr1[i%l] ^= s[i]
|
arr1[i%l] ^= ord(s[i])
|
||||||
|
|
||||||
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
||||||
for i in range(l):
|
for i in xrange(l):
|
||||||
arr1[i] ^= crc_bytes[i&3]
|
arr1[i] ^= crc_bytes[i&3]
|
||||||
|
|
||||||
pid = ''
|
pid = ''
|
||||||
for i in range(l):
|
for i in xrange(l):
|
||||||
b = arr1[i] & 0xff
|
b = arr1[i] & 0xff
|
||||||
pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
|
pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
|
||||||
|
|
||||||
return pid
|
return pid
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
print("Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky")
|
print(u"Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky")
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
if len(argv)==2:
|
if len(argv)==2:
|
||||||
serial = argv[1]
|
serial = argv[1]
|
||||||
else:
|
else:
|
||||||
print("Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>")
|
print(u"Usage: kindlepid.py <Kindle Serial Number>/<iPhone/iPod Touch UDID>")
|
||||||
return 1
|
return 1
|
||||||
if len(serial)==16:
|
if len(serial)==16:
|
||||||
if serial.startswith("B") or serial.startswith("9"):
|
if serial.startswith("B") or serial.startswith("9"):
|
||||||
print("Kindle serial number detected")
|
print(u"Kindle serial number detected")
|
||||||
else:
|
else:
|
||||||
print("Warning: unrecognized serial number. Please recheck input.")
|
print(u"Warning: unrecognized serial number. Please recheck input.")
|
||||||
return 1
|
return 1
|
||||||
pid = pidFromSerial(serial.encode("utf-8"),7)+'*'
|
pid = pidFromSerial(serial.encode("utf-8"),7)+'*'
|
||||||
print("Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid)))
|
print(u"Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid)))
|
||||||
return 0
|
return 0
|
||||||
elif len(serial)==40:
|
elif len(serial)==40:
|
||||||
print("iPhone serial number (UDID) detected")
|
print(u"iPhone serial number (UDID) detected")
|
||||||
pid = pidFromSerial(serial.encode("utf-8"),8)
|
pid = pidFromSerial(serial.encode("utf-8"),8)
|
||||||
print("Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid)))
|
print(u"Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid)))
|
||||||
return 0
|
return 0
|
||||||
print("Warning: unrecognized serial number. Please recheck input.")
|
print(u"Warning: unrecognized serial number. Please recheck input.")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# mobidedrm.py
|
# mobidedrm.py
|
||||||
# Copyright © 2008 The Dark Reverser
|
# Copyright © 2008 The Dark Reverser
|
||||||
# Portions © 2008–2020 Apprentice Harper et al.
|
# Portions © 2008–2017 Apprentice Harper et al.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "1.0"
|
__version__ = u"0.42"
|
||||||
|
|
||||||
# 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.
|
||||||
@@ -73,7 +73,6 @@ __version__ = "1.0"
|
|||||||
# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 0.41 - Fixed potential unicode problem in command line calls
|
# 0.41 - Fixed potential unicode problem in command line calls
|
||||||
# 0.42 - Added GPL v3 licence. updated/removed some print statements
|
# 0.42 - Added GPL v3 licence. updated/removed some print statements
|
||||||
# 1.0 - Python 3 compatibility for calibre 5.0
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -82,7 +81,7 @@ import binascii
|
|||||||
try:
|
try:
|
||||||
from alfcrypto import Pukall_Cipher
|
from alfcrypto import Pukall_Cipher
|
||||||
except:
|
except:
|
||||||
print("AlfCrypto not found. Using python PC1 implementation.")
|
print(u"AlfCrypto not found. Using python PC1 implementation.")
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
# and also make sure that any unicode strings get
|
# and also make sure that any unicode strings get
|
||||||
@@ -94,11 +93,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -133,13 +131,15 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["mobidedrm.py"]
|
return [u"mobidedrm.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = 'utf-8'
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
|
|
||||||
class DrmException(Exception):
|
class DrmException(Exception):
|
||||||
@@ -165,30 +165,30 @@ def PC1(key, src, decryption=True):
|
|||||||
sum2 = 0;
|
sum2 = 0;
|
||||||
keyXorVal = 0;
|
keyXorVal = 0;
|
||||||
if len(key)!=16:
|
if len(key)!=16:
|
||||||
DrmException ("PC1: Bad key length")
|
DrmException (u"PC1: Bad key length")
|
||||||
wkey = []
|
wkey = []
|
||||||
for i in range(8):
|
for i in xrange(8):
|
||||||
wkey.append(key[i*2]<<8 | key[i*2+1])
|
wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
|
||||||
dst = b''
|
dst = ""
|
||||||
for i in range(len(src)):
|
for i in xrange(len(src)):
|
||||||
temp1 = 0;
|
temp1 = 0;
|
||||||
byteXorVal = 0;
|
byteXorVal = 0;
|
||||||
for j in range(8):
|
for j in xrange(8):
|
||||||
temp1 ^= wkey[j]
|
temp1 ^= wkey[j]
|
||||||
sum2 = (sum2+j)*20021 + sum1
|
sum2 = (sum2+j)*20021 + sum1
|
||||||
sum1 = (temp1*346)&0xFFFF
|
sum1 = (temp1*346)&0xFFFF
|
||||||
sum2 = (sum2+sum1)&0xFFFF
|
sum2 = (sum2+sum1)&0xFFFF
|
||||||
temp1 = (temp1*20021+1)&0xFFFF
|
temp1 = (temp1*20021+1)&0xFFFF
|
||||||
byteXorVal ^= temp1 ^ sum2
|
byteXorVal ^= temp1 ^ sum2
|
||||||
curByte = src[i]
|
curByte = ord(src[i])
|
||||||
if not decryption:
|
if not decryption:
|
||||||
keyXorVal = curByte * 257;
|
keyXorVal = curByte * 257;
|
||||||
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
|
||||||
if decryption:
|
if decryption:
|
||||||
keyXorVal = curByte * 257;
|
keyXorVal = curByte * 257;
|
||||||
for j in range(8):
|
for j in xrange(8):
|
||||||
wkey[j] ^= keyXorVal;
|
wkey[j] ^= keyXorVal;
|
||||||
dst+=bytes([curByte])
|
dst+=chr(curByte)
|
||||||
return dst
|
return dst
|
||||||
|
|
||||||
def checksumPid(s):
|
def checksumPid(s):
|
||||||
@@ -200,7 +200,7 @@ def checksumPid(s):
|
|||||||
for i in (0,1):
|
for i in (0,1):
|
||||||
b = crc & 0xff
|
b = crc & 0xff
|
||||||
pos = (b // l) ^ (b % l)
|
pos = (b // l) ^ (b % l)
|
||||||
res += letters[pos%l].encode('ascii')
|
res += letters[pos%l]
|
||||||
crc >>= 8
|
crc >>= 8
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
|
|||||||
if size <= 0:
|
if size <= 0:
|
||||||
return result
|
return result
|
||||||
while True:
|
while True:
|
||||||
v = ptr[size-1]
|
v = ord(ptr[size-1])
|
||||||
result |= (v & 0x7F) << bitpos
|
result |= (v & 0x7F) << bitpos
|
||||||
bitpos += 7
|
bitpos += 7
|
||||||
size -= 1
|
size -= 1
|
||||||
@@ -226,7 +226,7 @@ def getSizeOfTrailingDataEntries(ptr, size, flags):
|
|||||||
# if multibyte data is included in the encryped data, we'll
|
# if multibyte data is included in the encryped data, we'll
|
||||||
# have already cleared this flag.
|
# have already cleared this flag.
|
||||||
if flags & 1:
|
if flags & 1:
|
||||||
num += (ptr[size - num - 1] & 0x3) + 1
|
num += (ord(ptr[size - num - 1]) & 0x3) + 1
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
|
||||||
@@ -245,26 +245,26 @@ class MobiBook:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, infile):
|
def __init__(self, infile):
|
||||||
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2020 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
print(u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from alfcrypto import Pukall_Cipher
|
from alfcrypto import Pukall_Cipher
|
||||||
except:
|
except:
|
||||||
print("AlfCrypto not found. Using python PC1 implementation.")
|
print(u"AlfCrypto not found. Using python PC1 implementation.")
|
||||||
|
|
||||||
# initial sanity check on file
|
# initial sanity check on file
|
||||||
self.data_file = open(infile, 'rb').read()
|
self.data_file = file(infile, 'rb').read()
|
||||||
self.mobi_data = ''
|
self.mobi_data = ''
|
||||||
self.header = self.data_file[0:78]
|
self.header = self.data_file[0:78]
|
||||||
if self.header[0x3C:0x3C+8] != b'BOOKMOBI' and self.header[0x3C:0x3C+8] != b'TEXtREAd':
|
if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
|
||||||
raise DrmException("Invalid file format")
|
raise DrmException(u"Invalid file format")
|
||||||
self.magic = self.header[0x3C:0x3C+8]
|
self.magic = self.header[0x3C:0x3C+8]
|
||||||
self.crypto_type = -1
|
self.crypto_type = -1
|
||||||
|
|
||||||
# build up section offset and flag info
|
# build up section offset and flag info
|
||||||
self.num_sections, = struct.unpack('>H', self.header[76:78])
|
self.num_sections, = struct.unpack('>H', self.header[76:78])
|
||||||
self.sections = []
|
self.sections = []
|
||||||
for i in range(self.num_sections):
|
for i in xrange(self.num_sections):
|
||||||
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
|
offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.data_file[78+i*8:78+i*8+8])
|
||||||
flags, val = a1, a2<<16|a3<<8|a4
|
flags, val = a1, a2<<16|a3<<8|a4
|
||||||
self.sections.append( (offset, flags, val) )
|
self.sections.append( (offset, flags, val) )
|
||||||
@@ -283,16 +283,16 @@ class MobiBook:
|
|||||||
self.mobi_version = -1
|
self.mobi_version = -1
|
||||||
|
|
||||||
if self.magic == 'TEXtREAd':
|
if self.magic == 'TEXtREAd':
|
||||||
print("PalmDoc format book detected.")
|
print(u"PalmDoc format book detected.")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
|
self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
|
||||||
self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
|
self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
|
||||||
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
|
self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
|
||||||
#print "MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
|
#print u"MOBI header version {0:d}, header length {1:d}".format(self.mobi_version, self.mobi_length)
|
||||||
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
|
if (self.mobi_length >= 0xE4) and (self.mobi_version >= 5):
|
||||||
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
|
self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
|
||||||
#print "Extra Data Flags: {0:d}".format(self.extra_data_flags)
|
#print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
|
||||||
if (self.compression != 17480):
|
if (self.compression != 17480):
|
||||||
# multibyte utf8 data is included in the encryption for PalmDoc compression
|
# multibyte utf8 data is included in the encryption for PalmDoc compression
|
||||||
# so clear that byte so that we leave it to be decrypted.
|
# so clear that byte so that we leave it to be decrypted.
|
||||||
@@ -304,24 +304,24 @@ class MobiBook:
|
|||||||
exth = ''
|
exth = ''
|
||||||
if exth_flag & 0x40:
|
if exth_flag & 0x40:
|
||||||
exth = self.sect[16 + self.mobi_length:]
|
exth = self.sect[16 + self.mobi_length:]
|
||||||
if (len(exth) >= 12) and (exth[:4] == b'EXTH'):
|
if (len(exth) >= 12) and (exth[:4] == 'EXTH'):
|
||||||
nitems, = struct.unpack('>I', exth[8:12])
|
nitems, = struct.unpack('>I', exth[8:12])
|
||||||
pos = 12
|
pos = 12
|
||||||
for i in range(nitems):
|
for i in xrange(nitems):
|
||||||
type, size = struct.unpack('>II', exth[pos: pos + 8])
|
type, size = struct.unpack('>II', exth[pos: pos + 8])
|
||||||
content = exth[pos + 8: pos + size]
|
content = exth[pos + 8: pos + size]
|
||||||
self.meta_array[type] = content
|
self.meta_array[type] = content
|
||||||
# reset the text to speech flag and clipping limit, if present
|
# reset the text to speech flag and clipping limit, if present
|
||||||
if type == 401 and size == 9:
|
if type == 401 and size == 9:
|
||||||
# set clipping limit to 100%
|
# set clipping limit to 100%
|
||||||
self.patchSection(0, b'\144', 16 + self.mobi_length + pos + 8)
|
self.patchSection(0, '\144', 16 + self.mobi_length + pos + 8)
|
||||||
elif type == 404 and size == 9:
|
elif type == 404 and size == 9:
|
||||||
# make sure text to speech is enabled
|
# make sure text to speech is enabled
|
||||||
self.patchSection(0, b'\0', 16 + self.mobi_length + pos + 8)
|
self.patchSection(0, '\0', 16 + self.mobi_length + pos + 8)
|
||||||
# print type, size, content, content.encode('hex')
|
# print type, size, content, content.encode('hex')
|
||||||
pos += size
|
pos += size
|
||||||
except Exception as e:
|
except:
|
||||||
print("Cannot set meta_array: Error: {:s}".format(e.args[0]))
|
pass
|
||||||
|
|
||||||
def getBookTitle(self):
|
def getBookTitle(self):
|
||||||
codec_map = {
|
codec_map = {
|
||||||
@@ -330,7 +330,7 @@ class MobiBook:
|
|||||||
}
|
}
|
||||||
title = ''
|
title = ''
|
||||||
codec = 'windows-1252'
|
codec = 'windows-1252'
|
||||||
if self.magic == b'BOOKMOBI':
|
if self.magic == 'BOOKMOBI':
|
||||||
if 503 in self.meta_array:
|
if 503 in self.meta_array:
|
||||||
title = self.meta_array[503]
|
title = self.meta_array[503]
|
||||||
else:
|
else:
|
||||||
@@ -341,19 +341,19 @@ class MobiBook:
|
|||||||
codec = codec_map[self.mobi_codepage]
|
codec = codec_map[self.mobi_codepage]
|
||||||
if title == '':
|
if title == '':
|
||||||
title = self.header[:32]
|
title = self.header[:32]
|
||||||
title = title.split(b'\0')[0]
|
title = title.split('\0')[0]
|
||||||
return title.decode(codec)
|
return unicode(title, codec)
|
||||||
|
|
||||||
def getPIDMetaInfo(self):
|
def getPIDMetaInfo(self):
|
||||||
rec209 = b''
|
rec209 = ''
|
||||||
token = b''
|
token = ''
|
||||||
if 209 in self.meta_array:
|
if 209 in self.meta_array:
|
||||||
rec209 = self.meta_array[209]
|
rec209 = self.meta_array[209]
|
||||||
data = rec209
|
data = rec209
|
||||||
# The 209 data comes in five byte groups. Interpret the last four bytes
|
# The 209 data comes in five byte groups. Interpret the last four bytes
|
||||||
# of each group as a big endian unsigned integer to get a key value
|
# of each group as a big endian unsigned integer to get a key value
|
||||||
# if that key exists in the meta_array, append its contents to the token
|
# if that key exists in the meta_array, append its contents to the token
|
||||||
for i in range(0,len(data),5):
|
for i in xrange(0,len(data),5):
|
||||||
val, = struct.unpack('>I',data[i+1:i+5])
|
val, = struct.unpack('>I',data[i+1:i+5])
|
||||||
sval = self.meta_array.get(val,'')
|
sval = self.meta_array.get(val,'')
|
||||||
token += sval
|
token += sval
|
||||||
@@ -373,14 +373,13 @@ class MobiBook:
|
|||||||
|
|
||||||
def parseDRM(self, data, count, pidlist):
|
def parseDRM(self, data, count, pidlist):
|
||||||
found_key = None
|
found_key = None
|
||||||
keyvec1 = b'\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96'
|
keyvec1 = '\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96'
|
||||||
for pid in pidlist:
|
for pid in pidlist:
|
||||||
bigpid = pid.ljust(16,b'\0')
|
bigpid = pid.ljust(16,'\0')
|
||||||
bigpid = bigpid
|
|
||||||
temp_key = PC1(keyvec1, bigpid, False)
|
temp_key = PC1(keyvec1, bigpid, False)
|
||||||
temp_key_sum = sum(temp_key) & 0xff
|
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
||||||
found_key = None
|
found_key = None
|
||||||
for i in range(count):
|
for i in xrange(count):
|
||||||
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
||||||
if cksum == temp_key_sum:
|
if cksum == temp_key_sum:
|
||||||
cookie = PC1(temp_key, cookie)
|
cookie = PC1(temp_key, cookie)
|
||||||
@@ -394,8 +393,8 @@ class MobiBook:
|
|||||||
# Then try the default encoding that doesn't require a PID
|
# Then try the default encoding that doesn't require a PID
|
||||||
pid = '00000000'
|
pid = '00000000'
|
||||||
temp_key = keyvec1
|
temp_key = keyvec1
|
||||||
temp_key_sum = sum(temp_key) & 0xff
|
temp_key_sum = sum(map(ord,temp_key)) & 0xff
|
||||||
for i in range(count):
|
for i in xrange(count):
|
||||||
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
|
||||||
if cksum == temp_key_sum:
|
if cksum == temp_key_sum:
|
||||||
cookie = PC1(temp_key, cookie)
|
cookie = PC1(temp_key, cookie)
|
||||||
@@ -406,55 +405,52 @@ class MobiBook:
|
|||||||
return [found_key,pid]
|
return [found_key,pid]
|
||||||
|
|
||||||
def getFile(self, outpath):
|
def getFile(self, outpath):
|
||||||
open(outpath,'wb').write(self.mobi_data)
|
file(outpath,'wb').write(self.mobi_data)
|
||||||
|
|
||||||
def getBookType(self):
|
def getBookType(self):
|
||||||
if self.print_replica:
|
if self.print_replica:
|
||||||
return "Print Replica"
|
return u"Print Replica"
|
||||||
if self.mobi_version >= 8:
|
if self.mobi_version >= 8:
|
||||||
return "Kindle Format 8"
|
return u"Kindle Format 8"
|
||||||
if self.mobi_version >= 0:
|
if self.mobi_version >= 0:
|
||||||
return "Mobipocket {0:d}".format(self.mobi_version)
|
return u"Mobipocket {0:d}".format(self.mobi_version)
|
||||||
return "PalmDoc"
|
return u"PalmDoc"
|
||||||
|
|
||||||
def getBookExtension(self):
|
def getBookExtension(self):
|
||||||
if self.print_replica:
|
if self.print_replica:
|
||||||
return ".azw4"
|
return u".azw4"
|
||||||
if self.mobi_version >= 8:
|
if self.mobi_version >= 8:
|
||||||
return ".azw3"
|
return u".azw3"
|
||||||
return ".mobi"
|
return u".mobi"
|
||||||
|
|
||||||
def processBook(self, pidlist):
|
def processBook(self, pidlist):
|
||||||
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
|
crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
|
||||||
print("Crypto Type is: {0:d}".format(crypto_type))
|
print(u"Crypto Type is: {0:d}".format(crypto_type))
|
||||||
self.crypto_type = crypto_type
|
self.crypto_type = crypto_type
|
||||||
if crypto_type == 0:
|
if crypto_type == 0:
|
||||||
print("This book is not encrypted.")
|
print(u"This book is not encrypted.")
|
||||||
# we must still check for Print Replica
|
# we must still check for Print Replica
|
||||||
self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
|
self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
|
||||||
self.mobi_data = self.data_file
|
self.mobi_data = self.data_file
|
||||||
return
|
return
|
||||||
if crypto_type != 2 and crypto_type != 1:
|
if crypto_type != 2 and crypto_type != 1:
|
||||||
raise DrmException("Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type))
|
raise DrmException(u"Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type))
|
||||||
if 406 in self.meta_array:
|
if 406 in self.meta_array:
|
||||||
data406 = self.meta_array[406]
|
data406 = self.meta_array[406]
|
||||||
val406, = struct.unpack('>Q',data406)
|
val406, = struct.unpack('>Q',data406)
|
||||||
if val406 != 0:
|
if val406 != 0:
|
||||||
raise DrmException("Cannot decode library or rented ebooks.")
|
raise DrmException(u"Cannot decode library or rented ebooks.")
|
||||||
|
|
||||||
goodpids = []
|
goodpids = []
|
||||||
# print("DEBUG ==== pidlist = ", pidlist)
|
|
||||||
for pid in pidlist:
|
for pid in pidlist:
|
||||||
if len(pid)==10:
|
if len(pid)==10:
|
||||||
if checksumPid(pid[0:-2]) != pid:
|
if checksumPid(pid[0:-2]) != pid:
|
||||||
print("Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2])))
|
print(u"Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2])))
|
||||||
goodpids.append(pid[0:-2])
|
goodpids.append(pid[0:-2])
|
||||||
elif len(pid)==8:
|
elif len(pid)==8:
|
||||||
goodpids.append(pid)
|
goodpids.append(pid)
|
||||||
else:
|
else:
|
||||||
print("Warning: PID {0} has wrong number of digits".format(pid))
|
print(u"Warning: PID {0} has wrong number of digits".format(pid))
|
||||||
|
|
||||||
# print("======= DEBUG good pids = ", goodpids)
|
|
||||||
|
|
||||||
if self.crypto_type == 1:
|
if self.crypto_type == 1:
|
||||||
t1_keyvec = 'QDCVEPMU675RUBSZ'
|
t1_keyvec = 'QDCVEPMU675RUBSZ'
|
||||||
@@ -470,32 +466,32 @@ class MobiBook:
|
|||||||
# calculate the keys
|
# calculate the keys
|
||||||
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
|
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
|
||||||
if drm_count == 0:
|
if drm_count == 0:
|
||||||
raise DrmException("Encryption not initialised. Must be opened with Mobipocket Reader first.")
|
raise DrmException(u"Encryption not initialised. Must be opened with Mobipocket Reader first.")
|
||||||
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
|
found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
|
||||||
if not found_key:
|
if not found_key:
|
||||||
raise DrmException("No key found in {0:d} keys tried.".format(len(goodpids)))
|
raise DrmException(u"No key found in {0:d} keys tried.".format(len(goodpids)))
|
||||||
# kill the drm keys
|
# kill the drm keys
|
||||||
self.patchSection(0, b'\0' * drm_size, drm_ptr)
|
self.patchSection(0, '\0' * drm_size, drm_ptr)
|
||||||
# kill the drm pointers
|
# kill the drm pointers
|
||||||
self.patchSection(0, b'\xff' * 4 + b'\0' * 12, 0xA8)
|
self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8)
|
||||||
|
|
||||||
if pid=='00000000':
|
if pid=='00000000':
|
||||||
print("File has default encryption, no specific key needed.")
|
print(u"File has default encryption, no specific key needed.")
|
||||||
else:
|
else:
|
||||||
print("File is encoded with PID {0}.".format(checksumPid(pid)))
|
print(u"File is encoded with PID {0}.".format(checksumPid(pid)))
|
||||||
|
|
||||||
# clear the crypto type
|
# clear the crypto type
|
||||||
self.patchSection(0, b'\0' * 2, 0xC)
|
self.patchSection(0, "\0" * 2, 0xC)
|
||||||
|
|
||||||
# decrypt sections
|
# decrypt sections
|
||||||
print("Decrypting. Please wait . . .", end=' ')
|
print(u"Decrypting. Please wait . . .", end=' ')
|
||||||
mobidataList = []
|
mobidataList = []
|
||||||
mobidataList.append(self.data_file[:self.sections[1][0]])
|
mobidataList.append(self.data_file[:self.sections[1][0]])
|
||||||
for i in range(1, self.records+1):
|
for i in xrange(1, self.records+1):
|
||||||
data = self.loadSection(i)
|
data = self.loadSection(i)
|
||||||
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
|
extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
|
||||||
if i%100 == 0:
|
if i%100 == 0:
|
||||||
print(".", end=' ')
|
print(u".", end=' ')
|
||||||
# print "record %d, extra_size %d" %(i,extra_size)
|
# print "record %d, extra_size %d" %(i,extra_size)
|
||||||
decoded_data = PC1(found_key, data[0:len(data) - extra_size])
|
decoded_data = PC1(found_key, data[0:len(data) - extra_size])
|
||||||
if i==1:
|
if i==1:
|
||||||
@@ -505,13 +501,13 @@ class MobiBook:
|
|||||||
mobidataList.append(data[-extra_size:])
|
mobidataList.append(data[-extra_size:])
|
||||||
if self.num_sections > self.records+1:
|
if self.num_sections > self.records+1:
|
||||||
mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
|
mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
|
||||||
self.mobi_data = b''.join(mobidataList)
|
self.mobi_data = "".join(mobidataList)
|
||||||
print("done")
|
print(u"done")
|
||||||
return
|
return
|
||||||
|
|
||||||
def getUnencryptedBook(infile,pidlist):
|
def getUnencryptedBook(infile,pidlist):
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
raise DrmException("Input File Not Found.")
|
raise DrmException(u"Input File Not Found.")
|
||||||
book = MobiBook(infile)
|
book = MobiBook(infile)
|
||||||
book.processBook(pidlist)
|
book.processBook(pidlist)
|
||||||
return book.mobi_data
|
return book.mobi_data
|
||||||
@@ -521,24 +517,23 @@ def cli_main():
|
|||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if len(argv)<3 or len(argv)>4:
|
if len(argv)<3 or len(argv)>4:
|
||||||
print("MobiDeDrm v{0:s}.\nCopyright © 2008-2020 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
print(u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__))
|
||||||
print("Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks")
|
print(u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks")
|
||||||
print("Usage:")
|
print(u"Usage:")
|
||||||
print(" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname))
|
print(u" {0} <infile> <outfile> [<Comma separated list of PIDs to try>]".format(progname))
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
infile = argv[1]
|
infile = argv[1]
|
||||||
outfile = argv[2]
|
outfile = argv[2]
|
||||||
if len(argv) == 4:
|
if len(argv) == 4:
|
||||||
# convert from unicode to bytearray before splitting.
|
pidlist = argv[3].split(',')
|
||||||
pidlist = argv[3].encode('utf-8').split(b',')
|
|
||||||
else:
|
else:
|
||||||
pidlist = []
|
pidlist = []
|
||||||
try:
|
try:
|
||||||
stripped_file = getUnencryptedBook(infile, pidlist)
|
stripped_file = getUnencryptedBook(infile, pidlist)
|
||||||
open(outfile, 'wb').write(stripped_file)
|
file(outfile, 'wb').write(stripped_file)
|
||||||
except DrmException as e:
|
except DrmException, e:
|
||||||
print("MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0]))
|
print(u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0]))
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
56
DeDRM_plugin/prefs.py
Executable file → Normal file
56
DeDRM_plugin/prefs.py
Executable file → Normal file
@@ -1,12 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
# Standard Python modules.
|
# Standard Python modules.
|
||||||
import os, sys, re, hashlib
|
import os, sys, re, hashlib
|
||||||
import codecs, json
|
import json
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from calibre.utils.config import dynamic, config_dir, JSONConfig
|
from calibre.utils.config import dynamic, config_dir, JSONConfig
|
||||||
@@ -15,7 +15,7 @@ from calibre.constants import iswindows, isosx
|
|||||||
|
|
||||||
class DeDRM_Prefs():
|
class DeDRM_Prefs():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
JSON_PATH = os.path.join("plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
|
JSON_PATH = os.path.join(u"plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
|
||||||
self.dedrmprefs = JSONConfig(JSON_PATH)
|
self.dedrmprefs = JSONConfig(JSON_PATH)
|
||||||
|
|
||||||
self.dedrmprefs.defaults['configured'] = False
|
self.dedrmprefs.defaults['configured'] = False
|
||||||
@@ -98,12 +98,12 @@ def convertprefs(always = False):
|
|||||||
try:
|
try:
|
||||||
name, ccn = keystring.split(',')
|
name, ccn = keystring.split(',')
|
||||||
# Generate Barnes & Noble EPUB user key from name and credit card number.
|
# Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||||
keyname = "{0}_{1}".format(name.strip(),ccn.strip()[-4:])
|
keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:])
|
||||||
keyvalue = generate_key(name, ccn)
|
keyvalue = generate_key(name, ccn)
|
||||||
userkeys.append([keyname,keyvalue])
|
userkeys.append([keyname,keyvalue])
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print(e.args[0])
|
print e.args[0]
|
||||||
pass
|
pass
|
||||||
return userkeys
|
return userkeys
|
||||||
|
|
||||||
@@ -115,12 +115,12 @@ def convertprefs(always = False):
|
|||||||
try:
|
try:
|
||||||
name, cc = keystring.split(',')
|
name, cc = keystring.split(',')
|
||||||
# Generate eReader user key from name and credit card number.
|
# Generate eReader user key from name and credit card number.
|
||||||
keyname = "{0}_{1}".format(name.strip(),cc.strip()[-4:])
|
keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:])
|
||||||
keyvalue = codecs.encode(getuser_key(name,cc),'hex')
|
keyvalue = getuser_key(name,cc).encode('hex')
|
||||||
userkeys.append([keyname,keyvalue])
|
userkeys.append([keyname,keyvalue])
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print(e.args[0])
|
print e.args[0]
|
||||||
pass
|
pass
|
||||||
return userkeys
|
return userkeys
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ def convertprefs(always = False):
|
|||||||
key = os.path.splitext(filename)[0]
|
key = os.path.splitext(filename)[0]
|
||||||
value = open(fpath, 'rb').read()
|
value = open(fpath, 'rb').read()
|
||||||
if encoding is not None:
|
if encoding is not None:
|
||||||
value = codecs.encode(value,encoding)
|
value = value.encode(encoding)
|
||||||
userkeys.append([key,value])
|
userkeys.append([key,value])
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -161,15 +161,15 @@ def convertprefs(always = False):
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
print("{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
|
|
||||||
IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM"
|
IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM"
|
||||||
EREADERPLUGINNAME = "eReader PDB 2 PML"
|
EREADERPLUGINNAME = "eReader PDB 2 PML"
|
||||||
OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM"
|
OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM"
|
||||||
|
|
||||||
# get prefs from older tools
|
# get prefs from older tools
|
||||||
kindleprefs = JSONConfig(os.path.join("plugins", "K4MobiDeDRM"))
|
kindleprefs = JSONConfig(os.path.join(u"plugins", u"K4MobiDeDRM"))
|
||||||
ignobleprefs = JSONConfig(os.path.join("plugins", "ignoble_epub_dedrm"))
|
ignobleprefs = JSONConfig(os.path.join(u"plugins", u"ignoble_epub_dedrm"))
|
||||||
|
|
||||||
# Handle the old ignoble plugin's customization string by converting the
|
# Handle the old ignoble plugin's customization string by converting the
|
||||||
# old string to stored keys... get that personal data out of plain sight.
|
# old string to stored keys... get that personal data out of plain sight.
|
||||||
@@ -177,7 +177,7 @@ def convertprefs(always = False):
|
|||||||
sc = config['plugin_customization']
|
sc = config['plugin_customization']
|
||||||
val = sc.pop(IGNOBLEPLUGINNAME, None)
|
val = sc.pop(IGNOBLEPLUGINNAME, None)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
print("{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
priorkeycount = len(dedrmprefs['bandnkeys'])
|
priorkeycount = len(dedrmprefs['bandnkeys'])
|
||||||
userkeys = parseIgnobleString(str(val))
|
userkeys = parseIgnobleString(str(val))
|
||||||
for keypair in userkeys:
|
for keypair in userkeys:
|
||||||
@@ -185,7 +185,7 @@ def convertprefs(always = False):
|
|||||||
value = keypair[1]
|
value = keypair[1]
|
||||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||||
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
||||||
print("{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key" if addedkeycount==1 else "keys"))
|
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||||
# Make the json write all the prefs to disk
|
# Make the json write all the prefs to disk
|
||||||
dedrmprefs.writeprefs(False)
|
dedrmprefs.writeprefs(False)
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ def convertprefs(always = False):
|
|||||||
# old string to stored keys... get that personal data out of plain sight.
|
# old string to stored keys... get that personal data out of plain sight.
|
||||||
val = sc.pop(EREADERPLUGINNAME, None)
|
val = sc.pop(EREADERPLUGINNAME, None)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
print("{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
priorkeycount = len(dedrmprefs['ereaderkeys'])
|
priorkeycount = len(dedrmprefs['ereaderkeys'])
|
||||||
userkeys = parseeReaderString(str(val))
|
userkeys = parseeReaderString(str(val))
|
||||||
for keypair in userkeys:
|
for keypair in userkeys:
|
||||||
@@ -201,14 +201,14 @@ def convertprefs(always = False):
|
|||||||
value = keypair[1]
|
value = keypair[1]
|
||||||
dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value)
|
dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value)
|
||||||
addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount
|
addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount
|
||||||
print("{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key" if addedkeycount==1 else "keys"))
|
print u"{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||||
# Make the json write all the prefs to disk
|
# Make the json write all the prefs to disk
|
||||||
dedrmprefs.writeprefs(False)
|
dedrmprefs.writeprefs(False)
|
||||||
|
|
||||||
# get old Kindle plugin configuration string
|
# get old Kindle plugin configuration string
|
||||||
val = sc.pop(OLDKINDLEPLUGINNAME, None)
|
val = sc.pop(OLDKINDLEPLUGINNAME, None)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
print("{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
priorpidcount = len(dedrmprefs['pids'])
|
priorpidcount = len(dedrmprefs['pids'])
|
||||||
priorserialcount = len(dedrmprefs['serials'])
|
priorserialcount = len(dedrmprefs['serials'])
|
||||||
pids, serials = parseKindleString(val)
|
pids, serials = parseKindleString(val)
|
||||||
@@ -218,7 +218,7 @@ def convertprefs(always = False):
|
|||||||
dedrmprefs.addvaluetoprefs('serials',serial)
|
dedrmprefs.addvaluetoprefs('serials',serial)
|
||||||
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
||||||
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
||||||
print("{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, "PID" if addedpidcount==1 else "PIDs", addedserialcount, "serial number" if addedserialcount==1 else "serial numbers"))
|
print u"{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs", addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
|
||||||
# Make the json write all the prefs to disk
|
# Make the json write all the prefs to disk
|
||||||
dedrmprefs.writeprefs(False)
|
dedrmprefs.writeprefs(False)
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ def convertprefs(always = False):
|
|||||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
|
||||||
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
|
||||||
if addedkeycount > 0:
|
if addedkeycount > 0:
|
||||||
print("{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key file" if addedkeycount==1 else "key files"))
|
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key file" if addedkeycount==1 else u"key files")
|
||||||
# Make the json write all the prefs to disk
|
# Make the json write all the prefs to disk
|
||||||
dedrmprefs.writeprefs(False)
|
dedrmprefs.writeprefs(False)
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ def convertprefs(always = False):
|
|||||||
dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value)
|
dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value)
|
||||||
addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount
|
addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount
|
||||||
if addedkeycount > 0:
|
if addedkeycount > 0:
|
||||||
print("{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "keyfile" if addedkeycount==1 else "keyfiles"))
|
print u"{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"keyfile" if addedkeycount==1 else u"keyfiles")
|
||||||
# Make the json write all the prefs to disk
|
# Make the json write all the prefs to disk
|
||||||
dedrmprefs.writeprefs(False)
|
dedrmprefs.writeprefs(False)
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ def convertprefs(always = False):
|
|||||||
addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount
|
addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount
|
||||||
# no need to delete old prefs, since they contain no recoverable private data
|
# no need to delete old prefs, since they contain no recoverable private data
|
||||||
if addedkeycount > 0:
|
if addedkeycount > 0:
|
||||||
print("{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, "key" if addedkeycount==1 else "keys"))
|
print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
|
||||||
# Make the json write all the prefs to disk
|
# Make the json write all the prefs to disk
|
||||||
dedrmprefs.writeprefs(False)
|
dedrmprefs.writeprefs(False)
|
||||||
|
|
||||||
@@ -277,19 +277,19 @@ def convertprefs(always = False):
|
|||||||
dedrmprefs.addvaluetoprefs('serials',serial)
|
dedrmprefs.addvaluetoprefs('serials',serial)
|
||||||
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
addedpidcount = len(dedrmprefs['pids']) - priorpidcount
|
||||||
if addedpidcount > 0:
|
if addedpidcount > 0:
|
||||||
print("{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, "PID" if addedpidcount==1 else "PIDs"))
|
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs")
|
||||||
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
addedserialcount = len(dedrmprefs['serials']) - priorserialcount
|
||||||
if addedserialcount > 0:
|
if addedserialcount > 0:
|
||||||
print("{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, "serial number" if addedserialcount==1 else "serial numbers"))
|
print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
|
||||||
try:
|
try:
|
||||||
if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "":
|
if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "":
|
||||||
dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix'])
|
dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix'])
|
||||||
dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix'])
|
dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix'])
|
||||||
print("{0} v{1}: WINEPREFIX ‘(2)’ imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix']))
|
print u"{0} v{1}: WINEPREFIX ‘(2)’ imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix'])
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
# Make the json write all the prefs to disk
|
# Make the json write all the prefs to disk
|
||||||
dedrmprefs.writeprefs()
|
dedrmprefs.writeprefs()
|
||||||
print("{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print u"{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import ineptepub
|
||||||
|
import ignobleepub
|
||||||
|
import epubtest
|
||||||
|
import zipfix
|
||||||
|
import ineptpdf
|
||||||
|
import erdr2pml
|
||||||
|
import k4mobidedrm
|
||||||
import traceback
|
import traceback
|
||||||
import calibre_plugins.dedrm.ineptepub
|
|
||||||
import calibre_plugins.dedrm.ignobleepub
|
|
||||||
import calibre_plugins.dedrm.epubtest
|
|
||||||
import calibre_plugins.dedrm.zipfix
|
|
||||||
import calibre_plugins.dedrm.ineptpdf
|
|
||||||
import calibre_plugins.dedrm.erdr2pml
|
|
||||||
import calibre_plugins.dedrm.k4mobidedrm
|
|
||||||
|
|
||||||
def decryptepub(infile, outdir, rscpath):
|
def decryptepub(infile, outdir, rscpath):
|
||||||
errlog = ''
|
errlog = ''
|
||||||
@@ -46,7 +46,7 @@ def decryptepub(infile, outdir, rscpath):
|
|||||||
if rv == 0:
|
if rv == 0:
|
||||||
print("Decrypted Adobe ePub with key file {0}".format(filename))
|
print("Decrypted Adobe ePub with key file {0}".format(filename))
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
errlog += traceback.format_exc()
|
errlog += traceback.format_exc()
|
||||||
errlog += str(e)
|
errlog += str(e)
|
||||||
rv = 1
|
rv = 1
|
||||||
@@ -66,7 +66,7 @@ def decryptepub(infile, outdir, rscpath):
|
|||||||
if rv == 0:
|
if rv == 0:
|
||||||
print("Decrypted B&N ePub with key file {0}".format(filename))
|
print("Decrypted B&N ePub with key file {0}".format(filename))
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
errlog += traceback.format_exc()
|
errlog += traceback.format_exc()
|
||||||
errlog += str(e)
|
errlog += str(e)
|
||||||
rv = 1
|
rv = 1
|
||||||
@@ -104,7 +104,7 @@ def decryptpdf(infile, outdir, rscpath):
|
|||||||
rv = ineptpdf.decryptBook(userkey, infile, outfile)
|
rv = ineptpdf.decryptBook(userkey, infile, outfile)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
errlog += traceback.format_exc()
|
errlog += traceback.format_exc()
|
||||||
errlog += str(e)
|
errlog += str(e)
|
||||||
rv = 1
|
rv = 1
|
||||||
@@ -132,7 +132,7 @@ def decryptpdb(infile, outdir, rscpath):
|
|||||||
return 1
|
return 1
|
||||||
try:
|
try:
|
||||||
rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8))
|
rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8))
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
errlog += traceback.format_exc()
|
errlog += traceback.format_exc()
|
||||||
errlog += str(e)
|
errlog += str(e)
|
||||||
rv = 1
|
rv = 1
|
||||||
@@ -193,7 +193,7 @@ def decryptk4mobi(infile, outdir, rscpath):
|
|||||||
androidFiles.append(dpath)
|
androidFiles.append(dpath)
|
||||||
try:
|
try:
|
||||||
rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serialnums, pidnums)
|
rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serialnums, pidnums)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
errlog += traceback.format_exc()
|
errlog += traceback.format_exc()
|
||||||
errlog += str(e)
|
errlog += str(e)
|
||||||
rv = 1
|
rv = 1
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
import tkinter
|
import Tkinter
|
||||||
import tkinter.constants
|
import Tkconstants
|
||||||
|
|
||||||
# basic scrolled text widget
|
# basic scrolled text widget
|
||||||
class ScrolledText(tkinter.Text):
|
class ScrolledText(Tkinter.Text):
|
||||||
def __init__(self, master=None, **kw):
|
def __init__(self, master=None, **kw):
|
||||||
self.frame = tkinter.Frame(master)
|
self.frame = Tkinter.Frame(master)
|
||||||
self.vbar = tkinter.Scrollbar(self.frame)
|
self.vbar = Tkinter.Scrollbar(self.frame)
|
||||||
self.vbar.pack(side=tkinter.constants.RIGHT, fill=tkinter.constants.Y)
|
self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y)
|
||||||
kw.update({'yscrollcommand': self.vbar.set})
|
kw.update({'yscrollcommand': self.vbar.set})
|
||||||
tkinter.Text.__init__(self, self.frame, **kw)
|
Tkinter.Text.__init__(self, self.frame, **kw)
|
||||||
self.pack(side=tkinter.constants.LEFT, fill=tkinter.constants.BOTH, expand=True)
|
self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True)
|
||||||
self.vbar['command'] = self.yview
|
self.vbar['command'] = self.yview
|
||||||
# Copy geometry methods of self.frame without overriding Text
|
# Copy geometry methods of self.frame without overriding Text
|
||||||
# methods = hack!
|
# methods = hack!
|
||||||
text_meths = list(vars(tkinter.Text).keys())
|
text_meths = vars(Tkinter.Text).keys()
|
||||||
methods = list(vars(tkinter.Pack).keys()) + list(vars(tkinter.Grid).keys()) + list(vars(tkinter.Place).keys())
|
methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys()
|
||||||
methods = set(methods).difference(text_meths)
|
methods = set(methods).difference(text_meths)
|
||||||
for m in methods:
|
for m in methods:
|
||||||
if m[0] != '_' and m != 'config' and m != 'configure':
|
if m[0] != '_' and m != 'config' and m != 'configure':
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -20,7 +19,7 @@ class SimplePrefs(object):
|
|||||||
self.file2key[filename] = key
|
self.file2key[filename] = key
|
||||||
self.target = target + 'Prefs'
|
self.target = target + 'Prefs'
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
import winreg
|
import _winreg as winreg
|
||||||
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]
|
||||||
prefdir = path + os.sep + self.target
|
prefdir = path + os.sep + self.target
|
||||||
@@ -47,7 +46,7 @@ class SimplePrefs(object):
|
|||||||
try :
|
try :
|
||||||
data = file(filepath,'rb').read()
|
data = file(filepath,'rb').read()
|
||||||
self.prefs[key] = data
|
self.prefs[key] = data
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def getPreferences(self):
|
def getPreferences(self):
|
||||||
@@ -72,7 +71,7 @@ class SimplePrefs(object):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
file(filepath,'wb').write(data)
|
file(filepath,'wb').write(data)
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
pass
|
pass
|
||||||
self.prefs = newprefs
|
self.prefs = newprefs
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
||||||
# For use with Topaz Scripts Version 2.6
|
# For use with Topaz Scripts Version 2.6
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
import csv
|
import csv
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -15,36 +15,36 @@ debug = False
|
|||||||
|
|
||||||
class DocParser(object):
|
class DocParser(object):
|
||||||
def __init__(self, flatxml, fontsize, ph, pw):
|
def __init__(self, flatxml, fontsize, ph, pw):
|
||||||
self.flatdoc = flatxml.split(b'\n')
|
self.flatdoc = flatxml.split('\n')
|
||||||
self.fontsize = int(fontsize)
|
self.fontsize = int(fontsize)
|
||||||
self.ph = int(ph) * 1.0
|
self.ph = int(ph) * 1.0
|
||||||
self.pw = int(pw) * 1.0
|
self.pw = int(pw) * 1.0
|
||||||
|
|
||||||
stags = {
|
stags = {
|
||||||
b'paragraph' : 'p',
|
'paragraph' : 'p',
|
||||||
b'graphic' : '.graphic'
|
'graphic' : '.graphic'
|
||||||
}
|
}
|
||||||
|
|
||||||
attr_val_map = {
|
attr_val_map = {
|
||||||
b'hang' : 'text-indent: ',
|
'hang' : 'text-indent: ',
|
||||||
b'indent' : 'text-indent: ',
|
'indent' : 'text-indent: ',
|
||||||
b'line-space' : 'line-height: ',
|
'line-space' : 'line-height: ',
|
||||||
b'margin-bottom' : 'margin-bottom: ',
|
'margin-bottom' : 'margin-bottom: ',
|
||||||
b'margin-left' : 'margin-left: ',
|
'margin-left' : 'margin-left: ',
|
||||||
b'margin-right' : 'margin-right: ',
|
'margin-right' : 'margin-right: ',
|
||||||
b'margin-top' : 'margin-top: ',
|
'margin-top' : 'margin-top: ',
|
||||||
b'space-after' : 'padding-bottom: ',
|
'space-after' : 'padding-bottom: ',
|
||||||
}
|
}
|
||||||
|
|
||||||
attr_str_map = {
|
attr_str_map = {
|
||||||
b'align-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
|
'align-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
|
||||||
b'align-left' : 'text-align: left;',
|
'align-left' : 'text-align: left;',
|
||||||
b'align-right' : 'text-align: right;',
|
'align-right' : 'text-align: right;',
|
||||||
b'align-justify' : 'text-align: justify;',
|
'align-justify' : 'text-align: justify;',
|
||||||
b'display-inline' : 'display: inline;',
|
'display-inline' : 'display: inline;',
|
||||||
b'pos-left' : 'text-align: left;',
|
'pos-left' : 'text-align: left;',
|
||||||
b'pos-right' : 'text-align: right;',
|
'pos-right' : 'text-align: right;',
|
||||||
b'pos-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
|
'pos-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -58,15 +58,13 @@ class DocParser(object):
|
|||||||
else:
|
else:
|
||||||
end = min(cnt,end)
|
end = min(cnt,end)
|
||||||
foundat = -1
|
foundat = -1
|
||||||
for j in range(pos, end):
|
for j in xrange(pos, end):
|
||||||
item = docList[j]
|
item = docList[j]
|
||||||
if item.find(b'=') >= 0:
|
if item.find('=') >= 0:
|
||||||
(name, argres) = item.split(b'=',1)
|
(name, argres) = item.split('=',1)
|
||||||
else :
|
else :
|
||||||
name = item
|
name = item
|
||||||
argres = b''
|
argres = ''
|
||||||
if (isinstance(tagpath,str)):
|
|
||||||
tagpath = tagpath.encode('utf-8')
|
|
||||||
if name.endswith(tagpath) :
|
if name.endswith(tagpath) :
|
||||||
result = argres
|
result = argres
|
||||||
foundat = j
|
foundat = j
|
||||||
@@ -78,7 +76,7 @@ class DocParser(object):
|
|||||||
def posinDoc(self, tagpath):
|
def posinDoc(self, tagpath):
|
||||||
startpos = []
|
startpos = []
|
||||||
pos = 0
|
pos = 0
|
||||||
res = b""
|
res = ""
|
||||||
while res != None :
|
while res != None :
|
||||||
(foundpos, res) = self.findinDoc(tagpath, pos, -1)
|
(foundpos, res) = self.findinDoc(tagpath, pos, -1)
|
||||||
if res != None :
|
if res != None :
|
||||||
@@ -89,11 +87,11 @@ class DocParser(object):
|
|||||||
# returns a vector of integers for the tagpath
|
# returns a vector of integers for the tagpath
|
||||||
def getData(self, tagpath, pos, end, clean=False):
|
def getData(self, tagpath, pos, end, clean=False):
|
||||||
if clean:
|
if clean:
|
||||||
digits_only = re.compile(rb'''([0-9]+)''')
|
digits_only = re.compile(r'''([0-9]+)''')
|
||||||
argres=[]
|
argres=[]
|
||||||
(foundat, argt) = self.findinDoc(tagpath, pos, end)
|
(foundat, argt) = self.findinDoc(tagpath, pos, end)
|
||||||
if (argt != None) and (len(argt) > 0) :
|
if (argt != None) and (len(argt) > 0) :
|
||||||
argList = argt.split(b'|')
|
argList = argt.split('|')
|
||||||
for strval in argList:
|
for strval in argList:
|
||||||
if clean:
|
if clean:
|
||||||
m = re.search(digits_only, strval)
|
m = re.search(digits_only, strval)
|
||||||
@@ -111,42 +109,42 @@ class DocParser(object):
|
|||||||
csspage += '.cl-justify { text-align: justify; }\n'
|
csspage += '.cl-justify { text-align: justify; }\n'
|
||||||
|
|
||||||
# generate a list of each <style> starting point in the stylesheet
|
# generate a list of each <style> starting point in the stylesheet
|
||||||
styleList= self.posinDoc(b'book.stylesheet.style')
|
styleList= self.posinDoc('book.stylesheet.style')
|
||||||
stylecnt = len(styleList)
|
stylecnt = len(styleList)
|
||||||
styleList.append(-1)
|
styleList.append(-1)
|
||||||
|
|
||||||
# process each style converting what you can
|
# process each style converting what you can
|
||||||
|
|
||||||
if debug: print(' ', 'Processing styles.')
|
if debug: print(' ', 'Processing styles.')
|
||||||
for j in range(stylecnt):
|
for j in xrange(stylecnt):
|
||||||
if debug: print(' ', 'Processing style %d' %(j))
|
if debug: print(' ', 'Processing style %d' %(j))
|
||||||
start = styleList[j]
|
start = styleList[j]
|
||||||
end = styleList[j+1]
|
end = styleList[j+1]
|
||||||
|
|
||||||
(pos, tag) = self.findinDoc(b'style._tag',start,end)
|
(pos, tag) = self.findinDoc('style._tag',start,end)
|
||||||
if tag == None :
|
if tag == None :
|
||||||
(pos, tag) = self.findinDoc(b'style.type',start,end)
|
(pos, tag) = self.findinDoc('style.type',start,end)
|
||||||
|
|
||||||
# Is this something we know how to convert to css
|
# Is this something we know how to convert to css
|
||||||
if tag in self.stags :
|
if tag in self.stags :
|
||||||
|
|
||||||
# get the style class
|
# get the style class
|
||||||
(pos, sclass) = self.findinDoc(b'style.class',start,end)
|
(pos, sclass) = self.findinDoc('style.class',start,end)
|
||||||
if sclass != None:
|
if sclass != None:
|
||||||
sclass = sclass.replace(b' ',b'-')
|
sclass = sclass.replace(' ','-')
|
||||||
sclass = b'.cl-' + sclass.lower()
|
sclass = '.cl-' + sclass.lower()
|
||||||
else :
|
else :
|
||||||
sclass = b''
|
sclass = ''
|
||||||
|
|
||||||
if debug: print('sclass', sclass)
|
if debug: print('sclass', sclass)
|
||||||
|
|
||||||
# check for any "after class" specifiers
|
# check for any "after class" specifiers
|
||||||
(pos, aftclass) = self.findinDoc(b'style._after_class',start,end)
|
(pos, aftclass) = self.findinDoc('style._after_class',start,end)
|
||||||
if aftclass != None:
|
if aftclass != None:
|
||||||
aftclass = aftclass.replace(b' ',b'-')
|
aftclass = aftclass.replace(' ','-')
|
||||||
aftclass = b'.cl-' + aftclass.lower()
|
aftclass = '.cl-' + aftclass.lower()
|
||||||
else :
|
else :
|
||||||
aftclass = b''
|
aftclass = ''
|
||||||
|
|
||||||
if debug: print('aftclass', aftclass)
|
if debug: print('aftclass', aftclass)
|
||||||
|
|
||||||
@@ -154,37 +152,34 @@ class DocParser(object):
|
|||||||
|
|
||||||
while True :
|
while True :
|
||||||
|
|
||||||
(pos1, attr) = self.findinDoc(b'style.rule.attr', start, end)
|
(pos1, attr) = self.findinDoc('style.rule.attr', start, end)
|
||||||
(pos2, val) = self.findinDoc(b'style.rule.value', start, end)
|
(pos2, val) = self.findinDoc('style.rule.value', start, end)
|
||||||
|
|
||||||
if debug: print('attr', attr)
|
if debug: print('attr', attr)
|
||||||
if debug: print('val', val)
|
if debug: print('val', val)
|
||||||
|
|
||||||
if attr == None : break
|
if attr == None : break
|
||||||
|
|
||||||
if (attr == b'display') or (attr == b'pos') or (attr == b'align'):
|
if (attr == 'display') or (attr == 'pos') or (attr == 'align'):
|
||||||
# handle text based attributess
|
# handle text based attributess
|
||||||
attr = attr + b'-' + val
|
attr = attr + '-' + val
|
||||||
if attr in self.attr_str_map :
|
if attr in self.attr_str_map :
|
||||||
cssargs[attr] = (self.attr_str_map[attr], b'')
|
cssargs[attr] = (self.attr_str_map[attr], '')
|
||||||
else :
|
else :
|
||||||
# handle value based attributes
|
# handle value based attributes
|
||||||
if attr in self.attr_val_map :
|
if attr in self.attr_val_map :
|
||||||
name = self.attr_val_map[attr]
|
name = self.attr_val_map[attr]
|
||||||
if attr in (b'margin-bottom', b'margin-top', b'space-after') :
|
if attr in ('margin-bottom', 'margin-top', 'space-after') :
|
||||||
scale = self.ph
|
scale = self.ph
|
||||||
elif attr in (b'margin-right', b'indent', b'margin-left', b'hang') :
|
elif attr in ('margin-right', 'indent', 'margin-left', 'hang') :
|
||||||
scale = self.pw
|
scale = self.pw
|
||||||
elif attr == b'line-space':
|
elif attr == 'line-space':
|
||||||
scale = self.fontsize * 2.0
|
scale = self.fontsize * 2.0
|
||||||
else:
|
|
||||||
print("Scale not defined!")
|
|
||||||
scale = 1.0
|
|
||||||
|
|
||||||
if val == "":
|
if val == "":
|
||||||
val = 0
|
val = 0
|
||||||
|
|
||||||
if not ((attr == b'hang') and (int(val) == 0)):
|
if not ((attr == 'hang') and (int(val) == 0)):
|
||||||
try:
|
try:
|
||||||
f = float(val)
|
f = float(val)
|
||||||
except:
|
except:
|
||||||
@@ -203,32 +198,32 @@ class DocParser(object):
|
|||||||
if debug: print('keeping style')
|
if debug: print('keeping style')
|
||||||
# make sure line-space does not go below 100% or above 300% since
|
# make sure line-space does not go below 100% or above 300% since
|
||||||
# it can be wacky in some styles
|
# it can be wacky in some styles
|
||||||
if b'line-space' in cssargs:
|
if 'line-space' in cssargs:
|
||||||
seg = cssargs[b'line-space'][0]
|
seg = cssargs['line-space'][0]
|
||||||
val = cssargs[b'line-space'][1]
|
val = cssargs['line-space'][1]
|
||||||
if val < 1.0: val = 1.0
|
if val < 1.0: val = 1.0
|
||||||
if val > 3.0: val = 3.0
|
if val > 3.0: val = 3.0
|
||||||
del cssargs[b'line-space']
|
del cssargs['line-space']
|
||||||
cssargs[b'line-space'] = (self.attr_val_map[b'line-space'], val)
|
cssargs['line-space'] = (self.attr_val_map['line-space'], val)
|
||||||
|
|
||||||
|
|
||||||
# handle modifications for css style hanging indents
|
# handle modifications for css style hanging indents
|
||||||
if b'hang' in cssargs:
|
if 'hang' in cssargs:
|
||||||
hseg = cssargs[b'hang'][0]
|
hseg = cssargs['hang'][0]
|
||||||
hval = cssargs[b'hang'][1]
|
hval = cssargs['hang'][1]
|
||||||
del cssargs[b'hang']
|
del cssargs['hang']
|
||||||
cssargs[b'hang'] = (self.attr_val_map[b'hang'], -hval)
|
cssargs['hang'] = (self.attr_val_map['hang'], -hval)
|
||||||
mval = 0
|
mval = 0
|
||||||
mseg = 'margin-left: '
|
mseg = 'margin-left: '
|
||||||
mval = hval
|
mval = hval
|
||||||
if b'margin-left' in cssargs:
|
if 'margin-left' in cssargs:
|
||||||
mseg = cssargs[b'margin-left'][0]
|
mseg = cssargs['margin-left'][0]
|
||||||
mval = cssargs[b'margin-left'][1]
|
mval = cssargs['margin-left'][1]
|
||||||
if mval < 0: mval = 0
|
if mval < 0: mval = 0
|
||||||
mval = hval + mval
|
mval = hval + mval
|
||||||
cssargs[b'margin-left'] = (mseg, mval)
|
cssargs['margin-left'] = (mseg, mval)
|
||||||
if b'indent' in cssargs:
|
if 'indent' in cssargs:
|
||||||
del cssargs[b'indent']
|
del cssargs['indent']
|
||||||
|
|
||||||
cssline = sclass + ' { '
|
cssline = sclass + ' { '
|
||||||
for key in iter(cssargs):
|
for key in iter(cssargs):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# topazextract.py
|
# topazextract.py
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 4.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 5.0 - Fixed potential unicode problem with command line interface
|
# 5.0 - Fixed potential unicode problem with command line interface
|
||||||
# 6.0 - Added Python 3 compatibility for calibre 5.0
|
|
||||||
|
|
||||||
__version__ = '6.0'
|
from __future__ import print_function
|
||||||
|
__version__ = '5.0'
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os, csv, getopt
|
import os, csv, getopt
|
||||||
@@ -17,14 +17,8 @@ import zlib, zipfile, tempfile, shutil
|
|||||||
import traceback
|
import traceback
|
||||||
from struct import pack
|
from struct import pack
|
||||||
from struct import unpack
|
from struct import unpack
|
||||||
try:
|
from alfcrypto import Topaz_Cipher
|
||||||
from calibre_plugins.dedrm.alfcrypto import Topaz_Cipher
|
|
||||||
except:
|
|
||||||
from alfcrypto import Topaz_Cipher
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
|
||||||
# and also make sure that any unicode strings get
|
|
||||||
# encoded using "replace" before writing them.
|
|
||||||
class SafeUnbuffered:
|
class SafeUnbuffered:
|
||||||
def __init__(self, stream):
|
def __init__(self, stream):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
@@ -32,11 +26,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -71,13 +64,15 @@ def unicode_argv():
|
|||||||
# Remove Python executable and commands if present
|
# Remove Python executable and commands if present
|
||||||
start = argc.value - len(sys.argv)
|
start = argc.value - len(sys.argv)
|
||||||
return [argv[i] for i in
|
return [argv[i] for i in
|
||||||
range(start, argc.value)]
|
xrange(start, argc.value)]
|
||||||
# if we don't have any arguments at all, just pass back script name
|
# if we don't have any arguments at all, just pass back script name
|
||||||
# this should never happen
|
# this should never happen
|
||||||
return ["mobidedrm.py"]
|
return [u"mobidedrm.py"]
|
||||||
else:
|
else:
|
||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding
|
||||||
return [arg if isinstance(arg, str) else str(arg, argvencoding) for arg in sys.argv]
|
if argvencoding == None:
|
||||||
|
argvencoding = 'utf-8'
|
||||||
|
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
#global switch
|
#global switch
|
||||||
debug = False
|
debug = False
|
||||||
@@ -97,7 +92,7 @@ class DrmException(Exception):
|
|||||||
# recursive zip creation support routine
|
# recursive zip creation support routine
|
||||||
def zipUpDir(myzip, tdir, localname):
|
def zipUpDir(myzip, tdir, localname):
|
||||||
currentdir = tdir
|
currentdir = tdir
|
||||||
if localname != "":
|
if localname != u"":
|
||||||
currentdir = os.path.join(currentdir,localname)
|
currentdir = os.path.join(currentdir,localname)
|
||||||
list = os.listdir(currentdir)
|
list = os.listdir(currentdir)
|
||||||
for file in list:
|
for file in list:
|
||||||
@@ -173,21 +168,21 @@ def decryptRecord(data,PID):
|
|||||||
def decryptDkeyRecord(data,PID):
|
def decryptDkeyRecord(data,PID):
|
||||||
record = decryptRecord(data,PID)
|
record = decryptRecord(data,PID)
|
||||||
fields = unpack('3sB8sB8s3s',record)
|
fields = unpack('3sB8sB8s3s',record)
|
||||||
if fields[0] != b'PID' or fields[5] != b'pid' :
|
if fields[0] != 'PID' or fields[5] != 'pid' :
|
||||||
raise DrmException("Didn't find PID magic numbers in record")
|
raise DrmException(u"Didn't find PID magic numbers in record")
|
||||||
elif fields[1] != 8 or fields[3] != 8 :
|
elif fields[1] != 8 or fields[3] != 8 :
|
||||||
raise DrmException("Record didn't contain correct length fields")
|
raise DrmException(u"Record didn't contain correct length fields")
|
||||||
elif fields[2] != PID :
|
elif fields[2] != PID :
|
||||||
raise DrmException("Record didn't contain PID")
|
raise DrmException(u"Record didn't contain PID")
|
||||||
return fields[4]
|
return fields[4]
|
||||||
|
|
||||||
# Decrypt all dkey records (contain the book PID)
|
# Decrypt all dkey records (contain the book PID)
|
||||||
def decryptDkeyRecords(data,PID):
|
def decryptDkeyRecords(data,PID):
|
||||||
nbKeyRecords = data[0]
|
nbKeyRecords = ord(data[0])
|
||||||
records = []
|
records = []
|
||||||
data = data[1:]
|
data = data[1:]
|
||||||
for i in range (0,nbKeyRecords):
|
for i in range (0,nbKeyRecords):
|
||||||
length = data[0]
|
length = ord(data[0])
|
||||||
try:
|
try:
|
||||||
key = decryptDkeyRecord(data[1:length+1],PID)
|
key = decryptDkeyRecord(data[1:length+1],PID)
|
||||||
records.append(key)
|
records.append(key)
|
||||||
@@ -195,13 +190,13 @@ def decryptDkeyRecords(data,PID):
|
|||||||
pass
|
pass
|
||||||
data = data[1+length:]
|
data = data[1+length:]
|
||||||
if len(records) == 0:
|
if len(records) == 0:
|
||||||
raise DrmException("BookKey Not Found")
|
raise DrmException(u"BookKey Not Found")
|
||||||
return records
|
return records
|
||||||
|
|
||||||
|
|
||||||
class TopazBook:
|
class TopazBook:
|
||||||
def __init__(self, filename):
|
def __init__(self, filename):
|
||||||
self.fo = open(filename, 'rb')
|
self.fo = file(filename, 'rb')
|
||||||
self.outdir = tempfile.mkdtemp()
|
self.outdir = tempfile.mkdtemp()
|
||||||
# self.outdir = 'rawdat'
|
# self.outdir = 'rawdat'
|
||||||
self.bookPayloadOffset = 0
|
self.bookPayloadOffset = 0
|
||||||
@@ -209,8 +204,8 @@ class TopazBook:
|
|||||||
self.bookMetadata = {}
|
self.bookMetadata = {}
|
||||||
self.bookKey = None
|
self.bookKey = None
|
||||||
magic = unpack('4s',self.fo.read(4))[0]
|
magic = unpack('4s',self.fo.read(4))[0]
|
||||||
if magic != b'TPZ0':
|
if magic != 'TPZ0':
|
||||||
raise DrmException("Parse Error : Invalid Header, not a Topaz file")
|
raise DrmException(u"Parse Error : Invalid Header, not a Topaz file")
|
||||||
self.parseTopazHeaders()
|
self.parseTopazHeaders()
|
||||||
self.parseMetadata()
|
self.parseMetadata()
|
||||||
|
|
||||||
@@ -228,7 +223,7 @@ class TopazBook:
|
|||||||
# Read and parse one header record at the current book file position and return the associated data
|
# Read and parse one header record at the current book file position and return the associated data
|
||||||
# [[offset,decompressedLength,compressedLength],...]
|
# [[offset,decompressedLength,compressedLength],...]
|
||||||
if ord(self.fo.read(1)) != 0x63:
|
if ord(self.fo.read(1)) != 0x63:
|
||||||
raise DrmException("Parse Error : Invalid Header")
|
raise DrmException(u"Parse Error : Invalid Header")
|
||||||
tag = bookReadString(self.fo)
|
tag = bookReadString(self.fo)
|
||||||
record = bookReadHeaderRecordData()
|
record = bookReadHeaderRecordData()
|
||||||
return [tag,record]
|
return [tag,record]
|
||||||
@@ -239,15 +234,15 @@ class TopazBook:
|
|||||||
if debug: print(result[0], ": ", result[1])
|
if debug: print(result[0], ": ", result[1])
|
||||||
self.bookHeaderRecords[result[0]] = result[1]
|
self.bookHeaderRecords[result[0]] = result[1]
|
||||||
if ord(self.fo.read(1)) != 0x64 :
|
if ord(self.fo.read(1)) != 0x64 :
|
||||||
raise DrmException("Parse Error : Invalid Header")
|
raise DrmException(u"Parse Error : Invalid Header")
|
||||||
self.bookPayloadOffset = self.fo.tell()
|
self.bookPayloadOffset = self.fo.tell()
|
||||||
|
|
||||||
def parseMetadata(self):
|
def parseMetadata(self):
|
||||||
# Parse the metadata record from the book payload and return a list of [key,values]
|
# Parse the metadata record from the book payload and return a list of [key,values]
|
||||||
self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords[b'metadata'][0][0])
|
self.fo.seek(self.bookPayloadOffset + self.bookHeaderRecords['metadata'][0][0])
|
||||||
tag = bookReadString(self.fo)
|
tag = bookReadString(self.fo)
|
||||||
if tag != b'metadata' :
|
if tag != 'metadata' :
|
||||||
raise DrmException("Parse Error : Record Names Don't Match")
|
raise DrmException(u"Parse Error : Record Names Don't Match")
|
||||||
flags = ord(self.fo.read(1))
|
flags = ord(self.fo.read(1))
|
||||||
nbRecords = ord(self.fo.read(1))
|
nbRecords = ord(self.fo.read(1))
|
||||||
if debug: print("Metadata Records: %d" % nbRecords)
|
if debug: print("Metadata Records: %d" % nbRecords)
|
||||||
@@ -260,18 +255,18 @@ class TopazBook:
|
|||||||
return self.bookMetadata
|
return self.bookMetadata
|
||||||
|
|
||||||
def getPIDMetaInfo(self):
|
def getPIDMetaInfo(self):
|
||||||
keysRecord = self.bookMetadata.get(b'keys',b'')
|
keysRecord = self.bookMetadata.get('keys','')
|
||||||
keysRecordRecord = b''
|
keysRecordRecord = ''
|
||||||
if keysRecord != b'':
|
if keysRecord != '':
|
||||||
keylst = keysRecord.split(b',')
|
keylst = keysRecord.split(',')
|
||||||
for keyval in keylst:
|
for keyval in keylst:
|
||||||
keysRecordRecord += self.bookMetadata.get(keyval,b'')
|
keysRecordRecord += self.bookMetadata.get(keyval,'')
|
||||||
return keysRecord, keysRecordRecord
|
return keysRecord, keysRecordRecord
|
||||||
|
|
||||||
def getBookTitle(self):
|
def getBookTitle(self):
|
||||||
title = b''
|
title = ''
|
||||||
if b'Title' in self.bookMetadata:
|
if 'Title' in self.bookMetadata:
|
||||||
title = self.bookMetadata[b'Title']
|
title = self.bookMetadata['Title']
|
||||||
return title.decode('utf-8')
|
return title.decode('utf-8')
|
||||||
|
|
||||||
def setBookKey(self, key):
|
def setBookKey(self, key):
|
||||||
@@ -323,13 +318,13 @@ class TopazBook:
|
|||||||
raw = 0
|
raw = 0
|
||||||
fixedimage=True
|
fixedimage=True
|
||||||
try:
|
try:
|
||||||
keydata = self.getBookPayloadRecord(b'dkey', 0)
|
keydata = self.getBookPayloadRecord('dkey', 0)
|
||||||
except DrmException as e:
|
except DrmException, e:
|
||||||
print("no dkey record found, book may not be encrypted")
|
print(u"no dkey record found, book may not be encrypted")
|
||||||
print("attempting to extrct files without a book key")
|
print(u"attempting to extrct files without a book key")
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print("Successfully Extracted Topaz contents")
|
print(u"Successfully Extracted Topaz contents")
|
||||||
if inCalibre:
|
if inCalibre:
|
||||||
from calibre_plugins.dedrm import genbook
|
from calibre_plugins.dedrm import genbook
|
||||||
else:
|
else:
|
||||||
@@ -337,7 +332,7 @@ class TopazBook:
|
|||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print("Book Successfully generated.")
|
print(u"Book Successfully generated.")
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
# try each pid to decode the file
|
# try each pid to decode the file
|
||||||
@@ -345,25 +340,25 @@ class TopazBook:
|
|||||||
for pid in pidlst:
|
for pid in pidlst:
|
||||||
# use 8 digit pids here
|
# use 8 digit pids here
|
||||||
pid = pid[0:8]
|
pid = pid[0:8]
|
||||||
print("Trying: {0}".format(pid))
|
print(u"Trying: {0}".format(pid))
|
||||||
bookKeys = []
|
bookKeys = []
|
||||||
data = keydata
|
data = keydata
|
||||||
try:
|
try:
|
||||||
bookKeys+=decryptDkeyRecords(data,pid)
|
bookKeys+=decryptDkeyRecords(data,pid)
|
||||||
except DrmException as e:
|
except DrmException, e:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
bookKey = bookKeys[0]
|
bookKey = bookKeys[0]
|
||||||
print("Book Key Found! ({0})".format(bookKey.hex()))
|
print(u"Book Key Found! ({0})".format(bookKey.encode('hex')))
|
||||||
break
|
break
|
||||||
|
|
||||||
if not bookKey:
|
if not bookKey:
|
||||||
raise DrmException("No key found in {0:d} keys tried. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(len(pidlst)))
|
raise DrmException(u"No key found in {0:d} keys tried. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(len(pidlst)))
|
||||||
|
|
||||||
self.setBookKey(bookKey)
|
self.setBookKey(bookKey)
|
||||||
self.createBookDirectory()
|
self.createBookDirectory()
|
||||||
self.extractFiles()
|
self.extractFiles()
|
||||||
print("Successfully Extracted Topaz contents")
|
print(u"Successfully Extracted Topaz contents")
|
||||||
if inCalibre:
|
if inCalibre:
|
||||||
from calibre_plugins.dedrm import genbook
|
from calibre_plugins.dedrm import genbook
|
||||||
else:
|
else:
|
||||||
@@ -371,7 +366,7 @@ class TopazBook:
|
|||||||
|
|
||||||
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
rv = genbook.generateBook(self.outdir, raw, fixedimage)
|
||||||
if rv == 0:
|
if rv == 0:
|
||||||
print("Book Successfully generated")
|
print(u"Book Successfully generated")
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def createBookDirectory(self):
|
def createBookDirectory(self):
|
||||||
@@ -379,16 +374,16 @@ class TopazBook:
|
|||||||
# create output directory structure
|
# create output directory structure
|
||||||
if not os.path.exists(outdir):
|
if not os.path.exists(outdir):
|
||||||
os.makedirs(outdir)
|
os.makedirs(outdir)
|
||||||
destdir = os.path.join(outdir,"img")
|
destdir = os.path.join(outdir,u"img")
|
||||||
if not os.path.exists(destdir):
|
if not os.path.exists(destdir):
|
||||||
os.makedirs(destdir)
|
os.makedirs(destdir)
|
||||||
destdir = os.path.join(outdir,"color_img")
|
destdir = os.path.join(outdir,u"color_img")
|
||||||
if not os.path.exists(destdir):
|
if not os.path.exists(destdir):
|
||||||
os.makedirs(destdir)
|
os.makedirs(destdir)
|
||||||
destdir = os.path.join(outdir,"page")
|
destdir = os.path.join(outdir,u"page")
|
||||||
if not os.path.exists(destdir):
|
if not os.path.exists(destdir):
|
||||||
os.makedirs(destdir)
|
os.makedirs(destdir)
|
||||||
destdir = os.path.join(outdir,"glyphs")
|
destdir = os.path.join(outdir,u"glyphs")
|
||||||
if not os.path.exists(destdir):
|
if not os.path.exists(destdir):
|
||||||
os.makedirs(destdir)
|
os.makedirs(destdir)
|
||||||
|
|
||||||
@@ -396,50 +391,50 @@ class TopazBook:
|
|||||||
outdir = self.outdir
|
outdir = self.outdir
|
||||||
for headerRecord in self.bookHeaderRecords:
|
for headerRecord in self.bookHeaderRecords:
|
||||||
name = headerRecord
|
name = headerRecord
|
||||||
if name != b'dkey':
|
if name != 'dkey':
|
||||||
ext = ".dat"
|
ext = u".dat"
|
||||||
if name == b'img': ext = ".jpg"
|
if name == 'img': ext = u".jpg"
|
||||||
if name == b'color' : ext = ".jpg"
|
if name == 'color' : ext = u".jpg"
|
||||||
print("Processing Section: {0}\n. . .".format(name.decode('utf-8')), end=' ')
|
print(u"Processing Section: {0}\n. . .".format(name), end=' ')
|
||||||
for index in range (0,len(self.bookHeaderRecords[name])) :
|
for index in range (0,len(self.bookHeaderRecords[name])) :
|
||||||
fname = "{0}{1:04d}{2}".format(name.decode('utf-8'),index,ext)
|
fname = u"{0}{1:04d}{2}".format(name,index,ext)
|
||||||
destdir = outdir
|
destdir = outdir
|
||||||
if name == b'img':
|
if name == 'img':
|
||||||
destdir = os.path.join(outdir,"img")
|
destdir = os.path.join(outdir,u"img")
|
||||||
if name == b'color':
|
if name == 'color':
|
||||||
destdir = os.path.join(outdir,"color_img")
|
destdir = os.path.join(outdir,u"color_img")
|
||||||
if name == b'page':
|
if name == 'page':
|
||||||
destdir = os.path.join(outdir,"page")
|
destdir = os.path.join(outdir,u"page")
|
||||||
if name == b'glyphs':
|
if name == 'glyphs':
|
||||||
destdir = os.path.join(outdir,"glyphs")
|
destdir = os.path.join(outdir,u"glyphs")
|
||||||
outputFile = os.path.join(destdir,fname)
|
outputFile = os.path.join(destdir,fname)
|
||||||
print(".", end=' ')
|
print(u".", end=' ')
|
||||||
record = self.getBookPayloadRecord(name,index)
|
record = self.getBookPayloadRecord(name,index)
|
||||||
if record != b'':
|
if record != '':
|
||||||
open(outputFile, 'wb').write(record)
|
file(outputFile, 'wb').write(record)
|
||||||
print(" ")
|
print(u" ")
|
||||||
|
|
||||||
def getFile(self, zipname):
|
def getFile(self, zipname):
|
||||||
htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
htmlzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
||||||
htmlzip.write(os.path.join(self.outdir,"book.html"),"book.html")
|
htmlzip.write(os.path.join(self.outdir,u"book.html"),u"book.html")
|
||||||
htmlzip.write(os.path.join(self.outdir,"book.opf"),"book.opf")
|
htmlzip.write(os.path.join(self.outdir,u"book.opf"),u"book.opf")
|
||||||
if os.path.isfile(os.path.join(self.outdir,"cover.jpg")):
|
if os.path.isfile(os.path.join(self.outdir,u"cover.jpg")):
|
||||||
htmlzip.write(os.path.join(self.outdir,"cover.jpg"),"cover.jpg")
|
htmlzip.write(os.path.join(self.outdir,u"cover.jpg"),u"cover.jpg")
|
||||||
htmlzip.write(os.path.join(self.outdir,"style.css"),"style.css")
|
htmlzip.write(os.path.join(self.outdir,u"style.css"),u"style.css")
|
||||||
zipUpDir(htmlzip, self.outdir, "img")
|
zipUpDir(htmlzip, self.outdir, u"img")
|
||||||
htmlzip.close()
|
htmlzip.close()
|
||||||
|
|
||||||
def getBookType(self):
|
def getBookType(self):
|
||||||
return "Topaz"
|
return u"Topaz"
|
||||||
|
|
||||||
def getBookExtension(self):
|
def getBookExtension(self):
|
||||||
return ".htmlz"
|
return u".htmlz"
|
||||||
|
|
||||||
def getSVGZip(self, zipname):
|
def getSVGZip(self, zipname):
|
||||||
svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
svgzip = zipfile.ZipFile(zipname,'w',zipfile.ZIP_DEFLATED, False)
|
||||||
svgzip.write(os.path.join(self.outdir,"index_svg.xhtml"),"index_svg.xhtml")
|
svgzip.write(os.path.join(self.outdir,u"index_svg.xhtml"),u"index_svg.xhtml")
|
||||||
zipUpDir(svgzip, self.outdir, "svg")
|
zipUpDir(svgzip, self.outdir, u"svg")
|
||||||
zipUpDir(svgzip, self.outdir, "img")
|
zipUpDir(svgzip, self.outdir, u"img")
|
||||||
svgzip.close()
|
svgzip.close()
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
@@ -447,20 +442,20 @@ class TopazBook:
|
|||||||
shutil.rmtree(self.outdir, True)
|
shutil.rmtree(self.outdir, True)
|
||||||
|
|
||||||
def usage(progname):
|
def usage(progname):
|
||||||
print("Removes DRM protection from Topaz ebooks and extracts the contents")
|
print(u"Removes DRM protection from Topaz ebooks and extracts the contents")
|
||||||
print("Usage:")
|
print(u"Usage:")
|
||||||
print(" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname))
|
print(u" {0} [-k <kindle.k4i>] [-p <comma separated PIDs>] [-s <comma separated Kindle serial numbers>] <infile> <outdir>".format(progname))
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
def cli_main():
|
def cli_main():
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
print("TopazExtract v{0}.".format(__version__))
|
print(u"TopazExtract v{0}.".format(__version__))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], "k:p:s:x")
|
opts, args = getopt.getopt(argv[1:], "k:p:s:x")
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError, err:
|
||||||
print("Error in options or arguments: {0}".format(err.args[0]))
|
print(u"Error in options or arguments: {0}".format(err.args[0]))
|
||||||
usage(progname)
|
usage(progname)
|
||||||
return 1
|
return 1
|
||||||
if len(args)<2:
|
if len(args)<2:
|
||||||
@@ -470,11 +465,11 @@ def cli_main():
|
|||||||
infile = args[0]
|
infile = args[0]
|
||||||
outdir = args[1]
|
outdir = args[1]
|
||||||
if not os.path.isfile(infile):
|
if not os.path.isfile(infile):
|
||||||
print("Input File {0} Does Not Exist.".format(infile))
|
print(u"Input File {0} Does Not Exist.".format(infile))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if not os.path.exists(outdir):
|
if not os.path.exists(outdir):
|
||||||
print("Output Directory {0} Does Not Exist.".format(outdir))
|
print(u"Output Directory {0} Does Not Exist.".format(outdir))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
kDatabaseFiles = []
|
kDatabaseFiles = []
|
||||||
@@ -499,27 +494,27 @@ def cli_main():
|
|||||||
|
|
||||||
tb = TopazBook(infile)
|
tb = TopazBook(infile)
|
||||||
title = tb.getBookTitle()
|
title = tb.getBookTitle()
|
||||||
print("Processing Book: {0}".format(title))
|
print(u"Processing Book: {0}".format(title))
|
||||||
md1, md2 = tb.getPIDMetaInfo()
|
md1, md2 = tb.getPIDMetaInfo()
|
||||||
pids.extend(kgenpids.getPidList(md1, md2, serials, kDatabaseFiles))
|
pids.extend(kgenpids.getPidList(md1, md2, serials, kDatabaseFiles))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print("Decrypting Book")
|
print(u"Decrypting Book")
|
||||||
tb.processBook(pids)
|
tb.processBook(pids)
|
||||||
|
|
||||||
print(" Creating HTML ZIP Archive")
|
print(u" Creating HTML ZIP Archive")
|
||||||
zipname = os.path.join(outdir, bookname + "_nodrm.htmlz")
|
zipname = os.path.join(outdir, bookname + u"_nodrm.htmlz")
|
||||||
tb.getFile(zipname)
|
tb.getFile(zipname)
|
||||||
|
|
||||||
print(" Creating SVG ZIP Archive")
|
print(u" Creating SVG ZIP Archive")
|
||||||
zipname = os.path.join(outdir, bookname + "_SVG.zip")
|
zipname = os.path.join(outdir, bookname + u"_SVG.zip")
|
||||||
tb.getSVGZip(zipname)
|
tb.getSVGZip(zipname)
|
||||||
|
|
||||||
# removing internal temporary directory of pieces
|
# removing internal temporary directory of pieces
|
||||||
tb.cleanup()
|
tb.cleanup()
|
||||||
|
|
||||||
except DrmException as e:
|
except DrmException, e:
|
||||||
print("Decryption failed\n{0}".format(traceback.format_exc()))
|
print(u"Decryption failed\n{0}".format(traceback.format_exc()))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tb.cleanup()
|
tb.cleanup()
|
||||||
@@ -527,8 +522,8 @@ def cli_main():
|
|||||||
pass
|
pass
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
print("Decryption failed\n{0}".format(traceback.format_exc()))
|
print(u"Decryption failed\m{0}".format(traceback.format_exc()))
|
||||||
try:
|
try:
|
||||||
tb.cleanup()
|
tb.cleanup()
|
||||||
except:
|
except:
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
from ignoblekeygen import generate_key
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
@@ -19,8 +21,8 @@ DETAILED_MESSAGE = \
|
|||||||
|
|
||||||
def uStrCmp (s1, s2, caseless=False):
|
def uStrCmp (s1, s2, caseless=False):
|
||||||
import unicodedata as ud
|
import unicodedata as ud
|
||||||
str1 = s1 if isinstance(s1, str) else str(s1)
|
str1 = s1 if isinstance(s1, unicode) else unicode(s1)
|
||||||
str2 = s2 if isinstance(s2, str) else str(s2)
|
str2 = s2 if isinstance(s2, unicode) else unicode(s2)
|
||||||
if caseless:
|
if caseless:
|
||||||
return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower())
|
return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower())
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,95 +1,59 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
# Standard Python modules.
|
# Standard Python modules.
|
||||||
import os, sys, re, hashlib, traceback
|
import os, sys, re, hashlib, traceback
|
||||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||||
|
|
||||||
|
|
||||||
class NoWinePython3Exception(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class WinePythonCLI:
|
|
||||||
py3_test = "import sys; sys.exit(0 if (sys.version_info.major==3) else 1)"
|
|
||||||
def __init__(self, wineprefix=""):
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
if wineprefix != "":
|
|
||||||
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
|
||||||
|
|
||||||
if wineprefix != "" and os.path.exists(wineprefix):
|
|
||||||
self.wineprefix = wineprefix
|
|
||||||
else:
|
|
||||||
self.wineprefix = None
|
|
||||||
|
|
||||||
candidate_execs = [
|
|
||||||
["wine", "py.exe", "-3"],
|
|
||||||
["wine", "python3.exe"],
|
|
||||||
["wine", "python.exe"],
|
|
||||||
["wine", "C:\\Python27\\python.exe"], # Should likely be removed
|
|
||||||
]
|
|
||||||
for e in candidate_execs:
|
|
||||||
self.python_exec = e
|
|
||||||
try:
|
|
||||||
self.check_call(["-c", self.py3_test])
|
|
||||||
print("{0} v{1}: Python3 exec found as {2}".format(
|
|
||||||
PLUGIN_NAME, PLUGIN_VERSION, " ".join(self.python_exec)
|
|
||||||
))
|
|
||||||
return None
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
if e.returncode == 1:
|
|
||||||
print("{0} v{1}: {2} is not python3".format(
|
|
||||||
PLUGIN_NAME, PLUGIN_VERSION, " ".join(self.python_exec)
|
|
||||||
))
|
|
||||||
elif e.returncode == 53:
|
|
||||||
print("{0} v{1}: {2} does not exist".format(
|
|
||||||
PLUGIN_NAME, PLUGIN_VERSION, " ".join(self.python_exec)
|
|
||||||
))
|
|
||||||
raise NoWinePython3Exception("Could not find python3 executable on specified wine prefix")
|
|
||||||
|
|
||||||
|
|
||||||
def check_call(self, cli_args):
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
env_dict = os.environ
|
|
||||||
env_dict["PYTHONPATH"] = ""
|
|
||||||
if self.wineprefix is not None:
|
|
||||||
env_dict["WINEPREFIX"] = self.wineprefix
|
|
||||||
|
|
||||||
subprocess.check_call(self.python_exec + cli_args, env=env_dict,
|
|
||||||
stdin=None, stdout=sys.stdout,
|
|
||||||
stderr=subprocess.STDOUT, close_fds=False,
|
|
||||||
bufsize=1)
|
|
||||||
|
|
||||||
|
|
||||||
def WineGetKeys(scriptpath, extension, wineprefix=""):
|
def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||||
|
import subprocess
|
||||||
|
from subprocess import Popen, PIPE, STDOUT
|
||||||
|
|
||||||
if extension == ".k4i":
|
import subasyncio
|
||||||
|
from subasyncio import Process
|
||||||
|
|
||||||
|
if extension == u".k4i":
|
||||||
import json
|
import json
|
||||||
|
|
||||||
try:
|
|
||||||
pyexec = WinePythonCLI(wineprefix)
|
|
||||||
except NoWinePython3Exception:
|
|
||||||
print('{0} v{1}: Unable to find python3 executable in WINEPREFIX="{2}"'.format(PLUGIN_NAME, PLUGIN_VERSION, wineprefix))
|
|
||||||
return []
|
|
||||||
|
|
||||||
basepath, script = os.path.split(scriptpath)
|
basepath, script = os.path.split(scriptpath)
|
||||||
print("{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script))
|
print(u"{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script))
|
||||||
|
|
||||||
outdirpath = os.path.join(basepath, "winekeysdir")
|
outdirpath = os.path.join(basepath, u"winekeysdir")
|
||||||
if not os.path.exists(outdirpath):
|
if not os.path.exists(outdirpath):
|
||||||
os.makedirs(outdirpath)
|
os.makedirs(outdirpath)
|
||||||
|
|
||||||
if wineprefix != "":
|
if wineprefix != "":
|
||||||
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
wineprefix = os.path.abspath(os.path.expanduser(os.path.expandvars(wineprefix)))
|
||||||
|
|
||||||
|
if wineprefix != "" and os.path.exists(wineprefix):
|
||||||
|
cmdline = u"WINEPREFIX=\"{2}\" wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||||
|
else:
|
||||||
|
cmdline = u"wine python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
||||||
|
print(u"{0} v{1}: Command line: '{2}'".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = pyexec.check_call([scriptpath, outdirpath])
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
except Exception as e:
|
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
||||||
print("{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
|
result = p2.wait("wait")
|
||||||
|
except Exception, e:
|
||||||
|
print(u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
|
||||||
|
if wineprefix != "" and os.path.exists(wineprefix):
|
||||||
|
cmdline = u"WINEPREFIX=\"{2}\" wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath,wineprefix)
|
||||||
|
else:
|
||||||
|
cmdline = u"wine C:\\Python27\\python.exe \"{0}\" \"{1}\"".format(scriptpath,outdirpath)
|
||||||
|
print(u"{0} v{1}: Command line: “{2}”".format(PLUGIN_NAME, PLUGIN_VERSION, cmdline))
|
||||||
|
|
||||||
|
try:
|
||||||
|
cmdline = cmdline.encode(sys.getfilesystemencoding())
|
||||||
|
p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=sys.stdout, stderr=STDOUT, close_fds=False)
|
||||||
|
result = p2.wait("wait")
|
||||||
|
except Exception, e:
|
||||||
|
print(u"{0} v{1}: Wine subprocess call error: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0]))
|
||||||
|
|
||||||
# try finding winekeys anyway, even if above code errored
|
# try finding winekeys anyway, even if above code errored
|
||||||
winekeys = []
|
winekeys = []
|
||||||
@@ -99,14 +63,14 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
|||||||
try:
|
try:
|
||||||
fpath = os.path.join(outdirpath, filename)
|
fpath = os.path.join(outdirpath, filename)
|
||||||
with open(fpath, 'rb') as keyfile:
|
with open(fpath, 'rb') as keyfile:
|
||||||
if extension == ".k4i":
|
if extension == u".k4i":
|
||||||
new_key_value = json.loads(keyfile.read())
|
new_key_value = json.loads(keyfile.read())
|
||||||
else:
|
else:
|
||||||
new_key_value = keyfile.read()
|
new_key_value = keyfile.read()
|
||||||
winekeys.append(new_key_value)
|
winekeys.append(new_key_value)
|
||||||
except:
|
except:
|
||||||
print("{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename))
|
print(u"{0} v{1}: Error loading file {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
os.remove(fpath)
|
os.remove(fpath)
|
||||||
print("{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), "key file" if len(winekeys) == 1 else "key files"))
|
print(u"{0} v{1}: Found and decrypted {2} {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(winekeys), u"key file" if len(winekeys) == 1 else u"key files"))
|
||||||
return winekeys
|
return winekeys
|
||||||
|
|||||||
184
DeDRM_plugin/zipfilerugged.py
Executable file → Normal file
184
DeDRM_plugin/zipfilerugged.py
Executable file → Normal file
@@ -1,16 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Read and write ZIP files.
|
Read and write ZIP files.
|
||||||
"""
|
"""
|
||||||
import struct, os, time, sys, shutil
|
import struct, os, time, sys, shutil
|
||||||
import binascii, stat
|
import binascii, cStringIO, stat
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import zlib # We may need its compression method
|
import zlib # We may need its compression method
|
||||||
crc32 = zlib.crc32
|
crc32 = zlib.crc32
|
||||||
@@ -50,8 +45,8 @@ ZIP_DEFLATED = 8
|
|||||||
|
|
||||||
# The "end of central directory" structure, magic number, size, and indices
|
# The "end of central directory" structure, magic number, size, and indices
|
||||||
# (section V.I in the format document)
|
# (section V.I in the format document)
|
||||||
structEndArchive = b"<4s4H2LH"
|
structEndArchive = "<4s4H2LH"
|
||||||
stringEndArchive = b"PK\005\006"
|
stringEndArchive = "PK\005\006"
|
||||||
sizeEndCentDir = struct.calcsize(structEndArchive)
|
sizeEndCentDir = struct.calcsize(structEndArchive)
|
||||||
|
|
||||||
_ECD_SIGNATURE = 0
|
_ECD_SIGNATURE = 0
|
||||||
@@ -69,8 +64,8 @@ _ECD_LOCATION = 9
|
|||||||
|
|
||||||
# The "central directory" structure, magic number, size, and indices
|
# The "central directory" structure, magic number, size, and indices
|
||||||
# of entries in the structure (section V.F in the format document)
|
# of entries in the structure (section V.F in the format document)
|
||||||
structCentralDir = b"<4s4B4HL2L5H2L"
|
structCentralDir = "<4s4B4HL2L5H2L"
|
||||||
stringCentralDir = b"PK\001\002"
|
stringCentralDir = "PK\001\002"
|
||||||
sizeCentralDir = struct.calcsize(structCentralDir)
|
sizeCentralDir = struct.calcsize(structCentralDir)
|
||||||
|
|
||||||
# indexes of entries in the central directory structure
|
# indexes of entries in the central directory structure
|
||||||
@@ -96,8 +91,8 @@ _CD_LOCAL_HEADER_OFFSET = 18
|
|||||||
|
|
||||||
# The "local file header" structure, magic number, size, and indices
|
# The "local file header" structure, magic number, size, and indices
|
||||||
# (section V.A in the format document)
|
# (section V.A in the format document)
|
||||||
structFileHeader = b"<4s2B4HL2L2H"
|
structFileHeader = "<4s2B4HL2L2H"
|
||||||
stringFileHeader = b"PK\003\004"
|
stringFileHeader = "PK\003\004"
|
||||||
sizeFileHeader = struct.calcsize(structFileHeader)
|
sizeFileHeader = struct.calcsize(structFileHeader)
|
||||||
|
|
||||||
_FH_SIGNATURE = 0
|
_FH_SIGNATURE = 0
|
||||||
@@ -114,14 +109,14 @@ _FH_FILENAME_LENGTH = 10
|
|||||||
_FH_EXTRA_FIELD_LENGTH = 11
|
_FH_EXTRA_FIELD_LENGTH = 11
|
||||||
|
|
||||||
# The "Zip64 end of central directory locator" structure, magic number, and size
|
# The "Zip64 end of central directory locator" structure, magic number, and size
|
||||||
structEndArchive64Locator = b"<4sLQL"
|
structEndArchive64Locator = "<4sLQL"
|
||||||
stringEndArchive64Locator = b"PK\x06\x07"
|
stringEndArchive64Locator = "PK\x06\x07"
|
||||||
sizeEndCentDir64Locator = struct.calcsize(structEndArchive64Locator)
|
sizeEndCentDir64Locator = struct.calcsize(structEndArchive64Locator)
|
||||||
|
|
||||||
# The "Zip64 end of central directory" record, magic number, size, and indices
|
# The "Zip64 end of central directory" record, magic number, size, and indices
|
||||||
# (section V.G in the format document)
|
# (section V.G in the format document)
|
||||||
structEndArchive64 = b"<4sQ2H2L4Q"
|
structEndArchive64 = "<4sQ2H2L4Q"
|
||||||
stringEndArchive64 = b"PK\x06\x06"
|
stringEndArchive64 = "PK\x06\x06"
|
||||||
sizeEndCentDir64 = struct.calcsize(structEndArchive64)
|
sizeEndCentDir64 = struct.calcsize(structEndArchive64)
|
||||||
|
|
||||||
_CD64_SIGNATURE = 0
|
_CD64_SIGNATURE = 0
|
||||||
@@ -280,21 +275,21 @@ class ZipInfo (object):
|
|||||||
|
|
||||||
# Terminate the file name at the first null byte. Null bytes in file
|
# Terminate the file name at the first null byte. Null bytes in file
|
||||||
# names are used as tricks by viruses in archives.
|
# names are used as tricks by viruses in archives.
|
||||||
null_byte = filename.find(b"\0")
|
null_byte = filename.find(chr(0))
|
||||||
if null_byte >= 0:
|
if null_byte >= 0:
|
||||||
filename = filename[0:null_byte]
|
filename = filename[0:null_byte]
|
||||||
# This is used to ensure paths in generated ZIP files always use
|
# This is used to ensure paths in generated ZIP files always use
|
||||||
# forward slashes as the directory separator, as required by the
|
# forward slashes as the directory separator, as required by the
|
||||||
# ZIP format specification.
|
# ZIP format specification.
|
||||||
if os.sep != "/" and os.sep.encode('utf-8') in filename:
|
if os.sep != "/" and os.sep in filename:
|
||||||
filename = filename.replace(os.sep.encode('utf-8'), b"/")
|
filename = filename.replace(os.sep, "/")
|
||||||
|
|
||||||
self.filename = filename # Normalized file name
|
self.filename = filename # Normalized file name
|
||||||
self.date_time = date_time # year, month, day, hour, min, sec
|
self.date_time = date_time # year, month, day, hour, min, sec
|
||||||
# Standard values:
|
# Standard values:
|
||||||
self.compress_type = ZIP_STORED # Type of compression for the file
|
self.compress_type = ZIP_STORED # Type of compression for the file
|
||||||
self.comment = b"" # Comment for each file
|
self.comment = "" # Comment for each file
|
||||||
self.extra = b"" # ZIP extra data
|
self.extra = "" # ZIP extra data
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
self.create_system = 0 # System which created ZIP archive
|
self.create_system = 0 # System which created ZIP archive
|
||||||
else:
|
else:
|
||||||
@@ -348,13 +343,23 @@ class ZipInfo (object):
|
|||||||
return header + filename + extra
|
return header + filename + extra
|
||||||
|
|
||||||
def _encodeFilenameFlags(self):
|
def _encodeFilenameFlags(self):
|
||||||
if isinstance(self.filename, bytes):
|
if isinstance(self.filename, unicode):
|
||||||
return self.filename, self.flag_bits
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
return self.filename.encode('ascii'), self.flag_bits
|
return self.filename.encode('ascii'), self.flag_bits
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
return self.filename.encode('utf-8'), self.flag_bits | 0x800
|
return self.filename.encode('utf-8'), self.flag_bits | 0x800
|
||||||
|
else:
|
||||||
|
return self.filename, self.flag_bits
|
||||||
|
|
||||||
|
def _decodeFilename(self):
|
||||||
|
if self.flag_bits & 0x800:
|
||||||
|
try:
|
||||||
|
#print "decoding filename",self.filename
|
||||||
|
return self.filename.decode('utf-8')
|
||||||
|
except:
|
||||||
|
return self.filename
|
||||||
|
else:
|
||||||
|
return self.filename
|
||||||
|
|
||||||
def _decodeExtra(self):
|
def _decodeExtra(self):
|
||||||
# Try to decode the extra field.
|
# Try to decode the extra field.
|
||||||
@@ -372,20 +377,20 @@ class ZipInfo (object):
|
|||||||
elif ln == 0:
|
elif ln == 0:
|
||||||
counts = ()
|
counts = ()
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Corrupt extra field %s"%(ln,))
|
raise RuntimeError, "Corrupt extra field %s"%(ln,)
|
||||||
|
|
||||||
idx = 0
|
idx = 0
|
||||||
|
|
||||||
# ZIP64 extension (large files and/or large archives)
|
# ZIP64 extension (large files and/or large archives)
|
||||||
if self.file_size in (0xffffffffffffffff, 0xffffffff):
|
if self.file_size in (0xffffffffffffffffL, 0xffffffffL):
|
||||||
self.file_size = counts[idx]
|
self.file_size = counts[idx]
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
if self.compress_size == 0xFFFFFFFF:
|
if self.compress_size == 0xFFFFFFFFL:
|
||||||
self.compress_size = counts[idx]
|
self.compress_size = counts[idx]
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
if self.header_offset == 0xffffffff:
|
if self.header_offset == 0xffffffffL:
|
||||||
old = self.header_offset
|
old = self.header_offset
|
||||||
self.header_offset = counts[idx]
|
self.header_offset = counts[idx]
|
||||||
idx+=1
|
idx+=1
|
||||||
@@ -476,9 +481,9 @@ class ZipExtFile(io.BufferedIOBase):
|
|||||||
|
|
||||||
if self._compress_type == ZIP_DEFLATED:
|
if self._compress_type == ZIP_DEFLATED:
|
||||||
self._decompressor = zlib.decompressobj(-15)
|
self._decompressor = zlib.decompressobj(-15)
|
||||||
self._unconsumed = b''
|
self._unconsumed = ''
|
||||||
|
|
||||||
self._readbuffer = b''
|
self._readbuffer = ''
|
||||||
self._offset = 0
|
self._offset = 0
|
||||||
|
|
||||||
self._universal = 'U' in mode
|
self._universal = 'U' in mode
|
||||||
@@ -509,10 +514,10 @@ class ZipExtFile(io.BufferedIOBase):
|
|||||||
if not self._universal:
|
if not self._universal:
|
||||||
return io.BufferedIOBase.readline(self, limit)
|
return io.BufferedIOBase.readline(self, limit)
|
||||||
|
|
||||||
line = b''
|
line = ''
|
||||||
while limit < 0 or len(line) < limit:
|
while limit < 0 or len(line) < limit:
|
||||||
readahead = self.peek(2)
|
readahead = self.peek(2)
|
||||||
if readahead == b'':
|
if readahead == '':
|
||||||
return line
|
return line
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -559,7 +564,7 @@ class ZipExtFile(io.BufferedIOBase):
|
|||||||
If the argument is omitted, None, or negative, data is read and returned until EOF is reached..
|
If the argument is omitted, None, or negative, data is read and returned until EOF is reached..
|
||||||
"""
|
"""
|
||||||
|
|
||||||
buf = b''
|
buf = ''
|
||||||
while n < 0 or n is None or n > len(buf):
|
while n < 0 or n is None or n > len(buf):
|
||||||
data = self.read1(n)
|
data = self.read1(n)
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
@@ -589,7 +594,7 @@ class ZipExtFile(io.BufferedIOBase):
|
|||||||
self._compress_left -= len(data)
|
self._compress_left -= len(data)
|
||||||
|
|
||||||
if data and self._decrypter is not None:
|
if data and self._decrypter is not None:
|
||||||
data = b''.join(map(self._decrypter, data))
|
data = ''.join(map(self._decrypter, data))
|
||||||
|
|
||||||
if self._compress_type == ZIP_STORED:
|
if self._compress_type == ZIP_STORED:
|
||||||
self._readbuffer = self._readbuffer[self._offset:] + data
|
self._readbuffer = self._readbuffer[self._offset:] + data
|
||||||
@@ -646,10 +651,10 @@ class ZipFile:
|
|||||||
pass
|
pass
|
||||||
elif compression == ZIP_DEFLATED:
|
elif compression == ZIP_DEFLATED:
|
||||||
if not zlib:
|
if not zlib:
|
||||||
raise RuntimeError(
|
raise RuntimeError,\
|
||||||
"Compression requires the (missing) zlib module")
|
"Compression requires the (missing) zlib module"
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("That compression method is not supported")
|
raise RuntimeError, "That compression method is not supported"
|
||||||
|
|
||||||
self._allowZip64 = allowZip64
|
self._allowZip64 = allowZip64
|
||||||
self._didModify = False
|
self._didModify = False
|
||||||
@@ -659,10 +664,10 @@ class ZipFile:
|
|||||||
self.compression = compression # Method of compression
|
self.compression = compression # Method of compression
|
||||||
self.mode = key = mode.replace('b', '')[0]
|
self.mode = key = mode.replace('b', '')[0]
|
||||||
self.pwd = None
|
self.pwd = None
|
||||||
self.comment = b''
|
self.comment = ''
|
||||||
|
|
||||||
# Check if we were passed a file-like object
|
# Check if we were passed a file-like object
|
||||||
if isinstance(file, str):
|
if isinstance(file, basestring):
|
||||||
self._filePassed = 0
|
self._filePassed = 0
|
||||||
self.filename = file
|
self.filename = file
|
||||||
modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'}
|
modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'}
|
||||||
@@ -694,7 +699,7 @@ class ZipFile:
|
|||||||
if not self._filePassed:
|
if not self._filePassed:
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
self.fp = None
|
self.fp = None
|
||||||
raise RuntimeError('Mode must be "r", "w" or "a"')
|
raise RuntimeError, 'Mode must be "r", "w" or "a"'
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
return self
|
return self
|
||||||
@@ -718,9 +723,9 @@ class ZipFile:
|
|||||||
fp = self.fp
|
fp = self.fp
|
||||||
endrec = _EndRecData(fp)
|
endrec = _EndRecData(fp)
|
||||||
if not endrec:
|
if not endrec:
|
||||||
raise BadZipfile("File is not a zip file")
|
raise BadZipfile, "File is not a zip file"
|
||||||
if self.debug > 1:
|
if self.debug > 1:
|
||||||
print(endrec)
|
print endrec
|
||||||
size_cd = endrec[_ECD_SIZE] # bytes in central directory
|
size_cd = endrec[_ECD_SIZE] # bytes in central directory
|
||||||
offset_cd = endrec[_ECD_OFFSET] # offset of central directory
|
offset_cd = endrec[_ECD_OFFSET] # offset of central directory
|
||||||
self.comment = endrec[_ECD_COMMENT] # archive comment
|
self.comment = endrec[_ECD_COMMENT] # archive comment
|
||||||
@@ -733,20 +738,20 @@ class ZipFile:
|
|||||||
|
|
||||||
if self.debug > 2:
|
if self.debug > 2:
|
||||||
inferred = concat + offset_cd
|
inferred = concat + offset_cd
|
||||||
print("given, inferred, offset", offset_cd, inferred, concat)
|
print "given, inferred, offset", offset_cd, inferred, concat
|
||||||
# self.start_dir: Position of start of central directory
|
# self.start_dir: Position of start of central directory
|
||||||
self.start_dir = offset_cd + concat
|
self.start_dir = offset_cd + concat
|
||||||
fp.seek(self.start_dir, 0)
|
fp.seek(self.start_dir, 0)
|
||||||
data = fp.read(size_cd)
|
data = fp.read(size_cd)
|
||||||
fp = BytesIO(data)
|
fp = cStringIO.StringIO(data)
|
||||||
total = 0
|
total = 0
|
||||||
while total < size_cd:
|
while total < size_cd:
|
||||||
centdir = fp.read(sizeCentralDir)
|
centdir = fp.read(sizeCentralDir)
|
||||||
if centdir[0:4] != stringCentralDir:
|
if centdir[0:4] != stringCentralDir:
|
||||||
raise BadZipfile("Bad magic number for central directory")
|
raise BadZipfile, "Bad magic number for central directory"
|
||||||
centdir = struct.unpack(structCentralDir, centdir)
|
centdir = struct.unpack(structCentralDir, centdir)
|
||||||
if self.debug > 2:
|
if self.debug > 2:
|
||||||
print(centdir)
|
print centdir
|
||||||
filename = fp.read(centdir[_CD_FILENAME_LENGTH])
|
filename = fp.read(centdir[_CD_FILENAME_LENGTH])
|
||||||
# Create ZipInfo instance to store file information
|
# Create ZipInfo instance to store file information
|
||||||
x = ZipInfo(filename)
|
x = ZipInfo(filename)
|
||||||
@@ -764,6 +769,7 @@ class ZipFile:
|
|||||||
|
|
||||||
x._decodeExtra()
|
x._decodeExtra()
|
||||||
x.header_offset = x.header_offset + concat
|
x.header_offset = x.header_offset + concat
|
||||||
|
x.filename = x._decodeFilename()
|
||||||
self.filelist.append(x)
|
self.filelist.append(x)
|
||||||
self.NameToInfo[x.filename] = x
|
self.NameToInfo[x.filename] = x
|
||||||
|
|
||||||
@@ -773,7 +779,7 @@ class ZipFile:
|
|||||||
+ centdir[_CD_COMMENT_LENGTH])
|
+ centdir[_CD_COMMENT_LENGTH])
|
||||||
|
|
||||||
if self.debug > 2:
|
if self.debug > 2:
|
||||||
print("total", total)
|
print "total", total
|
||||||
|
|
||||||
|
|
||||||
def namelist(self):
|
def namelist(self):
|
||||||
@@ -790,10 +796,10 @@ class ZipFile:
|
|||||||
|
|
||||||
def printdir(self):
|
def printdir(self):
|
||||||
"""Print a table of contents for the zip file."""
|
"""Print a table of contents for the zip file."""
|
||||||
print("%-46s %19s %12s" % ("File Name", "Modified ", "Size"))
|
print "%-46s %19s %12s" % ("File Name", "Modified ", "Size")
|
||||||
for zinfo in self.filelist:
|
for zinfo in self.filelist:
|
||||||
date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time[:6]
|
date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time[:6]
|
||||||
print("%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size))
|
print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size)
|
||||||
|
|
||||||
def testzip(self):
|
def testzip(self):
|
||||||
"""Read all the files and check the CRC."""
|
"""Read all the files and check the CRC."""
|
||||||
@@ -827,11 +833,11 @@ class ZipFile:
|
|||||||
|
|
||||||
def open(self, name, mode="r", pwd=None):
|
def open(self, name, mode="r", pwd=None):
|
||||||
"""Return file-like object for 'name'."""
|
"""Return file-like object for 'name'."""
|
||||||
if mode not in ("r", "", "rU"):
|
if mode not in ("r", "U", "rU"):
|
||||||
raise RuntimeError('open() requires mode "r", "", or "rU"')
|
raise RuntimeError, 'open() requires mode "r", "U", or "rU"'
|
||||||
if not self.fp:
|
if not self.fp:
|
||||||
raise RuntimeError(
|
raise RuntimeError, \
|
||||||
"Attempt to read ZIP archive that was already closed")
|
"Attempt to read ZIP archive that was already closed"
|
||||||
|
|
||||||
# Only open a new file for instances where we were not
|
# Only open a new file for instances where we were not
|
||||||
# given a file object in the constructor
|
# given a file object in the constructor
|
||||||
@@ -853,7 +859,7 @@ class ZipFile:
|
|||||||
# Skip the file header:
|
# Skip the file header:
|
||||||
fheader = zef_file.read(sizeFileHeader)
|
fheader = zef_file.read(sizeFileHeader)
|
||||||
if fheader[0:4] != stringFileHeader:
|
if fheader[0:4] != stringFileHeader:
|
||||||
raise BadZipfile("Bad magic number for file header")
|
raise BadZipfile, "Bad magic number for file header"
|
||||||
|
|
||||||
fheader = struct.unpack(structFileHeader, fheader)
|
fheader = struct.unpack(structFileHeader, fheader)
|
||||||
fname = zef_file.read(fheader[_FH_FILENAME_LENGTH])
|
fname = zef_file.read(fheader[_FH_FILENAME_LENGTH])
|
||||||
@@ -861,9 +867,9 @@ class ZipFile:
|
|||||||
zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH])
|
zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH])
|
||||||
|
|
||||||
if fname != zinfo.orig_filename:
|
if fname != zinfo.orig_filename:
|
||||||
raise BadZipfile(
|
raise BadZipfile, \
|
||||||
'File name in directory "%s" and header "%s" differ.' % (
|
'File name in directory "%s" and header "%s" differ.' % (
|
||||||
zinfo.orig_filename, fname))
|
zinfo.orig_filename, fname)
|
||||||
|
|
||||||
# check for encrypted flag & handle password
|
# check for encrypted flag & handle password
|
||||||
is_encrypted = zinfo.flag_bits & 0x1
|
is_encrypted = zinfo.flag_bits & 0x1
|
||||||
@@ -872,8 +878,8 @@ class ZipFile:
|
|||||||
if not pwd:
|
if not pwd:
|
||||||
pwd = self.pwd
|
pwd = self.pwd
|
||||||
if not pwd:
|
if not pwd:
|
||||||
raise RuntimeError("File %s is encrypted, " \
|
raise RuntimeError, "File %s is encrypted, " \
|
||||||
"password required for extraction" % name)
|
"password required for extraction" % name
|
||||||
|
|
||||||
zd = _ZipDecrypter(pwd)
|
zd = _ZipDecrypter(pwd)
|
||||||
# The first 12 bytes in the cypher stream is an encryption header
|
# The first 12 bytes in the cypher stream is an encryption header
|
||||||
@@ -950,7 +956,7 @@ class ZipFile:
|
|||||||
return targetpath
|
return targetpath
|
||||||
|
|
||||||
source = self.open(member, pwd=pwd)
|
source = self.open(member, pwd=pwd)
|
||||||
target = open(targetpath, "wb")
|
target = file(targetpath, "wb")
|
||||||
shutil.copyfileobj(source, target)
|
shutil.copyfileobj(source, target)
|
||||||
source.close()
|
source.close()
|
||||||
target.close()
|
target.close()
|
||||||
@@ -961,18 +967,18 @@ class ZipFile:
|
|||||||
"""Check for errors before writing a file to the archive."""
|
"""Check for errors before writing a file to the archive."""
|
||||||
if zinfo.filename in self.NameToInfo:
|
if zinfo.filename in self.NameToInfo:
|
||||||
if self.debug: # Warning for duplicate names
|
if self.debug: # Warning for duplicate names
|
||||||
print("Duplicate name:", zinfo.filename)
|
print "Duplicate name:", zinfo.filename
|
||||||
if self.mode not in ("w", "a"):
|
if self.mode not in ("w", "a"):
|
||||||
raise RuntimeError('write() requires mode "w" or "a"')
|
raise RuntimeError, 'write() requires mode "w" or "a"'
|
||||||
if not self.fp:
|
if not self.fp:
|
||||||
raise RuntimeError(
|
raise RuntimeError, \
|
||||||
"Attempt to write ZIP archive that was already closed")
|
"Attempt to write ZIP archive that was already closed"
|
||||||
if zinfo.compress_type == ZIP_DEFLATED and not zlib:
|
if zinfo.compress_type == ZIP_DEFLATED and not zlib:
|
||||||
raise RuntimeError(
|
raise RuntimeError, \
|
||||||
"Compression requires the (missing) zlib module")
|
"Compression requires the (missing) zlib module"
|
||||||
if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
|
if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
|
||||||
raise RuntimeError(
|
raise RuntimeError, \
|
||||||
"That compression method is not supported")
|
"That compression method is not supported"
|
||||||
if zinfo.file_size > ZIP64_LIMIT:
|
if zinfo.file_size > ZIP64_LIMIT:
|
||||||
if not self._allowZip64:
|
if not self._allowZip64:
|
||||||
raise LargeZipFile("Filesize would require ZIP64 extensions")
|
raise LargeZipFile("Filesize would require ZIP64 extensions")
|
||||||
@@ -1000,7 +1006,7 @@ class ZipFile:
|
|||||||
if isdir:
|
if isdir:
|
||||||
arcname += '/'
|
arcname += '/'
|
||||||
zinfo = ZipInfo(arcname, date_time)
|
zinfo = ZipInfo(arcname, date_time)
|
||||||
zinfo.external_attr = (st[0] & 0xFFFF) << 16 # Unix attributes
|
zinfo.external_attr = (st[0] & 0xFFFF) << 16L # Unix attributes
|
||||||
if compress_type is None:
|
if compress_type is None:
|
||||||
zinfo.compress_type = self.compression
|
zinfo.compress_type = self.compression
|
||||||
else:
|
else:
|
||||||
@@ -1070,7 +1076,7 @@ class ZipFile:
|
|||||||
date_time=time.localtime(time.time())[:6])
|
date_time=time.localtime(time.time())[:6])
|
||||||
|
|
||||||
zinfo.compress_type = self.compression
|
zinfo.compress_type = self.compression
|
||||||
zinfo.external_attr = 0x0600 << 16
|
zinfo.external_attr = 0600 << 16
|
||||||
else:
|
else:
|
||||||
zinfo = zinfo_or_arcname
|
zinfo = zinfo_or_arcname
|
||||||
|
|
||||||
@@ -1135,7 +1141,7 @@ class ZipFile:
|
|||||||
|
|
||||||
if zinfo.header_offset > ZIP64_LIMIT:
|
if zinfo.header_offset > ZIP64_LIMIT:
|
||||||
extra.append(zinfo.header_offset)
|
extra.append(zinfo.header_offset)
|
||||||
header_offset = 0xffffffff
|
header_offset = 0xffffffffL
|
||||||
else:
|
else:
|
||||||
header_offset = zinfo.header_offset
|
header_offset = zinfo.header_offset
|
||||||
|
|
||||||
@@ -1163,14 +1169,14 @@ class ZipFile:
|
|||||||
0, zinfo.internal_attr, zinfo.external_attr,
|
0, zinfo.internal_attr, zinfo.external_attr,
|
||||||
header_offset)
|
header_offset)
|
||||||
except DeprecationWarning:
|
except DeprecationWarning:
|
||||||
print(structCentralDir,
|
print >>sys.stderr, (structCentralDir,
|
||||||
stringCentralDir, create_version,
|
stringCentralDir, create_version,
|
||||||
zinfo.create_system, extract_version, zinfo.reserved,
|
zinfo.create_system, extract_version, zinfo.reserved,
|
||||||
zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
|
zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
|
||||||
zinfo.CRC, compress_size, file_size,
|
zinfo.CRC, compress_size, file_size,
|
||||||
len(zinfo.filename), len(extra_data), len(zinfo.comment),
|
len(zinfo.filename), len(extra_data), len(zinfo.comment),
|
||||||
0, zinfo.internal_attr, zinfo.external_attr,
|
0, zinfo.internal_attr, zinfo.external_attr,
|
||||||
header_offset, sys.stderr)
|
header_offset)
|
||||||
raise
|
raise
|
||||||
self.fp.write(centdir)
|
self.fp.write(centdir)
|
||||||
self.fp.write(filename)
|
self.fp.write(filename)
|
||||||
@@ -1244,10 +1250,10 @@ class PyZipFile(ZipFile):
|
|||||||
else:
|
else:
|
||||||
basename = name
|
basename = name
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("Adding package in", pathname, "as", basename)
|
print "Adding package in", pathname, "as", basename
|
||||||
fname, arcname = self._get_codename(initname[0:-3], basename)
|
fname, arcname = self._get_codename(initname[0:-3], basename)
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("Adding", arcname)
|
print "Adding", arcname
|
||||||
self.write(fname, arcname)
|
self.write(fname, arcname)
|
||||||
dirlist = os.listdir(pathname)
|
dirlist = os.listdir(pathname)
|
||||||
dirlist.remove("__init__.py")
|
dirlist.remove("__init__.py")
|
||||||
@@ -1263,12 +1269,12 @@ class PyZipFile(ZipFile):
|
|||||||
fname, arcname = self._get_codename(path[0:-3],
|
fname, arcname = self._get_codename(path[0:-3],
|
||||||
basename)
|
basename)
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("Adding", arcname)
|
print "Adding", arcname
|
||||||
self.write(fname, arcname)
|
self.write(fname, arcname)
|
||||||
else:
|
else:
|
||||||
# This is NOT a package directory, add its files at top level
|
# This is NOT a package directory, add its files at top level
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("Adding files from directory", pathname)
|
print "Adding files from directory", pathname
|
||||||
for filename in os.listdir(pathname):
|
for filename in os.listdir(pathname):
|
||||||
path = os.path.join(pathname, filename)
|
path = os.path.join(pathname, filename)
|
||||||
root, ext = os.path.splitext(filename)
|
root, ext = os.path.splitext(filename)
|
||||||
@@ -1276,15 +1282,15 @@ class PyZipFile(ZipFile):
|
|||||||
fname, arcname = self._get_codename(path[0:-3],
|
fname, arcname = self._get_codename(path[0:-3],
|
||||||
basename)
|
basename)
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("Adding", arcname)
|
print "Adding", arcname
|
||||||
self.write(fname, arcname)
|
self.write(fname, arcname)
|
||||||
else:
|
else:
|
||||||
if pathname[-3:] != ".py":
|
if pathname[-3:] != ".py":
|
||||||
raise RuntimeError(
|
raise RuntimeError, \
|
||||||
'Files added with writepy() must end with ".py"')
|
'Files added with writepy() must end with ".py"'
|
||||||
fname, arcname = self._get_codename(pathname[0:-3], basename)
|
fname, arcname = self._get_codename(pathname[0:-3], basename)
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("Adding file", arcname)
|
print "Adding file", arcname
|
||||||
self.write(fname, arcname)
|
self.write(fname, arcname)
|
||||||
|
|
||||||
def _get_codename(self, pathname, basename):
|
def _get_codename(self, pathname, basename):
|
||||||
@@ -1304,11 +1310,11 @@ class PyZipFile(ZipFile):
|
|||||||
os.stat(file_pyc).st_mtime < os.stat(file_py).st_mtime:
|
os.stat(file_pyc).st_mtime < os.stat(file_py).st_mtime:
|
||||||
import py_compile
|
import py_compile
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print("Compiling", file_py)
|
print "Compiling", file_py
|
||||||
try:
|
try:
|
||||||
py_compile.compile(file_py, file_pyc, None, True)
|
py_compile.compile(file_py, file_pyc, None, True)
|
||||||
except py_compile.PyCompileError as err:
|
except py_compile.PyCompileError,err:
|
||||||
print(err.msg)
|
print err.msg
|
||||||
fname = file_pyc
|
fname = file_pyc
|
||||||
else:
|
else:
|
||||||
fname = file_pyc
|
fname = file_pyc
|
||||||
@@ -1331,12 +1337,12 @@ def main(args = None):
|
|||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
|
|
||||||
if not args or args[0] not in ('-l', '-c', '-e', '-t'):
|
if not args or args[0] not in ('-l', '-c', '-e', '-t'):
|
||||||
print(USAGE)
|
print USAGE
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if args[0] == '-l':
|
if args[0] == '-l':
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
print(USAGE)
|
print USAGE
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
zf = ZipFile(args[1], 'r')
|
zf = ZipFile(args[1], 'r')
|
||||||
zf.printdir()
|
zf.printdir()
|
||||||
@@ -1344,15 +1350,15 @@ def main(args = None):
|
|||||||
|
|
||||||
elif args[0] == '-t':
|
elif args[0] == '-t':
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
print(USAGE)
|
print USAGE
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
zf = ZipFile(args[1], 'r')
|
zf = ZipFile(args[1], 'r')
|
||||||
zf.testzip()
|
zf.testzip()
|
||||||
print("Done testing")
|
print "Done testing"
|
||||||
|
|
||||||
elif args[0] == '-e':
|
elif args[0] == '-e':
|
||||||
if len(args) != 3:
|
if len(args) != 3:
|
||||||
print(USAGE)
|
print USAGE
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
zf = ZipFile(args[1], 'r')
|
zf = ZipFile(args[1], 'r')
|
||||||
@@ -1372,7 +1378,7 @@ def main(args = None):
|
|||||||
|
|
||||||
elif args[0] == '-c':
|
elif args[0] == '-c':
|
||||||
if len(args) < 3:
|
if len(args) < 3:
|
||||||
print(USAGE)
|
print USAGE
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def addToZip(zf, path, zippath):
|
def addToZip(zf, path, zippath):
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# zipfix.py
|
# zipfix.py, version 1.1
|
||||||
# Copyright © 2010-2020 by Apprentice Harper et al.
|
# Copyright © 2010-2013 by some_updates, DiapDealer and Apprentice Alf
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
@@ -10,22 +10,18 @@
|
|||||||
# Revision history:
|
# Revision history:
|
||||||
# 1.0 - Initial release
|
# 1.0 - Initial release
|
||||||
# 1.1 - Updated to handle zip file metadata correctly
|
# 1.1 - Updated to handle zip file metadata correctly
|
||||||
# 2.0 - Python 3 for calibre 5.0
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Re-write zip (or ePub) fixing problems with file names (and mimetype entry).
|
Re-write zip (or ePub) fixing problems with file names (and mimetype entry).
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "1.1"
|
__version__ = "1.1"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import zlib
|
import zlib
|
||||||
try:
|
import zipfilerugged
|
||||||
import zipfilerugged
|
|
||||||
except:
|
|
||||||
import calibre_plugins.dedrm.zipfilerugged as zipfilerugged
|
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import getopt
|
import getopt
|
||||||
@@ -53,7 +49,7 @@ class fixZip:
|
|||||||
self.inzip = zipfilerugged.ZipFile(zinput,'r')
|
self.inzip = zipfilerugged.ZipFile(zinput,'r')
|
||||||
self.outzip = zipfilerugged.ZipFile(zoutput,'w')
|
self.outzip = zipfilerugged.ZipFile(zoutput,'w')
|
||||||
# open the input zip for reading only as a raw file
|
# open the input zip for reading only as a raw file
|
||||||
self.bzf = open(zinput,'rb')
|
self.bzf = file(zinput,'rb')
|
||||||
|
|
||||||
def getlocalname(self, zi):
|
def getlocalname(self, zi):
|
||||||
local_header_offset = zi.header_offset
|
local_header_offset = zi.header_offset
|
||||||
@@ -119,7 +115,7 @@ class fixZip:
|
|||||||
# if epub write mimetype file first, with no compression
|
# if epub write mimetype file first, with no compression
|
||||||
if self.ztype == 'epub':
|
if self.ztype == 'epub':
|
||||||
# first get a ZipInfo with current time and no compression
|
# first get a ZipInfo with current time and no compression
|
||||||
mimeinfo = ZipInfo(b'mimetype',compress_type=zipfilerugged.ZIP_STORED)
|
mimeinfo = ZipInfo('mimetype',compress_type=zipfilerugged.ZIP_STORED)
|
||||||
mimeinfo.internal_attr = 1 # text file
|
mimeinfo.internal_attr = 1 # text file
|
||||||
try:
|
try:
|
||||||
# if the mimetype is present, get its info, including time-stamp
|
# if the mimetype is present, get its info, including time-stamp
|
||||||
@@ -133,7 +129,7 @@ class fixZip:
|
|||||||
mimeinfo.create_system = oldmimeinfo.create_system
|
mimeinfo.create_system = oldmimeinfo.create_system
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self.outzip.writestr(mimeinfo, _MIMETYPE.encode('ascii'))
|
self.outzip.writestr(mimeinfo, _MIMETYPE)
|
||||||
|
|
||||||
# write the rest of the files
|
# write the rest of the files
|
||||||
for zinfo in self.inzip.infolist():
|
for zinfo in self.inzip.infolist():
|
||||||
@@ -175,7 +171,7 @@ def repairBook(infile, outfile):
|
|||||||
fr = fixZip(infile, outfile)
|
fr = fixZip(infile, outfile)
|
||||||
fr.fix()
|
fr.fix()
|
||||||
return 0
|
return 0
|
||||||
except Exception as e:
|
except Exception, e:
|
||||||
print("Error Occurred ", e)
|
print("Error Occurred ", e)
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
_license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
import codecs
|
|
||||||
import os, traceback, zipfile
|
import os, traceback, zipfile
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PyQt5.Qt import QToolButton, QUrl
|
from PyQt5.Qt import QToolButton, QUrl
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from PyQt4.Qt import QToolButton, QUrl
|
from PyQt4.Qt import QToolButton, QUrl
|
||||||
|
|
||||||
from calibre.gui2 import open_url, question_dialog
|
from calibre.gui2 import open_url, question_dialog
|
||||||
from calibre.gui2.actions import InterfaceAction
|
from calibre.gui2.actions import InterfaceAction
|
||||||
from calibre.utils.config import config_dir
|
from calibre.utils.config import config_dir
|
||||||
@@ -25,7 +24,7 @@ from calibre.ebooks.metadata.meta import get_metadata
|
|||||||
from calibre_plugins.obok_dedrm.dialogs import (SelectionDialog, DecryptAddProgressDialog,
|
from calibre_plugins.obok_dedrm.dialogs import (SelectionDialog, DecryptAddProgressDialog,
|
||||||
AddEpubFormatsProgressDialog, ResultsSummaryDialog)
|
AddEpubFormatsProgressDialog, ResultsSummaryDialog)
|
||||||
from calibre_plugins.obok_dedrm.config import plugin_prefs as cfg
|
from calibre_plugins.obok_dedrm.config import plugin_prefs as cfg
|
||||||
from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME, PLUGIN_SAFE_NAME,
|
from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME, PLUGIN_SAFE_NAME,
|
||||||
PLUGIN_VERSION, PLUGIN_DESCRIPTION, HELPFILE_NAME)
|
PLUGIN_VERSION, PLUGIN_DESCRIPTION, HELPFILE_NAME)
|
||||||
from calibre_plugins.obok_dedrm.utilities import (
|
from calibre_plugins.obok_dedrm.utilities import (
|
||||||
get_icon, set_plugin_icon_resources, showErrorDlg, format_plural,
|
get_icon, set_plugin_icon_resources, showErrorDlg, format_plural,
|
||||||
@@ -54,7 +53,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
def genesis(self):
|
def genesis(self):
|
||||||
icon_resources = self.load_resources(PLUGIN_ICONS)
|
icon_resources = self.load_resources(PLUGIN_ICONS)
|
||||||
set_plugin_icon_resources(PLUGIN_NAME, icon_resources)
|
set_plugin_icon_resources(PLUGIN_NAME, icon_resources)
|
||||||
|
|
||||||
self.qaction.setIcon(get_icon(PLUGIN_ICONS[0]))
|
self.qaction.setIcon(get_icon(PLUGIN_ICONS[0]))
|
||||||
self.qaction.triggered.connect(self.launchObok)
|
self.qaction.triggered.connect(self.launchObok)
|
||||||
self.gui.keyboard.finalize()
|
self.gui.keyboard.finalize()
|
||||||
@@ -107,10 +106,10 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
# Get a list of Kobo titles
|
# Get a list of Kobo titles
|
||||||
books = self.build_book_list()
|
books = self.build_book_list()
|
||||||
if len(books) < 1:
|
if len(books) < 1:
|
||||||
msg = _('<p>No books found in Kobo Library\nAre you sure it\'s installed/configured/synchronized?')
|
msg = _('<p>No books found in Kobo Library\nAre you sure it\'s installed\configured\synchronized?')
|
||||||
showErrorDlg(msg, None)
|
showErrorDlg(msg, None)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check to see if a key can be retrieved using the legacy obok method.
|
# Check to see if a key can be retrieved using the legacy obok method.
|
||||||
legacy_key = legacy_obok().get_legacy_cookie_id
|
legacy_key = legacy_obok().get_legacy_cookie_id
|
||||||
if legacy_key is not None:
|
if legacy_key is not None:
|
||||||
@@ -155,7 +154,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
# Close Kobo Library object
|
# Close Kobo Library object
|
||||||
self.library.close()
|
self.library.close()
|
||||||
|
|
||||||
# If we have decrypted books to work with, feed the list of decrypted books details
|
# If we have decrypted books to work with, feed the list of decrypted books details
|
||||||
# and the callback function (self.add_new_books) to the ProgressDialog dispatcher.
|
# and the callback function (self.add_new_books) to the ProgressDialog dispatcher.
|
||||||
if len(self.books_to_add):
|
if len(self.books_to_add):
|
||||||
d = DecryptAddProgressDialog(self.gui, self.books_to_add, self.add_new_books, self.db, 'calibre',
|
d = DecryptAddProgressDialog(self.gui, self.books_to_add, self.add_new_books, self.db, 'calibre',
|
||||||
@@ -213,7 +212,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
def get_decrypted_kobo_books(self, book):
|
def get_decrypted_kobo_books(self, book):
|
||||||
'''
|
'''
|
||||||
This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to decrypt Kobo books
|
This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to decrypt Kobo books
|
||||||
|
|
||||||
:param book: A KoboBook object that is to be decrypted.
|
:param book: A KoboBook object that is to be decrypted.
|
||||||
'''
|
'''
|
||||||
print (_('{0} - Decrypting {1}').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, book.title))
|
print (_('{0} - Decrypting {1}').format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, book.title))
|
||||||
@@ -234,7 +233,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
'''
|
'''
|
||||||
This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to add books to calibre
|
This method is a call-back function used by DecryptAddProgressDialog in dialogs.py to add books to calibre
|
||||||
(It's set up to handle multiple books, but will only be fed books one at a time by DecryptAddProgressDialog)
|
(It's set up to handle multiple books, but will only be fed books one at a time by DecryptAddProgressDialog)
|
||||||
|
|
||||||
:param books_to_add: List of calibre bookmaps (created in get_decrypted_kobo_books)
|
:param books_to_add: List of calibre bookmaps (created in get_decrypted_kobo_books)
|
||||||
'''
|
'''
|
||||||
added = self.db.add_books(books_to_add, add_duplicates=False, run_hooks=False)
|
added = self.db.add_books(books_to_add, add_duplicates=False, run_hooks=False)
|
||||||
@@ -254,7 +253,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
def add_epub_format(self, book_id, mi, path):
|
def add_epub_format(self, book_id, mi, path):
|
||||||
'''
|
'''
|
||||||
This method is a call-back function used by AddEpubFormatsProgressDialog in dialogs.py
|
This method is a call-back function used by AddEpubFormatsProgressDialog in dialogs.py
|
||||||
|
|
||||||
:param book_id: calibre ID of the book to add the encrypted epub to.
|
:param book_id: calibre ID of the book to add the encrypted epub to.
|
||||||
:param mi: calibre metadata object
|
:param mi: calibre metadata object
|
||||||
:param path: path to the decrypted epub (temp file)
|
:param path: path to the decrypted epub (temp file)
|
||||||
@@ -282,7 +281,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
self.formats_to_add.append((home_id, mi, tmp_file))
|
self.formats_to_add.append((home_id, mi, tmp_file))
|
||||||
else:
|
else:
|
||||||
self.no_home_for_book.append(mi)
|
self.no_home_for_book.append(mi)
|
||||||
# If we found homes for decrypted epubs in existing calibre entries, feed the list of decrypted book
|
# If we found homes for decrypted epubs in existing calibre entries, feed the list of decrypted book
|
||||||
# details and the callback function (self.add_epub_format) to the ProgressDialog dispatcher.
|
# details and the callback function (self.add_epub_format) to the ProgressDialog dispatcher.
|
||||||
if self.formats_to_add:
|
if self.formats_to_add:
|
||||||
d = AddEpubFormatsProgressDialog(self.gui, self.formats_to_add, self.add_epub_format)
|
d = AddEpubFormatsProgressDialog(self.gui, self.formats_to_add, self.add_epub_format)
|
||||||
@@ -307,10 +306,10 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
sd = ResultsSummaryDialog(self.gui, caption, msg, log)
|
sd = ResultsSummaryDialog(self.gui, caption, msg, log)
|
||||||
sd.exec_()
|
sd.exec_()
|
||||||
return
|
return
|
||||||
|
|
||||||
def ask_about_inserting_epubs(self):
|
def ask_about_inserting_epubs(self):
|
||||||
'''
|
'''
|
||||||
Build question dialog with details about kobo books
|
Build question dialog with details about kobo books
|
||||||
that couldn't be added to calibre as new books.
|
that couldn't be added to calibre as new books.
|
||||||
'''
|
'''
|
||||||
''' Terisa: Improve the message
|
''' Terisa: Improve the message
|
||||||
@@ -328,13 +327,13 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
msg = _('<p><b>{0}</b> -- not added because of {1} in your library.<br /><br />').format(self.duplicate_book_list[0][0].title, self.duplicate_book_list[0][2])
|
msg = _('<p><b>{0}</b> -- not added because of {1} in your library.<br /><br />').format(self.duplicate_book_list[0][0].title, self.duplicate_book_list[0][2])
|
||||||
msg += _('Would you like to try and add the EPUB format to an available calibre duplicate?<br /><br />')
|
msg += _('Would you like to try and add the EPUB format to an available calibre duplicate?<br /><br />')
|
||||||
msg += _('NOTE: no pre-existing EPUB will be overwritten.')
|
msg += _('NOTE: no pre-existing EPUB will be overwritten.')
|
||||||
|
|
||||||
return question_dialog(self.gui, caption, msg, det_msg)
|
return question_dialog(self.gui, caption, msg, det_msg)
|
||||||
|
|
||||||
def find_a_home(self, ids):
|
def find_a_home(self, ids):
|
||||||
'''
|
'''
|
||||||
Find the ID of the first EPUB-Free duplicate available
|
Find the ID of the first EPUB-Free duplicate available
|
||||||
|
|
||||||
:param ids: List of calibre IDs that might serve as a home.
|
:param ids: List of calibre IDs that might serve as a home.
|
||||||
'''
|
'''
|
||||||
for id in ids:
|
for id in ids:
|
||||||
@@ -374,7 +373,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
zin = zipfile.ZipFile(book.filename, 'r')
|
zin = zipfile.ZipFile(book.filename, 'r')
|
||||||
#print ('Kobo library filename: {0}'.format(book.filename))
|
#print ('Kobo library filename: {0}'.format(book.filename))
|
||||||
for userkey in self.userkeys:
|
for userkey in self.userkeys:
|
||||||
print (_('Trying key: '), codecs.encode(userkey, 'hex'))
|
print (_('Trying key: '), userkey.encode('hex_codec'))
|
||||||
check = True
|
check = True
|
||||||
try:
|
try:
|
||||||
fileout = PersistentTemporaryFile('.epub', dir=self.tdir)
|
fileout = PersistentTemporaryFile('.epub', dir=self.tdir)
|
||||||
@@ -456,7 +455,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
if cancelled_count > 0:
|
if cancelled_count > 0:
|
||||||
log += _('<p><b>Book imports cancelled by user:</b> {}</p>\n').format(cancelled_count)
|
log += _('<p><b>Book imports cancelled by user:</b> {}</p>\n').format(cancelled_count)
|
||||||
return (msg, log)
|
return (msg, log)
|
||||||
log += _('<p><b>New EPUB formats inserted in existing calibre books:</b> {0}</p>\n').format(len(self.successful_format_adds))
|
log += _('<p><b>New EPUB formats inserted in existing calibre books:</b> {0}</p>\n').format(len(self.successful_format_adds))
|
||||||
if self.successful_format_adds:
|
if self.successful_format_adds:
|
||||||
log += '<ul>\n'
|
log += '<ul>\n'
|
||||||
for id, mi in self.successful_format_adds:
|
for id, mi in self.successful_format_adds:
|
||||||
@@ -475,7 +474,7 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
log += _('<p><b>Format imports cancelled by user:</b> {}</p>\n').format(cancelled_count)
|
log += _('<p><b>Format imports cancelled by user:</b> {}</p>\n').format(cancelled_count)
|
||||||
return (msg, log)
|
return (msg, log)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# Single book ... don't get fancy.
|
# Single book ... don't get fancy.
|
||||||
if self.ids_of_new_books:
|
if self.ids_of_new_books:
|
||||||
title = self.ids_of_new_books[0][1].title
|
title = self.ids_of_new_books[0][1].title
|
||||||
@@ -495,4 +494,4 @@ class InterfacePluginAction(InterfaceAction):
|
|||||||
reason = _('of unknown reasons. Gosh I\'m embarrassed!')
|
reason = _('of unknown reasons. Gosh I\'m embarrassed!')
|
||||||
msg = _('<p>{0} not added because {1}').format(title, reason)
|
msg = _('<p>{0} not added because {1}').format(title, reason)
|
||||||
return (msg, log)
|
return (msg, log)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2012, David Forrester <davidfor@internode.on.net>'
|
__copyright__ = '2012, David Forrester <davidfor@internode.on.net>'
|
||||||
@@ -8,7 +9,13 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import os, time, re, sys
|
import os, time, re, sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from PyQt5.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
|
try:
|
||||||
|
from PyQt5.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
|
||||||
|
QTableWidgetItem, QFont, QLineEdit, QComboBox,
|
||||||
|
QVBoxLayout, QDialogButtonBox, QStyledItemDelegate, QDateTime,
|
||||||
|
QRegExpValidator, QRegExp, QDate, QDateEdit)
|
||||||
|
except ImportError:
|
||||||
|
from PyQt4.Qt import (Qt, QIcon, QPixmap, QLabel, QDialog, QHBoxLayout, QProgressBar,
|
||||||
QTableWidgetItem, QFont, QLineEdit, QComboBox,
|
QTableWidgetItem, QFont, QLineEdit, QComboBox,
|
||||||
QVBoxLayout, QDialogButtonBox, QStyledItemDelegate, QDateTime,
|
QVBoxLayout, QDialogButtonBox, QStyledItemDelegate, QDateTime,
|
||||||
QRegExpValidator, QRegExp, QDate, QDateEdit)
|
QRegExpValidator, QRegExp, QDate, QDateEdit)
|
||||||
@@ -420,7 +427,7 @@ class KeyValueComboBox(QComboBox):
|
|||||||
|
|
||||||
def selected_key(self):
|
def selected_key(self):
|
||||||
for key, value in self.values.iteritems():
|
for key, value in self.values.iteritems():
|
||||||
if value == self.currentText().strip():
|
if value == unicode(self.currentText()).strip():
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
@@ -443,7 +450,7 @@ class KeyComboBox(QComboBox):
|
|||||||
|
|
||||||
def selected_key(self):
|
def selected_key(self):
|
||||||
for key, value in self.values.iteritems():
|
for key, value in self.values.iteritems():
|
||||||
if key == self.currentText().strip():
|
if key == unicode(self.currentText()).strip():
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
|
try:
|
||||||
from PyQt5 import Qt as QtGui
|
from PyQt5.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
|
||||||
|
except ImportError:
|
||||||
|
from PyQt4.Qt import (Qt, QGroupBox, QListWidget, QLineEdit, QDialogButtonBox, QWidget, QLabel, QDialog, QVBoxLayout, QAbstractItemView, QIcon, QHBoxLayout, QComboBox, QListWidgetItem, QFileDialog)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PyQt5 import Qt as QtGui
|
||||||
|
except ImportError:
|
||||||
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url)
|
from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url)
|
||||||
from calibre.utils.config import JSONConfig, config_dir
|
from calibre.utils.config import JSONConfig, config_dir
|
||||||
@@ -44,32 +50,32 @@ class ConfigWidget(QWidget):
|
|||||||
self.find_homes.setCurrentIndex(index)
|
self.find_homes.setCurrentIndex(index)
|
||||||
|
|
||||||
self.serials_button = QtGui.QPushButton(self)
|
self.serials_button = QtGui.QPushButton(self)
|
||||||
self.serials_button.setToolTip(_("Click to manage Kobo serial numbers for Kobo ebooks"))
|
self.serials_button.setToolTip(_(u"Click to manage Kobo serial numbers for Kobo ebooks"))
|
||||||
self.serials_button.setText("Kobo devices serials")
|
self.serials_button.setText(u"Kobo devices serials")
|
||||||
self.serials_button.clicked.connect(self.edit_serials)
|
self.serials_button.clicked.connect(self.edit_serials)
|
||||||
layout.addWidget(self.serials_button)
|
layout.addWidget(self.serials_button)
|
||||||
|
|
||||||
self.kobo_directory_button = QtGui.QPushButton(self)
|
self.kobo_directory_button = QtGui.QPushButton(self)
|
||||||
self.kobo_directory_button.setToolTip(_("Click to specify the Kobo directory"))
|
self.kobo_directory_button.setToolTip(_(u"Click to specify the Kobo directory"))
|
||||||
self.kobo_directory_button.setText("Kobo directory")
|
self.kobo_directory_button.setText(u"Kobo directory")
|
||||||
self.kobo_directory_button.clicked.connect(self.edit_kobo_directory)
|
self.kobo_directory_button.clicked.connect(self.edit_kobo_directory)
|
||||||
layout.addWidget(self.kobo_directory_button)
|
layout.addWidget(self.kobo_directory_button)
|
||||||
|
|
||||||
|
|
||||||
def edit_serials(self):
|
def edit_serials(self):
|
||||||
d = ManageKeysDialog(self,"Kobo device serial number",self.tmpserials, AddSerialDialog)
|
d = ManageKeysDialog(self,u"Kobo device serial number",self.tmpserials, AddSerialDialog)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
|
|
||||||
def edit_kobo_directory(self):
|
def edit_kobo_directory(self):
|
||||||
tmpkobodirectory = QFileDialog.getExistingDirectory(self, "Select Kobo directory", self.kobodirectory or "/home", QFileDialog.ShowDirsOnly)
|
tmpkobodirectory = QFileDialog.getExistingDirectory(self, u"Select Kobo directory", self.kobodirectory or "/home", QFileDialog.ShowDirsOnly)
|
||||||
|
|
||||||
if tmpkobodirectory != u"" and tmpkobodirectory is not None:
|
if tmpkobodirectory != u"" and tmpkobodirectory is not None:
|
||||||
self.kobodirectory = tmpkobodirectory
|
self.kobodirectory = tmpkobodirectory
|
||||||
|
|
||||||
|
|
||||||
def save_settings(self):
|
def save_settings(self):
|
||||||
plugin_prefs['finding_homes_for_formats'] = self.find_homes.currentText()
|
plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText())
|
||||||
plugin_prefs['kobo_serials'] = self.tmpserials
|
plugin_prefs['kobo_serials'] = self.tmpserials
|
||||||
plugin_prefs['kobo_directory'] = self.kobodirectory
|
plugin_prefs['kobo_directory'] = self.kobodirectory
|
||||||
|
|
||||||
@@ -85,7 +91,7 @@ class ManageKeysDialog(QDialog):
|
|||||||
self.plugin_keys = plugin_keys
|
self.plugin_keys = plugin_keys
|
||||||
self.create_key = create_key
|
self.create_key = create_key
|
||||||
self.keyfile_ext = keyfile_ext
|
self.keyfile_ext = keyfile_ext
|
||||||
self.json_file = (keyfile_ext == "k4i")
|
self.json_file = (keyfile_ext == u"k4i")
|
||||||
|
|
||||||
self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
|
self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
|
||||||
|
|
||||||
@@ -93,13 +99,13 @@ class ManageKeysDialog(QDialog):
|
|||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
keys_group_box = QGroupBox(_("{0}s".format(self.key_type_name)), self)
|
keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self)
|
||||||
layout.addWidget(keys_group_box)
|
layout.addWidget(keys_group_box)
|
||||||
keys_group_box_layout = QHBoxLayout()
|
keys_group_box_layout = QHBoxLayout()
|
||||||
keys_group_box.setLayout(keys_group_box_layout)
|
keys_group_box.setLayout(keys_group_box_layout)
|
||||||
|
|
||||||
self.listy = QListWidget(self)
|
self.listy = QListWidget(self)
|
||||||
self.listy.setToolTip("{0}s that will be used to decrypt ebooks".format(self.key_type_name))
|
self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name))
|
||||||
self.listy.setSelectionMode(QAbstractItemView.SingleSelection)
|
self.listy.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||||
self.populate_list()
|
self.populate_list()
|
||||||
keys_group_box_layout.addWidget(self.listy)
|
keys_group_box_layout.addWidget(self.listy)
|
||||||
@@ -108,12 +114,12 @@ class ManageKeysDialog(QDialog):
|
|||||||
keys_group_box_layout.addLayout(button_layout)
|
keys_group_box_layout.addLayout(button_layout)
|
||||||
self._add_key_button = QtGui.QToolButton(self)
|
self._add_key_button = QtGui.QToolButton(self)
|
||||||
self._add_key_button.setIcon(QIcon(I('plus.png')))
|
self._add_key_button.setIcon(QIcon(I('plus.png')))
|
||||||
self._add_key_button.setToolTip("Create new {0}".format(self.key_type_name))
|
self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name))
|
||||||
self._add_key_button.clicked.connect(self.add_key)
|
self._add_key_button.clicked.connect(self.add_key)
|
||||||
button_layout.addWidget(self._add_key_button)
|
button_layout.addWidget(self._add_key_button)
|
||||||
|
|
||||||
self._delete_key_button = QtGui.QToolButton(self)
|
self._delete_key_button = QtGui.QToolButton(self)
|
||||||
self._delete_key_button.setToolTip(_("Delete highlighted key"))
|
self._delete_key_button.setToolTip(_(u"Delete highlighted key"))
|
||||||
self._delete_key_button.setIcon(QIcon(I('list_remove.png')))
|
self._delete_key_button.setIcon(QIcon(I('list_remove.png')))
|
||||||
self._delete_key_button.clicked.connect(self.delete_key)
|
self._delete_key_button.clicked.connect(self.delete_key)
|
||||||
button_layout.addWidget(self._delete_key_button)
|
button_layout.addWidget(self._delete_key_button)
|
||||||
@@ -149,7 +155,7 @@ class ManageKeysDialog(QDialog):
|
|||||||
new_key_value = d.key_value
|
new_key_value = d.key_value
|
||||||
if new_key_value in self.plugin_keys:
|
if new_key_value in self.plugin_keys:
|
||||||
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
|
||||||
"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
|
u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.plugin_keys.append(d.key_value)
|
self.plugin_keys.append(d.key_value)
|
||||||
@@ -159,8 +165,8 @@ class ManageKeysDialog(QDialog):
|
|||||||
def delete_key(self):
|
def delete_key(self):
|
||||||
if not self.listy.currentItem():
|
if not self.listy.currentItem():
|
||||||
return
|
return
|
||||||
keyname = self.listy.currentItem().text()
|
keyname = unicode(self.listy.currentItem().text())
|
||||||
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), "Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
|
if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} <strong>{0}</strong>?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
|
||||||
return
|
return
|
||||||
self.plugin_keys.remove(keyname)
|
self.plugin_keys.remove(keyname)
|
||||||
|
|
||||||
@@ -171,7 +177,7 @@ class AddSerialDialog(QDialog):
|
|||||||
def __init__(self, parent=None,):
|
def __init__(self, parent=None,):
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.setWindowTitle("{0} {1}: Add New eInk Kobo Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
|
self.setWindowTitle(u"{0} {1}: Add New eInk Kobo Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
@@ -182,9 +188,9 @@ class AddSerialDialog(QDialog):
|
|||||||
|
|
||||||
key_group = QHBoxLayout()
|
key_group = QHBoxLayout()
|
||||||
data_group_box_layout.addLayout(key_group)
|
data_group_box_layout.addLayout(key_group)
|
||||||
key_group.addWidget(QLabel("EInk Kobo Serial Number:", self))
|
key_group.addWidget(QLabel(u"EInk Kobo Serial Number:", self))
|
||||||
self.key_ledit = QLineEdit("", self)
|
self.key_ledit = QLineEdit("", self)
|
||||||
self.key_ledit.setToolTip("Enter an eInk Kobo serial number. EInk Kobo serial numbers are 13 characters long and usually start with a 'N'. Kobo Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
self.key_ledit.setToolTip(u"Enter an eInk Kobo serial number. EInk Kobo serial numbers are 13 characters long and usually start with a 'N'. Kobo Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
|
||||||
key_group.addWidget(self.key_ledit)
|
key_group.addWidget(self.key_ledit)
|
||||||
|
|
||||||
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||||
@@ -196,17 +202,17 @@ class AddSerialDialog(QDialog):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def key_name(self):
|
def key_name(self):
|
||||||
return self.key_ledit.text().strip()
|
return unicode(self.key_ledit.text()).strip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key_value(self):
|
def key_value(self):
|
||||||
return self.key_ledit.text().strip()
|
return unicode(self.key_ledit.text()).strip()
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
if len(self.key_name) == 0 or self.key_name.isspace():
|
if len(self.key_name) == 0 or self.key_name.isspace():
|
||||||
errmsg = "Please enter an eInk Kindle Serial Number or click Cancel in the dialog."
|
errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog."
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
if len(self.key_name) != 13:
|
if len(self.key_name) != 13:
|
||||||
errmsg = "EInk Kobo Serial Numbers must be 13 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
errmsg = u"EInk Kobo Serial Numbers must be 13 characters long. This is {0:d} characters long.".format(len(self.key_name))
|
||||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|||||||
@@ -37,14 +37,14 @@ class legacy_obok(object):
|
|||||||
|
|
||||||
def __oldcookiedeviceid(self):
|
def __oldcookiedeviceid(self):
|
||||||
'''Optionally attempt to get a device id using the old cookie method.
|
'''Optionally attempt to get a device id using the old cookie method.
|
||||||
Must have winreg installed on Windows machines for successful key retrieval.'''
|
Must have _winreg installed on Windows machines for successful key retrieval.'''
|
||||||
wsuid = ''
|
wsuid = ''
|
||||||
pwsdid = ''
|
pwsdid = ''
|
||||||
try:
|
try:
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
import winreg
|
import _winreg
|
||||||
regkey_browser = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\Kobo\\Kobo Desktop Edition\\Browser')
|
regkey_browser = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, 'Software\\Kobo\\Kobo Desktop Edition\\Browser')
|
||||||
cookies = winreg.QueryValueEx(regkey_browser, 'cookies')
|
cookies = _winreg.QueryValueEx(regkey_browser, 'cookies')
|
||||||
bytearrays = cookies[0]
|
bytearrays = cookies[0]
|
||||||
elif sys.platform.startswith('darwin'):
|
elif sys.platform.startswith('darwin'):
|
||||||
prefs = os.path.join(os.environ['HOME'], 'Library/Preferences/com.kobo.Kobo Desktop Edition.plist')
|
prefs = os.path.join(os.environ['HOME'], 'Library/Preferences/com.kobo.Kobo Desktop Edition.plist')
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Version 4.0.0 September 2020
|
|
||||||
# Python 3.0
|
|
||||||
#
|
|
||||||
# Version 3.2.5 December 2016
|
# Version 3.2.5 December 2016
|
||||||
# Improve detection of good text decryption.
|
# Improve detection of good text decryption.
|
||||||
#
|
#
|
||||||
@@ -155,8 +152,8 @@
|
|||||||
"""Manage all Kobo books, either encrypted or DRM-free."""
|
"""Manage all Kobo books, either encrypted or DRM-free."""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
__version__ = '4.0.0'
|
__version__ = '3.2.4'
|
||||||
__about__ = "Obok v{0}\nCopyright © 2012-2020 Physisticated et al.".format(__version__)
|
__about__ = u"Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -176,10 +173,10 @@ import tempfile
|
|||||||
can_parse_xml = True
|
can_parse_xml = True
|
||||||
try:
|
try:
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
# print "using xml.etree for xml parsing"
|
# print u"using xml.etree for xml parsing"
|
||||||
except ImportError:
|
except ImportError:
|
||||||
can_parse_xml = False
|
can_parse_xml = False
|
||||||
# print "Cannot find xml.etree, disabling extraction of serial numbers"
|
# print u"Cannot find xml.etree, disabling extraction of serial numbers"
|
||||||
|
|
||||||
# List of all known hash keys
|
# List of all known hash keys
|
||||||
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
|
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
|
||||||
@@ -234,7 +231,7 @@ def _load_crypto_libcrypto():
|
|||||||
raise ENCRYPTIONError(_('Failed to initialize AES key'))
|
raise ENCRYPTIONError(_('Failed to initialize AES key'))
|
||||||
|
|
||||||
def decrypt(self, data):
|
def decrypt(self, data):
|
||||||
clear = b''
|
clear = ''
|
||||||
for i in range(0, len(data), 16):
|
for i in range(0, len(data), 16):
|
||||||
out = create_string_buffer(16)
|
out = create_string_buffer(16)
|
||||||
rv = AES_ecb_encrypt(data[i:i+16], out, self._key, 0)
|
rv = AES_ecb_encrypt(data[i:i+16], out, self._key, 0)
|
||||||
@@ -279,10 +276,10 @@ class SafeUnbuffered:
|
|||||||
if self.encoding == None:
|
if self.encoding == None:
|
||||||
self.encoding = "utf-8"
|
self.encoding = "utf-8"
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data,str):
|
if isinstance(data,unicode):
|
||||||
data = data.encode(self.encoding,"replace")
|
data = data.encode(self.encoding,"replace")
|
||||||
self.stream.buffer.write(data)
|
self.stream.write(data)
|
||||||
self.stream.buffer.flush()
|
self.stream.flush()
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.stream, attr)
|
return getattr(self.stream, attr)
|
||||||
|
|
||||||
@@ -312,9 +309,9 @@ class KoboLibrary(object):
|
|||||||
# step 1. check whether this looks like a real device
|
# step 1. check whether this looks like a real device
|
||||||
if (device_path):
|
if (device_path):
|
||||||
# we got a device path
|
# we got a device path
|
||||||
self.kobodir = os.path.join(device_path, ".kobo")
|
self.kobodir = os.path.join(device_path, u".kobo")
|
||||||
# devices use KoboReader.sqlite
|
# devices use KoboReader.sqlite
|
||||||
kobodb = os.path.join(self.kobodir, "KoboReader.sqlite")
|
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite")
|
||||||
if (not(os.path.isfile(kobodb))):
|
if (not(os.path.isfile(kobodb))):
|
||||||
# device path seems to be wrong, unset it
|
# device path seems to be wrong, unset it
|
||||||
device_path = u""
|
device_path = u""
|
||||||
@@ -326,22 +323,22 @@ class KoboLibrary(object):
|
|||||||
if (len(serials) == 0):
|
if (len(serials) == 0):
|
||||||
# we got a device path but no saved serial
|
# we got a device path but no saved serial
|
||||||
# try to get the serial from the device
|
# try to get the serial from the device
|
||||||
# print "get_device_settings - device_path = {0}".format(device_path)
|
# print u"get_device_settings - device_path = {0}".format(device_path)
|
||||||
# get serial from device_path/.adobe-digital-editions/device.xml
|
# get serial from device_path/.adobe-digital-editions/device.xml
|
||||||
if can_parse_xml:
|
if can_parse_xml:
|
||||||
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
|
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
|
||||||
# print "trying to load {0}".format(devicexml)
|
# print u"trying to load {0}".format(devicexml)
|
||||||
if (os.path.exists(devicexml)):
|
if (os.path.exists(devicexml)):
|
||||||
# print "trying to parse {0}".format(devicexml)
|
# print u"trying to parse {0}".format(devicexml)
|
||||||
xmltree = ET.parse(devicexml)
|
xmltree = ET.parse(devicexml)
|
||||||
for node in xmltree.iter():
|
for node in xmltree.iter():
|
||||||
if "deviceSerial" in node.tag:
|
if "deviceSerial" in node.tag:
|
||||||
serial = node.text
|
serial = node.text
|
||||||
# print "found serial {0}".format(serial)
|
# print u"found serial {0}".format(serial)
|
||||||
serials.append(serial)
|
serials.append(serial)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# print "cannot get serials from device."
|
# print u"cannot get serials from device."
|
||||||
device_path = u""
|
device_path = u""
|
||||||
self.kobodir = u""
|
self.kobodir = u""
|
||||||
kobodb = u""
|
kobodb = u""
|
||||||
@@ -353,23 +350,23 @@ class KoboLibrary(object):
|
|||||||
|
|
||||||
if (self.kobodir == u""):
|
if (self.kobodir == u""):
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
import winreg
|
import _winreg as winreg
|
||||||
if sys.getwindowsversion().major > 5:
|
if sys.getwindowsversion().major > 5:
|
||||||
if 'LOCALAPPDATA' in os.environ.keys():
|
if 'LOCALAPPDATA' in os.environ.keys():
|
||||||
# Python 2.x does not return unicode env. Use Python 3.x
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
self.kobodir = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
self.kobodir = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||||
if (self.kobodir == u""):
|
if (self.kobodir == u""):
|
||||||
if 'USERPROFILE' in os.environ.keys():
|
if 'USERPROFILE' in os.environ.keys():
|
||||||
# Python 2.x does not return unicode env. Use Python 3.x
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%USERPROFILE%"), "Local Settings", "Application Data")
|
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings(u"%USERPROFILE%"), u"Local Settings", u"Application Data")
|
||||||
self.kobodir = os.path.join(self.kobodir, "Kobo", "Kobo Desktop Edition")
|
self.kobodir = os.path.join(self.kobodir, u"Kobo", u"Kobo Desktop Edition")
|
||||||
elif sys.platform.startswith('darwin'):
|
elif sys.platform.startswith('darwin'):
|
||||||
self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "Kobo Desktop Edition")
|
self.kobodir = os.path.join(os.environ['HOME'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition")
|
||||||
#elif linux_path != None:
|
#elif linux_path != None:
|
||||||
# Probably Linux, let's get the wine prefix and path to Kobo.
|
# Probably Linux, let's get the wine prefix and path to Kobo.
|
||||||
# self.kobodir = os.path.join(linux_path, "Local Settings", "Application Data", "Kobo", "Kobo Desktop Edition")
|
# self.kobodir = os.path.join(linux_path, u"Local Settings", u"Application Data", u"Kobo", u"Kobo Desktop Edition")
|
||||||
# desktop versions use Kobo.sqlite
|
# desktop versions use Kobo.sqlite
|
||||||
kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
|
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite")
|
||||||
# check for existence of file
|
# check for existence of file
|
||||||
if (not(os.path.isfile(kobodb))):
|
if (not(os.path.isfile(kobodb))):
|
||||||
# give up here, we haven't found anything useful
|
# give up here, we haven't found anything useful
|
||||||
@@ -377,14 +374,14 @@ class KoboLibrary(object):
|
|||||||
kobodb = u""
|
kobodb = u""
|
||||||
|
|
||||||
if (self.kobodir != u""):
|
if (self.kobodir != u""):
|
||||||
self.bookdir = os.path.join(self.kobodir, "kepub")
|
self.bookdir = os.path.join(self.kobodir, u"kepub")
|
||||||
# make a copy of the database in a temporary file
|
# make a copy of the database in a temporary file
|
||||||
# so we can ensure it's not using WAL logging which sqlite3 can't do.
|
# so we can ensure it's not using WAL logging which sqlite3 can't do.
|
||||||
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
||||||
print(self.newdb.name)
|
print(self.newdb.name)
|
||||||
olddb = open(kobodb, 'rb')
|
olddb = open(kobodb, 'rb')
|
||||||
self.newdb.write(olddb.read(18))
|
self.newdb.write(olddb.read(18))
|
||||||
self.newdb.write(b'\x01\x01')
|
self.newdb.write('\x01\x01')
|
||||||
olddb.read(2)
|
olddb.read(2)
|
||||||
self.newdb.write(olddb.read())
|
self.newdb.write(olddb.read())
|
||||||
olddb.close()
|
olddb.close()
|
||||||
@@ -437,15 +434,15 @@ class KoboLibrary(object):
|
|||||||
|
|
||||||
def __bookfile (self, volumeid):
|
def __bookfile (self, volumeid):
|
||||||
"""The filename needed to open a given book."""
|
"""The filename needed to open a given book."""
|
||||||
return os.path.join(self.kobodir, "kepub", volumeid)
|
return os.path.join(self.kobodir, u"kepub", volumeid)
|
||||||
|
|
||||||
def __getmacaddrs (self):
|
def __getmacaddrs (self):
|
||||||
"""The list of all MAC addresses on this machine."""
|
"""The list of all MAC addresses on this machine."""
|
||||||
macaddrs = []
|
macaddrs = []
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
c = re.compile('\s(' + '[0-9a-f]{2}-' * 5 + '[0-9a-f]{2})(\s|$)', re.IGNORECASE)
|
||||||
output = subprocess.Popen('ipconfig /all', shell=True, stdout=subprocess.PIPE, text=True).stdout
|
(p_in, p_out, p_err) = os.popen3('ipconfig /all')
|
||||||
for line in output:
|
for line in p_out:
|
||||||
m = c.search(line)
|
m = c.search(line)
|
||||||
if m:
|
if m:
|
||||||
macaddrs.append(re.sub("-", ":", m.group(1)).upper())
|
macaddrs.append(re.sub("-", ":", m.group(1)).upper())
|
||||||
@@ -454,7 +451,7 @@ class KoboLibrary(object):
|
|||||||
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
||||||
matches = c.findall(output)
|
matches = c.findall(output)
|
||||||
for m in matches:
|
for m in matches:
|
||||||
# print "m:{0}".format(m[0])
|
# print u"m:{0}".format(m[0])
|
||||||
macaddrs.append(m[0].upper())
|
macaddrs.append(m[0].upper())
|
||||||
else:
|
else:
|
||||||
# probably linux
|
# probably linux
|
||||||
@@ -491,14 +488,14 @@ class KoboLibrary(object):
|
|||||||
pass
|
pass
|
||||||
row = cursor.fetchone()
|
row = cursor.fetchone()
|
||||||
return userids
|
return userids
|
||||||
|
|
||||||
def __getuserkeys (self, macaddr):
|
def __getuserkeys (self, macaddr):
|
||||||
userids = self.__getuserids()
|
userids = self.__getuserids()
|
||||||
userkeys = []
|
userkeys = []
|
||||||
for hash in KOBO_HASH_KEYS:
|
for hash in KOBO_HASH_KEYS:
|
||||||
deviceid = hashlib.sha256((hash + macaddr).encode('ascii')).hexdigest()
|
deviceid = hashlib.sha256(hash + macaddr).hexdigest()
|
||||||
for userid in userids:
|
for userid in userids:
|
||||||
userkey = hashlib.sha256((deviceid + userid).encode('ascii')).hexdigest()
|
userkey = hashlib.sha256(deviceid + userid).hexdigest()
|
||||||
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
userkeys.append(binascii.a2b_hex(userkey[32:]))
|
||||||
return userkeys
|
return userkeys
|
||||||
|
|
||||||
@@ -559,7 +556,7 @@ class KoboBook(object):
|
|||||||
# Convert relative URIs
|
# Convert relative URIs
|
||||||
href = item.attrib['href']
|
href = item.attrib['href']
|
||||||
if not c.match(href):
|
if not c.match(href):
|
||||||
href = ''.join((basedir, href))
|
href = string.join((basedir, href), '')
|
||||||
|
|
||||||
# Update books we've found from the DB.
|
# Update books we've found from the DB.
|
||||||
if href in self._encryptedfiles:
|
if href in self._encryptedfiles:
|
||||||
@@ -607,59 +604,59 @@ class KoboFile(object):
|
|||||||
# assume utf-8 with no BOM
|
# assume utf-8 with no BOM
|
||||||
textoffset = 0
|
textoffset = 0
|
||||||
stride = 1
|
stride = 1
|
||||||
print("Checking text:{0}:".format(contents[:10]))
|
print(u"Checking text:{0}:".format(contents[:10]))
|
||||||
# check for byte order mark
|
# check for byte order mark
|
||||||
if contents[:3]==b"\xef\xbb\xbf":
|
if contents[:3]=="\xef\xbb\xbf":
|
||||||
# seems to be utf-8 with BOM
|
# seems to be utf-8 with BOM
|
||||||
print("Could be utf-8 with BOM")
|
print(u"Could be utf-8 with BOM")
|
||||||
textoffset = 3
|
textoffset = 3
|
||||||
elif contents[:2]==b"\xfe\xff":
|
elif contents[:2]=="\xfe\xff":
|
||||||
# seems to be utf-16BE
|
# seems to be utf-16BE
|
||||||
print("Could be utf-16BE")
|
print(u"Could be utf-16BE")
|
||||||
textoffset = 3
|
textoffset = 3
|
||||||
stride = 2
|
stride = 2
|
||||||
elif contents[:2]==b"\xff\xfe":
|
elif contents[:2]=="\xff\xfe":
|
||||||
# seems to be utf-16LE
|
# seems to be utf-16LE
|
||||||
print("Could be utf-16LE")
|
print(u"Could be utf-16LE")
|
||||||
textoffset = 2
|
textoffset = 2
|
||||||
stride = 2
|
stride = 2
|
||||||
else:
|
else:
|
||||||
print("Perhaps utf-8 without BOM")
|
print(u"Perhaps utf-8 without BOM")
|
||||||
|
|
||||||
# now check that the first few characters are in the ASCII range
|
# now check that the first few characters are in the ASCII range
|
||||||
for i in range(textoffset,textoffset+5*stride,stride):
|
for i in xrange(textoffset,textoffset+5*stride,stride):
|
||||||
if contents[i]<32 or contents[i]>127:
|
if ord(contents[i])<32 or ord(contents[i])>127:
|
||||||
# Non-ascii, so decryption probably failed
|
# Non-ascii, so decryption probably failed
|
||||||
print("Bad character at {0}, value {1}".format(i,contents[i]))
|
print(u"Bad character at {0}, value {1}".format(i,ord(contents[i])))
|
||||||
raise ValueError
|
raise ValueError
|
||||||
print("Seems to be good text")
|
print(u"Seems to be good text")
|
||||||
return True
|
return True
|
||||||
if contents[:5]==b"<?xml" or contents[:8]==b"\xef\xbb\xbf<?xml":
|
if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml":
|
||||||
# utf-8
|
# utf-8
|
||||||
return True
|
return True
|
||||||
elif contents[:14]==b"\xfe\xff\x00<\x00?\x00x\x00m\x00l":
|
elif contents[:14]=="\xfe\xff\x00<\x00?\x00x\x00m\x00l":
|
||||||
# utf-16BE
|
# utf-16BE
|
||||||
return True
|
return True
|
||||||
elif contents[:14]==b"\xff\xfe<\x00?\x00x\x00m\x00l\x00":
|
elif contents[:14]=="\xff\xfe<\x00?\x00x\x00m\x00l\x00":
|
||||||
# utf-16LE
|
# utf-16LE
|
||||||
return True
|
return True
|
||||||
elif contents[:9]==b"<!DOCTYPE" or contents[:12]==b"\xef\xbb\xbf<!DOCTYPE":
|
elif contents[:9]=="<!DOCTYPE" or contents[:12]=="\xef\xbb\xbf<!DOCTYPE":
|
||||||
# utf-8 of weird <!DOCTYPE start
|
# utf-8 of weird <!DOCTYPE start
|
||||||
return True
|
return True
|
||||||
elif contents[:22]==b"\xfe\xff\x00<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E":
|
elif contents[:22]=="\xfe\xff\x00<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E":
|
||||||
# utf-16BE of weird <!DOCTYPE start
|
# utf-16BE of weird <!DOCTYPE start
|
||||||
return True
|
return True
|
||||||
elif contents[:22]==b"\xff\xfe<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E\x00":
|
elif contents[:22]=="\xff\xfe<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E\x00":
|
||||||
# utf-16LE of weird <!DOCTYPE start
|
# utf-16LE of weird <!DOCTYPE start
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print("Bad XML: {0}".format(contents[:8]))
|
print(u"Bad XML: {0}".format(contents[:8]))
|
||||||
raise ValueError
|
raise ValueError
|
||||||
elif self.mimetype == 'image/jpeg':
|
elif self.mimetype == 'image/jpeg':
|
||||||
if contents[:3] == b'\xff\xd8\xff':
|
if contents[:3] == '\xff\xd8\xff':
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print("Bad JPEG: {0}".format(contents[:3].hex()))
|
print(u"Bad JPEG: {0}".format(contents[:3].encode('hex')))
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -682,18 +679,18 @@ class KoboFile(object):
|
|||||||
return contents
|
return contents
|
||||||
|
|
||||||
def decrypt_book(book, lib):
|
def decrypt_book(book, lib):
|
||||||
print("Converting {0}".format(book.title))
|
print(u"Converting {0}".format(book.title))
|
||||||
zin = zipfile.ZipFile(book.filename, "r")
|
zin = zipfile.ZipFile(book.filename, "r")
|
||||||
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
||||||
outname = "{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||||
if (book.type == 'drm-free'):
|
if (book.type == 'drm-free'):
|
||||||
print("DRM-free book, conversion is not needed")
|
print(u"DRM-free book, conversion is not needed")
|
||||||
shutil.copyfile(book.filename, outname)
|
shutil.copyfile(book.filename, outname)
|
||||||
print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||||
return 0
|
return 0
|
||||||
result = 1
|
result = 1
|
||||||
for userkey in lib.userkeys:
|
for userkey in lib.userkeys:
|
||||||
print("Trying key: {0}".format(userkey.hex()))
|
print(u"Trying key: {0}".format(userkey.encode('hex_codec')))
|
||||||
try:
|
try:
|
||||||
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
||||||
for filename in zin.namelist():
|
for filename in zin.namelist():
|
||||||
@@ -705,12 +702,12 @@ def decrypt_book(book, lib):
|
|||||||
file.check(contents)
|
file.check(contents)
|
||||||
zout.writestr(filename, contents)
|
zout.writestr(filename, contents)
|
||||||
zout.close()
|
zout.close()
|
||||||
print("Decryption succeeded.")
|
print(u"Decryption succeeded.")
|
||||||
print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||||
result = 0
|
result = 0
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print("Decryption failed.")
|
print(u"Decryption failed.")
|
||||||
zout.close()
|
zout.close()
|
||||||
os.remove(outname)
|
os.remove(outname)
|
||||||
zin.close()
|
zin.close()
|
||||||
@@ -719,7 +716,7 @@ def decrypt_book(book, lib):
|
|||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
description = __about__
|
description = __about__
|
||||||
epilog = "Parsing of arguments failed."
|
epilog = u"Parsing of arguments failed."
|
||||||
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
||||||
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
||||||
parser.add_argument('--all', action='store_true', help="flag for converting all books on device")
|
parser.add_argument('--all', action='store_true', help="flag for converting all books on device")
|
||||||
@@ -735,25 +732,25 @@ def cli_main():
|
|||||||
books = lib.books
|
books = lib.books
|
||||||
else:
|
else:
|
||||||
for i, book in enumerate(lib.books):
|
for i, book in enumerate(lib.books):
|
||||||
print("{0}: {1}".format(i + 1, book.title))
|
print(u"{0}: {1}".format(i + 1, book.title))
|
||||||
print("Or 'all'")
|
print(u"Or 'all'")
|
||||||
|
|
||||||
choice = input("Convert book number... ")
|
choice = raw_input(u"Convert book number... ")
|
||||||
if choice == "all":
|
if choice == u'all':
|
||||||
books = list(lib.books)
|
books = list(lib.books)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
num = int(choice)
|
num = int(choice)
|
||||||
books = [lib.books[num - 1]]
|
books = [lib.books[num - 1]]
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
print("Invalid choice. Exiting...")
|
print(u"Invalid choice. Exiting...")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
results = [decrypt_book(book, lib) for book in books]
|
results = [decrypt_book(book, lib) for book in books]
|
||||||
lib.close()
|
lib.close()
|
||||||
overall_result = all(result != 0 for result in results)
|
overall_result = all(result != 0 for result in results)
|
||||||
if overall_result != 0:
|
if overall_result != 0:
|
||||||
print("Could not decrypt book with any of the keys found.")
|
print(u"Could not decrypt book with any of the keys found.")
|
||||||
return overall_result
|
return overall_result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
import os, struct, time
|
import os, struct, time
|
||||||
try:
|
from StringIO import StringIO
|
||||||
from StringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
from io import StringIO
|
|
||||||
from traceback import print_exc
|
from traceback import print_exc
|
||||||
|
|
||||||
from PyQt5.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
|
try:
|
||||||
|
from PyQt5.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
|
||||||
|
except ImportError:
|
||||||
|
from PyQt4.Qt import (Qt, QDialog, QPixmap, QIcon, QLabel, QHBoxLayout, QFont, QTableWidgetItem)
|
||||||
|
|
||||||
from calibre.utils.config import config_dir
|
from calibre.utils.config import config_dir
|
||||||
from calibre.constants import iswindows, DEBUG
|
from calibre.constants import iswindows, DEBUG
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.gui2 import (error_dialog, gprefs)
|
from calibre.gui2 import (error_dialog, gprefs)
|
||||||
from calibre.gui2.actions import menu_action_unique_name
|
from calibre.gui2.actions import menu_action_unique_name
|
||||||
|
|
||||||
from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME,
|
from calibre_plugins.obok_dedrm.__init__ import (PLUGIN_NAME,
|
||||||
PLUGIN_SAFE_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION)
|
PLUGIN_SAFE_NAME, PLUGIN_VERSION, PLUGIN_DESCRIPTION)
|
||||||
|
|
||||||
plugin_ID = None
|
plugin_ID = None
|
||||||
@@ -39,7 +39,7 @@ else:
|
|||||||
def convert_qvariant(x):
|
def convert_qvariant(x):
|
||||||
vt = x.type()
|
vt = x.type()
|
||||||
if vt == x.String:
|
if vt == x.String:
|
||||||
return x.toString()
|
return unicode(x.toString())
|
||||||
if vt == x.List:
|
if vt == x.List:
|
||||||
return [convert_qvariant(i) for i in x.toList()]
|
return [convert_qvariant(i) for i in x.toList()]
|
||||||
return x.toPyObject()
|
return x.toPyObject()
|
||||||
@@ -62,7 +62,7 @@ except NameError:
|
|||||||
def format_plural(number, possessive=False):
|
def format_plural(number, possessive=False):
|
||||||
'''
|
'''
|
||||||
Cosmetic ditty to provide the proper string formatting variable to handle singular/plural situations
|
Cosmetic ditty to provide the proper string formatting variable to handle singular/plural situations
|
||||||
|
|
||||||
:param: number: variable that represents the count/len of something
|
:param: number: variable that represents the count/len of something
|
||||||
'''
|
'''
|
||||||
if not possessive:
|
if not possessive:
|
||||||
@@ -141,7 +141,7 @@ def showErrorDlg(errmsg, parent, trcbk=False):
|
|||||||
'''
|
'''
|
||||||
if trcbk:
|
if trcbk:
|
||||||
error= ''
|
error= ''
|
||||||
f=StringIO()
|
f=StringIO()
|
||||||
print_exc(file=f)
|
print_exc(file=f)
|
||||||
error_mess = f.getvalue().splitlines()
|
error_mess = f.getvalue().splitlines()
|
||||||
for line in error_mess:
|
for line in error_mess:
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ if iswindows:
|
|||||||
c_long, c_ulong
|
c_long, c_ulong
|
||||||
|
|
||||||
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
||||||
import winreg
|
import _winreg as winreg
|
||||||
|
|
||||||
def _load_crypto_libcrypto():
|
def _load_crypto_libcrypto():
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
|
|
||||||
# ignoblekey.py
|
# ignoblekey.py
|
||||||
# Copyright © 2015-2020 Apprentice Harper et al.
|
# Copyright © 2015 Apprentice Alf and Apprentice Harper
|
||||||
|
|
||||||
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||||
|
|
||||||
@@ -14,14 +14,13 @@ from __future__ import with_statement
|
|||||||
# Revision history:
|
# Revision history:
|
||||||
# 1.0 - Initial release
|
# 1.0 - Initial release
|
||||||
# 1.1 - remove duplicates and return last key as single key
|
# 1.1 - remove duplicates and return last key as single key
|
||||||
# 2.0 - Python 3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Get Barnes & Noble EPUB user key from nook Studio log file
|
Get Barnes & Noble EPUB user key from nook Studio log file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "2.0"
|
__version__ = "1.1"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -98,7 +97,7 @@ def getNookLogFiles():
|
|||||||
logFiles = []
|
logFiles = []
|
||||||
found = False
|
found = False
|
||||||
if iswindows:
|
if iswindows:
|
||||||
import winreg
|
import _winreg as winreg
|
||||||
|
|
||||||
# some 64 bit machines do not have the proper registry key for some reason
|
# some 64 bit machines do not have the proper registry key for some reason
|
||||||
# or the python interface to the 32 vs 64 bit registry is broken
|
# or the python interface to the 32 vs 64 bit registry is broken
|
||||||
@@ -319,7 +318,7 @@ def gui_main():
|
|||||||
keyfileout.write(key)
|
keyfileout.write(key)
|
||||||
success = True
|
success = True
|
||||||
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
|
||||||
except DrmException as e:
|
except DrmException, e:
|
||||||
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
|
||||||
except Exception:
|
except Exception:
|
||||||
root.wm_state('normal')
|
root.wm_state('normal')
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ if iswindows:
|
|||||||
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
create_unicode_buffer, create_string_buffer, CFUNCTYPE, addressof, \
|
||||||
string_at, Structure, c_void_p, cast
|
string_at, Structure, c_void_p, cast
|
||||||
|
|
||||||
import winreg
|
import _winreg as winreg
|
||||||
MAX_PATH = 255
|
MAX_PATH = 255
|
||||||
kernel32 = windll.kernel32
|
kernel32 = windll.kernel32
|
||||||
advapi32 = windll.advapi32
|
advapi32 = windll.advapi32
|
||||||
|
|||||||
@@ -153,7 +153,7 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
__version__ = '3.2.4'
|
__version__ = '3.2.4'
|
||||||
__about__ = "Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__)
|
__about__ = u"Obok v{0}\nCopyright © 2012-2016 Physisticated et al.".format(__version__)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@@ -173,10 +173,10 @@ import tempfile
|
|||||||
can_parse_xml = True
|
can_parse_xml = True
|
||||||
try:
|
try:
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
# print "using xml.etree for xml parsing"
|
# print u"using xml.etree for xml parsing"
|
||||||
except ImportError:
|
except ImportError:
|
||||||
can_parse_xml = False
|
can_parse_xml = False
|
||||||
# print "Cannot find xml.etree, disabling extraction of serial numbers"
|
# print u"Cannot find xml.etree, disabling extraction of serial numbers"
|
||||||
|
|
||||||
# List of all known hash keys
|
# List of all known hash keys
|
||||||
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
|
KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
|
||||||
@@ -309,9 +309,9 @@ class KoboLibrary(object):
|
|||||||
# step 1. check whether this looks like a real device
|
# step 1. check whether this looks like a real device
|
||||||
if (device_path):
|
if (device_path):
|
||||||
# we got a device path
|
# we got a device path
|
||||||
self.kobodir = os.path.join(device_path, ".kobo")
|
self.kobodir = os.path.join(device_path, u".kobo")
|
||||||
# devices use KoboReader.sqlite
|
# devices use KoboReader.sqlite
|
||||||
kobodb = os.path.join(self.kobodir, "KoboReader.sqlite")
|
kobodb = os.path.join(self.kobodir, u"KoboReader.sqlite")
|
||||||
if (not(os.path.isfile(kobodb))):
|
if (not(os.path.isfile(kobodb))):
|
||||||
# device path seems to be wrong, unset it
|
# device path seems to be wrong, unset it
|
||||||
device_path = u""
|
device_path = u""
|
||||||
@@ -323,22 +323,22 @@ class KoboLibrary(object):
|
|||||||
if (len(serials) == 0):
|
if (len(serials) == 0):
|
||||||
# we got a device path but no saved serial
|
# we got a device path but no saved serial
|
||||||
# try to get the serial from the device
|
# try to get the serial from the device
|
||||||
# print "get_device_settings - device_path = {0}".format(device_path)
|
# print u"get_device_settings - device_path = {0}".format(device_path)
|
||||||
# get serial from device_path/.adobe-digital-editions/device.xml
|
# get serial from device_path/.adobe-digital-editions/device.xml
|
||||||
if can_parse_xml:
|
if can_parse_xml:
|
||||||
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
|
devicexml = os.path.join(device_path, '.adobe-digital-editions', 'device.xml')
|
||||||
# print "trying to load {0}".format(devicexml)
|
# print u"trying to load {0}".format(devicexml)
|
||||||
if (os.path.exists(devicexml)):
|
if (os.path.exists(devicexml)):
|
||||||
# print "trying to parse {0}".format(devicexml)
|
# print u"trying to parse {0}".format(devicexml)
|
||||||
xmltree = ET.parse(devicexml)
|
xmltree = ET.parse(devicexml)
|
||||||
for node in xmltree.iter():
|
for node in xmltree.iter():
|
||||||
if "deviceSerial" in node.tag:
|
if "deviceSerial" in node.tag:
|
||||||
serial = node.text
|
serial = node.text
|
||||||
# print "found serial {0}".format(serial)
|
# print u"found serial {0}".format(serial)
|
||||||
serials.append(serial)
|
serials.append(serial)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# print "cannot get serials from device."
|
# print u"cannot get serials from device."
|
||||||
device_path = u""
|
device_path = u""
|
||||||
self.kobodir = u""
|
self.kobodir = u""
|
||||||
kobodb = u""
|
kobodb = u""
|
||||||
@@ -346,23 +346,23 @@ class KoboLibrary(object):
|
|||||||
if (self.kobodir == u""):
|
if (self.kobodir == u""):
|
||||||
# step 4. we haven't found a device with serials, so try desktop apps
|
# step 4. we haven't found a device with serials, so try desktop apps
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
import winreg
|
import _winreg as winreg
|
||||||
if sys.getwindowsversion().major > 5:
|
if sys.getwindowsversion().major > 5:
|
||||||
if 'LOCALAPPDATA' in os.environ.keys():
|
if 'LOCALAPPDATA' in os.environ.keys():
|
||||||
# Python 2.x does not return unicode env. Use Python 3.x
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
self.kobodir = winreg.ExpandEnvironmentStrings("%LOCALAPPDATA%")
|
self.kobodir = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
|
||||||
if (self.kobodir == u""):
|
if (self.kobodir == u""):
|
||||||
if 'USERPROFILE' in os.environ.keys():
|
if 'USERPROFILE' in os.environ.keys():
|
||||||
# Python 2.x does not return unicode env. Use Python 3.x
|
# Python 2.x does not return unicode env. Use Python 3.x
|
||||||
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings("%USERPROFILE%"), "Local Settings", "Application Data")
|
self.kobodir = os.path.join(winreg.ExpandEnvironmentStrings(u"%USERPROFILE%"), u"Local Settings", u"Application Data")
|
||||||
self.kobodir = os.path.join(self.kobodir, "Kobo", "Kobo Desktop Edition")
|
self.kobodir = os.path.join(self.kobodir, u"Kobo", u"Kobo Desktop Edition")
|
||||||
elif sys.platform.startswith('darwin'):
|
elif sys.platform.startswith('darwin'):
|
||||||
self.kobodir = os.path.join(os.environ['HOME'], "Library", "Application Support", "Kobo", "Kobo Desktop Edition")
|
self.kobodir = os.path.join(os.environ['HOME'], u"Library", u"Application Support", u"Kobo", u"Kobo Desktop Edition")
|
||||||
#elif linux_path != None:
|
#elif linux_path != None:
|
||||||
# Probably Linux, let's get the wine prefix and path to Kobo.
|
# Probably Linux, let's get the wine prefix and path to Kobo.
|
||||||
# self.kobodir = os.path.join(linux_path, "Local Settings", "Application Data", "Kobo", "Kobo Desktop Edition")
|
# self.kobodir = os.path.join(linux_path, u"Local Settings", u"Application Data", u"Kobo", u"Kobo Desktop Edition")
|
||||||
# desktop versions use Kobo.sqlite
|
# desktop versions use Kobo.sqlite
|
||||||
kobodb = os.path.join(self.kobodir, "Kobo.sqlite")
|
kobodb = os.path.join(self.kobodir, u"Kobo.sqlite")
|
||||||
# check for existence of file
|
# check for existence of file
|
||||||
if (not(os.path.isfile(kobodb))):
|
if (not(os.path.isfile(kobodb))):
|
||||||
# give up here, we haven't found anything useful
|
# give up here, we haven't found anything useful
|
||||||
@@ -371,7 +371,7 @@ class KoboLibrary(object):
|
|||||||
|
|
||||||
|
|
||||||
if (self.kobodir != u""):
|
if (self.kobodir != u""):
|
||||||
self.bookdir = os.path.join(self.kobodir, "kepub")
|
self.bookdir = os.path.join(self.kobodir, u"kepub")
|
||||||
# make a copy of the database in a temporary file
|
# make a copy of the database in a temporary file
|
||||||
# so we can ensure it's not using WAL logging which sqlite3 can't do.
|
# so we can ensure it's not using WAL logging which sqlite3 can't do.
|
||||||
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
self.newdb = tempfile.NamedTemporaryFile(mode='wb', delete=False)
|
||||||
@@ -431,7 +431,7 @@ class KoboLibrary(object):
|
|||||||
|
|
||||||
def __bookfile (self, volumeid):
|
def __bookfile (self, volumeid):
|
||||||
"""The filename needed to open a given book."""
|
"""The filename needed to open a given book."""
|
||||||
return os.path.join(self.kobodir, "kepub", volumeid)
|
return os.path.join(self.kobodir, u"kepub", volumeid)
|
||||||
|
|
||||||
def __getmacaddrs (self):
|
def __getmacaddrs (self):
|
||||||
"""The list of all MAC addresses on this machine."""
|
"""The list of all MAC addresses on this machine."""
|
||||||
@@ -448,7 +448,7 @@ class KoboLibrary(object):
|
|||||||
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
output = subprocess.check_output('/sbin/ifconfig -a', shell=True)
|
||||||
matches = c.findall(output)
|
matches = c.findall(output)
|
||||||
for m in matches:
|
for m in matches:
|
||||||
# print "m:{0}".format(m[0])
|
# print u"m:{0}".format(m[0])
|
||||||
macaddrs.append(m[0].upper())
|
macaddrs.append(m[0].upper())
|
||||||
elif sys.platform.startswith('linux'):
|
elif sys.platform.startswith('linux'):
|
||||||
p_out = subprocess.check_output("ip -br link show | awk '{print $3}'", shell=True)
|
p_out = subprocess.check_output("ip -br link show | awk '{print $3}'", shell=True)
|
||||||
@@ -596,32 +596,32 @@ class KoboFile(object):
|
|||||||
# assume utf-8 with no BOM
|
# assume utf-8 with no BOM
|
||||||
textoffset = 0
|
textoffset = 0
|
||||||
stride = 1
|
stride = 1
|
||||||
print("Checking text:{0}:".format(contents[:10]))
|
print(u"Checking text:{0}:".format(contents[:10]))
|
||||||
# check for byte order mark
|
# check for byte order mark
|
||||||
if contents[:3]=="\xef\xbb\xbf":
|
if contents[:3]=="\xef\xbb\xbf":
|
||||||
# seems to be utf-8 with BOM
|
# seems to be utf-8 with BOM
|
||||||
print("Could be utf-8 with BOM")
|
print(u"Could be utf-8 with BOM")
|
||||||
textoffset = 3
|
textoffset = 3
|
||||||
elif contents[:2]=="\xfe\xff":
|
elif contents[:2]=="\xfe\xff":
|
||||||
# seems to be utf-16BE
|
# seems to be utf-16BE
|
||||||
print("Could be utf-16BE")
|
print(u"Could be utf-16BE")
|
||||||
textoffset = 3
|
textoffset = 3
|
||||||
stride = 2
|
stride = 2
|
||||||
elif contents[:2]=="\xff\xfe":
|
elif contents[:2]=="\xff\xfe":
|
||||||
# seems to be utf-16LE
|
# seems to be utf-16LE
|
||||||
print("Could be utf-16LE")
|
print(u"Could be utf-16LE")
|
||||||
textoffset = 2
|
textoffset = 2
|
||||||
stride = 2
|
stride = 2
|
||||||
else:
|
else:
|
||||||
print("Perhaps utf-8 without BOM")
|
print(u"Perhaps utf-8 without BOM")
|
||||||
|
|
||||||
# now check that the first few characters are in the ASCII range
|
# now check that the first few characters are in the ASCII range
|
||||||
for i in xrange(textoffset,textoffset+5*stride,stride):
|
for i in xrange(textoffset,textoffset+5*stride,stride):
|
||||||
if ord(contents[i])<32 or ord(contents[i])>127:
|
if ord(contents[i])<32 or ord(contents[i])>127:
|
||||||
# Non-ascii, so decryption probably failed
|
# Non-ascii, so decryption probably failed
|
||||||
print("Bad character at {0}, value {1}".format(i,ord(contents[i])))
|
print(u"Bad character at {0}, value {1}".format(i,ord(contents[i])))
|
||||||
raise ValueError
|
raise ValueError
|
||||||
print("Seems to be good text")
|
print(u"Seems to be good text")
|
||||||
return True
|
return True
|
||||||
if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml":
|
if contents[:5]=="<?xml" or contents[:8]=="\xef\xbb\xbf<?xml":
|
||||||
# utf-8
|
# utf-8
|
||||||
@@ -642,13 +642,13 @@ class KoboFile(object):
|
|||||||
# utf-16LE of weird <!DOCTYPE start
|
# utf-16LE of weird <!DOCTYPE start
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print("Bad XML: {0}".format(contents[:8]))
|
print(u"Bad XML: {0}".format(contents[:8]))
|
||||||
raise ValueError
|
raise ValueError
|
||||||
elif self.mimetype == 'image/jpeg':
|
elif self.mimetype == 'image/jpeg':
|
||||||
if contents[:3] == '\xff\xd8\xff':
|
if contents[:3] == '\xff\xd8\xff':
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print("Bad JPEG: {0}".format(contents[:3].encode('hex')))
|
print(u"Bad JPEG: {0}".format(contents[:3].encode('hex')))
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -671,18 +671,18 @@ class KoboFile(object):
|
|||||||
return contents
|
return contents
|
||||||
|
|
||||||
def decrypt_book(book, lib):
|
def decrypt_book(book, lib):
|
||||||
print("Converting {0}".format(book.title))
|
print(u"Converting {0}".format(book.title))
|
||||||
zin = zipfile.ZipFile(book.filename, "r")
|
zin = zipfile.ZipFile(book.filename, "r")
|
||||||
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
# make filename out of Unicode alphanumeric and whitespace equivalents from title
|
||||||
outname = "{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
outname = u"{0}.epub".format(re.sub('[^\s\w]', '_', book.title, 0, re.UNICODE))
|
||||||
if (book.type == 'drm-free'):
|
if (book.type == 'drm-free'):
|
||||||
print("DRM-free book, conversion is not needed")
|
print(u"DRM-free book, conversion is not needed")
|
||||||
shutil.copyfile(book.filename, outname)
|
shutil.copyfile(book.filename, outname)
|
||||||
print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||||
return 0
|
return 0
|
||||||
result = 1
|
result = 1
|
||||||
for userkey in lib.userkeys:
|
for userkey in lib.userkeys:
|
||||||
print("Trying key: {0}".format(userkey.encode('hex_codec')))
|
print(u"Trying key: {0}".format(userkey.encode('hex_codec')))
|
||||||
try:
|
try:
|
||||||
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
zout = zipfile.ZipFile(outname, "w", zipfile.ZIP_DEFLATED)
|
||||||
for filename in zin.namelist():
|
for filename in zin.namelist():
|
||||||
@@ -694,12 +694,12 @@ def decrypt_book(book, lib):
|
|||||||
file.check(contents)
|
file.check(contents)
|
||||||
zout.writestr(filename, contents)
|
zout.writestr(filename, contents)
|
||||||
zout.close()
|
zout.close()
|
||||||
print("Decryption succeeded.")
|
print(u"Decryption succeeded.")
|
||||||
print("Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
print(u"Book saved as {0}".format(os.path.join(os.getcwd(), outname)))
|
||||||
result = 0
|
result = 0
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print("Decryption failed.")
|
print(u"Decryption failed.")
|
||||||
zout.close()
|
zout.close()
|
||||||
os.remove(outname)
|
os.remove(outname)
|
||||||
zin.close()
|
zin.close()
|
||||||
@@ -708,7 +708,7 @@ def decrypt_book(book, lib):
|
|||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
description = __about__
|
description = __about__
|
||||||
epilog = "Parsing of arguments failed."
|
epilog = u"Parsing of arguments failed."
|
||||||
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
parser = argparse.ArgumentParser(prog=sys.argv[0], description=description, epilog=epilog)
|
||||||
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
parser.add_argument('--devicedir', default='/media/KOBOeReader', help="directory of connected Kobo device")
|
||||||
parser.add_argument('--all', action='store_true', help="flag for converting all books on device")
|
parser.add_argument('--all', action='store_true', help="flag for converting all books on device")
|
||||||
@@ -724,25 +724,25 @@ def cli_main():
|
|||||||
books = lib.books
|
books = lib.books
|
||||||
else:
|
else:
|
||||||
for i, book in enumerate(lib.books):
|
for i, book in enumerate(lib.books):
|
||||||
print("{0}: {1}".format(i + 1, book.title))
|
print(u"{0}: {1}".format(i + 1, book.title))
|
||||||
print("Or 'all'")
|
print(u"Or 'all'")
|
||||||
|
|
||||||
choice = raw_input("Convert book number... ")
|
choice = raw_input(u"Convert book number... ")
|
||||||
if choice == "all":
|
if choice == u'all':
|
||||||
books = list(lib.books)
|
books = list(lib.books)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
num = int(choice)
|
num = int(choice)
|
||||||
books = [lib.books[num - 1]]
|
books = [lib.books[num - 1]]
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
print("Invalid choice. Exiting...")
|
print(u"Invalid choice. Exiting...")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
results = [decrypt_book(book, lib) for book in books]
|
results = [decrypt_book(book, lib) for book in books]
|
||||||
lib.close()
|
lib.close()
|
||||||
overall_result = all(result != 0 for result in results)
|
overall_result = all(result != 0 for result in results)
|
||||||
if overall_result != 0:
|
if overall_result != 0:
|
||||||
print("Could not decrypt book with any of the keys found.")
|
print(u"Could not decrypt book with any of the keys found.")
|
||||||
return overall_result
|
return overall_result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2290,7 +2290,7 @@ class PDFDocument(object):
|
|||||||
import win32api
|
import win32api
|
||||||
import win32security
|
import win32security
|
||||||
import win32file
|
import win32file
|
||||||
import winreg
|
import _winreg as winreg
|
||||||
except:
|
except:
|
||||||
raise ADEPTError('PyWin Extension (Win32API module) needed.\n'+\
|
raise ADEPTError('PyWin Extension (Win32API module) needed.\n'+\
|
||||||
'Download from http://sourceforge.net/projects/pywin32/files/ ')
|
'Download from http://sourceforge.net/projects/pywin32/files/ ')
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# DeDRM_tools
|
# DeDRM_tools
|
||||||
DeDRM tools for ebooks
|
DeDRM tools for ebooks
|
||||||
# [Guide] How to remove DRM
|
|
||||||
Refer to [Wiki Page](https://github.com/apprenticeharper/DeDRM_tools/wiki/Exactly-how-to-remove-DRM)
|
|
||||||
# calibre 5.0
|
|
||||||
|
|
||||||
Yes, we know that the plugin doesn't work with calibre 5.0. It will eventually, but there's a lot of code to be translated that does complicated stuff with bits and bytes that must be converted by hand. It'll take a while.
|
|
||||||
|
|
||||||
|
|
||||||
This is a repository of all the scripts and other tools for removing DRM from ebooks that I could find, committed in date order as best as I could manage. (Except for the Requiem tools for Apple's iBooks, and Convert LIT for Microsoft's .lit ebooks.)
|
This is a repository of all the scripts and other tools for removing DRM from ebooks that I could find, committed in date order as best as I could manage. (Except for the Requiem tools for Apple's iBooks, and Convert LIT for Microsoft's .lit ebooks.)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# code: utf-8
|
||||||
|
|
||||||
'''
|
'''
|
||||||
A wrapper script to generate zip files for GitHub releases.
|
A wrapper script to generate zip files for GitHub releases.
|
||||||
@@ -13,12 +13,25 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
DEDRM_SRC_DIR = 'DeDRM_plugin'
|
DEDRM_SRC_DIR = 'DeDRM_Plugin'
|
||||||
DEDRM_README= 'DeDRM_plugin_ReadMe.txt'
|
DEDRM_README= 'DeDRM_Plugin_ReadMe.txt'
|
||||||
OBOK_SRC_DIR = 'Obok_plugin'
|
OBOK_SRC_DIR = 'Obok_plugin'
|
||||||
OBOK_README = 'obok_plugin_ReadMe.txt'
|
OBOK_README = 'Obok_plugin_ReadMe.txt'
|
||||||
RELEASE_DIR = 'release'
|
RELEASE_DIR = 'release'
|
||||||
|
|
||||||
|
def make_calibre_plugin():
|
||||||
|
|
||||||
|
shutil.make_archive(core_dir, 'zip', core_dir)
|
||||||
|
shutil.rmtree(core_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def make_obok_plugin():
|
||||||
|
obok_plugin_dir = os.path.join(SHELLS_BASE, 'Obok_calibre_plugin')
|
||||||
|
core_dir = os.path.join(obok_plugin_dir, 'obok_plugin')
|
||||||
|
|
||||||
|
shutil.copytree(OBOK_SRC_DIR, core_dir)
|
||||||
|
shutil.make_archive(core_dir, 'zip')
|
||||||
|
shutil.rmtree(core_dir)
|
||||||
|
|
||||||
def make_release(version):
|
def make_release(version):
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user