]".format(progname)
-
-
-def cli_main():
- sys.stdout=SafeUnbuffered(sys.stdout)
- sys.stderr=SafeUnbuffered(sys.stderr)
- argv=unicode_argv()
- progname = os.path.basename(argv[0])
- print u"{0} v{1}\nCopyright © 2010-2015 Thom, some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
-
- try:
- opts, args = getopt.getopt(argv[1:], "hb:")
- except getopt.GetoptError, err:
- usage(progname)
- print u"\nError in options or arguments: {0}".format(err.args[0])
- return 2
-
- inpath = ""
- for o, a in opts:
- if o == "-h":
- usage(progname)
- return 0
- if o == "-b":
- inpath = a
-
- if len(args) > 1:
- usage(progname)
- return 2
-
- if len(args) == 1:
- # save to the specified file or directory
- outfile = args[0]
- if not os.path.isabs(outfile):
- outfile = os.path.join(os.path.dirname(argv[0]),outfile)
- outfile = os.path.abspath(outfile)
- if os.path.isdir(outfile):
- outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
- else:
- # save to the same directory as the script
- outfile = os.path.join(os.path.dirname(argv[0]),"androidkindlekey.k4a")
-
- # make sure the outpath is OK
- outfile = os.path.realpath(os.path.normpath(outfile))
-
- if not os.path.isfile(inpath):
- usage(progname)
- print u"\n{0:s} file not found".format(inpath)
- return 2
-
- if getkey(outfile, inpath):
- print u"\nSaved Kindle for Android key to {0}".format(outfile)
- else:
- print u"\nCould not retrieve Kindle for Android key."
- return 0
-
-
-def gui_main():
- try:
- import Tkinter
- import Tkconstants
- import tkMessageBox
- import tkFileDialog
- except:
- print "Tkinter not installed"
- return cli_main()
-
- class DecryptionDialog(Tkinter.Frame):
- def __init__(self, root):
- Tkinter.Frame.__init__(self, root, border=5)
- self.status = Tkinter.Label(self, text=u"Select backup.ab file")
- self.status.pack(fill=Tkconstants.X, expand=1)
- body = Tkinter.Frame(self)
- body.pack(fill=Tkconstants.X, expand=1)
- sticky = Tkconstants.E + Tkconstants.W
- body.grid_columnconfigure(1, weight=2)
- Tkinter.Label(body, text=u"Backup file").grid(row=0, column=0)
- self.keypath = Tkinter.Entry(body, width=40)
- self.keypath.grid(row=0, column=1, sticky=sticky)
- self.keypath.insert(2, u"backup.ab")
- button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
- button.grid(row=0, column=2)
- buttons = Tkinter.Frame(self)
- buttons.pack()
- button2 = Tkinter.Button(
- buttons, text=u"Extract", width=10, command=self.generate)
- button2.pack(side=Tkconstants.LEFT)
- Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
- button3 = Tkinter.Button(
- buttons, text=u"Quit", width=10, command=self.quit)
- button3.pack(side=Tkconstants.RIGHT)
-
- def get_keypath(self):
- keypath = tkFileDialog.askopenfilename(
- parent=None, title=u"Select backup.ab file",
- defaultextension=u".ab",
- filetypes=[('adb backup com.amazon.kindle', '.ab'),
- ('All Files', '.*')])
- if keypath:
- keypath = os.path.normpath(keypath)
- self.keypath.delete(0, Tkconstants.END)
- self.keypath.insert(0, keypath)
- return
-
- def generate(self):
- inpath = self.keypath.get()
- self.status['text'] = u"Getting key..."
- try:
- keys = get_serials(inpath)
- keycount = 0
- for key in keys:
- while True:
- keycount += 1
- outfile = os.path.join(progpath,u"kindlekey{0:d}.k4a".format(keycount))
- if not os.path.exists(outfile):
- break
-
- with file(outfile, 'w') as keyfileout:
- keyfileout.write(key)
- success = True
- tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
- except Exception, e:
- self.status['text'] = u"Error: {0}".format(e.args[0])
- return
- self.status['text'] = u"Select backup.ab file"
-
- argv=unicode_argv()
- progpath, progname = os.path.split(argv[0])
- root = Tkinter.Tk()
- root.title(u"Kindle for Android Key Extraction v.{0}".format(__version__))
- root.resizable(True, False)
- root.minsize(300, 0)
- DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
- root.mainloop()
- return 0
-
-if __name__ == '__main__':
- if len(sys.argv) > 1:
- sys.exit(cli_main())
- sys.exit(gui_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/argv_utils.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/argv_utils.py
deleted file mode 100644
index 85ffaa4..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/argv_utils.py
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import sys, os
-import locale
-import codecs
-
-# get sys.argv arguments and encode them into utf-8
-def unicode_argv():
- if sys.platform.startswith('win'):
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'.
-
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- # if we don't have any arguments at all, just pass back script name
- # this should never happen
- return [u"DeDRM.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-
-def add_cp65001_codec():
- try:
- codecs.lookup('cp65001')
- except LookupError:
- codecs.register(
- lambda name: name == 'cp65001' and codecs.lookup('utf-8') or None)
- return
-
-
-def set_utf8_default_encoding():
- if sys.getdefaultencoding() == 'utf-8':
- return
-
- # Regenerate setdefaultencoding.
- reload(sys)
- sys.setdefaultencoding('utf-8')
-
- for attr in dir(locale):
- if attr[0:3] != 'LC_':
- continue
- aref = getattr(locale, attr)
- try:
- locale.setlocale(aref, '')
- except locale.Error:
- continue
- try:
- lang = locale.getlocale(aref)[0]
- except (TypeError, ValueError):
- continue
- if lang:
- try:
- locale.setlocale(aref, (lang, 'UTF-8'))
- except locale.Error:
- os.environ[attr] = lang + '.UTF-8'
- try:
- locale.setlocale(locale.LC_ALL, '')
- except locale.Error:
- pass
- return
-
-
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/askfolder_ed.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/askfolder_ed.py
deleted file mode 100644
index a4a2ae0..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/askfolder_ed.py
+++ /dev/null
@@ -1,211 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-
-# to work around tk_chooseDirectory not properly returning unicode paths on Windows
-# need to use a dialog that can be hacked up to actually return full unicode paths
-# originally based on AskFolder from EasyDialogs for Windows but modified to fix it
-# to actually use unicode for path
-
-# The original license for EasyDialogs is as follows
-#
-# Copyright (c) 2003-2005 Jimmy Retzlaff
-#
-# Permission is hereby granted, free of charge, to any person obtaining a
-# copy of this software and associated documentation files (the "Software"),
-# to deal in the Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish, distribute, sublicense,
-# 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:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-# DEALINGS IN THE SOFTWARE.
-
-"""
-AskFolder(...) -- Ask the user to select a folder Windows specific
-"""
-
-import os
-
-import ctypes
-from ctypes import POINTER, byref, cdll, c_int, windll
-from ctypes.wintypes import LPCWSTR, LPWSTR
-import ctypes.wintypes as wintypes
-
-
-__all__ = ['AskFolder']
-
-# Load required Windows DLLs
-ole32 = ctypes.windll.ole32
-shell32 = ctypes.windll.shell32
-user32 = ctypes.windll.user32
-
-
-# Windows Constants
-BFFM_INITIALIZED = 1
-BFFM_SETOKTEXT = 1129
-BFFM_SETSELECTIONA = 1126
-BFFM_SETSELECTIONW = 1127
-BIF_EDITBOX = 16
-BS_DEFPUSHBUTTON = 1
-CB_ADDSTRING = 323
-CB_GETCURSEL = 327
-CB_SETCURSEL = 334
-CDM_SETCONTROLTEXT = 1128
-EM_GETLINECOUNT = 186
-EM_GETMARGINS = 212
-EM_POSFROMCHAR = 214
-EM_SETSEL = 177
-GWL_STYLE = -16
-IDC_STATIC = -1
-IDCANCEL = 2
-IDNO = 7
-IDOK = 1
-IDYES = 6
-MAX_PATH = 260
-OFN_ALLOWMULTISELECT = 512
-OFN_ENABLEHOOK = 32
-OFN_ENABLESIZING = 8388608
-OFN_ENABLETEMPLATEHANDLE = 128
-OFN_EXPLORER = 524288
-OFN_OVERWRITEPROMPT = 2
-OPENFILENAME_SIZE_VERSION_400 = 76
-PBM_GETPOS = 1032
-PBM_SETMARQUEE = 1034
-PBM_SETPOS = 1026
-PBM_SETRANGE = 1025
-PBM_SETRANGE32 = 1030
-PBS_MARQUEE = 8
-PM_REMOVE = 1
-SW_HIDE = 0
-SW_SHOW = 5
-SW_SHOWNORMAL = 1
-SWP_NOACTIVATE = 16
-SWP_NOMOVE = 2
-SWP_NOSIZE = 1
-SWP_NOZORDER = 4
-VER_PLATFORM_WIN32_NT = 2
-WM_COMMAND = 273
-WM_GETTEXT = 13
-WM_GETTEXTLENGTH = 14
-WM_INITDIALOG = 272
-WM_NOTIFY = 78
-
-# Windows function prototypes
-BrowseCallbackProc = ctypes.WINFUNCTYPE(ctypes.c_int, wintypes.HWND, ctypes.c_uint, wintypes.LPARAM, wintypes.LPARAM)
-
-# Windows types
-LPCTSTR = ctypes.c_char_p
-LPTSTR = ctypes.c_char_p
-LPVOID = ctypes.c_voidp
-TCHAR = ctypes.c_char
-
-class BROWSEINFO(ctypes.Structure):
- _fields_ = [
- ("hwndOwner", wintypes.HWND),
- ("pidlRoot", LPVOID),
- ("pszDisplayName", LPTSTR),
- ("lpszTitle", LPCTSTR),
- ("ulFlags", ctypes.c_uint),
- ("lpfn", BrowseCallbackProc),
- ("lParam", wintypes.LPARAM),
- ("iImage", ctypes.c_int)
- ]
-
-
-# Utilities
-def CenterWindow(hwnd):
- desktopRect = GetWindowRect(user32.GetDesktopWindow())
- myRect = GetWindowRect(hwnd)
- x = width(desktopRect) // 2 - width(myRect) // 2
- y = height(desktopRect) // 2 - height(myRect) // 2
- user32.SetWindowPos(hwnd, 0,
- desktopRect.left + x,
- desktopRect.top + y,
- 0, 0,
- SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER
- )
-
-
-def GetWindowRect(hwnd):
- rect = wintypes.RECT()
- user32.GetWindowRect(hwnd, ctypes.byref(rect))
- return rect
-
-def width(rect):
- return rect.right-rect.left
-
-def height(rect):
- return rect.bottom-rect.top
-
-
-def AskFolder(
- message=None,
- version=None,
- defaultLocation=None,
- location=None,
- windowTitle=None,
- actionButtonLabel=None,
- cancelButtonLabel=None,
- multiple=None):
- """Display a dialog asking the user for select a folder.
- modified to use unicode strings as much as possible
- returns unicode path
- """
-
- def BrowseCallback(hwnd, uMsg, lParam, lpData):
- if uMsg == BFFM_INITIALIZED:
- if actionButtonLabel:
- label = unicode(actionButtonLabel, errors='replace')
- user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label)
- if cancelButtonLabel:
- label = unicode(cancelButtonLabel, errors='replace')
- cancelButton = user32.GetDlgItem(hwnd, IDCANCEL)
- if cancelButton:
- user32.SetWindowTextW(cancelButton, label)
- if windowTitle:
- title = unicode(windowTitle, erros='replace')
- user32.SetWindowTextW(hwnd, title)
- if defaultLocation:
- user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\'))
- if location:
- x, y = location
- desktopRect = wintypes.RECT()
- user32.GetWindowRect(0, ctypes.byref(desktopRect))
- user32.SetWindowPos(hwnd, 0,
- desktopRect.left + x,
- desktopRect.top + y, 0, 0,
- SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER)
- else:
- CenterWindow(hwnd)
- return 0
-
- # This next line is needed to prevent gc of the callback
- callback = BrowseCallbackProc(BrowseCallback)
-
- browseInfo = BROWSEINFO()
- browseInfo.pszDisplayName = ctypes.c_char_p('\0' * (MAX_PATH+1))
- browseInfo.lpszTitle = message
- browseInfo.lpfn = callback
-
- pidl = shell32.SHBrowseForFolder(ctypes.byref(browseInfo))
- if not pidl:
- result = None
- else:
- path = LPCWSTR(u" " * (MAX_PATH+1))
- shell32.SHGetPathFromIDListW(pidl, path)
- ole32.CoTaskMemFree(pidl)
- result = path.value
- return result
-
-
-
-
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py
deleted file mode 100644
index 3a56e44..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/config.py
+++ /dev/null
@@ -1,1021 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-__license__ = 'GPL v3'
-
-# Standard Python modules.
-import os, traceback, json
-
-# PyQT4 modules (part of calibre).
-try:
- from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
- QGroupBox, QPushButton, QListWidget, QListWidgetItem,
- QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
-except ImportError:
- 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
-
-# calibre modules and constants.
-from calibre.gui2 import (error_dialog, question_dialog, info_dialog, open_url,
- choose_dir, choose_files, choose_save_file)
-from calibre.utils.config import dynamic, config_dir, JSONConfig
-from calibre.constants import iswindows, isosx
-
-# modules from this plugin's zipfile.
-from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
-from calibre_plugins.dedrm.__init__ import RESOURCE_NAME as help_file_name
-from calibre_plugins.dedrm.utilities import uStrCmp
-
-import calibre_plugins.dedrm.prefs as prefs
-import calibre_plugins.dedrm.androidkindlekey as androidkindlekey
-
-class ConfigWidget(QWidget):
- def __init__(self, plugin_path, alfdir):
- QWidget.__init__(self)
-
- self.plugin_path = plugin_path
- self.alfdir = alfdir
-
- # get the prefs
- self.dedrmprefs = prefs.DeDRM_Prefs()
-
- # make a local copy
- self.tempdedrmprefs = {}
- self.tempdedrmprefs['bandnkeys'] = self.dedrmprefs['bandnkeys'].copy()
- self.tempdedrmprefs['adeptkeys'] = self.dedrmprefs['adeptkeys'].copy()
- self.tempdedrmprefs['ereaderkeys'] = self.dedrmprefs['ereaderkeys'].copy()
- self.tempdedrmprefs['kindlekeys'] = self.dedrmprefs['kindlekeys'].copy()
- self.tempdedrmprefs['androidkeys'] = self.dedrmprefs['androidkeys'].copy()
- self.tempdedrmprefs['pids'] = list(self.dedrmprefs['pids'])
- self.tempdedrmprefs['serials'] = list(self.dedrmprefs['serials'])
- self.tempdedrmprefs['adobewineprefix'] = self.dedrmprefs['adobewineprefix']
- self.tempdedrmprefs['kindlewineprefix'] = self.dedrmprefs['kindlewineprefix']
-
- # Start Qt Gui dialog layout
- layout = QVBoxLayout(self)
- self.setLayout(layout)
-
- help_layout = QHBoxLayout()
- layout.addLayout(help_layout)
- # Add hyperlink to a help file at the right. We will replace the correct name when it is clicked.
- help_label = QLabel('Plugin Help ', self)
- help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
- help_label.setAlignment(Qt.AlignRight)
- help_label.linkActivated.connect(self.help_link_activated)
- help_layout.addWidget(help_label)
-
- keys_group_box = QGroupBox(_('Configuration:'), self)
- layout.addWidget(keys_group_box)
- keys_group_box_layout = QHBoxLayout()
- keys_group_box.setLayout(keys_group_box_layout)
-
-
- button_layout = QVBoxLayout()
- keys_group_box_layout.addLayout(button_layout)
- self.bandn_button = QtGui.QPushButton(self)
- self.bandn_button.setToolTip(_(u"Click to manage keys for Barnes and Noble ebooks"))
- self.bandn_button.setText(u"Barnes and Noble ebooks")
- self.bandn_button.clicked.connect(self.bandn_keys)
- self.kindle_android_button = QtGui.QPushButton(self)
- self.kindle_android_button.setToolTip(_(u"Click to manage keys for 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_serial_button = QtGui.QPushButton(self)
- self.kindle_serial_button.setToolTip(_(u"Click to manage eInk Kindle serial numbers for Kindle ebooks"))
- self.kindle_serial_button.setText(u"eInk Kindle ebooks")
- self.kindle_serial_button.clicked.connect(self.kindle_serials)
- self.kindle_key_button = QtGui.QPushButton(self)
- self.kindle_key_button.setToolTip(_(u"Click to manage keys for 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.adept_button = QtGui.QPushButton(self)
- self.adept_button.setToolTip(_(u"Click to manage keys for Adobe Digital Editions ebooks"))
- self.adept_button.setText(u"Adobe Digital Editions ebooks")
- self.adept_button.clicked.connect(self.adept_keys)
- self.mobi_button = QtGui.QPushButton(self)
- self.mobi_button.setToolTip(_(u"Click to manage PIDs for Mobipocket ebooks"))
- self.mobi_button.setText(u"Mobipocket ebooks")
- self.mobi_button.clicked.connect(self.mobi_keys)
- self.ereader_button = QtGui.QPushButton(self)
- self.ereader_button.setToolTip(_(u"Click to manage keys for eReader ebooks"))
- self.ereader_button.setText(u"eReader ebooks")
- self.ereader_button.clicked.connect(self.ereader_keys)
- button_layout.addWidget(self.kindle_serial_button)
- button_layout.addWidget(self.kindle_android_button)
- button_layout.addWidget(self.bandn_button)
- button_layout.addWidget(self.mobi_button)
- button_layout.addWidget(self.ereader_button)
- button_layout.addWidget(self.adept_button)
- button_layout.addWidget(self.kindle_key_button)
-
- self.resize(self.sizeHint())
-
- def kindle_serials(self):
- d = ManageKeysDialog(self,u"EInk Kindle Serial Number",self.tempdedrmprefs['serials'], AddSerialDialog)
- d.exec_()
-
- def kindle_android(self):
- d = ManageKeysDialog(self,u"Kindle for Android Key",self.tempdedrmprefs['androidkeys'], AddAndroidDialog, 'k4a')
- d.exec_()
-
- def kindle_keys(self):
- if isosx or iswindows:
- d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i')
- else:
- # linux
- d = ManageKeysDialog(self,u"Kindle for Mac and PC Key",self.tempdedrmprefs['kindlekeys'], AddKindleDialog, 'k4i', self.tempdedrmprefs['kindlewineprefix'])
- d.exec_()
- self.tempdedrmprefs['kindlewineprefix'] = d.getwineprefix()
-
- def adept_keys(self):
- if isosx or iswindows:
- d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der')
- else:
- # linux
- d = ManageKeysDialog(self,u"Adobe Digital Editions Key",self.tempdedrmprefs['adeptkeys'], AddAdeptDialog, 'der', self.tempdedrmprefs['adobewineprefix'])
- d.exec_()
- self.tempdedrmprefs['adobewineprefix'] = d.getwineprefix()
-
- def mobi_keys(self):
- d = ManageKeysDialog(self,u"Mobipocket PID",self.tempdedrmprefs['pids'], AddPIDDialog)
- d.exec_()
-
- def bandn_keys(self):
- d = ManageKeysDialog(self,u"Barnes and Noble Key",self.tempdedrmprefs['bandnkeys'], AddBandNKeyDialog, 'b64')
- d.exec_()
-
- def ereader_keys(self):
- d = ManageKeysDialog(self,u"eReader Key",self.tempdedrmprefs['ereaderkeys'], AddEReaderDialog, 'b63')
- d.exec_()
-
- def help_link_activated(self, url):
- def get_help_file_resource():
- # Copy the HTML helpfile to the plugin directory each time the
- # link is clicked in case the helpfile is updated in newer plugins.
- file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name)
- with open(file_path,'w') as f:
- f.write(self.load_resource(help_file_name))
- return file_path
- url = 'file:///' + get_help_file_resource()
- open_url(QUrl(url))
-
- def save_settings(self):
- self.dedrmprefs.set('bandnkeys', self.tempdedrmprefs['bandnkeys'])
- self.dedrmprefs.set('adeptkeys', self.tempdedrmprefs['adeptkeys'])
- self.dedrmprefs.set('ereaderkeys', self.tempdedrmprefs['ereaderkeys'])
- self.dedrmprefs.set('kindlekeys', self.tempdedrmprefs['kindlekeys'])
- self.dedrmprefs.set('androidkeys', self.tempdedrmprefs['androidkeys'])
- self.dedrmprefs.set('pids', self.tempdedrmprefs['pids'])
- self.dedrmprefs.set('serials', self.tempdedrmprefs['serials'])
- self.dedrmprefs.set('adobewineprefix', self.tempdedrmprefs['adobewineprefix'])
- self.dedrmprefs.set('kindlewineprefix', self.tempdedrmprefs['kindlewineprefix'])
- self.dedrmprefs.set('configured', True)
- self.dedrmprefs.writeprefs()
-
- def load_resource(self, name):
- with ZipFile(self.plugin_path, 'r') as zf:
- if name in zf.namelist():
- return zf.read(name)
- return ""
-
-
-
-class ManageKeysDialog(QDialog):
- def __init__(self, parent, key_type_name, plugin_keys, create_key, keyfile_ext = u"", wineprefix = None):
- QDialog.__init__(self,parent)
- self.parent = parent
- self.key_type_name = key_type_name
- self.plugin_keys = plugin_keys
- self.create_key = create_key
- self.keyfile_ext = keyfile_ext
- self.import_key = (keyfile_ext != u"")
- self.binary_file = (keyfile_ext == u"der")
- self.json_file = (keyfile_ext == u"k4i")
- self.android_file = (keyfile_ext == u"k4a")
- self.wineprefix = wineprefix
-
- self.setWindowTitle("{0} {1}: Manage {2}s".format(PLUGIN_NAME, PLUGIN_VERSION, self.key_type_name))
-
- # Start Qt Gui dialog layout
- layout = QVBoxLayout(self)
- self.setLayout(layout)
-
- help_layout = QHBoxLayout()
- layout.addLayout(help_layout)
- # Add hyperlink to a help file at the right. We will replace the correct name when it is clicked.
- help_label = QLabel('Help ', self)
- help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
- help_label.setAlignment(Qt.AlignRight)
- help_label.linkActivated.connect(self.help_link_activated)
- help_layout.addWidget(help_label)
-
- keys_group_box = QGroupBox(_(u"{0}s".format(self.key_type_name)), self)
- layout.addWidget(keys_group_box)
- keys_group_box_layout = QHBoxLayout()
- keys_group_box.setLayout(keys_group_box_layout)
-
- self.listy = QListWidget(self)
- self.listy.setToolTip(u"{0}s that will be used to decrypt ebooks".format(self.key_type_name))
- self.listy.setSelectionMode(QAbstractItemView.SingleSelection)
- self.populate_list()
- keys_group_box_layout.addWidget(self.listy)
-
- button_layout = QVBoxLayout()
- keys_group_box_layout.addLayout(button_layout)
- self._add_key_button = QtGui.QToolButton(self)
- self._add_key_button.setIcon(QIcon(I('plus.png')))
- self._add_key_button.setToolTip(u"Create new {0}".format(self.key_type_name))
- self._add_key_button.clicked.connect(self.add_key)
- button_layout.addWidget(self._add_key_button)
-
- self._delete_key_button = QtGui.QToolButton(self)
- self._delete_key_button.setToolTip(_(u"Delete highlighted key"))
- self._delete_key_button.setIcon(QIcon(I('list_remove.png')))
- self._delete_key_button.clicked.connect(self.delete_key)
- button_layout.addWidget(self._delete_key_button)
-
- if type(self.plugin_keys) == dict and self.import_key:
- self._rename_key_button = QtGui.QToolButton(self)
- self._rename_key_button.setToolTip(_(u"Rename highlighted key"))
- self._rename_key_button.setIcon(QIcon(I('edit-select-all.png')))
- self._rename_key_button.clicked.connect(self.rename_key)
- button_layout.addWidget(self._rename_key_button)
-
- self.export_key_button = QtGui.QToolButton(self)
- self.export_key_button.setToolTip(u"Save highlighted key to a .{0} file".format(self.keyfile_ext))
- self.export_key_button.setIcon(QIcon(I('save.png')))
- self.export_key_button.clicked.connect(self.export_key)
- button_layout.addWidget(self.export_key_button)
- spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
- button_layout.addItem(spacerItem)
-
- if self.wineprefix is not None:
- layout.addSpacing(5)
- wineprefix_layout = QHBoxLayout()
- layout.addLayout(wineprefix_layout)
- wineprefix_layout.setAlignment(Qt.AlignCenter)
- self.wp_label = QLabel(u"WINEPREFIX:")
- wineprefix_layout.addWidget(self.wp_label)
- self.wp_lineedit = QLineEdit(self)
- wineprefix_layout.addWidget(self.wp_lineedit)
- self.wp_label.setBuddy(self.wp_lineedit)
- self.wp_lineedit.setText(self.wineprefix)
-
- layout.addSpacing(5)
- migrate_layout = QHBoxLayout()
- layout.addLayout(migrate_layout)
- if self.import_key:
- migrate_layout.setAlignment(Qt.AlignJustify)
- self.migrate_btn = QPushButton(u"Import Existing Keyfiles", self)
- self.migrate_btn.setToolTip(u"Import *.{0} files (created using other tools).".format(self.keyfile_ext))
- self.migrate_btn.clicked.connect(self.migrate_wrapper)
- migrate_layout.addWidget(self.migrate_btn)
- migrate_layout.addStretch()
- self.button_box = QDialogButtonBox(QDialogButtonBox.Close)
- self.button_box.rejected.connect(self.close)
- migrate_layout.addWidget(self.button_box)
-
- self.resize(self.sizeHint())
-
- def getwineprefix(self):
- if self.wineprefix is not None:
- return unicode(self.wp_lineedit.text()).strip()
- return u""
-
- def populate_list(self):
- if type(self.plugin_keys) == dict:
- for key in self.plugin_keys.keys():
- self.listy.addItem(QListWidgetItem(key))
- else:
- for key in self.plugin_keys:
- self.listy.addItem(QListWidgetItem(key))
-
- def add_key(self):
- d = self.create_key(self)
- d.exec_()
-
- if d.result() != d.Accepted:
- # New key generation cancelled.
- return
- new_key_value = d.key_value
- if type(self.plugin_keys) == dict:
- if new_key_value in self.plugin_keys.values():
- old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
- info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
- u"The new {1} is the same as the existing {1} named {0} and has not been added.".format(old_key_name,self.key_type_name), show=True)
- return
- self.plugin_keys[d.key_name] = new_key_value
- else:
- if new_key_value in self.plugin_keys:
- info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
- u"This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
- return
-
- self.plugin_keys.append(d.key_value)
- self.listy.clear()
- self.populate_list()
-
- def rename_key(self):
- if not self.listy.currentItem():
- errmsg = u"No {0} selected to rename. Highlight a keyfile first.".format(self.key_type_name)
- r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
- _(errmsg), show=True, show_copy_button=False)
- return
-
- d = RenameKeyDialog(self)
- d.exec_()
-
- if d.result() != d.Accepted:
- # rename cancelled or moot.
- return
- keyname = unicode(self.listy.currentItem().text())
- if not question_dialog(self, "{0} {1}: Confirm Rename".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to rename the {2} named {0} to {1} ?".format(keyname,d.key_name,self.key_type_name), show_copy_button=False, default_yes=False):
- return
- self.plugin_keys[d.key_name] = self.plugin_keys[keyname]
- del self.plugin_keys[keyname]
-
- self.listy.clear()
- self.populate_list()
-
- def delete_key(self):
- if not self.listy.currentItem():
- return
- keyname = unicode(self.listy.currentItem().text())
- if not question_dialog(self, "{0} {1}: Confirm Delete".format(PLUGIN_NAME, PLUGIN_VERSION), u"Do you really want to delete the {1} {0} ?".format(keyname, self.key_type_name), show_copy_button=False, default_yes=False):
- return
- if type(self.plugin_keys) == dict:
- del self.plugin_keys[keyname]
- else:
- self.plugin_keys.remove(keyname)
-
- self.listy.clear()
- self.populate_list()
-
- def help_link_activated(self, url):
- def get_help_file_resource():
- # Copy the HTML helpfile to the plugin directory each time the
- # link is clicked in case the helpfile is updated in newer plugins.
- help_file_name = u"{0}_{1}_Help.htm".format(PLUGIN_NAME, self.key_type_name)
- file_path = os.path.join(config_dir, u"plugins", u"DeDRM", u"help", help_file_name)
- with open(file_path,'w') as f:
- f.write(self.parent.load_resource(help_file_name))
- return file_path
- url = 'file:///' + get_help_file_resource()
- open_url(QUrl(url))
-
- def migrate_files(self):
- unique_dlg_name = PLUGIN_NAME + u"import {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
- caption = u"Select {0} files to import".format(self.key_type_name)
- filters = [(u"{0} files".format(self.key_type_name), [self.keyfile_ext])]
- files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
- counter = 0
- skipped = 0
- if files:
- for filename in files:
- fpath = os.path.join(config_dir, filename)
- filename = os.path.basename(filename)
- new_key_name = os.path.splitext(os.path.basename(filename))[0]
- with open(fpath,'rb') as keyfile:
- new_key_value = keyfile.read()
- if self.binary_file:
- new_key_value = new_key_value.encode('hex')
- elif self.json_file:
- new_key_value = json.loads(new_key_value)
- elif self.android_file:
- # convert to list of the keys in the string
- new_key_value = new_key_value.splitlines()
- match = False
- for key in self.plugin_keys.keys():
- if uStrCmp(new_key_name, key, True):
- skipped += 1
- msg = u"A key with the name {0} already exists!\nSkipping key file {1} .\nRename the existing key and import again".format(new_key_name,filename)
- inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
- _(msg), show_copy_button=False, show=True)
- match = True
- break
- if not match:
- if new_key_value in self.plugin_keys.values():
- old_key_name = [name for name, value in self.plugin_keys.iteritems() if value == new_key_value][0]
- skipped += 1
- info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
- u"The key in file {0} is the same as the existing key {1} and has been skipped.".format(filename,old_key_name), show_copy_button=False, show=True)
- else:
- counter += 1
- self.plugin_keys[new_key_name] = new_key_value
-
- msg = u""
- if counter+skipped > 1:
- if counter > 0:
- msg += u"Imported {0:d} key {1}. ".format(counter, u"file" if counter == 1 else u"files")
- if skipped > 0:
- msg += u"Skipped {0:d} key {1}.".format(skipped, u"file" if counter == 1 else u"files")
- inf = info_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
- _(msg), show_copy_button=False, show=True)
- return counter > 0
-
- def migrate_wrapper(self):
- if self.migrate_files():
- self.listy.clear()
- self.populate_list()
-
- def export_key(self):
- if not self.listy.currentItem():
- errmsg = u"No keyfile selected to export. Highlight a keyfile first."
- r = error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
- _(errmsg), show=True, show_copy_button=False)
- return
- keyname = unicode(self.listy.currentItem().text())
- unique_dlg_name = PLUGIN_NAME + u"export {0} keys".format(self.key_type_name).replace(' ', '_') #takes care of automatically remembering last directory
- caption = u"Save {0} File as...".format(self.key_type_name)
- filters = [(u"{0} Files".format(self.key_type_name), [u"{0}".format(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)
- if filename:
- with file(filename, 'wb') as fname:
- if self.binary_file:
- fname.write(self.plugin_keys[keyname].decode('hex'))
- elif self.json_file:
- fname.write(json.dumps(self.plugin_keys[keyname]))
- elif self.android_file:
- for key in self.plugin_keys[keyname]:
- fname.write(key)
- fname.write("\n")
- else:
- fname.write(self.plugin_keys[keyname])
-
-
-
-
-class RenameKeyDialog(QDialog):
- def __init__(self, parent=None,):
- print repr(self), repr(parent)
- QDialog.__init__(self, parent)
- self.parent = parent
- self.setWindowTitle("{0} {1}: Rename {0}".format(PLUGIN_NAME, PLUGIN_VERSION, parent.key_type_name))
- layout = QVBoxLayout(self)
- self.setLayout(layout)
-
- data_group_box = QGroupBox('', self)
- layout.addWidget(data_group_box)
- data_group_box_layout = QVBoxLayout()
- data_group_box.setLayout(data_group_box_layout)
-
- data_group_box_layout.addWidget(QLabel('New Key Name:', self))
- self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self)
- self.key_ledit.setToolTip(u"Enter a new name for this existing {0}.".format(parent.key_type_name))
- data_group_box_layout.addWidget(self.key_ledit)
-
- layout.addSpacing(20)
-
- self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
- self.button_box.accepted.connect(self.accept)
- self.button_box.rejected.connect(self.reject)
- layout.addWidget(self.button_box)
-
- self.resize(self.sizeHint())
-
- def accept(self):
- if not unicode(self.key_ledit.text()) or unicode(self.key_ledit.text()).isspace():
- errmsg = u"Key name field cannot be empty!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
- _(errmsg), show=True, show_copy_button=False)
- if len(self.key_ledit.text()) < 4:
- errmsg = u"Key name must be at least 4 characters long!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
- _(errmsg), show=True, show_copy_button=False)
- if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()):
- # Same exact name ... do nothing.
- return QDialog.reject(self)
- for k in self.parent.plugin_keys.keys():
- if (uStrCmp(self.key_ledit.text(), k, True) and
- not uStrCmp(k, self.parent.listy.currentItem().text(), True)):
- errmsg = u"The key name {0} is already being used.".format(self.key_ledit.text())
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION),
- _(errmsg), show=True, show_copy_button=False)
- QDialog.accept(self)
-
- @property
- def key_name(self):
- return unicode(self.key_ledit.text()).strip()
-
-
-
-
-
-
-
-
-class AddBandNKeyDialog(QDialog):
- def __init__(self, parent=None,):
- QDialog.__init__(self, parent)
- self.parent = parent
- self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION))
- layout = QVBoxLayout(self)
- self.setLayout(layout)
-
- data_group_box = QGroupBox(u"", self)
- layout.addWidget(data_group_box)
- data_group_box_layout = QVBoxLayout()
- data_group_box.setLayout(data_group_box_layout)
-
- key_group = QHBoxLayout()
- data_group_box_layout.addLayout(key_group)
- key_group.addWidget(QLabel(u"Unique Key Name:", self))
- self.key_ledit = QLineEdit("", self)
- self.key_ledit.setToolTip(_(u"Enter an identifying name for this new key.
" +
- u"It should be something that will help you remember " +
- u"what personal information was used to create it."))
- key_group.addWidget(self.key_ledit)
-
- name_group = QHBoxLayout()
- data_group_box_layout.addLayout(name_group)
- name_group.addWidget(QLabel(u"B&N/nook account email address:", self))
- self.name_ledit = QLineEdit(u"", self)
- self.name_ledit.setToolTip(_(u"
Enter your email address as it appears in your B&N " +
- u"account.
" +
- u"It will only be used to generate this " +
- u"key and won\'t be stored anywhere " +
- u"in calibre or on your computer.
" +
- u"eg: apprenticeharper@gmail.com
"))
- name_group.addWidget(self.name_ledit)
- name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
- name_disclaimer_label.setAlignment(Qt.AlignHCenter)
- data_group_box_layout.addWidget(name_disclaimer_label)
-
- ccn_group = QHBoxLayout()
- data_group_box_layout.addLayout(ccn_group)
- ccn_group.addWidget(QLabel(u"B&N/nook account password:", self))
- self.cc_ledit = QLineEdit(u"", self)
- self.cc_ledit.setToolTip(_(u"Enter the password " +
- u"for your B&N account.
" +
- u"The password will only be used to generate this " +
- u"key and won\'t be stored anywhere in " +
- u"calibre or on your computer."))
- ccn_group.addWidget(self.cc_ledit)
- ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
- ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
- data_group_box_layout.addWidget(ccn_disclaimer_label)
- layout.addSpacing(10)
-
- key_group = QHBoxLayout()
- data_group_box_layout.addLayout(key_group)
- key_group.addWidget(QLabel(u"Retrieved key:", self))
- self.key_display = QLabel(u"", self)
- 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)
- self.retrieve_button = QtGui.QPushButton(self)
- self.retrieve_button.setToolTip(_(u"Click to retrieve your B&N encryption key from the B&N servers"))
- self.retrieve_button.setText(u"Retrieve Key")
- self.retrieve_button.clicked.connect(self.retrieve_key)
- key_group.addWidget(self.retrieve_button)
- layout.addSpacing(10)
-
- self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
- self.button_box.accepted.connect(self.accept)
- self.button_box.rejected.connect(self.reject)
- layout.addWidget(self.button_box)
-
- self.resize(self.sizeHint())
-
- @property
- def key_name(self):
- return unicode(self.key_ledit.text()).strip()
-
- @property
- def key_value(self):
- return unicode(self.key_display.text()).strip()
-
- @property
- def user_name(self):
- return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
-
- @property
- def cc_number(self):
- return unicode(self.cc_ledit.text()).strip()
-
- def retrieve_key(self):
- from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key
- fetched_key = fetch_bandn_key(self.user_name,self.cc_number)
- if fetched_key == "":
- 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)
- else:
- self.key_display.setText(fetched_key)
-
- def accept(self):
- if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
- errmsg = u"All fields are required!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- if len(self.key_name) < 4:
- errmsg = u"Key name must be at least 4 characters long!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- if len(self.key_value) == 0:
- self.retrieve_key()
- if len(self.key_value) == 0:
- return
- QDialog.accept(self)
-
-class AddEReaderDialog(QDialog):
- def __init__(self, parent=None,):
- QDialog.__init__(self, parent)
- self.parent = parent
- self.setWindowTitle(u"{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION))
- layout = QVBoxLayout(self)
- self.setLayout(layout)
-
- data_group_box = QGroupBox(u"", self)
- layout.addWidget(data_group_box)
- data_group_box_layout = QVBoxLayout()
- data_group_box.setLayout(data_group_box_layout)
-
- key_group = QHBoxLayout()
- data_group_box_layout.addLayout(key_group)
- key_group.addWidget(QLabel(u"Unique Key Name:", self))
- self.key_ledit = QLineEdit("", self)
- self.key_ledit.setToolTip(u"
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)
-
- name_group = QHBoxLayout()
- data_group_box_layout.addLayout(name_group)
- name_group.addWidget(QLabel(u"Your Name:", self))
- self.name_ledit = QLineEdit(u"", self)
- self.name_ledit.setToolTip(u"Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)")
- name_group.addWidget(self.name_ledit)
- name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
- name_disclaimer_label.setAlignment(Qt.AlignHCenter)
- data_group_box_layout.addWidget(name_disclaimer_label)
-
- ccn_group = QHBoxLayout()
- data_group_box_layout.addLayout(ccn_group)
- ccn_group.addWidget(QLabel(u"Credit Card#:", self))
- self.cc_ledit = QLineEdit(u"", self)
- self.cc_ledit.setToolTip(u"
Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.")
- ccn_group.addWidget(self.cc_ledit)
- ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
- ccn_disclaimer_label.setAlignment(Qt.AlignHCenter)
- data_group_box_layout.addWidget(ccn_disclaimer_label)
- layout.addSpacing(10)
-
- self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
- self.button_box.accepted.connect(self.accept)
- self.button_box.rejected.connect(self.reject)
- layout.addWidget(self.button_box)
-
- self.resize(self.sizeHint())
-
- @property
- def key_name(self):
- return unicode(self.key_ledit.text()).strip()
-
- @property
- def key_value(self):
- from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key
- return generate_ereader_key(self.user_name,self.cc_number).encode('hex')
-
- @property
- def user_name(self):
- return unicode(self.name_ledit.text()).strip().lower().replace(' ','')
-
- @property
- def cc_number(self):
- return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
-
-
- def accept(self):
- if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
- errmsg = u"All fields are required!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- if not self.cc_number.isdigit():
- errmsg = u"Numbers only in the credit card number field!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- if len(self.key_name) < 4:
- errmsg = u"Key name must be at least 4 characters long!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- QDialog.accept(self)
-
-
-class AddAdeptDialog(QDialog):
- def __init__(self, parent=None,):
- QDialog.__init__(self, parent)
- self.parent = parent
- self.setWindowTitle(u"{0} {1}: Getting Default Adobe Digital Editions Key".format(PLUGIN_NAME, PLUGIN_VERSION))
- layout = QVBoxLayout(self)
- self.setLayout(layout)
-
- try:
- if iswindows or isosx:
- from calibre_plugins.dedrm.adobekey import adeptkeys
-
- defaultkeys = adeptkeys()
- else: # linux
- from wineutils import WineGetKeys
-
- scriptpath = os.path.join(parent.parent.alfdir,u"adobekey.py")
- defaultkeys = WineGetKeys(scriptpath, u".der",parent.getwineprefix())
-
- self.default_key = defaultkeys[0]
- except:
- traceback.print_exc()
- self.default_key = u""
-
- self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
-
- if len(self.default_key)>0:
- data_group_box = QGroupBox(u"", self)
- layout.addWidget(data_group_box)
- data_group_box_layout = QVBoxLayout()
- data_group_box.setLayout(data_group_box_layout)
-
- key_group = QHBoxLayout()
- data_group_box_layout.addLayout(key_group)
- key_group.addWidget(QLabel(u"Unique Key Name:", self))
- self.key_ledit = QLineEdit(u"default_key", self)
- self.key_ledit.setToolTip(u"
Enter an identifying name for the current default Adobe Digital Editions key.")
- key_group.addWidget(self.key_ledit)
-
- self.button_box.accepted.connect(self.accept)
- else:
- default_key_error = QLabel(u"The default encryption key for Adobe Digital Editions could not be found.", self)
- default_key_error.setAlignment(Qt.AlignHCenter)
- layout.addWidget(default_key_error)
- # if no default, bot buttons do the same
- self.button_box.accepted.connect(self.reject)
-
- self.button_box.rejected.connect(self.reject)
- layout.addWidget(self.button_box)
-
- self.resize(self.sizeHint())
-
- @property
- def key_name(self):
- return unicode(self.key_ledit.text()).strip()
-
- @property
- def key_value(self):
- return self.default_key.encode('hex')
-
-
- def accept(self):
- if len(self.key_name) == 0 or self.key_name.isspace():
- errmsg = u"All fields are required!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- if len(self.key_name) < 4:
- errmsg = u"Key name must be at least 4 characters long!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- QDialog.accept(self)
-
-
-class AddKindleDialog(QDialog):
- def __init__(self, parent=None,):
- QDialog.__init__(self, parent)
- self.parent = parent
- self.setWindowTitle(u"{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION))
- layout = QVBoxLayout(self)
- self.setLayout(layout)
-
- try:
- if iswindows or isosx:
- from calibre_plugins.dedrm.kindlekey import kindlekeys
-
- defaultkeys = kindlekeys()
- else: # linux
- from wineutils import WineGetKeys
-
- scriptpath = os.path.join(parent.parent.alfdir,u"kindlekey.py")
- defaultkeys = WineGetKeys(scriptpath, u".k4i",parent.getwineprefix())
-
- self.default_key = defaultkeys[0]
- except:
- traceback.print_exc()
- self.default_key = u""
-
- self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
-
- if len(self.default_key)>0:
- data_group_box = QGroupBox(u"", self)
- layout.addWidget(data_group_box)
- data_group_box_layout = QVBoxLayout()
- data_group_box.setLayout(data_group_box_layout)
-
- key_group = QHBoxLayout()
- data_group_box_layout.addLayout(key_group)
- key_group.addWidget(QLabel(u"Unique Key Name:", self))
- self.key_ledit = QLineEdit(u"default_key", self)
- self.key_ledit.setToolTip(u"
Enter an identifying name for the current default Kindle for Mac/PC key.")
- key_group.addWidget(self.key_ledit)
-
- self.button_box.accepted.connect(self.accept)
- else:
- default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self)
- default_key_error.setAlignment(Qt.AlignHCenter)
- layout.addWidget(default_key_error)
-
- # if no default, both buttons do the same
- self.button_box.accepted.connect(self.reject)
-
- self.button_box.rejected.connect(self.reject)
- layout.addWidget(self.button_box)
-
- self.resize(self.sizeHint())
-
- @property
- def key_name(self):
- return unicode(self.key_ledit.text()).strip()
-
- @property
- def key_value(self):
- return self.default_key
-
-
- def accept(self):
- if len(self.key_name) == 0 or self.key_name.isspace():
- errmsg = u"All fields are required!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- if len(self.key_name) < 4:
- errmsg = u"Key name must be at least 4 characters long!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- QDialog.accept(self)
-
-
-class AddSerialDialog(QDialog):
- def __init__(self, parent=None,):
- QDialog.__init__(self, parent)
- self.parent = parent
- self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION))
- layout = QVBoxLayout(self)
- self.setLayout(layout)
-
- data_group_box = QGroupBox(u"", self)
- layout.addWidget(data_group_box)
- data_group_box_layout = QVBoxLayout()
- data_group_box.setLayout(data_group_box_layout)
-
- key_group = QHBoxLayout()
- data_group_box_layout.addLayout(key_group)
- key_group.addWidget(QLabel(u"EInk Kindle Serial Number:", self))
- self.key_ledit = QLineEdit("", self)
- self.key_ledit.setToolTip(u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
- key_group.addWidget(self.key_ledit)
-
- self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
- self.button_box.accepted.connect(self.accept)
- self.button_box.rejected.connect(self.reject)
- layout.addWidget(self.button_box)
-
- self.resize(self.sizeHint())
-
- @property
- def key_name(self):
- return unicode(self.key_ledit.text()).strip()
-
- @property
- def key_value(self):
- return unicode(self.key_ledit.text()).strip()
-
- def accept(self):
- if len(self.key_name) == 0 or self.key_name.isspace():
- errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog."
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- if len(self.key_name) != 16:
- errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format(len(self.key_name))
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- QDialog.accept(self)
-
-
-class AddAndroidDialog(QDialog):
- def __init__(self, parent=None,):
-
- QDialog.__init__(self, parent)
- self.parent = parent
- self.setWindowTitle(u"{0} {1}: Add new Kindle for Android Key".format(PLUGIN_NAME, PLUGIN_VERSION))
- layout = QVBoxLayout(self)
- self.setLayout(layout)
- self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
-
- data_group_box = QGroupBox(u"", self)
- layout.addWidget(data_group_box)
- data_group_box_layout = QVBoxLayout()
- data_group_box.setLayout(data_group_box_layout)
-
- file_group = QHBoxLayout()
- data_group_box_layout.addLayout(file_group)
- add_btn = QPushButton(u"Choose Backup File", self)
- add_btn.setToolTip(u"Import Kindle for Android backup file.")
- add_btn.clicked.connect(self.get_android_file)
- file_group.addWidget(add_btn)
- self.selected_file_name = QLabel(u"",self)
- self.selected_file_name.setAlignment(Qt.AlignHCenter)
- file_group.addWidget(self.selected_file_name)
-
- key_group = QHBoxLayout()
- data_group_box_layout.addLayout(key_group)
- key_group.addWidget(QLabel(u"Unique Key Name:", self))
- self.key_ledit = QLineEdit(u"", self)
- self.key_ledit.setToolTip(u"
Enter an identifying name for the Android for Kindle key.")
- key_group.addWidget(self.key_ledit)
- #key_label = QLabel(_(''), self)
- #key_label.setAlignment(Qt.AlignHCenter)
- #data_group_box_layout.addWidget(key_label)
-
- self.button_box.accepted.connect(self.accept)
- self.button_box.rejected.connect(self.reject)
- layout.addWidget(self.button_box)
- self.resize(self.sizeHint())
-
- @property
- def key_name(self):
- return unicode(self.key_ledit.text()).strip()
-
- @property
- def file_name(self):
- return unicode(self.selected_file_name.text()).strip()
-
- @property
- def key_value(self):
- return self.serials_from_file
-
- def get_android_file(self):
- unique_dlg_name = PLUGIN_NAME + u"Import Kindle for Android backup file" #takes care of automatically remembering last directory
- caption = u"Select Kindle for Android backup file to add"
- filters = [(u"Kindle for Android backup files", ['db','ab','xml'])]
- files = choose_files(self, unique_dlg_name, caption, filters, all_files=False)
- self.serials_from_file = []
- file_name = u""
- if files:
- # find the first selected file that yields some serial numbers
- for filename in files:
- fpath = os.path.join(config_dir, filename)
- self.filename = os.path.basename(filename)
- file_serials = androidkindlekey.get_serials(fpath)
- if len(file_serials)>0:
- file_name = os.path.basename(self.filename)
- self.serials_from_file.extend(file_serials)
- self.selected_file_name.setText(file_name)
-
-
- def accept(self):
- if len(self.file_name) == 0 or len(self.key_value) == 0:
- 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)
- if len(self.key_name) == 0 or self.key_name.isspace():
- 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)
- if len(self.key_name) < 4:
- errmsg = u"Key name must be at least 4 characters long!"
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- QDialog.accept(self)
-
-class AddPIDDialog(QDialog):
- def __init__(self, parent=None,):
- QDialog.__init__(self, parent)
- self.parent = parent
- self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION))
- layout = QVBoxLayout(self)
- self.setLayout(layout)
-
- data_group_box = QGroupBox(u"", self)
- layout.addWidget(data_group_box)
- data_group_box_layout = QVBoxLayout()
- data_group_box.setLayout(data_group_box_layout)
-
- key_group = QHBoxLayout()
- data_group_box_layout.addLayout(key_group)
- key_group.addWidget(QLabel(u"PID:", self))
- self.key_ledit = QLineEdit("", self)
- self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.")
- key_group.addWidget(self.key_ledit)
-
- self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
- self.button_box.accepted.connect(self.accept)
- self.button_box.rejected.connect(self.reject)
- layout.addWidget(self.button_box)
-
- self.resize(self.sizeHint())
-
- @property
- def key_name(self):
- return unicode(self.key_ledit.text()).strip()
-
- @property
- def key_value(self):
- return unicode(self.key_ledit.text()).strip()
-
- def accept(self):
- if len(self.key_name) == 0 or self.key_name.isspace():
- errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog."
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- if len(self.key_name) != 8 and len(self.key_name) != 10:
- errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name))
- return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
- QDialog.accept(self)
-
-
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/convert2xml.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/convert2xml.py
deleted file mode 100644
index 8c2d0f3..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/convert2xml.py
+++ /dev/null
@@ -1,884 +0,0 @@
-#! /usr/bin/python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-# For use with Topaz Scripts Version 2.6
-
-class Unbuffered:
- def __init__(self, stream):
- self.stream = stream
- def write(self, data):
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-import sys
-sys.stdout=Unbuffered(sys.stdout)
-
-import csv
-import os
-import getopt
-from struct import pack
-from struct import unpack
-
-class TpzDRMError(Exception):
- pass
-
-# Get a 7 bit encoded number from string. The most
-# significant byte comes first and has the high bit (8th) set
-
-def readEncodedNumber(file):
- flag = False
- c = file.read(1)
- if (len(c) == 0):
- return None
- data = ord(c)
-
- if data == 0xFF:
- flag = True
- c = file.read(1)
- if (len(c) == 0):
- return None
- data = ord(c)
-
- if data >= 0x80:
- datax = (data & 0x7F)
- while data >= 0x80 :
- c = file.read(1)
- if (len(c) == 0):
- return None
- data = ord(c)
- datax = (datax <<7) + (data & 0x7F)
- data = datax
-
- if flag:
- data = -data
- return data
-
-
-# returns a binary string that encodes a number into 7 bits
-# most significant byte first which has the high bit set
-
-def encodeNumber(number):
- result = ""
- negative = False
- flag = 0
-
- if number < 0 :
- number = -number + 1
- negative = True
-
- while True:
- byte = number & 0x7F
- number = number >> 7
- byte += flag
- result += chr(byte)
- flag = 0x80
- if number == 0 :
- if (byte == 0xFF and negative == False) :
- result += chr(0x80)
- break
-
- if negative:
- result += chr(0xFF)
-
- return result[::-1]
-
-
-
-# create / read a length prefixed string from the file
-
-def lengthPrefixString(data):
- return encodeNumber(len(data))+data
-
-def readString(file):
- stringLength = readEncodedNumber(file)
- if (stringLength == None):
- return ""
- sv = file.read(stringLength)
- if (len(sv) != stringLength):
- return ""
- return unpack(str(stringLength)+"s",sv)[0]
-
-
-# convert a binary string generated by encodeNumber (7 bit encoded number)
-# to the value you would find inside the page*.dat files to be processed
-
-def convert(i):
- result = ''
- val = encodeNumber(i)
- for j in xrange(len(val)):
- c = ord(val[j:j+1])
- result += '%02x' % c
- return result
-
-
-
-# the complete string table used to store all book text content
-# as well as the xml tokens and values that make sense out of it
-
-class Dictionary(object):
- def __init__(self, dictFile):
- self.filename = dictFile
- self.size = 0
- self.fo = file(dictFile,'rb')
- self.stable = []
- self.size = readEncodedNumber(self.fo)
- for i in xrange(self.size):
- self.stable.append(self.escapestr(readString(self.fo)))
- self.pos = 0
-
- def escapestr(self, str):
- str = str.replace('&','&')
- str = str.replace('<','<')
- str = str.replace('>','>')
- str = str.replace('=','=')
- return str
-
- def lookup(self,val):
- if ((val >= 0) and (val < self.size)) :
- self.pos = val
- return self.stable[self.pos]
- else:
- print "Error - %d outside of string table limits" % val
- raise TpzDRMError('outside of string table limits')
- # sys.exit(-1)
-
- def getSize(self):
- return self.size
-
- def getPos(self):
- return self.pos
-
- def dumpDict(self):
- for i in xrange(self.size):
- print "%d %s %s" % (i, convert(i), self.stable[i])
- return
-
-# parses the xml snippets that are represented by each page*.dat file.
-# also parses the other0.dat file - the main stylesheet
-# and information used to inject the xml snippets into page*.dat files
-
-class PageParser(object):
- def __init__(self, filename, dict, debug, flat_xml):
- self.fo = file(filename,'rb')
- self.id = os.path.basename(filename).replace('.dat','')
- self.dict = dict
- self.debug = debug
- self.first_unknown = True
- self.flat_xml = flat_xml
- self.tagpath = []
- self.doc = []
- self.snippetList = []
-
-
- # hash table used to enable the decoding process
- # This has all been developed by trial and error so it may still have omissions or
- # contain errors
- # Format:
- # tag : (number of arguments, argument type, subtags present, special case of subtags presents when escaped)
-
- token_tags = {
- 'x' : (1, 'scalar_number', 0, 0),
- 'y' : (1, 'scalar_number', 0, 0),
- 'h' : (1, 'scalar_number', 0, 0),
- 'w' : (1, 'scalar_number', 0, 0),
- 'firstWord' : (1, 'scalar_number', 0, 0),
- 'lastWord' : (1, 'scalar_number', 0, 0),
- 'rootID' : (1, 'scalar_number', 0, 0),
- 'stemID' : (1, 'scalar_number', 0, 0),
- 'type' : (1, 'scalar_text', 0, 0),
-
- 'info' : (0, 'number', 1, 0),
-
- 'info.word' : (0, 'number', 1, 1),
- 'info.word.ocrText' : (1, 'text', 0, 0),
- 'info.word.firstGlyph' : (1, 'raw', 0, 0),
- 'info.word.lastGlyph' : (1, 'raw', 0, 0),
- 'info.word.bl' : (1, 'raw', 0, 0),
- 'info.word.link_id' : (1, 'number', 0, 0),
-
- 'glyph' : (0, 'number', 1, 1),
- 'glyph.x' : (1, 'number', 0, 0),
- 'glyph.y' : (1, 'number', 0, 0),
- 'glyph.glyphID' : (1, 'number', 0, 0),
-
- 'dehyphen' : (0, 'number', 1, 1),
- 'dehyphen.rootID' : (1, 'number', 0, 0),
- 'dehyphen.stemID' : (1, 'number', 0, 0),
- 'dehyphen.stemPage' : (1, 'number', 0, 0),
- 'dehyphen.sh' : (1, 'number', 0, 0),
-
- 'links' : (0, 'number', 1, 1),
- 'links.page' : (1, 'number', 0, 0),
- 'links.rel' : (1, 'number', 0, 0),
- 'links.row' : (1, 'number', 0, 0),
- 'links.title' : (1, 'text', 0, 0),
- 'links.href' : (1, 'text', 0, 0),
- 'links.type' : (1, 'text', 0, 0),
- 'links.id' : (1, 'number', 0, 0),
-
- 'paraCont' : (0, 'number', 1, 1),
- 'paraCont.rootID' : (1, 'number', 0, 0),
- 'paraCont.stemID' : (1, 'number', 0, 0),
- 'paraCont.stemPage' : (1, 'number', 0, 0),
-
- 'paraStems' : (0, 'number', 1, 1),
- 'paraStems.stemID' : (1, 'number', 0, 0),
-
- 'wordStems' : (0, 'number', 1, 1),
- 'wordStems.stemID' : (1, 'number', 0, 0),
-
- 'empty' : (1, 'snippets', 1, 0),
-
- 'page' : (1, 'snippets', 1, 0),
- 'page.class' : (1, 'scalar_text', 0, 0),
- 'page.pageid' : (1, 'scalar_text', 0, 0),
- 'page.pagelabel' : (1, 'scalar_text', 0, 0),
- 'page.type' : (1, 'scalar_text', 0, 0),
- 'page.h' : (1, 'scalar_number', 0, 0),
- 'page.w' : (1, 'scalar_number', 0, 0),
- 'page.startID' : (1, 'scalar_number', 0, 0),
-
- 'group' : (1, 'snippets', 1, 0),
- 'group.class' : (1, 'scalar_text', 0, 0),
- 'group.type' : (1, 'scalar_text', 0, 0),
- 'group._tag' : (1, 'scalar_text', 0, 0),
- 'group.orientation': (1, 'scalar_text', 0, 0),
-
- 'region' : (1, 'snippets', 1, 0),
- 'region.class' : (1, 'scalar_text', 0, 0),
- 'region.type' : (1, 'scalar_text', 0, 0),
- 'region.x' : (1, 'scalar_number', 0, 0),
- 'region.y' : (1, 'scalar_number', 0, 0),
- 'region.h' : (1, 'scalar_number', 0, 0),
- 'region.w' : (1, 'scalar_number', 0, 0),
- 'region.orientation' : (1, 'scalar_text', 0, 0),
-
- 'empty_text_region' : (1, 'snippets', 1, 0),
-
- 'img' : (1, 'snippets', 1, 0),
- 'img.x' : (1, 'scalar_number', 0, 0),
- 'img.y' : (1, 'scalar_number', 0, 0),
- 'img.h' : (1, 'scalar_number', 0, 0),
- 'img.w' : (1, 'scalar_number', 0, 0),
- 'img.src' : (1, 'scalar_number', 0, 0),
- 'img.color_src' : (1, 'scalar_number', 0, 0),
- 'img.gridSize' : (1, 'scalar_number', 0, 0),
- 'img.gridBottomCenter' : (1, 'scalar_number', 0, 0),
- 'img.gridTopCenter' : (1, 'scalar_number', 0, 0),
- 'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
- 'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
- 'img.image_type' : (1, 'scalar_number', 0, 0),
-
- 'paragraph' : (1, 'snippets', 1, 0),
- 'paragraph.class' : (1, 'scalar_text', 0, 0),
- 'paragraph.firstWord' : (1, 'scalar_number', 0, 0),
- 'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
- 'paragraph.lastWord' : (1, 'scalar_number', 0, 0),
- 'paragraph.gridSize' : (1, 'scalar_number', 0, 0),
- 'paragraph.gridBottomCenter' : (1, 'scalar_number', 0, 0),
- 'paragraph.gridTopCenter' : (1, 'scalar_number', 0, 0),
- 'paragraph.gridBeginCenter' : (1, 'scalar_number', 0, 0),
- 'paragraph.gridEndCenter' : (1, 'scalar_number', 0, 0),
-
-
- 'word_semantic' : (1, 'snippets', 1, 1),
- 'word_semantic.type' : (1, 'scalar_text', 0, 0),
- 'word_semantic.class' : (1, 'scalar_text', 0, 0),
- 'word_semantic.firstWord' : (1, 'scalar_number', 0, 0),
- 'word_semantic.lastWord' : (1, 'scalar_number', 0, 0),
- 'word_semantic.gridBottomCenter' : (1, 'scalar_number', 0, 0),
- 'word_semantic.gridTopCenter' : (1, 'scalar_number', 0, 0),
- 'word_semantic.gridBeginCenter' : (1, 'scalar_number', 0, 0),
- 'word_semantic.gridEndCenter' : (1, 'scalar_number', 0, 0),
-
- 'word' : (1, 'snippets', 1, 0),
- 'word.type' : (1, 'scalar_text', 0, 0),
- 'word.class' : (1, 'scalar_text', 0, 0),
- 'word.firstGlyph' : (1, 'scalar_number', 0, 0),
- 'word.lastGlyph' : (1, 'scalar_number', 0, 0),
-
- '_span' : (1, 'snippets', 1, 0),
- '_span.class' : (1, 'scalar_text', 0, 0),
- '_span.firstWord' : (1, 'scalar_number', 0, 0),
- '_span.lastWord' : (1, 'scalar_number', 0, 0),
- '_span.gridSize' : (1, 'scalar_number', 0, 0),
- '_span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
- '_span.gridTopCenter' : (1, 'scalar_number', 0, 0),
- '_span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
- '_span.gridEndCenter' : (1, 'scalar_number', 0, 0),
-
- 'span' : (1, 'snippets', 1, 0),
- 'span.firstWord' : (1, 'scalar_number', 0, 0),
- 'span.lastWord' : (1, 'scalar_number', 0, 0),
- 'span.gridSize' : (1, 'scalar_number', 0, 0),
- 'span.gridBottomCenter' : (1, 'scalar_number', 0, 0),
- 'span.gridTopCenter' : (1, 'scalar_number', 0, 0),
- 'span.gridBeginCenter' : (1, 'scalar_number', 0, 0),
- 'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
-
- 'extratokens' : (1, 'snippets', 1, 0),
- 'extratokens.class' : (1, 'scalar_text', 0, 0),
- 'extratokens.type' : (1, 'scalar_text', 0, 0),
- 'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
- 'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0),
- 'extratokens.gridSize' : (1, 'scalar_number', 0, 0),
- 'extratokens.gridBottomCenter' : (1, 'scalar_number', 0, 0),
- 'extratokens.gridTopCenter' : (1, 'scalar_number', 0, 0),
- 'extratokens.gridBeginCenter' : (1, 'scalar_number', 0, 0),
- 'extratokens.gridEndCenter' : (1, 'scalar_number', 0, 0),
-
- 'glyph.h' : (1, 'number', 0, 0),
- 'glyph.w' : (1, 'number', 0, 0),
- 'glyph.use' : (1, 'number', 0, 0),
- 'glyph.vtx' : (1, 'number', 0, 1),
- 'glyph.len' : (1, 'number', 0, 1),
- 'glyph.dpi' : (1, 'number', 0, 0),
- 'vtx' : (0, 'number', 1, 1),
- 'vtx.x' : (1, 'number', 0, 0),
- 'vtx.y' : (1, 'number', 0, 0),
- 'len' : (0, 'number', 1, 1),
- 'len.n' : (1, 'number', 0, 0),
-
- 'book' : (1, 'snippets', 1, 0),
- 'version' : (1, 'snippets', 1, 0),
- 'version.FlowEdit_1_id' : (1, 'scalar_text', 0, 0),
- 'version.FlowEdit_1_version' : (1, 'scalar_text', 0, 0),
- 'version.Schema_id' : (1, 'scalar_text', 0, 0),
- 'version.Schema_version' : (1, 'scalar_text', 0, 0),
- 'version.Topaz_version' : (1, 'scalar_text', 0, 0),
- 'version.WordDetailEdit_1_id' : (1, 'scalar_text', 0, 0),
- 'version.WordDetailEdit_1_version' : (1, 'scalar_text', 0, 0),
- 'version.ZoneEdit_1_id' : (1, 'scalar_text', 0, 0),
- 'version.ZoneEdit_1_version' : (1, 'scalar_text', 0, 0),
- 'version.chapterheaders' : (1, 'scalar_text', 0, 0),
- 'version.creation_date' : (1, 'scalar_text', 0, 0),
- 'version.header_footer' : (1, 'scalar_text', 0, 0),
- 'version.init_from_ocr' : (1, 'scalar_text', 0, 0),
- 'version.letter_insertion' : (1, 'scalar_text', 0, 0),
- 'version.xmlinj_convert' : (1, 'scalar_text', 0, 0),
- 'version.xmlinj_reflow' : (1, 'scalar_text', 0, 0),
- 'version.xmlinj_transform' : (1, 'scalar_text', 0, 0),
- 'version.findlists' : (1, 'scalar_text', 0, 0),
- 'version.page_num' : (1, 'scalar_text', 0, 0),
- 'version.page_type' : (1, 'scalar_text', 0, 0),
- 'version.bad_text' : (1, 'scalar_text', 0, 0),
- 'version.glyph_mismatch' : (1, 'scalar_text', 0, 0),
- 'version.margins' : (1, 'scalar_text', 0, 0),
- 'version.staggered_lines' : (1, 'scalar_text', 0, 0),
- 'version.paragraph_continuation' : (1, 'scalar_text', 0, 0),
- 'version.toc' : (1, 'scalar_text', 0, 0),
-
- 'stylesheet' : (1, 'snippets', 1, 0),
- 'style' : (1, 'snippets', 1, 0),
- 'style._tag' : (1, 'scalar_text', 0, 0),
- 'style.type' : (1, 'scalar_text', 0, 0),
- 'style._after_type' : (1, 'scalar_text', 0, 0),
- 'style._parent_type' : (1, 'scalar_text', 0, 0),
- 'style._after_parent_type' : (1, 'scalar_text', 0, 0),
- 'style.class' : (1, 'scalar_text', 0, 0),
- 'style._after_class' : (1, 'scalar_text', 0, 0),
- 'rule' : (1, 'snippets', 1, 0),
- 'rule.attr' : (1, 'scalar_text', 0, 0),
- 'rule.value' : (1, 'scalar_text', 0, 0),
-
- 'original' : (0, 'number', 1, 1),
- 'original.pnum' : (1, 'number', 0, 0),
- 'original.pid' : (1, 'text', 0, 0),
- 'pages' : (0, 'number', 1, 1),
- 'pages.ref' : (1, 'number', 0, 0),
- 'pages.id' : (1, 'number', 0, 0),
- 'startID' : (0, 'number', 1, 1),
- 'startID.page' : (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),
-
- 'num_footers_maybe' : (1, 'number', 0, 0),
- 'num_footers_yes' : (1, 'number', 0, 0),
- 'num_headers_maybe' : (1, 'number', 0, 0),
- 'num_headers_yes' : (1, 'number', 0, 0),
-
- 'tracking' : (1, 'number', 0, 0),
- 'src' : (1, 'text', 0, 0),
-
- }
-
-
- # full tag path record keeping routines
- def tag_push(self, token):
- self.tagpath.append(token)
- def tag_pop(self):
- if len(self.tagpath) > 0 :
- self.tagpath.pop()
- def tagpath_len(self):
- return len(self.tagpath)
- def get_tagpath(self, i):
- cnt = len(self.tagpath)
- if i < cnt : result = self.tagpath[i]
- for j in xrange(i+1, cnt) :
- result += '.' + self.tagpath[j]
- return result
-
-
- # list of absolute command byte values values that indicate
- # various types of loop meachanisms typically used to generate vectors
-
- cmd_list = (0x76, 0x76)
-
- # peek at and return 1 byte that is ahead by i bytes
- def peek(self, aheadi):
- c = self.fo.read(aheadi)
- if (len(c) == 0):
- return None
- self.fo.seek(-aheadi,1)
- c = c[-1:]
- return ord(c)
-
-
- # get the next value from the file being processed
- def getNext(self):
- nbyte = self.peek(1);
- if (nbyte == None):
- return None
- val = readEncodedNumber(self.fo)
- return val
-
-
- # format an arg by argtype
- def formatArg(self, arg, argtype):
- if (argtype == 'text') or (argtype == 'scalar_text') :
- result = self.dict.lookup(arg)
- elif (argtype == 'raw') or (argtype == 'number') or (argtype == 'scalar_number') :
- result = arg
- elif (argtype == 'snippets') :
- result = arg
- else :
- print "Error Unknown argtype %s" % argtype
- sys.exit(-2)
- return result
-
-
- # process the next tag token, recursively handling subtags,
- # arguments, and commands
- def procToken(self, token):
-
- known_token = False
- self.tag_push(token)
-
- if self.debug : print 'Processing: ', self.get_tagpath(0)
- cnt = self.tagpath_len()
- for j in xrange(cnt):
- tkn = self.get_tagpath(j)
- if tkn in self.token_tags :
- num_args = self.token_tags[tkn][0]
- argtype = self.token_tags[tkn][1]
- subtags = self.token_tags[tkn][2]
- splcase = self.token_tags[tkn][3]
- ntags = -1
- known_token = True
- break
-
- if known_token :
-
- # handle subtags if present
- subtagres = []
- if (splcase == 1):
- # this type of tag uses of escape marker 0x74 indicate subtag count
- if self.peek(1) == 0x74:
- skip = readEncodedNumber(self.fo)
- subtags = 1
- num_args = 0
-
- if (subtags == 1):
- ntags = readEncodedNumber(self.fo)
- if self.debug : print 'subtags: ' + token + ' has ' + str(ntags)
- for j in xrange(ntags):
- val = readEncodedNumber(self.fo)
- subtagres.append(self.procToken(self.dict.lookup(val)))
-
- # arguments can be scalars or vectors of text or numbers
- argres = []
- if num_args > 0 :
- firstarg = self.peek(1)
- if (firstarg in self.cmd_list) and (argtype != 'scalar_number') and (argtype != 'scalar_text'):
- # single argument is a variable length vector of data
- arg = readEncodedNumber(self.fo)
- argres = self.decodeCMD(arg,argtype)
- else :
- # num_arg scalar arguments
- for i in xrange(num_args):
- argres.append(self.formatArg(readEncodedNumber(self.fo), argtype))
-
- # build the return tag
- result = []
- tkn = self.get_tagpath(0)
- result.append(tkn)
- result.append(subtagres)
- result.append(argtype)
- result.append(argres)
- self.tag_pop()
- return result
-
- # all tokens that need to be processed should be in the hash
- # table if it may indicate a problem, either new token
- # or an out of sync condition
- else:
- result = []
- if (self.debug or self.first_unknown):
- print 'Unknown Token:', token
- self.first_unknown = False
- self.tag_pop()
- return result
-
-
- # special loop used to process code snippets
- # it is NEVER used to format arguments.
- # builds the snippetList
- def doLoop72(self, argtype):
- cnt = readEncodedNumber(self.fo)
- if self.debug :
- result = 'Set of '+ str(cnt) + ' xml snippets. The overall structure \n'
- result += 'of the document is indicated by snippet number sets at the\n'
- result += 'end of each snippet. \n'
- print result
- for i in xrange(cnt):
- if self.debug: print 'Snippet:',str(i)
- snippet = []
- snippet.append(i)
- val = readEncodedNumber(self.fo)
- snippet.append(self.procToken(self.dict.lookup(val)))
- self.snippetList.append(snippet)
- return
-
-
-
- # general loop code gracisouly submitted by "skindle" - thank you!
- def doLoop76Mode(self, argtype, cnt, mode):
- result = []
- adj = 0
- if mode & 1:
- adj = readEncodedNumber(self.fo)
- mode = mode >> 1
- x = []
- for i in xrange(cnt):
- x.append(readEncodedNumber(self.fo) - adj)
- for i in xrange(mode):
- for j in xrange(1, cnt):
- x[j] = x[j] + x[j - 1]
- for i in xrange(cnt):
- result.append(self.formatArg(x[i],argtype))
- return result
-
-
- # dispatches loop commands bytes with various modes
- # The 0x76 style loops are used to build vectors
-
- # This was all derived by trial and error and
- # new loop types may exist that are not handled here
- # since they did not appear in the test cases
-
- def decodeCMD(self, cmd, argtype):
- if (cmd == 0x76):
-
- # loop with cnt, and mode to control loop styles
- cnt = readEncodedNumber(self.fo)
- mode = readEncodedNumber(self.fo)
-
- if self.debug : print 'Loop for', cnt, 'with mode', mode, ': '
- return self.doLoop76Mode(argtype, cnt, mode)
-
- if self.dbug: print "Unknown command", cmd
- result = []
- return result
-
-
-
- # add full tag path to injected snippets
- def updateName(self, tag, prefix):
- name = tag[0]
- subtagList = tag[1]
- argtype = tag[2]
- argList = tag[3]
- nname = prefix + '.' + name
- nsubtaglist = []
- for j in subtagList:
- nsubtaglist.append(self.updateName(j,prefix))
- ntag = []
- ntag.append(nname)
- ntag.append(nsubtaglist)
- ntag.append(argtype)
- ntag.append(argList)
- return ntag
-
-
-
- # perform depth first injection of specified snippets into this one
- def injectSnippets(self, snippet):
- snipno, tag = snippet
- name = tag[0]
- subtagList = tag[1]
- argtype = tag[2]
- argList = tag[3]
- nsubtagList = []
- if len(argList) > 0 :
- for j in argList:
- asnip = self.snippetList[j]
- aso, atag = self.injectSnippets(asnip)
- atag = self.updateName(atag, name)
- nsubtagList.append(atag)
- argtype='number'
- argList=[]
- if len(nsubtagList) > 0 :
- subtagList.extend(nsubtagList)
- tag = []
- tag.append(name)
- tag.append(subtagList)
- tag.append(argtype)
- tag.append(argList)
- snippet = []
- snippet.append(snipno)
- snippet.append(tag)
- return snippet
-
-
-
- # format the tag for output
- def formatTag(self, node):
- name = node[0]
- subtagList = node[1]
- argtype = node[2]
- argList = node[3]
- fullpathname = name.split('.')
- nodename = fullpathname.pop()
- ilvl = len(fullpathname)
- indent = ' ' * (3 * ilvl)
- rlst = []
- rlst.append(indent + '<' + nodename + '>')
- if len(argList) > 0:
- alst = []
- for j in argList:
- if (argtype == 'text') or (argtype == 'scalar_text') :
- alst.append(j + '|')
- else :
- alst.append(str(j) + ',')
- argres = "".join(alst)
- argres = argres[0:-1]
- if argtype == 'snippets' :
- rlst.append('snippets:' + argres)
- else :
- rlst.append(argres)
- if len(subtagList) > 0 :
- rlst.append('\n')
- for j in subtagList:
- if len(j) > 0 :
- rlst.append(self.formatTag(j))
- rlst.append(indent + '' + nodename + '>\n')
- else:
- rlst.append('' + nodename + '>\n')
- return "".join(rlst)
-
-
- # flatten tag
- def flattenTag(self, node):
- name = node[0]
- subtagList = node[1]
- argtype = node[2]
- argList = node[3]
- rlst = []
- rlst.append(name)
- if (len(argList) > 0):
- alst = []
- for j in argList:
- if (argtype == 'text') or (argtype == 'scalar_text') :
- alst.append(j + '|')
- else :
- alst.append(str(j) + '|')
- argres = "".join(alst)
- argres = argres[0:-1]
- if argtype == 'snippets' :
- rlst.append('.snippets=' + argres)
- else :
- rlst.append('=' + argres)
- rlst.append('\n')
- for j in subtagList:
- if len(j) > 0 :
- rlst.append(self.flattenTag(j))
- return "".join(rlst)
-
-
- # reduce create xml output
- def formatDoc(self, flat_xml):
- rlst = []
- for j in self.doc :
- if len(j) > 0:
- if flat_xml:
- rlst.append(self.flattenTag(j))
- else:
- rlst.append(self.formatTag(j))
- result = "".join(rlst)
- if self.debug : print result
- return result
-
-
-
- # main loop - parse the page.dat files
- # to create structured document and snippets
-
- # FIXME: value at end of magic appears to be a subtags count
- # but for what? For now, inject an 'info" tag as it is in
- # every dictionary and seems close to what is meant
- # The alternative is to special case the last _ "0x5f" to mean something
-
- def process(self):
-
- # peek at the first bytes to see what type of file it is
- magic = self.fo.read(9)
- if (magic[0:1] == 'p') and (magic[2:9] == 'marker_'):
- first_token = 'info'
- elif (magic[0:1] == 'p') and (magic[2:9] == '__PAGE_'):
- skip = self.fo.read(2)
- first_token = 'info'
- elif (magic[0:1] == 'p') and (magic[2:8] == '_PAGE_'):
- first_token = 'info'
- elif (magic[0:1] == 'g') and (magic[2:9] == '__GLYPH'):
- skip = self.fo.read(3)
- first_token = 'info'
- else :
- # other0.dat file
- first_token = None
- self.fo.seek(-9,1)
-
-
- # main loop to read and build the document tree
- while True:
-
- if first_token != None :
- # use "inserted" first token 'info' for page and glyph files
- tag = self.procToken(first_token)
- if len(tag) > 0 :
- self.doc.append(tag)
- first_token = None
-
- v = self.getNext()
- if (v == None):
- break
-
- if (v == 0x72):
- self.doLoop72('number')
- elif (v > 0) and (v < self.dict.getSize()) :
- tag = self.procToken(self.dict.lookup(v))
- if len(tag) > 0 :
- self.doc.append(tag)
- else:
- if self.debug:
- print "Main Loop: Unknown value: %x" % v
- if (v == 0):
- if (self.peek(1) == 0x5f):
- skip = self.fo.read(1)
- first_token = 'info'
-
- # now do snippet injection
- if len(self.snippetList) > 0 :
- if self.debug : print 'Injecting Snippets:'
- snippet = self.injectSnippets(self.snippetList[0])
- snipno = snippet[0]
- tag_add = snippet[1]
- if self.debug : print self.formatTag(tag_add)
- if len(tag_add) > 0:
- self.doc.append(tag_add)
-
- # handle generation of xml output
- xmlpage = self.formatDoc(self.flat_xml)
-
- return xmlpage
-
-
-def fromData(dict, fname):
- flat_xml = True
- debug = False
- pp = PageParser(fname, dict, debug, flat_xml)
- xmlpage = pp.process()
- return xmlpage
-
-def getXML(dict, fname):
- flat_xml = False
- debug = False
- pp = PageParser(fname, dict, debug, flat_xml)
- xmlpage = pp.process()
- return xmlpage
-
-def usage():
- print 'Usage: '
- print ' convert2xml.py dict0000.dat infile.dat '
- print ' '
- print ' Options:'
- print ' -h print this usage help message '
- print ' -d turn on debug output to check for potential errors '
- print ' --flat-xml output the flattened xml page description only '
- print ' '
- print ' This program will attempt to convert a page*.dat file or '
- print ' glyphs*.dat file, using the dict0000.dat file, to its xml description. '
- print ' '
- print ' Use "cmbtc_dump.py" first to unencrypt, uncompress, and dump '
- print ' the *.dat files from a Topaz format e-book.'
-
-#
-# Main
-#
-
-def main(argv):
- dictFile = ""
- pageFile = ""
- debug = False
- flat_xml = False
- printOutput = False
- if len(argv) == 0:
- printOutput = True
- argv = sys.argv
-
- try:
- opts, args = getopt.getopt(argv[1:], "hd", ["flat-xml"])
-
- except getopt.GetoptError, err:
-
- # print help information and exit:
- print str(err) # will print something like "option -a not recognized"
- usage()
- sys.exit(2)
-
- if len(opts) == 0 and len(args) == 0 :
- usage()
- sys.exit(2)
-
- for o, a in opts:
- if o =="-d":
- debug=True
- if o =="-h":
- usage()
- sys.exit(0)
- if o =="--flat-xml":
- flat_xml = True
-
- dictFile, pageFile = args[0], args[1]
-
- # read in the string table dictionary
- dict = Dictionary(dictFile)
- # dict.dumpDict()
-
- # create a page parser
- pp = PageParser(pageFile, dict, debug, flat_xml)
-
- xmlpage = pp.process()
-
- if printOutput:
- print xmlpage
- return 0
-
- return xmlpage
-
-if __name__ == '__main__':
- sys.exit(main(''))
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/encodebase64.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/encodebase64.py
deleted file mode 100644
index 6bb8c37..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/encodebase64.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/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.
-
-# Revision history:
-# 1 - Initial release. To allow Applescript to do base64 encoding
-
-"""
-Provide base64 encoding.
-"""
-
-from __future__ import with_statement
-
-__license__ = 'GPL v3'
-
-import sys
-import os
-import base64
-
-def usage(progname):
- print "Applies base64 encoding to the supplied file, sending to standard output"
- print "Usage:"
- print " %s " % progname
-
-def cli_main(argv=sys.argv):
- progname = os.path.basename(argv[0])
-
- if len(argv)<2:
- usage(progname)
- sys.exit(2)
-
- keypath = argv[1]
- with open(keypath, 'rb') as f:
- keyder = f.read()
- print keyder.encode('base64')
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(cli_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/epubtest.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/epubtest.py
deleted file mode 100644
index 11f1427..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/epubtest.py
+++ /dev/null
@@ -1,208 +0,0 @@
-#!/usr/bin/python
-#
-# This is a python script. You need a Python interpreter to run it.
-# For example, ActiveState Python, which exists for windows.
-#
-# Changelog drmcheck
-# 1.00 - Initial version, with code from various other scripts
-# 1.01 - Moved authorship announcement to usage section.
-#
-# Changelog epubtest
-# 1.00 - Cut to epubtest.py, testing ePub files only by Apprentice Alf
-# 1.01 - Added routine for use by Windows DeDRM
-#
-# Written in 2011 by Paul Durrant
-# Released with unlicense. See http://unlicense.org/
-#
-#############################################################################
-#
-# This is free and unencumbered software released into the public domain.
-#
-# Anyone is free to copy, modify, publish, use, compile, sell, or
-# distribute this software, either in source code form or as a compiled
-# binary, for any purpose, commercial or non-commercial, and by any
-# means.
-#
-# In jurisdictions that recognize copyright laws, the author or authors
-# of this software dedicate any and all copyright interest in the
-# software to the public domain. We make this dedication for the benefit
-# of the public at large and to the detriment of our heirs and
-# successors. We intend this dedication to be an overt act of
-# relinquishment in perpetuity of all present and future rights to this
-# software under copyright law.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-# OTHER DEALINGS IN THE SOFTWARE.
-#
-#############################################################################
-#
-# It's still polite to give attribution if you do reuse this code.
-#
-
-from __future__ import with_statement
-
-__version__ = '1.01'
-
-import sys, struct, os
-import zlib
-import zipfile
-import xml.etree.ElementTree as etree
-
-NSMAP = {'adept': 'http://ns.adobe.com/adept',
- 'enc': 'http://www.w3.org/2001/04/xmlenc#'}
-
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
- def __init__(self, stream):
- self.stream = stream
- self.encoding = stream.encoding
- if self.encoding == None:
- self.encoding = "utf-8"
- def write(self, data):
- if isinstance(data,unicode):
- data = data.encode(self.encoding,"replace")
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-try:
- from calibre.constants import iswindows, isosx
-except:
- iswindows = sys.platform.startswith('win')
- isosx = sys.platform.startswith('darwin')
-
-def unicode_argv():
- if iswindows:
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
- # as a list of Unicode strings and encode them as utf-8
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- # if we don't have any arguments at all, just pass back script name
- # this should never happen
- return [u"epubtest.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-_FILENAME_LEN_OFFSET = 26
-_EXTRA_LEN_OFFSET = 28
-_FILENAME_OFFSET = 30
-_MAX_SIZE = 64 * 1024
-
-
-def uncompress(cmpdata):
- dc = zlib.decompressobj(-15)
- data = ''
- while len(cmpdata) > 0:
- if len(cmpdata) > _MAX_SIZE :
- newdata = cmpdata[0:_MAX_SIZE]
- cmpdata = cmpdata[_MAX_SIZE:]
- else:
- newdata = cmpdata
- cmpdata = ''
- newdata = dc.decompress(newdata)
- unprocessed = dc.unconsumed_tail
- if len(unprocessed) == 0:
- newdata += dc.flush()
- data += newdata
- cmpdata += unprocessed
- unprocessed = ''
- return data
-
-def getfiledata(file, zi):
- # get file name length and exta data length to find start of file data
- local_header_offset = zi.header_offset
-
- file.seek(local_header_offset + _FILENAME_LEN_OFFSET)
- leninfo = file.read(2)
- local_name_length, = struct.unpack(' 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- # if we don't have any arguments at all, just pass back script name
- # this should never happen
- return [u"mobidedrm.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-Des = None
-if iswindows:
- # first try with pycrypto
- if inCalibre:
- from calibre_plugins.dedrm import pycrypto_des
- else:
- import pycrypto_des
- Des = pycrypto_des.load_pycrypto()
- if Des == None:
- # they try with openssl
- if inCalibre:
- from calibre_plugins.dedrm import openssl_des
- else:
- import openssl_des
- Des = openssl_des.load_libcrypto()
-else:
- # first try with openssl
- if inCalibre:
- from calibre_plugins.dedrm import openssl_des
- else:
- import openssl_des
- Des = openssl_des.load_libcrypto()
- if Des == None:
- # then try with pycrypto
- if inCalibre:
- from calibre_plugins.dedrm import pycrypto_des
- else:
- import pycrypto_des
- Des = pycrypto_des.load_pycrypto()
-
-# if that did not work then use pure python implementation
-# of DES and try to speed it up with Psycho
-if Des == None:
- if inCalibre:
- from calibre_plugins.dedrm import python_des
- else:
- import python_des
- Des = python_des.Des
- # Import Psyco if available
- try:
- # http://psyco.sourceforge.net
- import psyco
- psyco.full()
- except ImportError:
- pass
-
-try:
- from hashlib import sha1
-except ImportError:
- # older Python release
- import sha
- sha1 = lambda s: sha.new(s)
-
-import cgi
-import logging
-
-logging.basicConfig()
-#logging.basicConfig(level=logging.DEBUG)
-
-
-class Sectionizer(object):
- bkType = "Book"
-
- def __init__(self, filename, ident):
- self.contents = file(filename, 'rb').read()
- self.header = self.contents[0:72]
- self.num_sections, = struct.unpack('>H', self.contents[76:78])
- # Dictionary or normal content (TODO: Not hard-coded)
- if self.header[0x3C:0x3C+8] != ident:
- if self.header[0x3C:0x3C+8] == "PDctPPrs":
- self.bkType = "Dict"
- else:
- raise ValueError('Invalid file format')
- self.sections = []
- for i in xrange(self.num_sections):
- offset, a1,a2,a3,a4 = struct.unpack('>LBBBB', self.contents[78+i*8:78+i*8+8])
- flags, val = a1, a2<<16|a3<<8|a4
- self.sections.append( (offset, flags, val) )
- def loadSection(self, section):
- if section + 1 == self.num_sections:
- end_off = len(self.contents)
- else:
- end_off = self.sections[section + 1][0]
- off = self.sections[section][0]
- return self.contents[off:end_off]
-
-# cleanup unicode filenames
-# borrowed from calibre from calibre/src/calibre/__init__.py
-# added in removal of control (<32) chars
-# and removal of . at start and end
-# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
-def sanitizeFileName(name):
- # substitute filename unfriendly characters
- name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'")
- # delete control characters
- name = u"".join(char for char in name if ord(char)>=32)
- # white space to single space, delete leading and trailing while space
- name = re.sub(ur"\s", u" ", name).strip()
- # remove leading dots
- while len(name)>0 and name[0] == u".":
- name = name[1:]
- # remove trailing dots (Windows doesn't like them)
- if name.endswith(u'.'):
- name = name[:-1]
- return name
-
-def fixKey(key):
- def fixByte(b):
- return b ^ ((b ^ (b<<1) ^ (b<<2) ^ (b<<3) ^ (b<<4) ^ (b<<5) ^ (b<<6) ^ (b<<7) ^ 0x80) & 0x80)
- return "".join([chr(fixByte(ord(a))) for a in key])
-
-def deXOR(text, sp, table):
- r=''
- j = sp
- for i in xrange(len(text)):
- r += chr(ord(table[j]) ^ ord(text[i]))
- j = j + 1
- if j == len(table):
- j = 0
- return r
-
-class EreaderProcessor(object):
- def __init__(self, sect, user_key):
- self.section_reader = sect.loadSection
- data = self.section_reader(0)
- version, = struct.unpack('>H', data[0:2])
- self.version = version
- logging.info('eReader file format version %s', version)
- if version != 272 and version != 260 and version != 259:
- raise ValueError('incorrect eReader version %d (error 1)' % version)
- data = self.section_reader(1)
- self.data = data
- des = Des(fixKey(data[0:8]))
- cookie_shuf, cookie_size = struct.unpack('>LL', des.decrypt(data[-8:]))
- if cookie_shuf < 3 or cookie_shuf > 0x14 or cookie_size < 0xf0 or cookie_size > 0x200:
- raise ValueError('incorrect eReader version (error 2)')
- input = des.decrypt(data[-cookie_size:])
- def unshuff(data, shuf):
- r = [''] * len(data)
- j = 0
- for i in xrange(len(data)):
- j = (j + shuf) % len(data)
- r[j] = data[i]
- assert len("".join(r)) == len(data)
- return "".join(r)
- r = unshuff(input[0:-8], cookie_shuf)
-
- drm_sub_version = struct.unpack('>H', r[0:2])[0]
- self.num_text_pages = struct.unpack('>H', r[2:4])[0] - 1
- self.num_image_pages = struct.unpack('>H', r[26:26+2])[0]
- self.first_image_page = struct.unpack('>H', r[24:24+2])[0]
- # Default values
- self.num_footnote_pages = 0
- self.num_sidebar_pages = 0
- self.first_footnote_page = -1
- self.first_sidebar_page = -1
- if self.version == 272:
- self.num_footnote_pages = struct.unpack('>H', r[46:46+2])[0]
- self.first_footnote_page = struct.unpack('>H', r[44:44+2])[0]
- if (sect.bkType == "Book"):
- self.num_sidebar_pages = struct.unpack('>H', r[38:38+2])[0]
- self.first_sidebar_page = struct.unpack('>H', r[36:36+2])[0]
- # self.num_bookinfo_pages = struct.unpack('>H', r[34:34+2])[0]
- # self.first_bookinfo_page = struct.unpack('>H', r[32:32+2])[0]
- # self.num_chapter_pages = struct.unpack('>H', r[22:22+2])[0]
- # self.first_chapter_page = struct.unpack('>H', r[20:20+2])[0]
- # self.num_link_pages = struct.unpack('>H', r[30:30+2])[0]
- # self.first_link_page = struct.unpack('>H', r[28:28+2])[0]
- # self.num_xtextsize_pages = struct.unpack('>H', r[54:54+2])[0]
- # self.first_xtextsize_page = struct.unpack('>H', r[52:52+2])[0]
-
- # **before** data record 1 was decrypted and unshuffled, it contained data
- # to create an XOR table and which is used to fix footnote record 0, link records, chapter records, etc
- self.xortable_offset = struct.unpack('>H', r[40:40+2])[0]
- self.xortable_size = struct.unpack('>H', r[42:42+2])[0]
- self.xortable = self.data[self.xortable_offset:self.xortable_offset + self.xortable_size]
- else:
- # Nothing needs to be done
- pass
- # self.num_bookinfo_pages = 0
- # self.num_chapter_pages = 0
- # self.num_link_pages = 0
- # self.num_xtextsize_pages = 0
- # self.first_bookinfo_page = -1
- # self.first_chapter_page = -1
- # self.first_link_page = -1
- # self.first_xtextsize_page = -1
-
- logging.debug('self.num_text_pages %d', self.num_text_pages)
- logging.debug('self.num_footnote_pages %d, self.first_footnote_page %d', self.num_footnote_pages , self.first_footnote_page)
- logging.debug('self.num_sidebar_pages %d, self.first_sidebar_page %d', self.num_sidebar_pages , self.first_sidebar_page)
- self.flags = struct.unpack('>L', r[4:8])[0]
- reqd_flags = (1<<9) | (1<<7) | (1<<10)
- if (self.flags & reqd_flags) != reqd_flags:
- print "Flags: 0x%X" % self.flags
- raise ValueError('incompatible eReader file')
- des = Des(fixKey(user_key))
- if version == 259:
- if drm_sub_version != 7:
- raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version)
- encrypted_key_sha = r[44:44+20]
- encrypted_key = r[64:64+8]
- elif version == 260:
- if drm_sub_version != 13 and drm_sub_version != 11:
- raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version)
- if drm_sub_version == 13:
- encrypted_key = r[44:44+8]
- encrypted_key_sha = r[52:52+20]
- else:
- encrypted_key = r[64:64+8]
- encrypted_key_sha = r[44:44+20]
- elif version == 272:
- encrypted_key = r[172:172+8]
- encrypted_key_sha = r[56:56+20]
- self.content_key = des.decrypt(encrypted_key)
- if sha1(self.content_key).digest() != encrypted_key_sha:
- raise ValueError('Incorrect Name and/or Credit Card')
-
- def getNumImages(self):
- return self.num_image_pages
-
- def getImage(self, i):
- sect = self.section_reader(self.first_image_page + i)
- name = sect[4:4+32].strip('\0')
- data = sect[62:]
- return sanitizeFileName(unicode(name,'windows-1252')), data
-
-
- # def getChapterNamePMLOffsetData(self):
- # cv = ''
- # if self.num_chapter_pages > 0:
- # for i in xrange(self.num_chapter_pages):
- # chaps = self.section_reader(self.first_chapter_page + i)
- # j = i % self.xortable_size
- # offname = deXOR(chaps, j, self.xortable)
- # offset = struct.unpack('>L', offname[0:4])[0]
- # name = offname[4:].strip('\0')
- # cv += '%d|%s\n' % (offset, name)
- # return cv
-
- # def getLinkNamePMLOffsetData(self):
- # lv = ''
- # if self.num_link_pages > 0:
- # for i in xrange(self.num_link_pages):
- # links = self.section_reader(self.first_link_page + i)
- # j = i % self.xortable_size
- # offname = deXOR(links, j, self.xortable)
- # offset = struct.unpack('>L', offname[0:4])[0]
- # name = offname[4:].strip('\0')
- # lv += '%d|%s\n' % (offset, name)
- # return lv
-
- # def getExpandedTextSizesData(self):
- # ts = ''
- # if self.num_xtextsize_pages > 0:
- # tsize = deXOR(self.section_reader(self.first_xtextsize_page), 0, self.xortable)
- # for i in xrange(self.num_text_pages):
- # xsize = struct.unpack('>H', tsize[0:2])[0]
- # ts += "%d\n" % xsize
- # tsize = tsize[2:]
- # return ts
-
- # def getBookInfo(self):
- # bkinfo = ''
- # if self.num_bookinfo_pages > 0:
- # info = self.section_reader(self.first_bookinfo_page)
- # bkinfo = deXOR(info, 0, self.xortable)
- # bkinfo = bkinfo.replace('\0','|')
- # bkinfo += '\n'
- # return bkinfo
-
- def getText(self):
- des = Des(fixKey(self.content_key))
- r = ''
- for i in xrange(self.num_text_pages):
- logging.debug('get page %d', i)
- r += zlib.decompress(des.decrypt(self.section_reader(1 + i)))
-
- # now handle footnotes pages
- if self.num_footnote_pages > 0:
- r += '\n'
- # the record 0 of the footnote section must pass through the Xor Table to make it useful
- sect = self.section_reader(self.first_footnote_page)
- fnote_ids = deXOR(sect, 0, self.xortable)
- # the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated
- des = Des(fixKey(self.content_key))
- for i in xrange(1,self.num_footnote_pages):
- logging.debug('get footnotepage %d', i)
- id_len = ord(fnote_ids[2])
- id = fnote_ids[3:3+id_len]
- fmarker = '\n' % id
- fmarker += zlib.decompress(des.decrypt(self.section_reader(self.first_footnote_page + i)))
- fmarker += '\n \n'
- r += fmarker
- fnote_ids = fnote_ids[id_len+4:]
-
- # TODO: Handle dictionary index (?) pages - which are also marked as
- # sidebar_pages (?). For now dictionary sidebars are ignored
- # For dictionaries - record 0 is null terminated strings, followed by
- # blocks of around 62000 bytes and a final block. Not sure of the
- # encoding
-
- # now handle sidebar pages
- if self.num_sidebar_pages > 0:
- r += '\n'
- # the record 0 of the sidebar section must pass through the Xor Table to make it useful
- sect = self.section_reader(self.first_sidebar_page)
- sbar_ids = deXOR(sect, 0, self.xortable)
- # the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated
- des = Des(fixKey(self.content_key))
- for i in xrange(1,self.num_sidebar_pages):
- id_len = ord(sbar_ids[2])
- id = sbar_ids[3:3+id_len]
- smarker = '\n' % id
- smarker += zlib.decompress(des.decrypt(self.section_reader(self.first_sidebar_page + i)))
- smarker += '\n \n'
- r += smarker
- sbar_ids = sbar_ids[id_len+4:]
-
- return r
-
-def cleanPML(pml):
- # Convert special characters to proper PML code. High ASCII start at (\x80, \a128) and go up to (\xff, \a255)
- pml2 = pml
- for k in xrange(128,256):
- badChar = chr(k)
- pml2 = pml2.replace(badChar, '\\a%03d' % k)
- return pml2
-
-def decryptBook(infile, outpath, make_pmlz, user_key):
- bookname = os.path.splitext(os.path.basename(infile))[0]
- if make_pmlz:
- # outpath is actually pmlz name
- pmlzname = outpath
- outdir = tempfile.mkdtemp()
- imagedirpath = os.path.join(outdir,u"images")
- else:
- pmlzname = None
- outdir = outpath
- imagedirpath = os.path.join(outdir,bookname + u"_img")
-
- try:
- if not os.path.exists(outdir):
- os.makedirs(outdir)
- print u"Decoding File"
- sect = Sectionizer(infile, 'PNRdPPrs')
- er = EreaderProcessor(sect, user_key)
-
- if er.getNumImages() > 0:
- print u"Extracting images"
- if not os.path.exists(imagedirpath):
- os.makedirs(imagedirpath)
- for i in xrange(er.getNumImages()):
- name, contents = er.getImage(i)
- file(os.path.join(imagedirpath, name), 'wb').write(contents)
-
- print u"Extracting pml"
- pml_string = er.getText()
- pmlfilename = bookname + ".pml"
- file(os.path.join(outdir, pmlfilename),'wb').write(cleanPML(pml_string))
- if pmlzname is not None:
- import zipfile
- import shutil
- print u"Creating PMLZ file {0}".format(os.path.basename(pmlzname))
- myZipFile = zipfile.ZipFile(pmlzname,'w',zipfile.ZIP_STORED, False)
- list = os.listdir(outdir)
- for filename in list:
- localname = filename
- filePath = os.path.join(outdir,filename)
- if os.path.isfile(filePath):
- myZipFile.write(filePath, localname)
- elif os.path.isdir(filePath):
- imageList = os.listdir(filePath)
- localimgdir = os.path.basename(filePath)
- for image in imageList:
- localname = os.path.join(localimgdir,image)
- imagePath = os.path.join(filePath,image)
- if os.path.isfile(imagePath):
- myZipFile.write(imagePath, localname)
- myZipFile.close()
- # remove temporary directory
- shutil.rmtree(outdir, True)
- print u"Output is {0}".format(pmlzname)
- else :
- print u"Output is in {0}".format(outdir)
- print "done"
- except ValueError, e:
- print u"Error: {0}".format(e)
- traceback.print_exc()
- return 1
- return 0
-
-
-def usage():
- print u"Converts DRMed eReader books to PML Source"
- print u"Usage:"
- print u" erdr2pml [options] infile.pdb [outpath] \"your name\" credit_card_number"
- print u" "
- print u"Options: "
- print u" -h prints this message"
- print u" -p create PMLZ instead of source folder"
- print u" --make-pmlz create PMLZ instead of source folder"
- print u" "
- print u"Note:"
- print u" if outpath is ommitted, creates source in 'infile_Source' folder"
- print u" if outpath is ommitted and pmlz option, creates PMLZ 'infile.pmlz'"
- print u" if source folder created, images are in infile_img folder"
- print u" if pmlz file created, images are in images folder"
- print u" It's enough to enter the last 8 digits of the credit card number"
- return
-
-def getuser_key(name,cc):
- newname = "".join(c for c in name.lower() if c >= 'a' and c <= 'z' or c >= '0' and c <= '9')
- cc = cc.replace(" ","")
- return struct.pack('>LL', binascii.crc32(newname) & 0xffffffff,binascii.crc32(cc[-8:])& 0xffffffff)
-
-def cli_main():
- print u"eRdr2Pml v{0}. Copyright © 2009–2012 The Dark Reverser et al.".format(__version__)
-
- argv=unicode_argv()
- try:
- opts, args = getopt.getopt(argv[1:], "hp", ["make-pmlz"])
- except getopt.GetoptError, err:
- print err.args[0]
- usage()
- return 1
- make_pmlz = False
- for o, a in opts:
- if o == "-h":
- usage()
- return 0
- elif o == "-p":
- make_pmlz = True
- elif o == "--make-pmlz":
- make_pmlz = True
-
- if len(args)!=3 and len(args)!=4:
- usage()
- return 1
-
- if len(args)==3:
- infile, name, cc = args
- if make_pmlz:
- outpath = os.path.splitext(infile)[0] + u".pmlz"
- else:
- outpath = os.path.splitext(infile)[0] + u"_Source"
- elif len(args)==4:
- infile, outpath, name, cc = args
-
- print getuser_key(name,cc).encode('hex')
-
- return decryptBook(infile, outpath, make_pmlz, getuser_key(name,cc))
-
-
-if __name__ == "__main__":
- sys.stdout=SafeUnbuffered(sys.stdout)
- sys.stderr=SafeUnbuffered(sys.stderr)
- sys.exit(cli_main())
-
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2html.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2html.py
deleted file mode 100644
index 991591b..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2html.py
+++ /dev/null
@@ -1,801 +0,0 @@
-#! /usr/bin/python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-# For use with Topaz Scripts Version 2.6
-
-import sys
-import csv
-import os
-import math
-import getopt
-from struct import pack
-from struct import unpack
-
-
-class DocParser(object):
- def __init__(self, flatxml, classlst, fileid, bookDir, gdict, fixedimage):
- self.id = os.path.basename(fileid).replace('.dat','')
- self.svgcount = 0
- self.docList = flatxml.split('\n')
- self.docSize = len(self.docList)
- self.classList = {}
- self.bookDir = bookDir
- self.gdict = gdict
- tmpList = classlst.split('\n')
- for pclass in tmpList:
- if pclass != '':
- # remove the leading period from the css name
- cname = pclass[1:]
- self.classList[cname] = True
- self.fixedimage = fixedimage
- self.ocrtext = []
- self.link_id = []
- self.link_title = []
- self.link_page = []
- self.link_href = []
- self.link_type = []
- self.dehyphen_rootid = []
- self.paracont_stemid = []
- self.parastems_stemid = []
-
-
- def getGlyph(self, gid):
- result = ''
- id='id="gl%d"' % gid
- return self.gdict.lookup(id)
-
- def glyphs_to_image(self, glyphList):
-
- def extract(path, key):
- b = path.find(key) + len(key)
- e = path.find(' ',b)
- return int(path[b:e])
-
- svgDir = os.path.join(self.bookDir,'svg')
-
- imgDir = os.path.join(self.bookDir,'img')
- imgname = self.id + '_%04d.svg' % self.svgcount
- imgfile = os.path.join(imgDir,imgname)
-
- # get glyph information
- gxList = self.getData('info.glyph.x',0,-1)
- gyList = self.getData('info.glyph.y',0,-1)
- gidList = self.getData('info.glyph.glyphID',0,-1)
-
- gids = []
- maxws = []
- maxhs = []
- xs = []
- ys = []
- gdefs = []
-
- # get path defintions, positions, dimensions for each glyph
- # that makes up the image, and find min x and min y to reposition origin
- minx = -1
- miny = -1
- for j in glyphList:
- gid = gidList[j]
- gids.append(gid)
-
- xs.append(gxList[j])
- if minx == -1: minx = gxList[j]
- else : minx = min(minx, gxList[j])
-
- ys.append(gyList[j])
- if miny == -1: miny = gyList[j]
- else : miny = min(miny, gyList[j])
-
- path = self.getGlyph(gid)
- gdefs.append(path)
-
- maxws.append(extract(path,'width='))
- maxhs.append(extract(path,'height='))
-
-
- # change the origin to minx, miny and calc max height and width
- maxw = maxws[0] + xs[0] - minx
- maxh = maxhs[0] + ys[0] - miny
- for j in xrange(0, len(xs)):
- xs[j] = xs[j] - minx
- ys[j] = ys[j] - miny
- maxw = max( maxw, (maxws[j] + xs[j]) )
- maxh = max( maxh, (maxhs[j] + ys[j]) )
-
- # open the image file for output
- ifile = open(imgfile,'w')
- ifile.write('\n')
- ifile.write('\n')
- ifile.write('\n' % (math.floor(maxw/10), math.floor(maxh/10), maxw, maxh))
- ifile.write('\n')
- for j in xrange(0,len(gdefs)):
- ifile.write(gdefs[j])
- ifile.write(' \n')
- for j in xrange(0,len(gids)):
- ifile.write(' \n' % (gids[j], xs[j], ys[j]))
- ifile.write(' ')
- ifile.close()
-
- return 0
-
-
-
- # return tag at line pos in document
- def lineinDoc(self, pos) :
- if (pos >= 0) and (pos < self.docSize) :
- item = self.docList[pos]
- if item.find('=') >= 0:
- (name, argres) = item.split('=',1)
- else :
- name = item
- argres = ''
- return name, argres
-
-
- # find tag in doc if within pos to end inclusive
- def findinDoc(self, tagpath, pos, end) :
- result = None
- if end == -1 :
- end = self.docSize
- else:
- end = min(self.docSize, end)
- foundat = -1
- for j in xrange(pos, end):
- item = self.docList[j]
- if item.find('=') >= 0:
- (name, argres) = item.split('=',1)
- else :
- name = item
- argres = ''
- if name.endswith(tagpath) :
- result = argres
- foundat = j
- break
- return foundat, result
-
-
- # return list of start positions for the tagpath
- def posinDoc(self, tagpath):
- startpos = []
- pos = 0
- res = ""
- while res != None :
- (foundpos, res) = self.findinDoc(tagpath, pos, -1)
- if res != None :
- startpos.append(foundpos)
- pos = foundpos + 1
- return startpos
-
-
- # returns a vector of integers for the tagpath
- def getData(self, tagpath, pos, end):
- argres=[]
- (foundat, argt) = self.findinDoc(tagpath, pos, end)
- if (argt != None) and (len(argt) > 0) :
- argList = argt.split('|')
- argres = [ int(strval) for strval in argList]
- return argres
-
-
- # get the class
- def getClass(self, pclass):
- nclass = pclass
-
- # class names are an issue given topaz may start them with numerals (not allowed),
- # use a mix of cases (which cause some browsers problems), and actually
- # attach numbers after "_reclustered*" to the end to deal classeses that inherit
- # from a base class (but then not actually provide all of these _reclustereed
- # classes in the stylesheet!
-
- # so we clean this up by lowercasing, prepend 'cl-', and getting any baseclass
- # that exists in the stylesheet first, and then adding this specific class
- # after
-
- # also some class names have spaces in them so need to convert to dashes
- if nclass != None :
- nclass = nclass.replace(' ','-')
- classres = ''
- nclass = nclass.lower()
- nclass = 'cl-' + nclass
- baseclass = ''
- # graphic is the base class for captions
- if nclass.find('cl-cap-') >=0 :
- classres = 'graphic' + ' '
- else :
- # strip to find baseclass
- p = nclass.find('_')
- if p > 0 :
- baseclass = nclass[0:p]
- if baseclass in self.classList:
- classres += baseclass + ' '
- classres += nclass
- nclass = classres
- return nclass
-
-
- # develop a sorted description of the starting positions of
- # groups and regions on the page, as well as the page type
- def PageDescription(self):
-
- def compare(x, y):
- (xtype, xval) = x
- (ytype, yval) = y
- if xval > yval:
- return 1
- if xval == yval:
- return 0
- return -1
-
- result = []
- (pos, pagetype) = self.findinDoc('page.type',0,-1)
-
- groupList = self.posinDoc('page.group')
- groupregionList = self.posinDoc('page.group.region')
- pageregionList = self.posinDoc('page.region')
- # integrate into one list
- for j in groupList:
- result.append(('grpbeg',j))
- for j in groupregionList:
- result.append(('gregion',j))
- for j in pageregionList:
- result.append(('pregion',j))
- result.sort(compare)
-
- # insert group end and page end indicators
- inGroup = False
- j = 0
- while True:
- if j == len(result): break
- rtype = result[j][0]
- rval = result[j][1]
- if not inGroup and (rtype == 'grpbeg') :
- inGroup = True
- j = j + 1
- elif inGroup and (rtype in ('grpbeg', 'pregion')):
- result.insert(j,('grpend',rval))
- inGroup = False
- else:
- j = j + 1
- if inGroup:
- result.append(('grpend',-1))
- result.append(('pageend', -1))
- return pagetype, result
-
-
-
- # build a description of the paragraph
- def getParaDescription(self, start, end, regtype):
-
- result = []
-
- # paragraph
- (pos, pclass) = self.findinDoc('paragraph.class',start,end)
-
- pclass = self.getClass(pclass)
-
- # if paragraph uses extratokens (extra glyphs) then make it fixed
- (pos, extraglyphs) = self.findinDoc('paragraph.extratokens',start,end)
-
- # build up a description of the paragraph in result and return it
- # first check for the basic - all words paragraph
- (pos, sfirst) = self.findinDoc('paragraph.firstWord',start,end)
- (pos, slast) = self.findinDoc('paragraph.lastWord',start,end)
- if (sfirst != None) and (slast != None) :
- first = int(sfirst)
- last = int(slast)
-
- makeImage = (regtype == 'vertical') or (regtype == 'table')
- makeImage = makeImage or (extraglyphs != None)
- if self.fixedimage:
- makeImage = makeImage or (regtype == 'fixed')
-
- if (pclass != None):
- makeImage = makeImage or (pclass.find('.inverted') >= 0)
- if self.fixedimage :
- makeImage = makeImage or (pclass.find('cl-f-') >= 0)
-
- # before creating an image make sure glyph info exists
- gidList = self.getData('info.glyph.glyphID',0,-1)
-
- makeImage = makeImage & (len(gidList) > 0)
-
- if not makeImage :
- # standard all word paragraph
- for wordnum in xrange(first, last):
- result.append(('ocr', wordnum))
- return pclass, result
-
- # convert paragraph to svg image
- # translate first and last word into first and last glyphs
- # and generate inline image and include it
- glyphList = []
- firstglyphList = self.getData('word.firstGlyph',0,-1)
- gidList = self.getData('info.glyph.glyphID',0,-1)
- firstGlyph = firstglyphList[first]
- if last < len(firstglyphList):
- lastGlyph = firstglyphList[last]
- else :
- lastGlyph = len(gidList)
-
- # handle case of white sapce paragraphs with no actual glyphs in them
- # by reverting to text based paragraph
- if firstGlyph >= lastGlyph:
- # revert to standard text based paragraph
- for wordnum in xrange(first, last):
- result.append(('ocr', wordnum))
- return pclass, result
-
- for glyphnum in xrange(firstGlyph, lastGlyph):
- glyphList.append(glyphnum)
- # include any extratokens if they exist
- (pos, sfg) = self.findinDoc('extratokens.firstGlyph',start,end)
- (pos, slg) = self.findinDoc('extratokens.lastGlyph',start,end)
- if (sfg != None) and (slg != None):
- for glyphnum in xrange(int(sfg), int(slg)):
- glyphList.append(glyphnum)
- num = self.svgcount
- self.glyphs_to_image(glyphList)
- self.svgcount += 1
- result.append(('svg', num))
- return pclass, result
-
- # this type of paragraph may be made up of multiple spans, inline
- # word monograms (images), and words with semantic meaning,
- # plus glyphs used to form starting letter of first word
-
- # need to parse this type line by line
- line = start + 1
- word_class = ''
-
- # if end is -1 then we must search to end of document
- if end == -1 :
- end = self.docSize
-
- # seems some xml has last* coming before first* so we have to
- # handle any order
- sp_first = -1
- sp_last = -1
-
- gl_first = -1
- gl_last = -1
-
- ws_first = -1
- ws_last = -1
-
- word_class = ''
-
- word_semantic_type = ''
-
- while (line < end) :
-
- (name, argres) = self.lineinDoc(line)
-
- if name.endswith('span.firstWord') :
- sp_first = int(argres)
-
- elif name.endswith('span.lastWord') :
- sp_last = int(argres)
-
- elif name.endswith('word.firstGlyph') :
- gl_first = int(argres)
-
- elif name.endswith('word.lastGlyph') :
- gl_last = int(argres)
-
- elif name.endswith('word_semantic.firstWord'):
- ws_first = int(argres)
-
- elif name.endswith('word_semantic.lastWord'):
- ws_last = int(argres)
-
- elif name.endswith('word.class'):
- # we only handle spaceafter word class
- try:
- (cname, space) = argres.split('-',1)
- if space == '' : space = '0'
- if (cname == 'spaceafter') and (int(space) > 0) :
- word_class = 'sa'
- except:
- pass
-
- elif name.endswith('word.img.src'):
- result.append(('img' + word_class, int(argres)))
- word_class = ''
-
- elif name.endswith('region.img.src'):
- result.append(('img' + word_class, int(argres)))
-
- if (sp_first != -1) and (sp_last != -1):
- for wordnum in xrange(sp_first, sp_last):
- result.append(('ocr', wordnum))
- sp_first = -1
- sp_last = -1
-
- if (gl_first != -1) and (gl_last != -1):
- glyphList = []
- for glyphnum in xrange(gl_first, gl_last):
- glyphList.append(glyphnum)
- num = self.svgcount
- self.glyphs_to_image(glyphList)
- self.svgcount += 1
- result.append(('svg', num))
- gl_first = -1
- gl_last = -1
-
- if (ws_first != -1) and (ws_last != -1):
- for wordnum in xrange(ws_first, ws_last):
- result.append(('ocr', wordnum))
- ws_first = -1
- ws_last = -1
-
- line += 1
-
- return pclass, result
-
-
- def buildParagraph(self, pclass, pdesc, type, regtype) :
- parares = ''
- sep =''
-
- classres = ''
- if pclass :
- classres = ' class="' + pclass + '"'
-
- br_lb = (regtype == 'fixed') or (regtype == 'chapterheading') or (regtype == 'vertical')
-
- handle_links = len(self.link_id) > 0
-
- if (type == 'full') or (type == 'begin') :
- parares += ''
-
- if (type == 'end'):
- parares += ' '
-
- lstart = len(parares)
-
- cnt = len(pdesc)
-
- for j in xrange( 0, cnt) :
-
- (wtype, num) = pdesc[j]
-
- if wtype == 'ocr' :
- try:
- word = self.ocrtext[num]
- except:
- word = ""
-
- sep = ' '
-
- if handle_links:
- link = self.link_id[num]
- if (link > 0):
- linktype = self.link_type[link-1]
- title = self.link_title[link-1]
- if (title == "") or (parares.rfind(title) < 0):
- title=parares[lstart:]
- if linktype == 'external' :
- linkhref = self.link_href[link-1]
- linkhtml = '' % linkhref
- else :
- if len(self.link_page) >= link :
- ptarget = self.link_page[link-1] - 1
- linkhtml = ' ' % ptarget
- else :
- # just link to the current page
- linkhtml = ' '
- linkhtml += title + ' '
- pos = parares.rfind(title)
- if pos >= 0:
- parares = parares[0:pos] + linkhtml + parares[pos+len(title):]
- else :
- parares += linkhtml
- lstart = len(parares)
- if word == '_link_' : word = ''
- elif (link < 0) :
- if word == '_link_' : word = ''
-
- if word == '_lb_':
- if ((num-1) in self.dehyphen_rootid ) or handle_links:
- word = ''
- sep = ''
- elif br_lb :
- word = ' \n'
- sep = ''
- else :
- word = '\n'
- sep = ''
-
- if num in self.dehyphen_rootid :
- word = word[0:-1]
- sep = ''
-
- parares += word + sep
-
- elif wtype == 'img' :
- sep = ''
- parares += ' ' % num
- parares += sep
-
- elif wtype == 'imgsa' :
- sep = ' '
- parares += ' ' % num
- parares += sep
-
- elif wtype == 'svg' :
- sep = ''
- parares += ' ' % num
- parares += sep
-
- if len(sep) > 0 : parares = parares[0:-1]
- if (type == 'full') or (type == 'end') :
- parares += '
'
- return parares
-
-
- def buildTOCEntry(self, pdesc) :
- parares = ''
- sep =''
- tocentry = ''
- handle_links = len(self.link_id) > 0
-
- lstart = 0
-
- cnt = len(pdesc)
- for j in xrange( 0, cnt) :
-
- (wtype, num) = pdesc[j]
-
- if wtype == 'ocr' :
- word = self.ocrtext[num]
- sep = ' '
-
- if handle_links:
- link = self.link_id[num]
- if (link > 0):
- linktype = self.link_type[link-1]
- title = self.link_title[link-1]
- title = title.rstrip('. ')
- alt_title = parares[lstart:]
- alt_title = alt_title.strip()
- # now strip off the actual printed page number
- alt_title = alt_title.rstrip('01234567890ivxldIVXLD-.')
- alt_title = alt_title.rstrip('. ')
- # skip over any external links - can't have them in a books toc
- if linktype == 'external' :
- title = ''
- alt_title = ''
- linkpage = ''
- else :
- if len(self.link_page) >= link :
- ptarget = self.link_page[link-1] - 1
- linkpage = '%04d' % ptarget
- else :
- # just link to the current page
- linkpage = self.id[4:]
- if len(alt_title) >= len(title):
- title = alt_title
- if title != '' and linkpage != '':
- tocentry += title + '|' + linkpage + '\n'
- lstart = len(parares)
- if word == '_link_' : word = ''
- elif (link < 0) :
- if word == '_link_' : word = ''
-
- if word == '_lb_':
- word = ''
- sep = ''
-
- if num in self.dehyphen_rootid :
- word = word[0:-1]
- sep = ''
-
- parares += word + sep
-
- else :
- continue
-
- return tocentry
-
-
-
-
- # walk the document tree collecting the information needed
- # to build an html page using the ocrText
-
- def process(self):
-
- tocinfo = ''
- hlst = []
-
- # get the ocr text
- (pos, argres) = self.findinDoc('info.word.ocrText',0,-1)
- if argres : self.ocrtext = argres.split('|')
-
- # get information to dehyphenate the text
- self.dehyphen_rootid = self.getData('info.dehyphen.rootID',0,-1)
-
- # determine if first paragraph is continued from previous page
- (pos, self.parastems_stemid) = self.findinDoc('info.paraStems.stemID',0,-1)
- first_para_continued = (self.parastems_stemid != None)
-
- # determine if last paragraph is continued onto the next page
- (pos, self.paracont_stemid) = self.findinDoc('info.paraCont.stemID',0,-1)
- last_para_continued = (self.paracont_stemid != None)
-
- # collect link ids
- self.link_id = self.getData('info.word.link_id',0,-1)
-
- # collect link destination page numbers
- self.link_page = self.getData('info.links.page',0,-1)
-
- # collect link types (container versus external)
- (pos, argres) = self.findinDoc('info.links.type',0,-1)
- if argres : self.link_type = argres.split('|')
-
- # collect link destinations
- (pos, argres) = self.findinDoc('info.links.href',0,-1)
- if argres : self.link_href = argres.split('|')
-
- # collect link titles
- (pos, argres) = self.findinDoc('info.links.title',0,-1)
- if argres :
- self.link_title = argres.split('|')
- else:
- self.link_title.append('')
-
- # get a descriptions of the starting points of the regions
- # and groups on the page
- (pagetype, pageDesc) = self.PageDescription()
- regcnt = len(pageDesc) - 1
-
- anchorSet = False
- breakSet = False
- inGroup = False
-
- # process each region on the page and convert what you can to html
-
- for j in xrange(regcnt):
-
- (etype, start) = pageDesc[j]
- (ntype, end) = pageDesc[j+1]
-
-
- # set anchor for link target on this page
- if not anchorSet and not first_para_continued:
- hlst.append('
\n')
- anchorSet = True
-
- # handle groups of graphics with text captions
- if (etype == 'grpbeg'):
- (pos, grptype) = self.findinDoc('group.type', start, end)
- if grptype != None:
- if grptype == 'graphic':
- gcstr = ' class="' + grptype + '"'
- hlst.append('')
- inGroup = True
-
- elif (etype == 'grpend'):
- if inGroup:
- hlst.append('
\n')
- inGroup = False
-
- else:
- (pos, regtype) = self.findinDoc('region.type',start,end)
-
- if regtype == 'graphic' :
- (pos, simgsrc) = self.findinDoc('img.src',start,end)
- if simgsrc:
- if inGroup:
- hlst.append(' ' % int(simgsrc))
- else:
- hlst.append('' % int(simgsrc))
-
- elif regtype == 'chapterheading' :
- (pclass, pdesc) = self.getParaDescription(start,end, regtype)
- if not breakSet:
- hlst.append('
\n')
- breakSet = True
- tag = 'h1'
- if pclass and (len(pclass) >= 7):
- if pclass[3:7] == 'ch1-' : tag = 'h1'
- if pclass[3:7] == 'ch2-' : tag = 'h2'
- if pclass[3:7] == 'ch3-' : tag = 'h3'
- hlst.append('<' + tag + ' class="' + pclass + '">')
- else:
- hlst.append('<' + tag + '>')
- hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
- hlst.append('' + tag + '>')
-
- elif (regtype == 'text') or (regtype == 'fixed') or (regtype == 'insert') or (regtype == 'listitem'):
- ptype = 'full'
- # check to see if this is a continution from the previous page
- if first_para_continued :
- ptype = 'end'
- first_para_continued = False
- (pclass, pdesc) = self.getParaDescription(start,end, regtype)
- if pclass and (len(pclass) >= 6) and (ptype == 'full'):
- tag = 'p'
- if pclass[3:6] == 'h1-' : tag = 'h4'
- if pclass[3:6] == 'h2-' : tag = 'h5'
- if pclass[3:6] == 'h3-' : tag = 'h6'
- hlst.append('<' + tag + ' class="' + pclass + '">')
- hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
- hlst.append('' + tag + '>')
- else :
- hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
-
- elif (regtype == 'tocentry') :
- ptype = 'full'
- if first_para_continued :
- ptype = 'end'
- first_para_continued = False
- (pclass, pdesc) = self.getParaDescription(start,end, regtype)
- tocinfo += self.buildTOCEntry(pdesc)
- hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
-
- elif (regtype == 'vertical') or (regtype == 'table') :
- ptype = 'full'
- if inGroup:
- ptype = 'middle'
- if first_para_continued :
- ptype = 'end'
- first_para_continued = False
- (pclass, pdesc) = self.getParaDescription(start, end, regtype)
- hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
-
-
- elif (regtype == 'synth_fcvr.center'):
- (pos, simgsrc) = self.findinDoc('img.src',start,end)
- if simgsrc:
- hlst.append('' % int(simgsrc))
-
- else :
- print ' Making region type', regtype,
- (pos, temp) = self.findinDoc('paragraph',start,end)
- (pos2, temp) = self.findinDoc('span',start,end)
- if pos != -1 or pos2 != -1:
- print ' a "text" region'
- orig_regtype = regtype
- regtype = 'fixed'
- ptype = 'full'
- # check to see if this is a continution from the previous page
- if first_para_continued :
- ptype = 'end'
- first_para_continued = False
- (pclass, pdesc) = self.getParaDescription(start,end, regtype)
- if not pclass:
- if orig_regtype.endswith('.right') : pclass = 'cl-right'
- elif orig_regtype.endswith('.center') : pclass = 'cl-center'
- elif orig_regtype.endswith('.left') : pclass = 'cl-left'
- elif orig_regtype.endswith('.justify') : pclass = 'cl-justify'
- if pclass and (ptype == 'full') and (len(pclass) >= 6):
- tag = 'p'
- if pclass[3:6] == 'h1-' : tag = 'h4'
- if pclass[3:6] == 'h2-' : tag = 'h5'
- if pclass[3:6] == 'h3-' : tag = 'h6'
- hlst.append('<' + tag + ' class="' + pclass + '">')
- hlst.append(self.buildParagraph(pclass, pdesc, 'middle', regtype))
- hlst.append('' + tag + '>')
- else :
- hlst.append(self.buildParagraph(pclass, pdesc, ptype, regtype))
- else :
- print ' a "graphic" region'
- (pos, simgsrc) = self.findinDoc('img.src',start,end)
- if simgsrc:
- hlst.append('' % int(simgsrc))
-
-
- htmlpage = "".join(hlst)
- if last_para_continued :
- if htmlpage[-4:] == '
':
- htmlpage = htmlpage[0:-4]
- last_para_continued = False
-
- return htmlpage, tocinfo
-
-
-def convert2HTML(flatxml, classlst, fileid, bookDir, gdict, fixedimage):
- # create a document parser
- dp = DocParser(flatxml, classlst, fileid, bookDir, gdict, fixedimage)
- htmlpage, tocinfo = dp.process()
- return htmlpage, tocinfo
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2svg.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2svg.py
deleted file mode 100644
index 4dfd6c7..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/flatxml2svg.py
+++ /dev/null
@@ -1,249 +0,0 @@
-#! /usr/bin/python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-
-import sys
-import csv
-import os
-import getopt
-from struct import pack
-from struct import unpack
-
-
-class PParser(object):
- def __init__(self, gd, flatxml, meta_array):
- self.gd = gd
- self.flatdoc = flatxml.split('\n')
- self.docSize = len(self.flatdoc)
- self.temp = []
-
- self.ph = -1
- self.pw = -1
- startpos = self.posinDoc('page.h') or self.posinDoc('book.h')
- for p in startpos:
- (name, argres) = self.lineinDoc(p)
- self.ph = max(self.ph, int(argres))
- startpos = self.posinDoc('page.w') or self.posinDoc('book.w')
- for p in startpos:
- (name, argres) = self.lineinDoc(p)
- self.pw = max(self.pw, int(argres))
-
- if self.ph <= 0:
- self.ph = int(meta_array.get('pageHeight', '11000'))
- if self.pw <= 0:
- self.pw = int(meta_array.get('pageWidth', '8500'))
-
- res = []
- startpos = self.posinDoc('info.glyph.x')
- for p in startpos:
- argres = self.getDataatPos('info.glyph.x', p)
- res.extend(argres)
- self.gx = res
-
- res = []
- startpos = self.posinDoc('info.glyph.y')
- for p in startpos:
- argres = self.getDataatPos('info.glyph.y', p)
- res.extend(argres)
- self.gy = res
-
- res = []
- startpos = self.posinDoc('info.glyph.glyphID')
- for p in startpos:
- argres = self.getDataatPos('info.glyph.glyphID', p)
- res.extend(argres)
- self.gid = res
-
-
- # return tag at line pos in document
- def lineinDoc(self, pos) :
- if (pos >= 0) and (pos < self.docSize) :
- item = self.flatdoc[pos]
- if item.find('=') >= 0:
- (name, argres) = item.split('=',1)
- else :
- name = item
- argres = ''
- return name, argres
-
- # find tag in doc if within pos to end inclusive
- def findinDoc(self, tagpath, pos, end) :
- result = None
- if end == -1 :
- end = self.docSize
- else:
- end = min(self.docSize, end)
- foundat = -1
- for j in xrange(pos, end):
- item = self.flatdoc[j]
- if item.find('=') >= 0:
- (name, argres) = item.split('=',1)
- else :
- name = item
- argres = ''
- if name.endswith(tagpath) :
- result = argres
- foundat = j
- break
- return foundat, result
-
- # return list of start positions for the tagpath
- def posinDoc(self, tagpath):
- startpos = []
- pos = 0
- res = ""
- while res != None :
- (foundpos, res) = self.findinDoc(tagpath, pos, -1)
- if res != None :
- startpos.append(foundpos)
- pos = foundpos + 1
- return startpos
-
- def getData(self, path):
- result = None
- cnt = len(self.flatdoc)
- for j in xrange(cnt):
- item = self.flatdoc[j]
- if item.find('=') >= 0:
- (name, argt) = item.split('=')
- argres = argt.split('|')
- else:
- name = item
- argres = []
- if (name.endswith(path)):
- result = argres
- break
- if (len(argres) > 0) :
- for j in xrange(0,len(argres)):
- argres[j] = int(argres[j])
- return result
-
- def getDataatPos(self, path, pos):
- result = None
- item = self.flatdoc[pos]
- if item.find('=') >= 0:
- (name, argt) = item.split('=')
- argres = argt.split('|')
- else:
- name = item
- argres = []
- if (len(argres) > 0) :
- for j in xrange(0,len(argres)):
- argres[j] = int(argres[j])
- if (name.endswith(path)):
- result = argres
- return result
-
- def getDataTemp(self, path):
- result = None
- cnt = len(self.temp)
- for j in xrange(cnt):
- item = self.temp[j]
- if item.find('=') >= 0:
- (name, argt) = item.split('=')
- argres = argt.split('|')
- else:
- name = item
- argres = []
- if (name.endswith(path)):
- result = argres
- self.temp.pop(j)
- break
- if (len(argres) > 0) :
- for j in xrange(0,len(argres)):
- argres[j] = int(argres[j])
- return result
-
- def getImages(self):
- result = []
- self.temp = self.flatdoc
- while (self.getDataTemp('img') != None):
- h = self.getDataTemp('img.h')[0]
- w = self.getDataTemp('img.w')[0]
- x = self.getDataTemp('img.x')[0]
- y = self.getDataTemp('img.y')[0]
- src = self.getDataTemp('img.src')[0]
- result.append(' \n' % (src, x, y, w, h))
- return result
-
- def getGlyphs(self):
- result = []
- if (self.gid != None) and (len(self.gid) > 0):
- glyphs = []
- for j in set(self.gid):
- glyphs.append(j)
- glyphs.sort()
- for gid in glyphs:
- id='id="gl%d"' % gid
- path = self.gd.lookup(id)
- if path:
- result.append(id + ' ' + path)
- return result
-
-
-def convert2SVG(gdict, flat_xml, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi):
- mlst = []
- pp = PParser(gdict, flat_xml, meta_array)
- mlst.append('\n')
- if (raw):
- mlst.append('\n')
- mlst.append('\n' % (pp.pw / scaledpi, pp.ph / scaledpi, pp.pw -1, pp.ph -1))
- mlst.append('Page %d - %s by %s \n' % (pageid, meta_array['Title'],meta_array['Authors']))
- else:
- mlst.append('\n')
- mlst.append('\n')
- mlst.append('Page %d - %s by %s \n' % (pageid, meta_array['Title'],meta_array['Authors']))
- mlst.append('\n')
- mlst.append('\n')
- mlst.append('\n')
- mlst.append('\n')
- mlst.append('\n')
- mlst.append('\n')
- mlst.append('\n')
- return "".join(mlst)
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/genbook.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/genbook.py
deleted file mode 100644
index 4036896..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/genbook.py
+++ /dev/null
@@ -1,721 +0,0 @@
-#! /usr/bin/python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-
-class Unbuffered:
- def __init__(self, stream):
- self.stream = stream
- def write(self, data):
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-import sys
-sys.stdout=Unbuffered(sys.stdout)
-
-import csv
-import os
-import getopt
-from struct import pack
-from struct import unpack
-
-class TpzDRMError(Exception):
- pass
-
-# local support routines
-if 'calibre' in sys.modules:
- inCalibre = True
-else:
- inCalibre = False
-
-if inCalibre :
- from calibre_plugins.dedrm import convert2xml
- from calibre_plugins.dedrm import flatxml2html
- from calibre_plugins.dedrm import flatxml2svg
- from calibre_plugins.dedrm import stylexml2css
-else :
- import convert2xml
- import flatxml2html
- import flatxml2svg
- import stylexml2css
-
-# global switch
-buildXML = False
-
-# Get a 7 bit encoded number from a file
-def readEncodedNumber(file):
- flag = False
- c = file.read(1)
- if (len(c) == 0):
- return None
- data = ord(c)
- if data == 0xFF:
- flag = True
- c = file.read(1)
- if (len(c) == 0):
- return None
- data = ord(c)
- if data >= 0x80:
- datax = (data & 0x7F)
- while data >= 0x80 :
- c = file.read(1)
- if (len(c) == 0):
- return None
- data = ord(c)
- datax = (datax <<7) + (data & 0x7F)
- data = datax
- if flag:
- data = -data
- return data
-
-# Get a length prefixed string from the file
-def lengthPrefixString(data):
- return encodeNumber(len(data))+data
-
-def readString(file):
- stringLength = readEncodedNumber(file)
- if (stringLength == None):
- return None
- sv = file.read(stringLength)
- if (len(sv) != stringLength):
- return ""
- return unpack(str(stringLength)+"s",sv)[0]
-
-def getMetaArray(metaFile):
- # parse the meta file
- result = {}
- fo = file(metaFile,'rb')
- size = readEncodedNumber(fo)
- for i in xrange(size):
- tag = readString(fo)
- value = readString(fo)
- result[tag] = value
- # print tag, value
- fo.close()
- return result
-
-
-# dictionary of all text strings by index value
-class Dictionary(object):
- def __init__(self, dictFile):
- self.filename = dictFile
- self.size = 0
- self.fo = file(dictFile,'rb')
- self.stable = []
- self.size = readEncodedNumber(self.fo)
- for i in xrange(self.size):
- self.stable.append(self.escapestr(readString(self.fo)))
- self.pos = 0
- def escapestr(self, str):
- str = str.replace('&','&')
- str = str.replace('<','<')
- str = str.replace('>','>')
- str = str.replace('=','=')
- return str
- def lookup(self,val):
- if ((val >= 0) and (val < self.size)) :
- self.pos = val
- return self.stable[self.pos]
- else:
- print "Error: %d outside of string table limits" % val
- raise TpzDRMError('outside or string table limits')
- # sys.exit(-1)
- def getSize(self):
- return self.size
- def getPos(self):
- return self.pos
-
-
-class PageDimParser(object):
- def __init__(self, flatxml):
- self.flatdoc = flatxml.split('\n')
- # find tag if within pos to end inclusive
- def findinDoc(self, tagpath, pos, end) :
- result = None
- docList = self.flatdoc
- cnt = len(docList)
- if end == -1 :
- end = cnt
- else:
- end = min(cnt,end)
- foundat = -1
- for j in xrange(pos, end):
- item = docList[j]
- if item.find('=') >= 0:
- (name, argres) = item.split('=')
- else :
- name = item
- argres = ''
- if name.endswith(tagpath) :
- result = argres
- foundat = j
- break
- return foundat, result
- def process(self):
- (pos, sph) = self.findinDoc('page.h',0,-1)
- (pos, spw) = self.findinDoc('page.w',0,-1)
- if (sph == None): sph = '-1'
- if (spw == None): spw = '-1'
- return sph, spw
-
-def getPageDim(flatxml):
- # create a document parser
- dp = PageDimParser(flatxml)
- (ph, pw) = dp.process()
- return ph, pw
-
-class GParser(object):
- def __init__(self, flatxml):
- self.flatdoc = flatxml.split('\n')
- self.dpi = 1440
- self.gh = self.getData('info.glyph.h')
- self.gw = self.getData('info.glyph.w')
- self.guse = self.getData('info.glyph.use')
- if self.guse :
- self.count = len(self.guse)
- else :
- self.count = 0
- self.gvtx = self.getData('info.glyph.vtx')
- self.glen = self.getData('info.glyph.len')
- self.gdpi = self.getData('info.glyph.dpi')
- self.vx = self.getData('info.vtx.x')
- self.vy = self.getData('info.vtx.y')
- self.vlen = self.getData('info.len.n')
- if self.vlen :
- self.glen.append(len(self.vlen))
- elif self.glen:
- self.glen.append(0)
- if self.vx :
- self.gvtx.append(len(self.vx))
- elif self.gvtx :
- self.gvtx.append(0)
- def getData(self, path):
- result = None
- cnt = len(self.flatdoc)
- for j in xrange(cnt):
- item = self.flatdoc[j]
- if item.find('=') >= 0:
- (name, argt) = item.split('=')
- argres = argt.split('|')
- else:
- name = item
- argres = []
- if (name == path):
- result = argres
- break
- if (len(argres) > 0) :
- for j in xrange(0,len(argres)):
- argres[j] = int(argres[j])
- return result
- def getGlyphDim(self, gly):
- if self.gdpi[gly] == 0:
- return 0, 0
- maxh = (self.gh[gly] * self.dpi) / self.gdpi[gly]
- maxw = (self.gw[gly] * self.dpi) / self.gdpi[gly]
- return maxh, maxw
- def getPath(self, gly):
- path = ''
- if (gly < 0) or (gly >= self.count):
- return path
- tx = self.vx[self.gvtx[gly]:self.gvtx[gly+1]]
- ty = self.vy[self.gvtx[gly]:self.gvtx[gly+1]]
- p = 0
- for k in xrange(self.glen[gly], self.glen[gly+1]):
- if (p == 0):
- zx = tx[0:self.vlen[k]+1]
- zy = ty[0:self.vlen[k]+1]
- else:
- zx = tx[self.vlen[k-1]+1:self.vlen[k]+1]
- zy = ty[self.vlen[k-1]+1:self.vlen[k]+1]
- p += 1
- j = 0
- while ( j < len(zx) ):
- if (j == 0):
- # Start Position.
- path += 'M %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly])
- elif (j <= len(zx)-3):
- # Cubic Bezier Curve
- path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[j+2] * self.dpi / self.gdpi[gly], zy[j+2] * self.dpi / self.gdpi[gly])
- j += 2
- elif (j == len(zx)-2):
- # Cubic Bezier Curve to Start Position
- path += 'C %d %d %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[j+1] * self.dpi / self.gdpi[gly], zy[j+1] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly])
- j += 1
- elif (j == len(zx)-1):
- # Quadratic Bezier Curve to Start Position
- path += 'Q %d %d %d %d ' % (zx[j] * self.dpi / self.gdpi[gly], zy[j] * self.dpi / self.gdpi[gly], zx[0] * self.dpi / self.gdpi[gly], zy[0] * self.dpi / self.gdpi[gly])
-
- j += 1
- path += 'z'
- return path
-
-
-
-# dictionary of all text strings by index value
-class GlyphDict(object):
- def __init__(self):
- self.gdict = {}
- def lookup(self, id):
- # id='id="gl%d"' % val
- if id in self.gdict:
- return self.gdict[id]
- return None
- def addGlyph(self, val, path):
- id='id="gl%d"' % val
- self.gdict[id] = path
-
-
-def generateBook(bookDir, raw, fixedimage):
- # sanity check Topaz file extraction
- if not os.path.exists(bookDir) :
- print "Can not find directory with unencrypted book"
- return 1
-
- dictFile = os.path.join(bookDir,'dict0000.dat')
- if not os.path.exists(dictFile) :
- print "Can not find dict0000.dat file"
- return 1
-
- pageDir = os.path.join(bookDir,'page')
- if not os.path.exists(pageDir) :
- print "Can not find page directory in unencrypted book"
- return 1
-
- imgDir = os.path.join(bookDir,'img')
- if not os.path.exists(imgDir) :
- print "Can not find image directory in unencrypted book"
- return 1
-
- glyphsDir = os.path.join(bookDir,'glyphs')
- if not os.path.exists(glyphsDir) :
- print "Can not find glyphs directory in unencrypted book"
- return 1
-
- metaFile = os.path.join(bookDir,'metadata0000.dat')
- if not os.path.exists(metaFile) :
- print "Can not find metadata0000.dat in unencrypted book"
- return 1
-
- svgDir = os.path.join(bookDir,'svg')
- if not os.path.exists(svgDir) :
- os.makedirs(svgDir)
-
- if buildXML:
- xmlDir = os.path.join(bookDir,'xml')
- if not os.path.exists(xmlDir) :
- os.makedirs(xmlDir)
-
- otherFile = os.path.join(bookDir,'other0000.dat')
- if not os.path.exists(otherFile) :
- print "Can not find other0000.dat in unencrypted book"
- return 1
-
- print "Updating to color images if available"
- spath = os.path.join(bookDir,'color_img')
- dpath = os.path.join(bookDir,'img')
- filenames = os.listdir(spath)
- filenames = sorted(filenames)
- for filename in filenames:
- imgname = filename.replace('color','img')
- sfile = os.path.join(spath,filename)
- dfile = os.path.join(dpath,imgname)
- imgdata = file(sfile,'rb').read()
- file(dfile,'wb').write(imgdata)
-
- print "Creating cover.jpg"
- isCover = False
- cpath = os.path.join(bookDir,'img')
- cpath = os.path.join(cpath,'img0000.jpg')
- if os.path.isfile(cpath):
- cover = file(cpath, 'rb').read()
- cpath = os.path.join(bookDir,'cover.jpg')
- file(cpath, 'wb').write(cover)
- isCover = True
-
-
- print 'Processing Dictionary'
- dict = Dictionary(dictFile)
-
- print 'Processing Meta Data and creating OPF'
- meta_array = getMetaArray(metaFile)
-
- # replace special chars in title and authors like & < >
- title = meta_array.get('Title','No Title Provided')
- title = title.replace('&','&')
- title = title.replace('<','<')
- title = title.replace('>','>')
- meta_array['Title'] = title
- authors = meta_array.get('Authors','No Authors Provided')
- authors = authors.replace('&','&')
- authors = authors.replace('<','<')
- authors = authors.replace('>','>')
- meta_array['Authors'] = authors
-
- if buildXML:
- xname = os.path.join(xmlDir, 'metadata.xml')
- mlst = []
- for key in meta_array:
- mlst.append(' \n')
- metastr = "".join(mlst)
- mlst = None
- file(xname, 'wb').write(metastr)
-
- print 'Processing StyleSheet'
-
- # get some scaling info from metadata to use while processing styles
- # and first page info
-
- fontsize = '135'
- if 'fontSize' in meta_array:
- fontsize = meta_array['fontSize']
-
- # also get the size of a normal text page
- # get the total number of pages unpacked as a safety check
- filenames = os.listdir(pageDir)
- numfiles = len(filenames)
-
- spage = '1'
- if 'firstTextPage' in meta_array:
- spage = meta_array['firstTextPage']
- pnum = int(spage)
- if pnum >= numfiles or pnum < 0:
- # metadata is wrong so just select a page near the front
- # 10% of the book to get a normal text page
- pnum = int(0.10 * numfiles)
- # print "first normal text page is", spage
-
- # get page height and width from first text page for use in stylesheet scaling
- pname = 'page%04d.dat' % (pnum - 1)
- fname = os.path.join(pageDir,pname)
- flat_xml = convert2xml.fromData(dict, fname)
-
- (ph, pw) = getPageDim(flat_xml)
- if (ph == '-1') or (ph == '0') : ph = '11000'
- if (pw == '-1') or (pw == '0') : pw = '8500'
- meta_array['pageHeight'] = ph
- meta_array['pageWidth'] = pw
- if 'fontSize' not in meta_array.keys():
- meta_array['fontSize'] = fontsize
-
- # process other.dat for css info and for map of page files to svg images
- # this map is needed because some pages actually are made up of multiple
- # pageXXXX.xml files
- xname = os.path.join(bookDir, 'style.css')
- flat_xml = convert2xml.fromData(dict, otherFile)
-
- # extract info.original.pid to get original page information
- pageIDMap = {}
- pageidnums = stylexml2css.getpageIDMap(flat_xml)
- if len(pageidnums) == 0:
- filenames = os.listdir(pageDir)
- numfiles = len(filenames)
- for k in range(numfiles):
- pageidnums.append(k)
- # create a map from page ids to list of page file nums to process for that page
- for i in range(len(pageidnums)):
- id = pageidnums[i]
- if id in pageIDMap.keys():
- pageIDMap[id].append(i)
- else:
- pageIDMap[id] = [i]
-
- # now get the css info
- cssstr , classlst = stylexml2css.convert2CSS(flat_xml, fontsize, ph, pw)
- file(xname, 'wb').write(cssstr)
- if buildXML:
- xname = os.path.join(xmlDir, 'other0000.xml')
- file(xname, 'wb').write(convert2xml.getXML(dict, otherFile))
-
- print 'Processing Glyphs'
- gd = GlyphDict()
- filenames = os.listdir(glyphsDir)
- filenames = sorted(filenames)
- glyfname = os.path.join(svgDir,'glyphs.svg')
- glyfile = open(glyfname, 'w')
- glyfile.write('\n')
- glyfile.write('\n')
- glyfile.write('\n')
- glyfile.write('Glyphs for %s \n' % meta_array['Title'])
- glyfile.write('\n')
- counter = 0
- for filename in filenames:
- # print ' ', filename
- print '.',
- fname = os.path.join(glyphsDir,filename)
- flat_xml = convert2xml.fromData(dict, fname)
-
- if buildXML:
- xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
- file(xname, 'wb').write(convert2xml.getXML(dict, fname))
-
- gp = GParser(flat_xml)
- for i in xrange(0, gp.count):
- path = gp.getPath(i)
- maxh, maxw = gp.getGlyphDim(i)
- fullpath = ' \n' % (counter * 256 + i, path, maxw, maxh)
- glyfile.write(fullpath)
- gd.addGlyph(counter * 256 + i, fullpath)
- counter += 1
- glyfile.write(' \n')
- glyfile.write(' \n')
- glyfile.close()
- print " "
-
-
- # start up the html
- # also build up tocentries while processing html
- htmlFileName = "book.html"
- hlst = []
- hlst.append('\n')
- hlst.append('\n')
- hlst.append('\n')
- hlst.append('\n')
- hlst.append(' \n')
- hlst.append('' + meta_array['Title'] + ' by ' + meta_array['Authors'] + ' \n')
- hlst.append(' \n')
- hlst.append(' \n')
- if 'ASIN' in meta_array:
- hlst.append(' \n')
- if 'GUID' in meta_array:
- hlst.append(' \n')
- hlst.append(' \n')
- hlst.append('\n\n')
-
- print 'Processing Pages'
- # Books are at 1440 DPI. This is rendering at twice that size for
- # readability when rendering to the screen.
- scaledpi = 1440.0
-
- filenames = os.listdir(pageDir)
- filenames = sorted(filenames)
- numfiles = len(filenames)
-
- xmllst = []
- elst = []
-
- for filename in filenames:
- # print ' ', filename
- print ".",
- fname = os.path.join(pageDir,filename)
- flat_xml = convert2xml.fromData(dict, fname)
-
- # keep flat_xml for later svg processing
- xmllst.append(flat_xml)
-
- if buildXML:
- xname = os.path.join(xmlDir, filename.replace('.dat','.xml'))
- file(xname, 'wb').write(convert2xml.getXML(dict, fname))
-
- # first get the html
- pagehtml, tocinfo = flatxml2html.convert2HTML(flat_xml, classlst, fname, bookDir, gd, fixedimage)
- elst.append(tocinfo)
- hlst.append(pagehtml)
-
- # finish up the html string and output it
- hlst.append('\n\n')
- htmlstr = "".join(hlst)
- hlst = None
- file(os.path.join(bookDir, htmlFileName), 'wb').write(htmlstr)
-
- print " "
- print 'Extracting Table of Contents from Amazon OCR'
-
- # first create a table of contents file for the svg images
- tlst = []
- tlst.append('\n')
- tlst.append('\n')
- tlst.append('')
- tlst.append('\n')
- tlst.append('' + meta_array['Title'] + ' \n')
- tlst.append(' \n')
- tlst.append(' \n')
- if 'ASIN' in meta_array:
- tlst.append(' \n')
- if 'GUID' in meta_array:
- tlst.append(' \n')
- tlst.append('\n')
- tlst.append('\n')
-
- tlst.append('Table of Contents \n')
- start = pageidnums[0]
- if (raw):
- startname = 'page%04d.svg' % start
- else:
- startname = 'page%04d.xhtml' % start
-
- tlst.append('\n')
- # build up a table of contents for the svg xhtml output
- tocentries = "".join(elst)
- elst = None
- toclst = tocentries.split('\n')
- toclst.pop()
- for entry in toclst:
- print entry
- title, pagenum = entry.split('|')
- id = pageidnums[int(pagenum)]
- if (raw):
- fname = 'page%04d.svg' % id
- else:
- fname = 'page%04d.xhtml' % id
- tlst.append('\n')
- tlst.append('\n')
- tlst.append('\n')
- tochtml = "".join(tlst)
- file(os.path.join(svgDir, 'toc.xhtml'), 'wb').write(tochtml)
-
-
- # now create index_svg.xhtml that points to all required files
- slst = []
- slst.append('\n')
- slst.append('\n')
- slst.append('')
- slst.append('\n')
- slst.append('' + meta_array['Title'] + ' \n')
- slst.append(' \n')
- slst.append(' \n')
- if 'ASIN' in meta_array:
- slst.append(' \n')
- if 'GUID' in meta_array:
- slst.append(' \n')
- slst.append('\n')
- slst.append('\n')
-
- print "Building svg images of each book page"
- slst.append('List of Pages \n')
- slst.append('\n')
- idlst = sorted(pageIDMap.keys())
- numids = len(idlst)
- cnt = len(idlst)
- previd = None
- for j in range(cnt):
- pageid = idlst[j]
- if j < cnt - 1:
- nextid = idlst[j+1]
- else:
- nextid = None
- print '.',
- pagelst = pageIDMap[pageid]
- flst = []
- for page in pagelst:
- flst.append(xmllst[page])
- flat_svg = "".join(flst)
- flst=None
- svgxml = flatxml2svg.convert2SVG(gd, flat_svg, pageid, previd, nextid, svgDir, raw, meta_array, scaledpi)
- if (raw) :
- pfile = open(os.path.join(svgDir,'page%04d.svg' % pageid),'w')
- slst.append('
Page %d \n' % (pageid, pageid))
- else :
- pfile = open(os.path.join(svgDir,'page%04d.xhtml' % pageid), 'w')
- slst.append('
Page %d \n' % (pageid, pageid))
- previd = pageid
- pfile.write(svgxml)
- pfile.close()
- counter += 1
- slst.append('
\n')
- slst.append('\n')
- slst.append('\n\n')
- svgindex = "".join(slst)
- slst = None
- file(os.path.join(bookDir, 'index_svg.xhtml'), 'wb').write(svgindex)
-
- print " "
-
- # build the opf file
- opfname = os.path.join(bookDir, 'book.opf')
- olst = []
- olst.append('\n')
- olst.append('\n')
- # adding metadata
- olst.append(' \n')
- if 'GUID' in meta_array:
- olst.append(' ' + meta_array['GUID'] + ' \n')
- if 'ASIN' in meta_array:
- olst.append(' ' + meta_array['ASIN'] + ' \n')
- if 'oASIN' in meta_array:
- olst.append(' ' + meta_array['oASIN'] + ' \n')
- olst.append(' ' + meta_array['Title'] + ' \n')
- olst.append(' ' + meta_array['Authors'] + ' \n')
- olst.append(' en \n')
- olst.append(' ' + meta_array['UpdateTime'] + ' \n')
- if isCover:
- olst.append(' \n')
- olst.append(' \n')
- olst.append('\n')
- olst.append(' \n')
- olst.append(' \n')
- # adding image files to manifest
- filenames = os.listdir(imgDir)
- filenames = sorted(filenames)
- for filename in filenames:
- imgname, imgext = os.path.splitext(filename)
- if imgext == '.jpg':
- imgext = 'jpeg'
- if imgext == '.svg':
- imgext = 'svg+xml'
- olst.append(' \n')
- if isCover:
- olst.append(' \n')
- olst.append(' \n')
- # adding spine
- olst.append('\n \n \n')
- if isCover:
- olst.append(' \n')
- olst.append(' \n')
- olst.append(' \n')
- olst.append(' \n')
- opfstr = "".join(olst)
- olst = None
- file(opfname, 'wb').write(opfstr)
-
- print 'Processing Complete'
-
- return 0
-
-def usage():
- print "genbook.py generates a book from the extract Topaz Files"
- print "Usage:"
- print " genbook.py [-r] [-h [--fixed-image] "
- print " "
- print "Options:"
- print " -h : help - print this usage message"
- print " -r : generate raw svg files (not wrapped in xhtml)"
- print " --fixed-image : genearate any Fixed Area as an svg image in the html"
- print " "
-
-
-def main(argv):
- bookDir = ''
- if len(argv) == 0:
- argv = sys.argv
-
- try:
- opts, args = getopt.getopt(argv[1:], "rh:",["fixed-image"])
-
- except getopt.GetoptError, err:
- print str(err)
- usage()
- return 1
-
- if len(opts) == 0 and len(args) == 0 :
- usage()
- return 1
-
- raw = 0
- fixedimage = True
- for o, a in opts:
- if o =="-h":
- usage()
- return 0
- if o =="-r":
- raw = 1
- if o =="--fixed-image":
- fixedimage = True
-
- bookDir = args[0]
-
- rv = generateBook(bookDir, raw, fixedimage)
- return rv
-
-
-if __name__ == '__main__':
- sys.exit(main(''))
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignobleepub.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignobleepub.py
deleted file mode 100644
index 1dda116..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignobleepub.py
+++ /dev/null
@@ -1,455 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-# ignobleepub.pyw, version 4.1
-# Copyright © 2009-2010 by i♥cabbages
-
-# Released under the terms of the GNU General Public Licence, version 3
-#
-
-# 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 and PyCrypto from
-# (make sure to
-# install the version for Python 2.6). Save this script file as
-# ineptepub.pyw and double-click on it to run it.
-#
-# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
-# program from the command line (pythonw ineptepub.pyw) or by double-clicking
-# it when it has been associated with PythonLauncher.
-
-# Revision history:
-# 1 - Initial release
-# 2 - Added OS X support by using OpenSSL when available
-# 3 - screen out improper key lengths to prevent segfaults on Linux
-# 3.1 - Allow Windows versions of libcrypto to be found
-# 3.2 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
-# 3.3 - On Windows try PyCrypto first, OpenSSL next
-# 3.4 - Modify interface to allow use with import
-# 3.5 - Fix for potential problem with PyCrypto
-# 3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code
-# 3.7 - Tweaked to match ineptepub more closely
-# 3.8 - Fixed to retain zip file metadata (e.g. file modification date)
-# 3.9 - moved unicode_argv call inside main for Windows DeDRM compatibility
-# 4.0 - Work if TkInter is missing
-# 4.1 - Import tkFileDialog, don't assume something else will import it.
-
-"""
-Decrypt Barnes & Noble encrypted ePub books.
-"""
-
-__license__ = 'GPL v3'
-__version__ = "4.1"
-
-import sys
-import os
-import traceback
-import zlib
-import zipfile
-from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
-from contextlib import closing
-import xml.etree.ElementTree as etree
-
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
- def __init__(self, stream):
- self.stream = stream
- self.encoding = stream.encoding
- if self.encoding == None:
- self.encoding = "utf-8"
- def write(self, data):
- if isinstance(data,unicode):
- data = data.encode(self.encoding,"replace")
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-try:
- from calibre.constants import iswindows, isosx
-except:
- iswindows = sys.platform.startswith('win')
- isosx = sys.platform.startswith('darwin')
-
-def unicode_argv():
- if iswindows:
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'.
-
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- return [u"ineptepub.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-
-class IGNOBLEError(Exception):
- pass
-
-def _load_crypto_libcrypto():
- from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
- Structure, c_ulong, create_string_buffer, cast
- from ctypes.util import find_library
-
- if iswindows:
- libcrypto = find_library('libeay32')
- else:
- libcrypto = find_library('crypto')
-
- if libcrypto is None:
- raise IGNOBLEError('libcrypto not found')
- libcrypto = CDLL(libcrypto)
-
- AES_MAXNR = 14
-
- c_char_pp = POINTER(c_char_p)
- c_int_p = POINTER(c_int)
-
- class AES_KEY(Structure):
- _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
- ('rounds', c_int)]
- AES_KEY_p = POINTER(AES_KEY)
-
- def F(restype, name, argtypes):
- func = getattr(libcrypto, name)
- func.restype = restype
- func.argtypes = argtypes
- return func
-
- AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
- [c_char_p, c_int, AES_KEY_p])
- AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
- [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
- c_int])
-
- class AES(object):
- def __init__(self, userkey):
- self._blocksize = len(userkey)
- if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
- raise IGNOBLEError('AES improper key used')
- return
- key = self._key = AES_KEY()
- rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
- if rv < 0:
- raise IGNOBLEError('Failed to initialize AES key')
-
- def decrypt(self, data):
- out = create_string_buffer(len(data))
- iv = ("\x00" * self._blocksize)
- rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
- if rv == 0:
- raise IGNOBLEError('AES decryption failed')
- return out.raw
-
- return AES
-
-def _load_crypto_pycrypto():
- from Crypto.Cipher import AES as _AES
-
- class AES(object):
- def __init__(self, key):
- self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
-
- def decrypt(self, data):
- return self._aes.decrypt(data)
-
- return AES
-
-def _load_crypto():
- AES = None
- cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
- if sys.platform.startswith('win'):
- cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
- for loader in cryptolist:
- try:
- AES = loader()
- break
- except (ImportError, IGNOBLEError):
- pass
- return AES
-
-AES = _load_crypto()
-
-META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
-NSMAP = {'adept': 'http://ns.adobe.com/adept',
- 'enc': 'http://www.w3.org/2001/04/xmlenc#'}
-
-class Decryptor(object):
- def __init__(self, bookkey, encryption):
- enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
- self._aes = AES(bookkey)
- encryption = etree.fromstring(encryption)
- self._encrypted = encrypted = set()
- expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
- enc('CipherReference'))
- for elem in encryption.findall(expr):
- path = elem.get('URI', None)
- if path is not None:
- path = path.encode('utf-8')
- encrypted.add(path)
-
- def decompress(self, bytes):
- dc = zlib.decompressobj(-15)
- bytes = dc.decompress(bytes)
- ex = dc.decompress('Z') + dc.flush()
- if ex:
- bytes = bytes + ex
- return bytes
-
- def decrypt(self, path, data):
- if path in self._encrypted:
- data = self._aes.decrypt(data)[16:]
- data = data[:-ord(data[-1])]
- data = self.decompress(data)
- return data
-
-# check file to make check whether it's probably an Adobe Adept encrypted ePub
-def ignobleBook(inpath):
- with closing(ZipFile(open(inpath, 'rb'))) as inf:
- namelist = set(inf.namelist())
- if 'META-INF/rights.xml' not in namelist or \
- 'META-INF/encryption.xml' not in namelist:
- return False
- try:
- rights = etree.fromstring(inf.read('META-INF/rights.xml'))
- adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
- expr = './/%s' % (adept('encryptedKey'),)
- bookkey = ''.join(rights.findtext(expr))
- if len(bookkey) == 64:
- return True
- except:
- # if we couldn't check, assume it is
- return True
- return False
-
-def decryptBook(keyb64, inpath, outpath):
- if AES is None:
- raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.")
- key = keyb64.decode('base64')[:16]
- aes = AES(key)
- with closing(ZipFile(open(inpath, 'rb'))) as inf:
- namelist = set(inf.namelist())
- if 'META-INF/rights.xml' not in namelist or \
- 'META-INF/encryption.xml' not in namelist:
- print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
- return 1
- for name in META_NAMES:
- namelist.remove(name)
- try:
- rights = etree.fromstring(inf.read('META-INF/rights.xml'))
- adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
- expr = './/%s' % (adept('encryptedKey'),)
- bookkey = ''.join(rights.findtext(expr))
- if len(bookkey) != 64:
- print u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath))
- return 1
- bookkey = aes.decrypt(bookkey.decode('base64'))
- bookkey = bookkey[:-ord(bookkey[-1])]
- encryption = inf.read('META-INF/encryption.xml')
- decryptor = Decryptor(bookkey[-16:], encryption)
- kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
- with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
- zi = ZipInfo('mimetype')
- zi.compress_type=ZIP_STORED
- try:
- # if the mimetype is present, get its info, including time-stamp
- oldzi = inf.getinfo('mimetype')
- # copy across fields to be preserved
- zi.date_time = oldzi.date_time
- zi.comment = oldzi.comment
- zi.extra = oldzi.extra
- zi.internal_attr = oldzi.internal_attr
- # external attributes are dependent on the create system, so copy both.
- zi.external_attr = oldzi.external_attr
- zi.create_system = oldzi.create_system
- except:
- pass
- outf.writestr(zi, inf.read('mimetype'))
- for path in namelist:
- data = inf.read(path)
- zi = ZipInfo(path)
- zi.compress_type=ZIP_DEFLATED
- try:
- # get the file info, including time-stamp
- oldzi = inf.getinfo(path)
- # copy across useful fields
- zi.date_time = oldzi.date_time
- zi.comment = oldzi.comment
- zi.extra = oldzi.extra
- zi.internal_attr = oldzi.internal_attr
- # external attributes are dependent on the create system, so copy both.
- zi.external_attr = oldzi.external_attr
- zi.create_system = oldzi.create_system
- except:
- pass
- outf.writestr(zi, decryptor.decrypt(path, data))
- except:
- print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
- return 2
- return 0
-
-
-def cli_main():
- sys.stdout=SafeUnbuffered(sys.stdout)
- sys.stderr=SafeUnbuffered(sys.stderr)
- argv=unicode_argv()
- progname = os.path.basename(argv[0])
- if len(argv) != 4:
- print u"usage: {0} ".format(progname)
- return 1
- keypath, inpath, outpath = argv[1:]
- userkey = open(keypath,'rb').read()
- result = decryptBook(userkey, inpath, outpath)
- if result == 0:
- print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
- return result
-
-def gui_main():
- try:
- import Tkinter
- import Tkconstants
- import tkFileDialog
- import tkMessageBox
- import traceback
- except:
- return cli_main()
-
- class DecryptionDialog(Tkinter.Frame):
- def __init__(self, root):
- Tkinter.Frame.__init__(self, root, border=5)
- self.status = Tkinter.Label(self, text=u"Select files for decryption")
- self.status.pack(fill=Tkconstants.X, expand=1)
- body = Tkinter.Frame(self)
- body.pack(fill=Tkconstants.X, expand=1)
- sticky = Tkconstants.E + Tkconstants.W
- body.grid_columnconfigure(1, weight=2)
- Tkinter.Label(body, text=u"Key file").grid(row=0)
- self.keypath = Tkinter.Entry(body, width=30)
- self.keypath.grid(row=0, column=1, sticky=sticky)
- if os.path.exists(u"bnepubkey.b64"):
- self.keypath.insert(0, u"bnepubkey.b64")
- button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
- button.grid(row=0, column=2)
- Tkinter.Label(body, text=u"Input file").grid(row=1)
- self.inpath = Tkinter.Entry(body, width=30)
- self.inpath.grid(row=1, column=1, sticky=sticky)
- button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
- button.grid(row=1, column=2)
- Tkinter.Label(body, text=u"Output file").grid(row=2)
- self.outpath = Tkinter.Entry(body, width=30)
- self.outpath.grid(row=2, column=1, sticky=sticky)
- button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
- button.grid(row=2, column=2)
- buttons = Tkinter.Frame(self)
- buttons.pack()
- botton = Tkinter.Button(
- buttons, text=u"Decrypt", width=10, command=self.decrypt)
- botton.pack(side=Tkconstants.LEFT)
- Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
- button = Tkinter.Button(
- buttons, text=u"Quit", width=10, command=self.quit)
- button.pack(side=Tkconstants.RIGHT)
-
- def get_keypath(self):
- keypath = tkFileDialog.askopenfilename(
- parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
- defaultextension=u".b64",
- filetypes=[('base64-encoded files', '.b64'),
- ('All Files', '.*')])
- if keypath:
- keypath = os.path.normpath(keypath)
- self.keypath.delete(0, Tkconstants.END)
- self.keypath.insert(0, keypath)
- return
-
- def get_inpath(self):
- inpath = tkFileDialog.askopenfilename(
- parent=None, title=u"Select B&N-encrypted ePub file to decrypt",
- defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
- if inpath:
- inpath = os.path.normpath(inpath)
- self.inpath.delete(0, Tkconstants.END)
- self.inpath.insert(0, inpath)
- return
-
- def get_outpath(self):
- outpath = tkFileDialog.asksaveasfilename(
- parent=None, title=u"Select unencrypted ePub file to produce",
- defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
- if outpath:
- outpath = os.path.normpath(outpath)
- self.outpath.delete(0, Tkconstants.END)
- self.outpath.insert(0, outpath)
- return
-
- def decrypt(self):
- keypath = self.keypath.get()
- inpath = self.inpath.get()
- outpath = self.outpath.get()
- if not keypath or not os.path.exists(keypath):
- self.status['text'] = u"Specified key file does not exist"
- return
- if not inpath or not os.path.exists(inpath):
- self.status['text'] = u"Specified input file does not exist"
- return
- if not outpath:
- self.status['text'] = u"Output file not specified"
- return
- if inpath == outpath:
- self.status['text'] = u"Must have different input and output files"
- return
- userkey = open(keypath,'rb').read()
- self.status['text'] = u"Decrypting..."
- try:
- decrypt_status = decryptBook(userkey, inpath, outpath)
- except Exception, e:
- self.status['text'] = u"Error: {0}".format(e.args[0])
- return
- if decrypt_status == 0:
- self.status['text'] = u"File successfully decrypted"
- else:
- self.status['text'] = u"The was an error decrypting the file."
-
- root = Tkinter.Tk()
- root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))
- root.resizable(True, False)
- root.minsize(300, 0)
- DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
- root.mainloop()
- return 0
-
-if __name__ == '__main__':
- if len(sys.argv) > 1:
- sys.exit(cli_main())
- sys.exit(gui_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekey.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekey.py
deleted file mode 100644
index dbadc5d..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekey.py
+++ /dev/null
@@ -1,336 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-# ignoblekey.py
-# Copyright © 2015 Apprentice Alf and Apprentice Harper
-
-# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
-
-# Released under the terms of the GNU General Public Licence, version 3
-#
-
-# Revision history:
-# 1.0 - Initial release
-# 1.1 - remove duplicates and return last key as single key
-
-"""
-Get Barnes & Noble EPUB user key from nook Studio log file
-"""
-
-__license__ = 'GPL v3'
-__version__ = "1.1"
-
-import sys
-import os
-import hashlib
-import getopt
-import re
-
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
- def __init__(self, stream):
- self.stream = stream
- self.encoding = stream.encoding
- if self.encoding == None:
- self.encoding = "utf-8"
- def write(self, data):
- if isinstance(data,unicode):
- data = data.encode(self.encoding,"replace")
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-try:
- from calibre.constants import iswindows, isosx
-except:
- iswindows = sys.platform.startswith('win')
- isosx = sys.platform.startswith('darwin')
-
-def unicode_argv():
- if iswindows:
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
- # as a list of Unicode strings and encode them as utf-8
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- # if we don't have any arguments at all, just pass back script name
- # this should never happen
- return [u"ignoblekey.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-class DrmException(Exception):
- pass
-
-# Locate all of the nookStudy/nook for PC/Mac log file and return as list
-def getNookLogFiles():
- logFiles = []
- found = False
- if iswindows:
- import _winreg as winreg
-
- # 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
- paths = set()
- if 'LOCALAPPDATA' in os.environ.keys():
- # Python 2.x does not return unicode env. Use Python 3.x
- path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
- if os.path.isdir(path):
- paths.add(path)
- if 'USERPROFILE' in os.environ.keys():
- # Python 2.x does not return unicode env. Use Python 3.x
- path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Local"
- if os.path.isdir(path):
- paths.add(path)
- path = winreg.ExpandEnvironmentStrings(u"%USERPROFILE%")+u"\\AppData\\Roaming"
- if os.path.isdir(path):
- paths.add(path)
- # User Shell Folders show take precedent over Shell Folders if present
- try:
- regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
- path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
- if os.path.isdir(path):
- paths.add(path)
- except WindowsError:
- pass
- try:
- regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
- path = winreg.QueryValueEx(regkey, 'AppData')[0]
- if os.path.isdir(path):
- paths.add(path)
- except WindowsError:
- pass
- try:
- regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
- path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
- if os.path.isdir(path):
- paths.add(path)
- except WindowsError:
- pass
- try:
- regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
- path = winreg.QueryValueEx(regkey, 'AppData')[0]
- if os.path.isdir(path):
- paths.add(path)
- except WindowsError:
- pass
-
- for path in paths:
- # look for nookStudy log file
- logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt'
- if os.path.isfile(logpath):
- found = True
- print('Found nookStudy log file: ' + logpath.encode('ascii','ignore'))
- logFiles.append(logpath)
- else:
- home = os.getenv('HOME')
- # check for BNClientLog.txt in various locations
- testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/logs/BNClientLog.txt'
- if os.path.isfile(testpath):
- logFiles.append(testpath)
- print('Found nookStudy log file: ' + testpath)
- found = True
- testpath = home + '/Library/Application Support/Barnes & Noble/DesktopReader/indices/BNClientLog.txt'
- if os.path.isfile(testpath):
- logFiles.append(testpath)
- print('Found nookStudy log file: ' + testpath)
- found = True
- testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/logs/BNClientLog.txt'
- if os.path.isfile(testpath):
- logFiles.append(testpath)
- print('Found nookStudy log file: ' + testpath)
- found = True
- testpath = home + '/Library/Application Support/Barnes & Noble/BNDesktopReader/indices/BNClientLog.txt'
- if os.path.isfile(testpath):
- logFiles.append(testpath)
- print('Found nookStudy log file: ' + testpath)
- found = True
-
- if not found:
- print('No nook Study log files have been found.')
- return logFiles
-
-
-# Extract CCHash key(s) from log file
-def getKeysFromLog(kLogFile):
- keys = []
- regex = re.compile("ccHash: \"(.{28})\"");
- for line in open(kLogFile):
- for m in regex.findall(line):
- keys.append(m)
- return keys
-
-# interface for calibre plugin
-def nookkeys(files = []):
- keys = []
- if files == []:
- files = getNookLogFiles()
- for file in files:
- fileKeys = getKeysFromLog(file)
- if fileKeys:
- print u"Found {0} keys in the Nook Study log files".format(len(fileKeys))
- keys.extend(fileKeys)
- return list(set(keys))
-
-# interface for Python DeDRM
-# returns single key or multiple keys, depending on path or file passed in
-def getkey(outpath, files=[]):
- keys = nookkeys(files)
- if len(keys) > 0:
- if not os.path.isdir(outpath):
- outfile = outpath
- with file(outfile, 'w') as keyfileout:
- keyfileout.write(keys[-1])
- print u"Saved a key to {0}".format(outfile)
- else:
- keycount = 0
- for key in keys:
- while True:
- keycount += 1
- outfile = os.path.join(outpath,u"nookkey{0:d}.b64".format(keycount))
- if not os.path.exists(outfile):
- break
- with file(outfile, 'w') as keyfileout:
- keyfileout.write(key)
- print u"Saved a key to {0}".format(outfile)
- return True
- return False
-
-def usage(progname):
- print u"Finds the nook Study encryption keys."
- print u"Keys are saved to the current directory, or a specified output directory."
- print u"If a file name is passed instead of a directory, only the first key is saved, in that file."
- print u"Usage:"
- print u" {0:s} [-h] [-k ] []".format(progname)
-
-
-def cli_main():
- sys.stdout=SafeUnbuffered(sys.stdout)
- sys.stderr=SafeUnbuffered(sys.stderr)
- argv=unicode_argv()
- progname = os.path.basename(argv[0])
- print u"{0} v{1}\nCopyright © 2015 Apprentice Alf".format(progname,__version__)
-
- try:
- opts, args = getopt.getopt(argv[1:], "hk:")
- except getopt.GetoptError, err:
- print u"Error in options or arguments: {0}".format(err.args[0])
- usage(progname)
- sys.exit(2)
-
- files = []
- for o, a in opts:
- if o == "-h":
- usage(progname)
- sys.exit(0)
- if o == "-k":
- files = [a]
-
- if len(args) > 1:
- usage(progname)
- sys.exit(2)
-
- if len(args) == 1:
- # save to the specified file or directory
- outpath = args[0]
- if not os.path.isabs(outpath):
- outpath = os.path.abspath(outpath)
- else:
- # save to the same directory as the script
- outpath = os.path.dirname(argv[0])
-
- # make sure the outpath is the
- outpath = os.path.realpath(os.path.normpath(outpath))
-
- if not getkey(outpath, files):
- print u"Could not retrieve nook Study key."
- return 0
-
-
-def gui_main():
- try:
- import Tkinter
- import Tkconstants
- import tkMessageBox
- import traceback
- except:
- return cli_main()
-
- class ExceptionDialog(Tkinter.Frame):
- def __init__(self, root, text):
- Tkinter.Frame.__init__(self, root, border=5)
- label = Tkinter.Label(self, text=u"Unexpected error:",
- anchor=Tkconstants.W, justify=Tkconstants.LEFT)
- label.pack(fill=Tkconstants.X, expand=0)
- self.text = Tkinter.Text(self)
- self.text.pack(fill=Tkconstants.BOTH, expand=1)
-
- self.text.insert(Tkconstants.END, text)
-
-
- argv=unicode_argv()
- root = Tkinter.Tk()
- root.withdraw()
- progpath, progname = os.path.split(argv[0])
- success = False
- try:
- keys = nookkeys()
- keycount = 0
- for key in keys:
- print key
- while True:
- keycount += 1
- outfile = os.path.join(progpath,u"nookkey{0:d}.b64".format(keycount))
- if not os.path.exists(outfile):
- break
-
- with file(outfile, 'w') as keyfileout:
- keyfileout.write(key)
- success = True
- tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
- except DrmException, e:
- tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
- except Exception:
- root.wm_state('normal')
- root.title(progname)
- text = traceback.format_exc()
- ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
- root.mainloop()
- if not success:
- return 1
- return 0
-
-if __name__ == '__main__':
- if len(sys.argv) > 1:
- sys.exit(cli_main())
- sys.exit(gui_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeyfetch.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeyfetch.py
deleted file mode 100644
index e9637a1..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeyfetch.py
+++ /dev/null
@@ -1,258 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-# ignoblekeyfetch.pyw, version 1.1
-# Copyright © 2015 Apprentice Harper
-
-# Released under the terms of the GNU General Public Licence, version 3
-#
-
-# Based on discoveries by "Nobody You Know"
-# Code partly based on ignoblekeygen.py by several people.
-
-# Windows users: Before running this program, you must first install Python.
-# We recommend ActiveState Python 2.7.X for Windows from
-# http://www.activestate.com/activepython/downloads.
-# Then save this script file as ignoblekeyfetch.pyw and double-click on it to run it.
-#
-# Mac OS X users: Save this script file as ignoblekeyfetch.pyw. You can run this
-# program from the command line (python ignoblekeyfetch.pyw) or by double-clicking
-# it when it has been associated with PythonLauncher.
-
-# Revision history:
-# 1.0 - Initial version
-# 1.1 - Try second URL if first one fails
-
-"""
-Fetch Barnes & Noble EPUB user key from B&N servers using email and password
-"""
-
-__license__ = 'GPL v3'
-__version__ = "1.1"
-
-import sys
-import os
-
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
- def __init__(self, stream):
- self.stream = stream
- self.encoding = stream.encoding
- if self.encoding == None:
- self.encoding = "utf-8"
- def write(self, data):
- if isinstance(data,unicode):
- data = data.encode(self.encoding,"replace")
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-try:
- from calibre.constants import iswindows, isosx
-except:
- iswindows = sys.platform.startswith('win')
- isosx = sys.platform.startswith('darwin')
-
-def unicode_argv():
- if iswindows:
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
- # as a list of Unicode strings and encode them as utf-8
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- # if we don't have any arguments at all, just pass back script name
- # this should never happen
- return [u"ignoblekeyfetch.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-
-class IGNOBLEError(Exception):
- pass
-
-def fetch_key(email, password):
- # change email and password to utf-8 if unicode
- if type(email)==unicode:
- email = email.encode('utf-8')
- if type(password)==unicode:
- password = password.encode('utf-8')
-
- import random
- random = "%030x" % random.randrange(16**30)
-
- import urllib, urllib2, re
-
- # try the URL from nook for PC
- fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
- fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
- fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
- #print fetch_url
-
- found = ''
- try:
- req = urllib2.Request(fetch_url)
- response = urllib2.urlopen(req)
- the_page = response.read()
- #print the_page
- found = re.search('ccHash>(.+?)(.+?) ".format(progname)
- return 1
- email, password, keypath = argv[1:]
- userkey = fetch_key(email, password)
- if len(userkey) == 28:
- open(keypath,'wb').write(userkey)
- return 0
- print u"Failed to fetch key."
- return 1
-
-
-def gui_main():
- try:
- import Tkinter
- import tkFileDialog
- import Tkconstants
- import tkMessageBox
- import traceback
- except:
- return cli_main()
-
- class DecryptionDialog(Tkinter.Frame):
- def __init__(self, root):
- Tkinter.Frame.__init__(self, root, border=5)
- self.status = Tkinter.Label(self, text=u"Enter parameters")
- self.status.pack(fill=Tkconstants.X, expand=1)
- body = Tkinter.Frame(self)
- body.pack(fill=Tkconstants.X, expand=1)
- sticky = Tkconstants.E + Tkconstants.W
- body.grid_columnconfigure(1, weight=2)
- Tkinter.Label(body, text=u"Account email address").grid(row=0)
- self.name = Tkinter.Entry(body, width=40)
- self.name.grid(row=0, column=1, sticky=sticky)
- Tkinter.Label(body, text=u"Account password").grid(row=1)
- self.ccn = Tkinter.Entry(body, width=40)
- self.ccn.grid(row=1, column=1, sticky=sticky)
- Tkinter.Label(body, text=u"Output file").grid(row=2)
- self.keypath = Tkinter.Entry(body, width=40)
- self.keypath.grid(row=2, column=1, sticky=sticky)
- self.keypath.insert(2, u"bnepubkey.b64")
- button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
- button.grid(row=2, column=2)
- buttons = Tkinter.Frame(self)
- buttons.pack()
- botton = Tkinter.Button(
- buttons, text=u"Fetch", width=10, command=self.generate)
- botton.pack(side=Tkconstants.LEFT)
- Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
- button = Tkinter.Button(
- buttons, text=u"Quit", width=10, command=self.quit)
- button.pack(side=Tkconstants.RIGHT)
-
- def get_keypath(self):
- keypath = tkFileDialog.asksaveasfilename(
- parent=None, title=u"Select B&N ePub key file to produce",
- defaultextension=u".b64",
- filetypes=[('base64-encoded files', '.b64'),
- ('All Files', '.*')])
- if keypath:
- keypath = os.path.normpath(keypath)
- self.keypath.delete(0, Tkconstants.END)
- self.keypath.insert(0, keypath)
- return
-
- def generate(self):
- email = self.name.get()
- password = self.ccn.get()
- keypath = self.keypath.get()
- if not email:
- self.status['text'] = u"Email address not given"
- return
- if not password:
- self.status['text'] = u"Account password not given"
- return
- if not keypath:
- self.status['text'] = u"Output keyfile path not set"
- return
- self.status['text'] = u"Fetching..."
- try:
- userkey = fetch_key(email, password)
- except Exception, e:
- self.status['text'] = u"Error: {0}".format(e.args[0])
- return
- if len(userkey) == 28:
- open(keypath,'wb').write(userkey)
- self.status['text'] = u"Keyfile fetched successfully"
- else:
- self.status['text'] = u"Keyfile fetch failed."
-
- root = Tkinter.Tk()
- root.title(u"Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
- root.resizable(True, False)
- root.minsize(300, 0)
- DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
- root.mainloop()
- return 0
-
-if __name__ == '__main__':
- if len(sys.argv) > 1:
- sys.exit(cli_main())
- sys.exit(gui_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeygen.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeygen.py
deleted file mode 100644
index d2917c7..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ignoblekeygen.py
+++ /dev/null
@@ -1,332 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-# ignoblekeygen.pyw, version 2.5
-# Copyright © 2009-2010 i♥cabbages
-
-# Released under the terms of the GNU General Public Licence, version 3
-#
-
-# Modified 2010–2013 by some_updates, DiapDealer and Apprentice Alf
-
-# 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 ignoblekeygen.pyw and double-click on it to run it.
-#
-# Mac OS X users: Save this script file as ignoblekeygen.pyw. You can run this
-# program from the command line (python ignoblekeygen.pyw) or by double-clicking
-# it when it has been associated with PythonLauncher.
-
-# Revision history:
-# 1 - Initial release
-# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5)
-# 2.1 - Allow Windows versions of libcrypto to be found
-# 2.2 - On Windows try PyCrypto first and then OpenSSL next
-# 2.3 - Modify interface to allow use of import
-# 2.4 - Improvements to UI and now works in plugins
-# 2.5 - Additional improvement for unicode and plugin support
-# 2.6 - moved unicode_argv call inside main for Windows DeDRM compatibility
-# 2.7 - Work if TkInter is missing
-# 2.8 - Fix bug in stand-alone use (import tkFileDialog)
-
-"""
-Generate Barnes & Noble EPUB user key from name and credit card number.
-"""
-
-__license__ = 'GPL v3'
-__version__ = "2.8"
-
-import sys
-import os
-import hashlib
-
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
- def __init__(self, stream):
- self.stream = stream
- self.encoding = stream.encoding
- if self.encoding == None:
- self.encoding = "utf-8"
- def write(self, data):
- if isinstance(data,unicode):
- data = data.encode(self.encoding,"replace")
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-try:
- from calibre.constants import iswindows, isosx
-except:
- iswindows = sys.platform.startswith('win')
- isosx = sys.platform.startswith('darwin')
-
-def unicode_argv():
- if iswindows:
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
- # as a list of Unicode strings and encode them as utf-8
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- # if we don't have any arguments at all, just pass back script name
- # this should never happen
- return [u"ignoblekeygen.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-
-class IGNOBLEError(Exception):
- pass
-
-def _load_crypto_libcrypto():
- from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
- Structure, c_ulong, create_string_buffer, cast
- from ctypes.util import find_library
-
- if iswindows:
- libcrypto = find_library('libeay32')
- else:
- libcrypto = find_library('crypto')
-
- if libcrypto is None:
- raise IGNOBLEError('libcrypto not found')
- libcrypto = CDLL(libcrypto)
-
- AES_MAXNR = 14
-
- c_char_pp = POINTER(c_char_p)
- c_int_p = POINTER(c_int)
-
- class AES_KEY(Structure):
- _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
- ('rounds', c_int)]
- AES_KEY_p = POINTER(AES_KEY)
-
- def F(restype, name, argtypes):
- func = getattr(libcrypto, name)
- func.restype = restype
- func.argtypes = argtypes
- return func
-
- AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key',
- [c_char_p, c_int, AES_KEY_p])
- AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
- [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
- c_int])
-
- class AES(object):
- def __init__(self, userkey, iv):
- self._blocksize = len(userkey)
- self._iv = iv
- key = self._key = AES_KEY()
- rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
- if rv < 0:
- raise IGNOBLEError('Failed to initialize AES Encrypt key')
-
- def encrypt(self, data):
- out = create_string_buffer(len(data))
- rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
- if rv == 0:
- raise IGNOBLEError('AES encryption failed')
- return out.raw
-
- return AES
-
-def _load_crypto_pycrypto():
- from Crypto.Cipher import AES as _AES
-
- class AES(object):
- def __init__(self, key, iv):
- self._aes = _AES.new(key, _AES.MODE_CBC, iv)
-
- def encrypt(self, data):
- return self._aes.encrypt(data)
-
- return AES
-
-def _load_crypto():
- AES = None
- cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
- if sys.platform.startswith('win'):
- cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
- for loader in cryptolist:
- try:
- AES = loader()
- break
- except (ImportError, IGNOBLEError):
- pass
- return AES
-
-AES = _load_crypto()
-
-def normalize_name(name):
- return ''.join(x for x in name.lower() if x != ' ')
-
-
-def generate_key(name, ccn):
- # remove spaces and case from name and CC numbers.
- if type(name)==unicode:
- name = name.encode('utf-8')
- if type(ccn)==unicode:
- ccn = ccn.encode('utf-8')
-
- name = normalize_name(name) + '\x00'
- ccn = normalize_name(ccn) + '\x00'
-
- name_sha = hashlib.sha1(name).digest()[:16]
- ccn_sha = hashlib.sha1(ccn).digest()[:16]
- both_sha = hashlib.sha1(name + ccn).digest()
- aes = AES(ccn_sha, name_sha)
- crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c))
- userkey = hashlib.sha1(crypt).digest()
- return userkey.encode('base64')
-
-
-
-
-def cli_main():
- sys.stdout=SafeUnbuffered(sys.stdout)
- sys.stderr=SafeUnbuffered(sys.stderr)
- argv=unicode_argv()
- progname = os.path.basename(argv[0])
- if AES is None:
- print "%s: This script requires OpenSSL or PyCrypto, which must be installed " \
- "separately. Read the top-of-script comment for details." % \
- (progname,)
- return 1
- if len(argv) != 4:
- print u"usage: {0} ".format(progname)
- return 1
- name, ccn, keypath = argv[1:]
- userkey = generate_key(name, ccn)
- open(keypath,'wb').write(userkey)
- return 0
-
-
-def gui_main():
- try:
- import Tkinter
- import Tkconstants
- import tkMessageBox
- import tkFileDialog
- import traceback
- except:
- return cli_main()
-
- class DecryptionDialog(Tkinter.Frame):
- def __init__(self, root):
- Tkinter.Frame.__init__(self, root, border=5)
- self.status = Tkinter.Label(self, text=u"Enter parameters")
- self.status.pack(fill=Tkconstants.X, expand=1)
- body = Tkinter.Frame(self)
- body.pack(fill=Tkconstants.X, expand=1)
- sticky = Tkconstants.E + Tkconstants.W
- body.grid_columnconfigure(1, weight=2)
- Tkinter.Label(body, text=u"Account Name").grid(row=0)
- self.name = Tkinter.Entry(body, width=40)
- self.name.grid(row=0, column=1, sticky=sticky)
- Tkinter.Label(body, text=u"CC#").grid(row=1)
- self.ccn = Tkinter.Entry(body, width=40)
- self.ccn.grid(row=1, column=1, sticky=sticky)
- Tkinter.Label(body, text=u"Output file").grid(row=2)
- self.keypath = Tkinter.Entry(body, width=40)
- self.keypath.grid(row=2, column=1, sticky=sticky)
- self.keypath.insert(2, u"bnepubkey.b64")
- button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
- button.grid(row=2, column=2)
- buttons = Tkinter.Frame(self)
- buttons.pack()
- botton = Tkinter.Button(
- buttons, text=u"Generate", width=10, command=self.generate)
- botton.pack(side=Tkconstants.LEFT)
- Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
- button = Tkinter.Button(
- buttons, text=u"Quit", width=10, command=self.quit)
- button.pack(side=Tkconstants.RIGHT)
-
- def get_keypath(self):
- keypath = tkFileDialog.asksaveasfilename(
- parent=None, title=u"Select B&N ePub key file to produce",
- defaultextension=u".b64",
- filetypes=[('base64-encoded files', '.b64'),
- ('All Files', '.*')])
- if keypath:
- keypath = os.path.normpath(keypath)
- self.keypath.delete(0, Tkconstants.END)
- self.keypath.insert(0, keypath)
- return
-
- def generate(self):
- name = self.name.get()
- ccn = self.ccn.get()
- keypath = self.keypath.get()
- if not name:
- self.status['text'] = u"Name not specified"
- return
- if not ccn:
- self.status['text'] = u"Credit card number not specified"
- return
- if not keypath:
- self.status['text'] = u"Output keyfile path not specified"
- return
- self.status['text'] = u"Generating..."
- try:
- userkey = generate_key(name, ccn)
- except Exception, e:
- self.status['text'] = u"Error: (0}".format(e.args[0])
- return
- open(keypath,'wb').write(userkey)
- self.status['text'] = u"Keyfile successfully generated"
-
- root = Tkinter.Tk()
- if AES is None:
- root.withdraw()
- tkMessageBox.showerror(
- "Ignoble EPUB Keyfile Generator",
- "This script requires OpenSSL or PyCrypto, which must be installed "
- "separately. Read the top-of-script comment for details.")
- return 1
- root.title(u"Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
- root.resizable(True, False)
- root.minsize(300, 0)
- DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
- root.mainloop()
- return 0
-
-if __name__ == '__main__':
- if len(sys.argv) > 1:
- sys.exit(cli_main())
- sys.exit(gui_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptepub.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptepub.py
deleted file mode 100644
index ae30c04..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptepub.py
+++ /dev/null
@@ -1,601 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-# ineptepub.pyw, version 6.6
-# Copyright © 2009-2010 by i♥cabbages
-
-# Released under the terms of the GNU General Public Licence, version 3
-#
-
-# 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.7
-# from and PyCrypto from
-# (make sure to
-# install the version for Python 2.7). Save this script file as
-# ineptepub.pyw and double-click on it to run it.
-#
-# Mac OS X users: Save this script file as ineptepub.pyw. You can run this
-# program from the command line (pythonw ineptepub.pyw) or by double-clicking
-# it when it has been associated with PythonLauncher.
-
-# Revision history:
-# 1 - Initial release
-# 2 - Rename to INEPT, fix exit code
-# 5 - Version bump to avoid (?) confusion;
-# Improve OS X support by using OpenSSL when available
-# 5.1 - Improve OpenSSL error checking
-# 5.2 - Fix ctypes error causing segfaults on some systems
-# 5.3 - add support for OpenSSL on Windows, fix bug with some versions of libcrypto 0.9.8 prior to path level o
-# 5.4 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
-# 5.5 - On Windows try PyCrypto first, OpenSSL next
-# 5.6 - Modify interface to allow use with import
-# 5.7 - Fix for potential problem with PyCrypto
-# 5.8 - Revised to allow use in calibre plugins to eliminate need for duplicate code
-# 5.9 - Fixed to retain zip file metadata (e.g. file modification date)
-# 6.0 - moved unicode_argv call inside main for Windows DeDRM compatibility
-# 6.1 - Work if TkInter is missing
-# 6.2 - Handle UTF-8 file names inside an ePub, fix by Jose Luis
-# 6.3 - Add additional check on DER file sanity
-# 6.4 - Remove erroneous check on DER file sanity
-# 6.5 - Completely remove erroneous check on DER file sanity
-# 6.6 - Import tkFileDialog, don't assume something else will import it.
-
-"""
-Decrypt Adobe Digital Editions encrypted ePub books.
-"""
-
-__license__ = 'GPL v3'
-__version__ = "6.6"
-
-import sys
-import os
-import traceback
-import zlib
-import zipfile
-from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
-from contextlib import closing
-import xml.etree.ElementTree as etree
-
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
- def __init__(self, stream):
- self.stream = stream
- self.encoding = stream.encoding
- if self.encoding == None:
- self.encoding = "utf-8"
- def write(self, data):
- if isinstance(data,unicode):
- data = data.encode(self.encoding,"replace")
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-try:
- from calibre.constants import iswindows, isosx
-except:
- iswindows = sys.platform.startswith('win')
- isosx = sys.platform.startswith('darwin')
-
-def unicode_argv():
- if iswindows:
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'.
-
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- return [u"ineptepub.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-
-class ADEPTError(Exception):
- pass
-
-def _load_crypto_libcrypto():
- from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
- Structure, c_ulong, create_string_buffer, cast
- from ctypes.util import find_library
-
- if iswindows:
- libcrypto = find_library('libeay32')
- else:
- libcrypto = find_library('crypto')
-
- if libcrypto is None:
- raise ADEPTError('libcrypto not found')
- libcrypto = CDLL(libcrypto)
-
- RSA_NO_PADDING = 3
- AES_MAXNR = 14
-
- c_char_pp = POINTER(c_char_p)
- c_int_p = POINTER(c_int)
-
- class RSA(Structure):
- pass
- RSA_p = POINTER(RSA)
-
- class AES_KEY(Structure):
- _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
- ('rounds', c_int)]
- AES_KEY_p = POINTER(AES_KEY)
-
- def F(restype, name, argtypes):
- func = getattr(libcrypto, name)
- func.restype = restype
- func.argtypes = argtypes
- return func
-
- d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
- [RSA_p, c_char_pp, c_long])
- RSA_size = F(c_int, 'RSA_size', [RSA_p])
- RSA_private_decrypt = F(c_int, 'RSA_private_decrypt',
- [c_int, c_char_p, c_char_p, RSA_p, c_int])
- RSA_free = F(None, 'RSA_free', [RSA_p])
- AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
- [c_char_p, c_int, AES_KEY_p])
- AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
- [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
- c_int])
-
- class RSA(object):
- def __init__(self, der):
- buf = create_string_buffer(der)
- pp = c_char_pp(cast(buf, c_char_p))
- rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
- if rsa is None:
- raise ADEPTError('Error parsing ADEPT user key DER')
-
- def decrypt(self, from_):
- rsa = self._rsa
- to = create_string_buffer(RSA_size(rsa))
- dlen = RSA_private_decrypt(len(from_), from_, to, rsa,
- RSA_NO_PADDING)
- if dlen < 0:
- raise ADEPTError('RSA decryption failed')
- return to[:dlen]
-
- def __del__(self):
- if self._rsa is not None:
- RSA_free(self._rsa)
- self._rsa = None
-
- class AES(object):
- def __init__(self, userkey):
- self._blocksize = len(userkey)
- if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
- raise ADEPTError('AES improper key used')
- return
- key = self._key = AES_KEY()
- rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
- if rv < 0:
- raise ADEPTError('Failed to initialize AES key')
-
- def decrypt(self, data):
- out = create_string_buffer(len(data))
- iv = ("\x00" * self._blocksize)
- rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
- if rv == 0:
- raise ADEPTError('AES decryption failed')
- return out.raw
-
- return (AES, RSA)
-
-def _load_crypto_pycrypto():
- from Crypto.Cipher import AES as _AES
- from Crypto.PublicKey import RSA as _RSA
-
- # ASN.1 parsing code from tlslite
- class ASN1Error(Exception):
- pass
-
- class ASN1Parser(object):
- class Parser(object):
- def __init__(self, bytes):
- self.bytes = bytes
- self.index = 0
-
- def get(self, length):
- if self.index + length > len(self.bytes):
- raise ASN1Error("Error decoding ASN.1")
- x = 0
- for count in range(length):
- x <<= 8
- x |= self.bytes[self.index]
- self.index += 1
- return x
-
- def getFixBytes(self, lengthBytes):
- bytes = self.bytes[self.index : self.index+lengthBytes]
- self.index += lengthBytes
- return bytes
-
- def getVarBytes(self, lengthLength):
- lengthBytes = self.get(lengthLength)
- return self.getFixBytes(lengthBytes)
-
- def getFixList(self, length, lengthList):
- l = [0] * lengthList
- for x in range(lengthList):
- l[x] = self.get(length)
- return l
-
- def getVarList(self, length, lengthLength):
- lengthList = self.get(lengthLength)
- if lengthList % length != 0:
- raise ASN1Error("Error decoding ASN.1")
- lengthList = int(lengthList/length)
- l = [0] * lengthList
- for x in range(lengthList):
- l[x] = self.get(length)
- return l
-
- def startLengthCheck(self, lengthLength):
- self.lengthCheck = self.get(lengthLength)
- self.indexCheck = self.index
-
- def setLengthCheck(self, length):
- self.lengthCheck = length
- self.indexCheck = self.index
-
- def stopLengthCheck(self):
- if (self.index - self.indexCheck) != self.lengthCheck:
- raise ASN1Error("Error decoding ASN.1")
-
- def atLengthCheck(self):
- if (self.index - self.indexCheck) < self.lengthCheck:
- return False
- elif (self.index - self.indexCheck) == self.lengthCheck:
- return True
- else:
- raise ASN1Error("Error decoding ASN.1")
-
- def __init__(self, bytes):
- p = self.Parser(bytes)
- p.get(1)
- self.length = self._getASN1Length(p)
- self.value = p.getFixBytes(self.length)
-
- def getChild(self, which):
- p = self.Parser(self.value)
- for x in range(which+1):
- markIndex = p.index
- p.get(1)
- length = self._getASN1Length(p)
- p.getFixBytes(length)
- return ASN1Parser(p.bytes[markIndex:p.index])
-
- def _getASN1Length(self, p):
- firstLength = p.get(1)
- if firstLength<=127:
- return firstLength
- else:
- lengthLength = firstLength & 0x7F
- return p.get(lengthLength)
-
- class AES(object):
- def __init__(self, key):
- self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
-
- def decrypt(self, data):
- return self._aes.decrypt(data)
-
- class RSA(object):
- def __init__(self, der):
- key = ASN1Parser([ord(x) for x in der])
- key = [key.getChild(x).value for x in xrange(1, 4)]
- key = [self.bytesToNumber(v) for v in key]
- self._rsa = _RSA.construct(key)
-
- def bytesToNumber(self, bytes):
- total = 0L
- for byte in bytes:
- total = (total << 8) + byte
- return total
-
- def decrypt(self, data):
- return self._rsa.decrypt(data)
-
- return (AES, RSA)
-
-def _load_crypto():
- AES = RSA = None
- cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
- if sys.platform.startswith('win'):
- cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
- for loader in cryptolist:
- try:
- AES, RSA = loader()
- break
- except (ImportError, ADEPTError):
- pass
- return (AES, RSA)
-
-AES, RSA = _load_crypto()
-
-META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
-NSMAP = {'adept': 'http://ns.adobe.com/adept',
- 'enc': 'http://www.w3.org/2001/04/xmlenc#'}
-
-class Decryptor(object):
- def __init__(self, bookkey, encryption):
- enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
- self._aes = AES(bookkey)
- encryption = etree.fromstring(encryption)
- self._encrypted = encrypted = set()
- expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
- enc('CipherReference'))
- for elem in encryption.findall(expr):
- path = elem.get('URI', None)
- if path is not None:
- path = path.encode('utf-8')
- encrypted.add(path)
-
- def decompress(self, bytes):
- dc = zlib.decompressobj(-15)
- bytes = dc.decompress(bytes)
- ex = dc.decompress('Z') + dc.flush()
- if ex:
- bytes = bytes + ex
- return bytes
-
- def decrypt(self, path, data):
- if path.encode('utf-8') in self._encrypted:
- data = self._aes.decrypt(data)[16:]
- data = data[:-ord(data[-1])]
- data = self.decompress(data)
- return data
-
-# check file to make check whether it's probably an Adobe Adept encrypted ePub
-def adeptBook(inpath):
- with closing(ZipFile(open(inpath, 'rb'))) as inf:
- namelist = set(inf.namelist())
- if 'META-INF/rights.xml' not in namelist or \
- 'META-INF/encryption.xml' not in namelist:
- return False
- try:
- rights = etree.fromstring(inf.read('META-INF/rights.xml'))
- adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
- expr = './/%s' % (adept('encryptedKey'),)
- bookkey = ''.join(rights.findtext(expr))
- if len(bookkey) == 172:
- return True
- except:
- # if we couldn't check, assume it is
- return True
- return False
-
-def decryptBook(userkey, inpath, outpath):
- if AES is None:
- raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
- rsa = RSA(userkey)
- with closing(ZipFile(open(inpath, 'rb'))) as inf:
- namelist = set(inf.namelist())
- if 'META-INF/rights.xml' not in namelist or \
- 'META-INF/encryption.xml' not in namelist:
- print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
- return 1
- for name in META_NAMES:
- namelist.remove(name)
- try:
- rights = etree.fromstring(inf.read('META-INF/rights.xml'))
- adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
- expr = './/%s' % (adept('encryptedKey'),)
- bookkey = ''.join(rights.findtext(expr))
- if len(bookkey) != 172:
- print u"{0:s} is not a secure Adobe Adept ePub.".format(os.path.basename(inpath))
- return 1
- bookkey = rsa.decrypt(bookkey.decode('base64'))
- # Padded as per RSAES-PKCS1-v1_5
- if bookkey[-17] != '\x00':
- print u"Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath))
- return 2
- encryption = inf.read('META-INF/encryption.xml')
- decryptor = Decryptor(bookkey[-16:], encryption)
- kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
- with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
- zi = ZipInfo('mimetype')
- zi.compress_type=ZIP_STORED
- try:
- # if the mimetype is present, get its info, including time-stamp
- oldzi = inf.getinfo('mimetype')
- # copy across fields to be preserved
- zi.date_time = oldzi.date_time
- zi.comment = oldzi.comment
- zi.extra = oldzi.extra
- zi.internal_attr = oldzi.internal_attr
- # external attributes are dependent on the create system, so copy both.
- zi.external_attr = oldzi.external_attr
- zi.create_system = oldzi.create_system
- except:
- pass
- outf.writestr(zi, inf.read('mimetype'))
- for path in namelist:
- data = inf.read(path)
- zi = ZipInfo(path)
- zi.compress_type=ZIP_DEFLATED
- try:
- # get the file info, including time-stamp
- oldzi = inf.getinfo(path)
- # copy across useful fields
- zi.date_time = oldzi.date_time
- zi.comment = oldzi.comment
- zi.extra = oldzi.extra
- zi.internal_attr = oldzi.internal_attr
- # external attributes are dependent on the create system, so copy both.
- zi.external_attr = oldzi.external_attr
- zi.create_system = oldzi.create_system
- except:
- pass
- outf.writestr(zi, decryptor.decrypt(path, data))
- except:
- print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
- return 2
- return 0
-
-
-def cli_main():
- sys.stdout=SafeUnbuffered(sys.stdout)
- sys.stderr=SafeUnbuffered(sys.stderr)
- argv=unicode_argv()
- progname = os.path.basename(argv[0])
- if len(argv) != 4:
- print u"usage: {0} ".format(progname)
- return 1
- keypath, inpath, outpath = argv[1:]
- userkey = open(keypath,'rb').read()
- result = decryptBook(userkey, inpath, outpath)
- if result == 0:
- print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
- return result
-
-def gui_main():
- try:
- import Tkinter
- import Tkconstants
- import tkFileDialog
- import tkMessageBox
- import traceback
- except:
- return cli_main()
-
- class DecryptionDialog(Tkinter.Frame):
- def __init__(self, root):
- Tkinter.Frame.__init__(self, root, border=5)
- self.status = Tkinter.Label(self, text=u"Select files for decryption")
- self.status.pack(fill=Tkconstants.X, expand=1)
- body = Tkinter.Frame(self)
- body.pack(fill=Tkconstants.X, expand=1)
- sticky = Tkconstants.E + Tkconstants.W
- body.grid_columnconfigure(1, weight=2)
- Tkinter.Label(body, text=u"Key file").grid(row=0)
- self.keypath = Tkinter.Entry(body, width=30)
- self.keypath.grid(row=0, column=1, sticky=sticky)
- if os.path.exists(u"adeptkey.der"):
- self.keypath.insert(0, u"adeptkey.der")
- button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
- button.grid(row=0, column=2)
- Tkinter.Label(body, text=u"Input file").grid(row=1)
- self.inpath = Tkinter.Entry(body, width=30)
- self.inpath.grid(row=1, column=1, sticky=sticky)
- button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
- button.grid(row=1, column=2)
- Tkinter.Label(body, text=u"Output file").grid(row=2)
- self.outpath = Tkinter.Entry(body, width=30)
- self.outpath.grid(row=2, column=1, sticky=sticky)
- button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
- button.grid(row=2, column=2)
- buttons = Tkinter.Frame(self)
- buttons.pack()
- botton = Tkinter.Button(
- buttons, text=u"Decrypt", width=10, command=self.decrypt)
- botton.pack(side=Tkconstants.LEFT)
- Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
- button = Tkinter.Button(
- buttons, text=u"Quit", width=10, command=self.quit)
- button.pack(side=Tkconstants.RIGHT)
-
- def get_keypath(self):
- keypath = tkFileDialog.askopenfilename(
- parent=None, title=u"Select Adobe Adept \'.der\' key file",
- defaultextension=u".der",
- filetypes=[('Adobe Adept DER-encoded files', '.der'),
- ('All Files', '.*')])
- if keypath:
- keypath = os.path.normpath(keypath)
- self.keypath.delete(0, Tkconstants.END)
- self.keypath.insert(0, keypath)
- return
-
- def get_inpath(self):
- inpath = tkFileDialog.askopenfilename(
- parent=None, title=u"Select ADEPT-encrypted ePub file to decrypt",
- defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
- if inpath:
- inpath = os.path.normpath(inpath)
- self.inpath.delete(0, Tkconstants.END)
- self.inpath.insert(0, inpath)
- return
-
- def get_outpath(self):
- outpath = tkFileDialog.asksaveasfilename(
- parent=None, title=u"Select unencrypted ePub file to produce",
- defaultextension=u".epub", filetypes=[('ePub files', '.epub')])
- if outpath:
- outpath = os.path.normpath(outpath)
- self.outpath.delete(0, Tkconstants.END)
- self.outpath.insert(0, outpath)
- return
-
- def decrypt(self):
- keypath = self.keypath.get()
- inpath = self.inpath.get()
- outpath = self.outpath.get()
- if not keypath or not os.path.exists(keypath):
- self.status['text'] = u"Specified key file does not exist"
- return
- if not inpath or not os.path.exists(inpath):
- self.status['text'] = u"Specified input file does not exist"
- return
- if not outpath:
- self.status['text'] = u"Output file not specified"
- return
- if inpath == outpath:
- self.status['text'] = u"Must have different input and output files"
- return
- userkey = open(keypath,'rb').read()
- self.status['text'] = u"Decrypting..."
- try:
- decrypt_status = decryptBook(userkey, inpath, outpath)
- except Exception, e:
- self.status['text'] = u"Error: {0}".format(e.args[0])
- return
- if decrypt_status == 0:
- self.status['text'] = u"File successfully decrypted"
- else:
- self.status['text'] = u"The was an error decrypting the file."
-
- root = Tkinter.Tk()
- root.title(u"Adobe Adept ePub Decrypter v.{0}".format(__version__))
- root.resizable(True, False)
- root.minsize(300, 0)
- DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
- root.mainloop()
- return 0
-
-if __name__ == '__main__':
- if len(sys.argv) > 1:
- sys.exit(cli_main())
- sys.exit(gui_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptpdf.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptpdf.py
deleted file mode 100644
index 0da2993..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ineptpdf.py
+++ /dev/null
@@ -1,2350 +0,0 @@
-#! /usr/bin/python
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-# ineptpdf.pyw, version 8.0.6
-# Copyright © 2009-2010 by i♥cabbages
-
-# Released under the terms of the GNU General Public Licence, version 3
-#
-
-# Modified 2010–2012 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.7
-# from and PyCrypto from
-# (make sure to
-# install the version for Python 2.7). Save this script file as
-# ineptpdf.pyw and double-click on it to run it.
-#
-# Mac OS X users: Save this script file as ineptpdf.pyw. You can run this
-# program from the command line (pythonw ineptpdf.pyw) or by double-clicking
-# it when it has been associated with PythonLauncher.
-
-# Revision history:
-# 1 - Initial release
-# 2 - Improved determination of key-generation algorithm
-# 3 - Correctly handle PDF >=1.5 cross-reference streams
-# 4 - Removal of ciando's personal ID
-# 5 - Automated decryption of a complete directory
-# 6.1 - backward compatibility for 1.7.1 and old adeptkey.der
-# 7 - Get cross reference streams and object streams working for input.
-# Not yet supported on output but this only effects file size,
-# not functionality. (anon2)
-# 7.1 - Correct a problem when an old trailer is not followed by startxref
-# 7.2 - Correct malformed Mac OS resource forks for Stanza (anon2)
-# - Support for cross ref streams on output (decreases file size)
-# 7.3 - Correct bug in trailer with cross ref stream that caused the error
-# "The root object is missing or invalid" in Adobe Reader. (anon2)
-# 7.4 - Force all generation numbers in output file to be 0, like in v6.
-# Fallback code for wrong xref improved (search till last trailer
-# instead of first) (anon2)
-# 7.5 - allow support for OpenSSL to replace pycrypto on all platforms
-# implemented ARC4 interface to OpenSSL
-# fixed minor typos
-# 7.6 - backported AES and other fixes from version 8.4.48
-# 7.7 - On Windows try PyCrypto first and OpenSSL next
-# 7.8 - Modify interface to allow use of import
-# 7.9 - Bug fix for some session key errors when len(bookkey) > length required
-# 7.10 - Various tweaks to fix minor problems.
-# 7.11 - More tweaks to fix minor problems.
-# 7.12 - Revised to allow use in calibre plugins to eliminate need for duplicate code
-# 7.13 - Fixed erroneous mentions of ineptepub
-# 7.14 - moved unicode_argv call inside main for Windows DeDRM compatibility
-# 8.0 - Work if TkInter is missing
-# 8.0.1 - Broken Metadata fix.
-# 8.0.2 - Add additional check on DER file sanity
-# 8.0.3 - Remove erroneous check on DER file sanity
-# 8.0.4 - Completely remove erroneous check on DER file sanity
-# 8.0.5 - Do not process DRM-free documents
-# 8.0.6 - Replace use of float by Decimal for greater precision, and import tkFileDialog
-
-
-"""
-Decrypts Adobe ADEPT-encrypted PDF files.
-"""
-
-__license__ = 'GPL v3'
-__version__ = "8.0.6"
-
-import sys
-import os
-import re
-import zlib
-import struct
-import hashlib
-from decimal import *
-from itertools import chain, islice
-import xml.etree.ElementTree as etree
-
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
- def __init__(self, stream):
- self.stream = stream
- self.encoding = stream.encoding
- if self.encoding == None:
- self.encoding = "utf-8"
- def write(self, data):
- if isinstance(data,unicode):
- data = data.encode(self.encoding,"replace")
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-iswindows = sys.platform.startswith('win')
-isosx = sys.platform.startswith('darwin')
-
-def unicode_argv():
- if iswindows:
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'.
-
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- return [u"ineptpdf.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-
-class ADEPTError(Exception):
- pass
-
-
-import hashlib
-
-def SHA256(message):
- ctx = hashlib.sha256()
- ctx.update(message)
- return ctx.digest()
-
-
-def _load_crypto_libcrypto():
- from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
- Structure, c_ulong, create_string_buffer, cast
- from ctypes.util import find_library
-
- if sys.platform.startswith('win'):
- libcrypto = find_library('libeay32')
- else:
- libcrypto = find_library('crypto')
-
- if libcrypto is None:
- raise ADEPTError('libcrypto not found')
- libcrypto = CDLL(libcrypto)
-
- AES_MAXNR = 14
-
- RSA_NO_PADDING = 3
-
- c_char_pp = POINTER(c_char_p)
- c_int_p = POINTER(c_int)
-
- class AES_KEY(Structure):
- _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
- AES_KEY_p = POINTER(AES_KEY)
-
- class RC4_KEY(Structure):
- _fields_ = [('x', c_int), ('y', c_int), ('box', c_int * 256)]
- RC4_KEY_p = POINTER(RC4_KEY)
-
- class RSA(Structure):
- pass
- RSA_p = POINTER(RSA)
-
- def F(restype, name, argtypes):
- func = getattr(libcrypto, name)
- func.restype = restype
- func.argtypes = argtypes
- return func
-
- AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
- AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
-
- RC4_set_key = F(None,'RC4_set_key',[RC4_KEY_p, c_int, c_char_p])
- RC4_crypt = F(None,'RC4',[RC4_KEY_p, c_int, c_char_p, c_char_p])
-
- d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
- [RSA_p, c_char_pp, c_long])
- RSA_size = F(c_int, 'RSA_size', [RSA_p])
- RSA_private_decrypt = F(c_int, 'RSA_private_decrypt',
- [c_int, c_char_p, c_char_p, RSA_p, c_int])
- RSA_free = F(None, 'RSA_free', [RSA_p])
-
- class RSA(object):
- def __init__(self, der):
- buf = create_string_buffer(der)
- pp = c_char_pp(cast(buf, c_char_p))
- rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
- if rsa is None:
- raise ADEPTError('Error parsing ADEPT user key DER')
-
- def decrypt(self, from_):
- rsa = self._rsa
- to = create_string_buffer(RSA_size(rsa))
- dlen = RSA_private_decrypt(len(from_), from_, to, rsa,
- RSA_NO_PADDING)
- if dlen < 0:
- raise ADEPTError('RSA decryption failed')
- return to[1:dlen]
-
- def __del__(self):
- if self._rsa is not None:
- RSA_free(self._rsa)
- self._rsa = None
-
- class ARC4(object):
- @classmethod
- def new(cls, userkey):
- self = ARC4()
- self._blocksize = len(userkey)
- key = self._key = RC4_KEY()
- RC4_set_key(key, self._blocksize, userkey)
- return self
- def __init__(self):
- self._blocksize = 0
- self._key = None
- def decrypt(self, data):
- out = create_string_buffer(len(data))
- RC4_crypt(self._key, len(data), data, out)
- return out.raw
-
- class AES(object):
- MODE_CBC = 0
- @classmethod
- def new(cls, userkey, mode, iv):
- self = AES()
- self._blocksize = len(userkey)
- # mode is ignored since CBCMODE is only thing supported/used so far
- self._mode = mode
- if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
- raise ADEPTError('AES improper key used')
- return
- keyctx = self._keyctx = AES_KEY()
- self._iv = iv
- rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
- if rv < 0:
- raise ADEPTError('Failed to initialize AES key')
- return self
- def __init__(self):
- self._blocksize = 0
- self._keyctx = None
- self._iv = 0
- self._mode = 0
- def decrypt(self, data):
- out = create_string_buffer(len(data))
- rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self._iv, 0)
- if rv == 0:
- raise ADEPTError('AES decryption failed')
- return out.raw
-
- return (ARC4, RSA, AES)
-
-
-def _load_crypto_pycrypto():
- from Crypto.PublicKey import RSA as _RSA
- from Crypto.Cipher import ARC4 as _ARC4
- from Crypto.Cipher import AES as _AES
-
- # ASN.1 parsing code from tlslite
- class ASN1Error(Exception):
- pass
-
- class ASN1Parser(object):
- class Parser(object):
- def __init__(self, bytes):
- self.bytes = bytes
- self.index = 0
-
- def get(self, length):
- if self.index + length > len(self.bytes):
- raise ASN1Error("Error decoding ASN.1")
- x = 0
- for count in range(length):
- x <<= 8
- x |= self.bytes[self.index]
- self.index += 1
- return x
-
- def getFixBytes(self, lengthBytes):
- bytes = self.bytes[self.index : self.index+lengthBytes]
- self.index += lengthBytes
- return bytes
-
- def getVarBytes(self, lengthLength):
- lengthBytes = self.get(lengthLength)
- return self.getFixBytes(lengthBytes)
-
- def getFixList(self, length, lengthList):
- l = [0] * lengthList
- for x in range(lengthList):
- l[x] = self.get(length)
- return l
-
- def getVarList(self, length, lengthLength):
- lengthList = self.get(lengthLength)
- if lengthList % length != 0:
- raise ASN1Error("Error decoding ASN.1")
- lengthList = int(lengthList/length)
- l = [0] * lengthList
- for x in range(lengthList):
- l[x] = self.get(length)
- return l
-
- def startLengthCheck(self, lengthLength):
- self.lengthCheck = self.get(lengthLength)
- self.indexCheck = self.index
-
- def setLengthCheck(self, length):
- self.lengthCheck = length
- self.indexCheck = self.index
-
- def stopLengthCheck(self):
- if (self.index - self.indexCheck) != self.lengthCheck:
- raise ASN1Error("Error decoding ASN.1")
-
- def atLengthCheck(self):
- if (self.index - self.indexCheck) < self.lengthCheck:
- return False
- elif (self.index - self.indexCheck) == self.lengthCheck:
- return True
- else:
- raise ASN1Error("Error decoding ASN.1")
-
- def __init__(self, bytes):
- p = self.Parser(bytes)
- p.get(1)
- self.length = self._getASN1Length(p)
- self.value = p.getFixBytes(self.length)
-
- def getChild(self, which):
- p = self.Parser(self.value)
- for x in range(which+1):
- markIndex = p.index
- p.get(1)
- length = self._getASN1Length(p)
- p.getFixBytes(length)
- return ASN1Parser(p.bytes[markIndex:p.index])
-
- def _getASN1Length(self, p):
- firstLength = p.get(1)
- if firstLength<=127:
- return firstLength
- else:
- lengthLength = firstLength & 0x7F
- return p.get(lengthLength)
-
- class ARC4(object):
- @classmethod
- def new(cls, userkey):
- self = ARC4()
- self._arc4 = _ARC4.new(userkey)
- return self
- def __init__(self):
- self._arc4 = None
- def decrypt(self, data):
- return self._arc4.decrypt(data)
-
- class AES(object):
- MODE_CBC = _AES.MODE_CBC
- @classmethod
- def new(cls, userkey, mode, iv):
- self = AES()
- self._aes = _AES.new(userkey, mode, iv)
- return self
- def __init__(self):
- self._aes = None
- def decrypt(self, data):
- return self._aes.decrypt(data)
-
- class RSA(object):
- def __init__(self, der):
- key = ASN1Parser([ord(x) for x in der])
- key = [key.getChild(x).value for x in xrange(1, 4)]
- key = [self.bytesToNumber(v) for v in key]
- self._rsa = _RSA.construct(key)
-
- def bytesToNumber(self, bytes):
- total = 0L
- for byte in bytes:
- total = (total << 8) + byte
- return total
-
- def decrypt(self, data):
- return self._rsa.decrypt(data)
-
- return (ARC4, RSA, AES)
-
-def _load_crypto():
- ARC4 = RSA = AES = None
- cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
- if sys.platform.startswith('win'):
- cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
- for loader in cryptolist:
- try:
- ARC4, RSA, AES = loader()
- break
- except (ImportError, ADEPTError):
- pass
- return (ARC4, RSA, AES)
-ARC4, RSA, AES = _load_crypto()
-
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-
-# Do we generate cross reference streams on output?
-# 0 = never
-# 1 = only if present in input
-# 2 = always
-
-GEN_XREF_STM = 1
-
-# This is the value for the current document
-gen_xref_stm = False # will be set in PDFSerializer
-
-# PDF parsing routines from pdfminer, with changes for EBX_HANDLER
-
-# Utilities
-
-def choplist(n, seq):
- '''Groups every n elements of the list.'''
- r = []
- for x in seq:
- r.append(x)
- if len(r) == n:
- yield tuple(r)
- r = []
- return
-
-def nunpack(s, default=0):
- '''Unpacks up to 4 bytes big endian.'''
- l = len(s)
- if not l:
- return default
- elif l == 1:
- return ord(s)
- elif l == 2:
- return struct.unpack('>H', s)[0]
- elif l == 3:
- return struct.unpack('>L', '\x00'+s)[0]
- elif l == 4:
- return struct.unpack('>L', s)[0]
- else:
- return TypeError('invalid length: %d' % l)
-
-
-STRICT = 0
-
-
-# PS Exceptions
-
-class PSException(Exception): pass
-class PSEOF(PSException): pass
-class PSSyntaxError(PSException): pass
-class PSTypeError(PSException): pass
-class PSValueError(PSException): pass
-
-
-# Basic PostScript Types
-
-
-# PSLiteral
-class PSObject(object): pass
-
-class PSLiteral(PSObject):
- '''
- PS literals (e.g. "/Name").
- Caution: Never create these objects directly.
- Use PSLiteralTable.intern() instead.
- '''
- def __init__(self, name):
- self.name = name
- return
-
- def __repr__(self):
- name = []
- for char in self.name:
- if not char.isalnum():
- char = '#%02x' % ord(char)
- name.append(char)
- return '/%s' % ''.join(name)
-
-# PSKeyword
-class PSKeyword(PSObject):
- '''
- PS keywords (e.g. "showpage").
- Caution: Never create these objects directly.
- Use PSKeywordTable.intern() instead.
- '''
- def __init__(self, name):
- self.name = name
- return
-
- def __repr__(self):
- return self.name
-
-# PSSymbolTable
-class PSSymbolTable(object):
-
- '''
- Symbol table that stores PSLiteral or PSKeyword.
- '''
-
- def __init__(self, classe):
- self.dic = {}
- self.classe = classe
- return
-
- def intern(self, name):
- if name in self.dic:
- lit = self.dic[name]
- else:
- lit = self.classe(name)
- self.dic[name] = lit
- return lit
-
-PSLiteralTable = PSSymbolTable(PSLiteral)
-PSKeywordTable = PSSymbolTable(PSKeyword)
-LIT = PSLiteralTable.intern
-KWD = PSKeywordTable.intern
-KEYWORD_BRACE_BEGIN = KWD('{')
-KEYWORD_BRACE_END = KWD('}')
-KEYWORD_ARRAY_BEGIN = KWD('[')
-KEYWORD_ARRAY_END = KWD(']')
-KEYWORD_DICT_BEGIN = KWD('<<')
-KEYWORD_DICT_END = KWD('>>')
-
-
-def literal_name(x):
- if not isinstance(x, PSLiteral):
- if STRICT:
- raise PSTypeError('Literal required: %r' % x)
- else:
- return str(x)
- return x.name
-
-def keyword_name(x):
- if not isinstance(x, PSKeyword):
- if STRICT:
- raise PSTypeError('Keyword required: %r' % x)
- else:
- return str(x)
- return x.name
-
-
-## PSBaseParser
-##
-EOL = re.compile(r'[\r\n]')
-SPC = re.compile(r'\s')
-NONSPC = re.compile(r'\S')
-HEX = re.compile(r'[0-9a-fA-F]')
-END_LITERAL = re.compile(r'[#/%\[\]()<>{}\s]')
-END_HEX_STRING = re.compile(r'[^\s0-9a-fA-F]')
-HEX_PAIR = re.compile(r'[0-9a-fA-F]{2}|.')
-END_NUMBER = re.compile(r'[^0-9]')
-END_KEYWORD = re.compile(r'[#/%\[\]()<>{}\s]')
-END_STRING = re.compile(r'[()\134]')
-OCT_STRING = re.compile(r'[0-7]')
-ESC_STRING = { 'b':8, 't':9, 'n':10, 'f':12, 'r':13, '(':40, ')':41, '\\':92 }
-
-class PSBaseParser(object):
-
- '''
- Most basic PostScript parser that performs only basic tokenization.
- '''
- BUFSIZ = 4096
-
- def __init__(self, fp):
- self.fp = fp
- self.seek(0)
- return
-
- def __repr__(self):
- return '' % (self.fp, self.bufpos)
-
- def flush(self):
- return
-
- def close(self):
- self.flush()
- return
-
- def tell(self):
- return self.bufpos+self.charpos
-
- def poll(self, pos=None, n=80):
- pos0 = self.fp.tell()
- if not pos:
- pos = self.bufpos+self.charpos
- self.fp.seek(pos)
- ##print >>sys.stderr, 'poll(%d): %r' % (pos, self.fp.read(n))
- self.fp.seek(pos0)
- return
-
- def seek(self, pos):
- '''
- Seeks the parser to the given position.
- '''
- self.fp.seek(pos)
- # reset the status for nextline()
- self.bufpos = pos
- self.buf = ''
- self.charpos = 0
- # reset the status for nexttoken()
- self.parse1 = self.parse_main
- self.tokens = []
- return
-
- def fillbuf(self):
- if self.charpos < len(self.buf): return
- # fetch next chunk.
- self.bufpos = self.fp.tell()
- self.buf = self.fp.read(self.BUFSIZ)
- if not self.buf:
- raise PSEOF('Unexpected EOF')
- self.charpos = 0
- return
-
- def parse_main(self, s, i):
- m = NONSPC.search(s, i)
- if not m:
- return (self.parse_main, len(s))
- j = m.start(0)
- c = s[j]
- self.tokenstart = self.bufpos+j
- if c == '%':
- self.token = '%'
- return (self.parse_comment, j+1)
- if c == '/':
- self.token = ''
- return (self.parse_literal, j+1)
- if c in '-+' or c.isdigit():
- self.token = c
- return (self.parse_number, j+1)
- if c == '.':
- self.token = c
- return (self.parse_decimal, j+1)
- if c.isalpha():
- self.token = c
- return (self.parse_keyword, j+1)
- if c == '(':
- self.token = ''
- self.paren = 1
- return (self.parse_string, j+1)
- if c == '<':
- self.token = ''
- return (self.parse_wopen, j+1)
- if c == '>':
- self.token = ''
- return (self.parse_wclose, j+1)
- self.add_token(KWD(c))
- return (self.parse_main, j+1)
-
- def add_token(self, obj):
- self.tokens.append((self.tokenstart, obj))
- return
-
- def parse_comment(self, s, i):
- m = EOL.search(s, i)
- if not m:
- self.token += s[i:]
- return (self.parse_comment, len(s))
- j = m.start(0)
- self.token += s[i:j]
- # We ignore comments.
- #self.tokens.append(self.token)
- return (self.parse_main, j)
-
- def parse_literal(self, s, i):
- m = END_LITERAL.search(s, i)
- if not m:
- self.token += s[i:]
- return (self.parse_literal, len(s))
- j = m.start(0)
- self.token += s[i:j]
- c = s[j]
- if c == '#':
- self.hex = ''
- return (self.parse_literal_hex, j+1)
- self.add_token(LIT(self.token))
- return (self.parse_main, j)
-
- def parse_literal_hex(self, s, i):
- c = s[i]
- if HEX.match(c) and len(self.hex) < 2:
- self.hex += c
- return (self.parse_literal_hex, i+1)
- if self.hex:
- self.token += chr(int(self.hex, 16))
- return (self.parse_literal, i)
-
- def parse_number(self, s, i):
- m = END_NUMBER.search(s, i)
- if not m:
- self.token += s[i:]
- return (self.parse_number, len(s))
- j = m.start(0)
- self.token += s[i:j]
- c = s[j]
- if c == '.':
- self.token += c
- return (self.parse_decimal, j+1)
- try:
- self.add_token(int(self.token))
- except ValueError:
- pass
- return (self.parse_main, j)
-
- def parse_decimal(self, s, i):
- m = END_NUMBER.search(s, i)
- if not m:
- self.token += s[i:]
- return (self.parse_decimal, len(s))
- j = m.start(0)
- self.token += s[i:j]
- self.add_token(Decimal(self.token))
- return (self.parse_main, j)
-
- def parse_keyword(self, s, i):
- m = END_KEYWORD.search(s, i)
- if not m:
- self.token += s[i:]
- return (self.parse_keyword, len(s))
- j = m.start(0)
- self.token += s[i:j]
- if self.token == 'true':
- token = True
- elif self.token == 'false':
- token = False
- else:
- token = KWD(self.token)
- self.add_token(token)
- return (self.parse_main, j)
-
- def parse_string(self, s, i):
- m = END_STRING.search(s, i)
- if not m:
- self.token += s[i:]
- return (self.parse_string, len(s))
- j = m.start(0)
- self.token += s[i:j]
- c = s[j]
- if c == '\\':
- self.oct = ''
- return (self.parse_string_1, j+1)
- if c == '(':
- self.paren += 1
- self.token += c
- return (self.parse_string, j+1)
- if c == ')':
- self.paren -= 1
- if self.paren:
- self.token += c
- return (self.parse_string, j+1)
- self.add_token(self.token)
- return (self.parse_main, j+1)
- def parse_string_1(self, s, i):
- c = s[i]
- if OCT_STRING.match(c) and len(self.oct) < 3:
- self.oct += c
- return (self.parse_string_1, i+1)
- if self.oct:
- self.token += chr(int(self.oct, 8))
- return (self.parse_string, i)
- if c in ESC_STRING:
- self.token += chr(ESC_STRING[c])
- return (self.parse_string, i+1)
-
- def parse_wopen(self, s, i):
- c = s[i]
- if c.isspace() or HEX.match(c):
- return (self.parse_hexstring, i)
- if c == '<':
- self.add_token(KEYWORD_DICT_BEGIN)
- i += 1
- return (self.parse_main, i)
-
- def parse_wclose(self, s, i):
- c = s[i]
- if c == '>':
- self.add_token(KEYWORD_DICT_END)
- i += 1
- return (self.parse_main, i)
-
- def parse_hexstring(self, s, i):
- m = END_HEX_STRING.search(s, i)
- if not m:
- self.token += s[i:]
- return (self.parse_hexstring, len(s))
- j = m.start(0)
- self.token += s[i:j]
- token = HEX_PAIR.sub(lambda m: chr(int(m.group(0), 16)),
- SPC.sub('', self.token))
- self.add_token(token)
- return (self.parse_main, j)
-
- def nexttoken(self):
- while not self.tokens:
- self.fillbuf()
- (self.parse1, self.charpos) = self.parse1(self.buf, self.charpos)
- token = self.tokens.pop(0)
- return token
-
- def nextline(self):
- '''
- Fetches a next line that ends either with \\r or \\n.
- '''
- linebuf = ''
- linepos = self.bufpos + self.charpos
- eol = False
- while 1:
- self.fillbuf()
- if eol:
- c = self.buf[self.charpos]
- # handle '\r\n'
- if c == '\n':
- linebuf += c
- self.charpos += 1
- break
- m = EOL.search(self.buf, self.charpos)
- if m:
- linebuf += self.buf[self.charpos:m.end(0)]
- self.charpos = m.end(0)
- if linebuf[-1] == '\r':
- eol = True
- else:
- break
- else:
- linebuf += self.buf[self.charpos:]
- self.charpos = len(self.buf)
- return (linepos, linebuf)
-
- def revreadlines(self):
- '''
- Fetches a next line backword. This is used to locate
- the trailers at the end of a file.
- '''
- self.fp.seek(0, 2)
- pos = self.fp.tell()
- buf = ''
- while 0 < pos:
- prevpos = pos
- pos = max(0, pos-self.BUFSIZ)
- self.fp.seek(pos)
- s = self.fp.read(prevpos-pos)
- if not s: break
- while 1:
- n = max(s.rfind('\r'), s.rfind('\n'))
- if n == -1:
- buf = s + buf
- break
- yield s[n:]+buf
- s = s[:n]
- buf = ''
- return
-
-
-## PSStackParser
-##
-class PSStackParser(PSBaseParser):
-
- def __init__(self, fp):
- PSBaseParser.__init__(self, fp)
- self.reset()
- return
-
- def reset(self):
- self.context = []
- self.curtype = None
- self.curstack = []
- self.results = []
- return
-
- def seek(self, pos):
- PSBaseParser.seek(self, pos)
- self.reset()
- return
-
- def push(self, *objs):
- self.curstack.extend(objs)
- return
- def pop(self, n):
- objs = self.curstack[-n:]
- self.curstack[-n:] = []
- return objs
- def popall(self):
- objs = self.curstack
- self.curstack = []
- return objs
- def add_results(self, *objs):
- self.results.extend(objs)
- return
-
- def start_type(self, pos, type):
- self.context.append((pos, self.curtype, self.curstack))
- (self.curtype, self.curstack) = (type, [])
- return
- def end_type(self, type):
- if self.curtype != type:
- raise PSTypeError('Type mismatch: %r != %r' % (self.curtype, type))
- objs = [ obj for (_,obj) in self.curstack ]
- (pos, self.curtype, self.curstack) = self.context.pop()
- return (pos, objs)
-
- def do_keyword(self, pos, token):
- return
-
- def nextobject(self, direct=False):
- '''
- Yields a list of objects: keywords, literals, strings,
- numbers, arrays and dictionaries. Arrays and dictionaries
- are represented as Python sequence and dictionaries.
- '''
- while not self.results:
- (pos, token) = self.nexttoken()
- ##print (pos,token), (self.curtype, self.curstack)
- if (isinstance(token, int) or
- isinstance(token, Decimal) or
- isinstance(token, bool) or
- isinstance(token, str) or
- isinstance(token, PSLiteral)):
- # normal token
- self.push((pos, token))
- elif token == KEYWORD_ARRAY_BEGIN:
- # begin array
- self.start_type(pos, 'a')
- elif token == KEYWORD_ARRAY_END:
- # end array
- try:
- self.push(self.end_type('a'))
- except PSTypeError:
- if STRICT: raise
- elif token == KEYWORD_DICT_BEGIN:
- # begin dictionary
- self.start_type(pos, 'd')
- elif token == KEYWORD_DICT_END:
- # end dictionary
- try:
- (pos, objs) = self.end_type('d')
- if len(objs) % 2 != 0:
- print "Incomplete dictionary construct"
- objs.append("") # this isn't necessary.
- # temporary fix. is this due to rental books?
- # raise PSSyntaxError(
- # 'Invalid dictionary construct: %r' % objs)
- d = dict((literal_name(k), v) \
- for (k,v) in choplist(2, objs))
- self.push((pos, d))
- except PSTypeError:
- if STRICT: raise
- else:
- self.do_keyword(pos, token)
- if self.context:
- continue
- else:
- if direct:
- return self.pop(1)[0]
- self.flush()
- obj = self.results.pop(0)
- return obj
-
-
-LITERAL_CRYPT = PSLiteralTable.intern('Crypt')
-LITERALS_FLATE_DECODE = (PSLiteralTable.intern('FlateDecode'), PSLiteralTable.intern('Fl'))
-LITERALS_LZW_DECODE = (PSLiteralTable.intern('LZWDecode'), PSLiteralTable.intern('LZW'))
-LITERALS_ASCII85_DECODE = (PSLiteralTable.intern('ASCII85Decode'), PSLiteralTable.intern('A85'))
-
-
-## PDF Objects
-##
-class PDFObject(PSObject): pass
-
-class PDFException(PSException): pass
-class PDFTypeError(PDFException): pass
-class PDFValueError(PDFException): pass
-class PDFNotImplementedError(PSException): pass
-
-
-## PDFObjRef
-##
-class PDFObjRef(PDFObject):
-
- def __init__(self, doc, objid, genno):
- if objid == 0:
- if STRICT:
- raise PDFValueError('PDF object id cannot be 0.')
- self.doc = doc
- self.objid = objid
- self.genno = genno
- return
-
- def __repr__(self):
- return '' % (self.objid, self.genno)
-
- def resolve(self):
- return self.doc.getobj(self.objid)
-
-
-# resolve
-def resolve1(x):
- '''
- Resolve an object. If this is an array or dictionary,
- it may still contains some indirect objects inside.
- '''
- while isinstance(x, PDFObjRef):
- x = x.resolve()
- return x
-
-def resolve_all(x):
- '''
- Recursively resolve X and all the internals.
- Make sure there is no indirect reference within the nested object.
- This procedure might be slow.
- '''
- while isinstance(x, PDFObjRef):
- x = x.resolve()
- if isinstance(x, list):
- x = [ resolve_all(v) for v in x ]
- elif isinstance(x, dict):
- for (k,v) in x.iteritems():
- x[k] = resolve_all(v)
- return x
-
-def decipher_all(decipher, objid, genno, x):
- '''
- Recursively decipher X.
- '''
- if isinstance(x, str):
- return decipher(objid, genno, x)
- decf = lambda v: decipher_all(decipher, objid, genno, v)
- if isinstance(x, list):
- x = [decf(v) for v in x]
- elif isinstance(x, dict):
- x = dict((k, decf(v)) for (k, v) in x.iteritems())
- return x
-
-
-# Type cheking
-def int_value(x):
- x = resolve1(x)
- if not isinstance(x, int):
- if STRICT:
- raise PDFTypeError('Integer required: %r' % x)
- return 0
- return x
-
-def decimal_value(x):
- x = resolve1(x)
- if not isinstance(x, Decimal):
- if STRICT:
- raise PDFTypeError('Decimal required: %r' % x)
- return 0.0
- return x
-
-def num_value(x):
- x = resolve1(x)
- if not (isinstance(x, int) or isinstance(x, Decimal)):
- if STRICT:
- raise PDFTypeError('Int or Float required: %r' % x)
- return 0
- return x
-
-def str_value(x):
- x = resolve1(x)
- if not isinstance(x, str):
- if STRICT:
- raise PDFTypeError('String required: %r' % x)
- return ''
- return x
-
-def list_value(x):
- x = resolve1(x)
- if not (isinstance(x, list) or isinstance(x, tuple)):
- if STRICT:
- raise PDFTypeError('List required: %r' % x)
- return []
- return x
-
-def dict_value(x):
- x = resolve1(x)
- if not isinstance(x, dict):
- if STRICT:
- raise PDFTypeError('Dict required: %r' % x)
- return {}
- return x
-
-def stream_value(x):
- x = resolve1(x)
- if not isinstance(x, PDFStream):
- if STRICT:
- raise PDFTypeError('PDFStream required: %r' % x)
- return PDFStream({}, '')
- return x
-
-# ascii85decode(data)
-def ascii85decode(data):
- n = b = 0
- out = ''
- for c in data:
- if '!' <= c and c <= 'u':
- n += 1
- b = b*85+(ord(c)-33)
- if n == 5:
- out += struct.pack('>L',b)
- n = b = 0
- elif c == 'z':
- assert n == 0
- out += '\0\0\0\0'
- elif c == '~':
- if n:
- for _ in range(5-n):
- b = b*85+84
- out += struct.pack('>L',b)[:n-1]
- break
- return out
-
-
-## PDFStream type
-class PDFStream(PDFObject):
- def __init__(self, dic, rawdata, decipher=None):
- length = int_value(dic.get('Length', 0))
- eol = rawdata[length:]
- # quick and dirty fix for false length attribute,
- # might not work if the pdf stream parser has a problem
- if decipher != None and decipher.__name__ == 'decrypt_aes':
- if (len(rawdata) % 16) != 0:
- cutdiv = len(rawdata) // 16
- rawdata = rawdata[:16*cutdiv]
- else:
- if eol in ('\r', '\n', '\r\n'):
- rawdata = rawdata[:length]
-
- self.dic = dic
- self.rawdata = rawdata
- self.decipher = decipher
- self.data = None
- self.decdata = None
- self.objid = None
- self.genno = None
- return
-
- def set_objid(self, objid, genno):
- self.objid = objid
- self.genno = genno
- return
-
- def __repr__(self):
- if self.rawdata:
- return '' % \
- (self.objid, len(self.rawdata), self.dic)
- else:
- return '' % \
- (self.objid, len(self.data), self.dic)
-
- def decode(self):
- assert self.data is None and self.rawdata is not None
- data = self.rawdata
- if self.decipher:
- # Handle encryption
- data = self.decipher(self.objid, self.genno, data)
- if gen_xref_stm:
- self.decdata = data # keep decrypted data
- if 'Filter' not in self.dic:
- self.data = data
- self.rawdata = None
- ##print self.dict
- return
- filters = self.dic['Filter']
- if not isinstance(filters, list):
- filters = [ filters ]
- for f in filters:
- if f in LITERALS_FLATE_DECODE:
- # will get errors if the document is encrypted.
- data = zlib.decompress(data)
- elif f in LITERALS_LZW_DECODE:
- data = ''.join(LZWDecoder(StringIO(data)).run())
- elif f in LITERALS_ASCII85_DECODE:
- data = ascii85decode(data)
- elif f == LITERAL_CRYPT:
- raise PDFNotImplementedError('/Crypt filter is unsupported')
- else:
- raise PDFNotImplementedError('Unsupported filter: %r' % f)
- # apply predictors
- if 'DP' in self.dic:
- params = self.dic['DP']
- else:
- params = self.dic.get('DecodeParms', {})
- if 'Predictor' in params:
- pred = int_value(params['Predictor'])
- if pred:
- if pred != 12:
- raise PDFNotImplementedError(
- 'Unsupported predictor: %r' % pred)
- if 'Columns' not in params:
- raise PDFValueError(
- 'Columns undefined for predictor=12')
- columns = int_value(params['Columns'])
- buf = ''
- ent0 = '\x00' * columns
- for i in xrange(0, len(data), columns+1):
- pred = data[i]
- ent1 = data[i+1:i+1+columns]
- if pred == '\x02':
- ent1 = ''.join(chr((ord(a)+ord(b)) & 255) \
- for (a,b) in zip(ent0,ent1))
- buf += ent1
- ent0 = ent1
- data = buf
- self.data = data
- self.rawdata = None
- return
-
- def get_data(self):
- if self.data is None:
- self.decode()
- return self.data
-
- def get_rawdata(self):
- return self.rawdata
-
- def get_decdata(self):
- if self.decdata is not None:
- return self.decdata
- data = self.rawdata
- if self.decipher and data:
- # Handle encryption
- data = self.decipher(self.objid, self.genno, data)
- return data
-
-
-## PDF Exceptions
-##
-class PDFSyntaxError(PDFException): pass
-class PDFNoValidXRef(PDFSyntaxError): pass
-class PDFEncryptionError(PDFException): pass
-class PDFPasswordIncorrect(PDFEncryptionError): pass
-
-# some predefined literals and keywords.
-LITERAL_OBJSTM = PSLiteralTable.intern('ObjStm')
-LITERAL_XREF = PSLiteralTable.intern('XRef')
-LITERAL_PAGE = PSLiteralTable.intern('Page')
-LITERAL_PAGES = PSLiteralTable.intern('Pages')
-LITERAL_CATALOG = PSLiteralTable.intern('Catalog')
-
-
-## XRefs
-##
-
-## PDFXRef
-##
-class PDFXRef(object):
-
- def __init__(self):
- self.offsets = None
- return
-
- def __repr__(self):
- return '' % len(self.offsets)
-
- def objids(self):
- return self.offsets.iterkeys()
-
- def load(self, parser):
- self.offsets = {}
- while 1:
- try:
- (pos, line) = parser.nextline()
- except PSEOF:
- raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
- if not line:
- raise PDFNoValidXRef('Premature eof: %r' % parser)
- if line.startswith('trailer'):
- parser.seek(pos)
- break
- f = line.strip().split(' ')
- if len(f) != 2:
- raise PDFNoValidXRef('Trailer not found: %r: line=%r' % (parser, line))
- try:
- (start, nobjs) = map(int, f)
- except ValueError:
- raise PDFNoValidXRef('Invalid line: %r: line=%r' % (parser, line))
- for objid in xrange(start, start+nobjs):
- try:
- (_, line) = parser.nextline()
- except PSEOF:
- raise PDFNoValidXRef('Unexpected EOF - file corrupted?')
- f = line.strip().split(' ')
- if len(f) != 3:
- raise PDFNoValidXRef('Invalid XRef format: %r, line=%r' % (parser, line))
- (pos, genno, use) = f
- if use != 'n': continue
- self.offsets[objid] = (int(genno), int(pos))
- self.load_trailer(parser)
- return
-
- KEYWORD_TRAILER = PSKeywordTable.intern('trailer')
- def load_trailer(self, parser):
- try:
- (_,kwd) = parser.nexttoken()
- assert kwd is self.KEYWORD_TRAILER
- (_,dic) = parser.nextobject(direct=True)
- except PSEOF:
- x = parser.pop(1)
- if not x:
- raise PDFNoValidXRef('Unexpected EOF - file corrupted')
- (_,dic) = x[0]
- self.trailer = dict_value(dic)
- return
-
- def getpos(self, objid):
- try:
- (genno, pos) = self.offsets[objid]
- except KeyError:
- raise
- return (None, pos)
-
-
-## PDFXRefStream
-##
-class PDFXRefStream(object):
-
- def __init__(self):
- self.index = None
- self.data = None
- self.entlen = None
- self.fl1 = self.fl2 = self.fl3 = None
- return
-
- def __repr__(self):
- return '' % self.index
-
- def objids(self):
- for first, size in self.index:
- for objid in xrange(first, first + size):
- yield objid
-
- def load(self, parser, debug=0):
- (_,objid) = parser.nexttoken() # ignored
- (_,genno) = parser.nexttoken() # ignored
- (_,kwd) = parser.nexttoken()
- (_,stream) = parser.nextobject()
- if not isinstance(stream, PDFStream) or \
- stream.dic['Type'] is not LITERAL_XREF:
- raise PDFNoValidXRef('Invalid PDF stream spec.')
- size = stream.dic['Size']
- index = stream.dic.get('Index', (0,size))
- self.index = zip(islice(index, 0, None, 2),
- islice(index, 1, None, 2))
- (self.fl1, self.fl2, self.fl3) = stream.dic['W']
- self.data = stream.get_data()
- self.entlen = self.fl1+self.fl2+self.fl3
- self.trailer = stream.dic
- return
-
- def getpos(self, objid):
- offset = 0
- for first, size in self.index:
- if first <= objid and objid < (first + size):
- break
- offset += size
- else:
- raise KeyError(objid)
- i = self.entlen * ((objid - first) + offset)
- ent = self.data[i:i+self.entlen]
- f1 = nunpack(ent[:self.fl1], 1)
- if f1 == 1:
- pos = nunpack(ent[self.fl1:self.fl1+self.fl2])
- genno = nunpack(ent[self.fl1+self.fl2:])
- return (None, pos)
- elif f1 == 2:
- objid = nunpack(ent[self.fl1:self.fl1+self.fl2])
- index = nunpack(ent[self.fl1+self.fl2:])
- return (objid, index)
- # this is a free object
- raise KeyError(objid)
-
-
-## PDFDocument
-##
-## A PDFDocument object represents a PDF document.
-## Since a PDF file is usually pretty big, normally it is not loaded
-## at once. Rather it is parsed dynamically as processing goes.
-## A PDF parser is associated with the document.
-##
-class PDFDocument(object):
-
- def __init__(self):
- self.xrefs = []
- self.objs = {}
- self.parsed_objs = {}
- self.root = None
- self.catalog = None
- self.parser = None
- self.encryption = None
- self.decipher = None
- return
-
- # set_parser(parser)
- # Associates the document with an (already initialized) parser object.
- def set_parser(self, parser):
- if self.parser: return
- self.parser = parser
- # The document is set to be temporarily ready during collecting
- # all the basic information about the document, e.g.
- # the header, the encryption information, and the access rights
- # for the document.
- self.ready = True
- # Retrieve the information of each header that was appended
- # (maybe multiple times) at the end of the document.
- self.xrefs = parser.read_xref()
- for xref in self.xrefs:
- trailer = xref.trailer
- if not trailer: continue
-
- # If there's an encryption info, remember it.
- if 'Encrypt' in trailer:
- #assert not self.encryption
- try:
- self.encryption = (list_value(trailer['ID']),
- dict_value(trailer['Encrypt']))
- # fix for bad files
- except:
- self.encryption = ('ffffffffffffffffffffffffffffffffffff',
- dict_value(trailer['Encrypt']))
- if 'Root' in trailer:
- self.set_root(dict_value(trailer['Root']))
- break
- else:
- raise PDFSyntaxError('No /Root object! - Is this really a PDF?')
- # The document is set to be non-ready again, until all the
- # proper initialization (asking the password key and
- # verifying the access permission, so on) is finished.
- self.ready = False
- return
-
- # set_root(root)
- # Set the Root dictionary of the document.
- # Each PDF file must have exactly one /Root dictionary.
- def set_root(self, root):
- self.root = root
- self.catalog = dict_value(self.root)
- if self.catalog.get('Type') is not LITERAL_CATALOG:
- if STRICT:
- raise PDFSyntaxError('Catalog not found!')
- return
- # initialize(password='')
- # Perform the initialization with a given password.
- # This step is mandatory even if there's no password associated
- # with the document.
- def initialize(self, password=''):
- if not self.encryption:
- self.is_printable = self.is_modifiable = self.is_extractable = True
- self.ready = True
- raise PDFEncryptionError('Document is not encrypted.')
- return
- (docid, param) = self.encryption
- type = literal_name(param['Filter'])
- if type == 'Adobe.APS':
- return self.initialize_adobe_ps(password, docid, param)
- if type == 'Standard':
- return self.initialize_standard(password, docid, param)
- if type == 'EBX_HANDLER':
- return self.initialize_ebx(password, docid, param)
- raise PDFEncryptionError('Unknown filter: param=%r' % param)
-
- def initialize_adobe_ps(self, password, docid, param):
- global KEYFILEPATH
- self.decrypt_key = self.genkey_adobe_ps(param)
- self.genkey = self.genkey_v4
- self.decipher = self.decrypt_aes
- self.ready = True
- return
-
- def genkey_adobe_ps(self, param):
- # nice little offline principal keys dictionary
- # global static principal key for German Onleihe / Bibliothek Digital
- principalkeys = { 'bibliothek-digital.de': 'rRwGv2tbpKov1krvv7PO0ws9S436/lArPlfipz5Pqhw='.decode('base64')}
- self.is_printable = self.is_modifiable = self.is_extractable = True
- length = int_value(param.get('Length', 0)) / 8
- edcdata = str_value(param.get('EDCData')).decode('base64')
- pdrllic = str_value(param.get('PDRLLic')).decode('base64')
- pdrlpol = str_value(param.get('PDRLPol')).decode('base64')
- edclist = []
- for pair in edcdata.split('\n'):
- edclist.append(pair)
- # principal key request
- for key in principalkeys:
- if key in pdrllic:
- principalkey = principalkeys[key]
- else:
- raise ADEPTError('Cannot find principal key for this pdf')
- shakey = SHA256(principalkey)
- ivector = 16 * chr(0)
- plaintext = AES.new(shakey,AES.MODE_CBC,ivector).decrypt(edclist[9].decode('base64'))
- if plaintext[-16:] != 16 * chr(16):
- raise ADEPTError('Offlinekey cannot be decrypted, aborting ...')
- pdrlpol = AES.new(plaintext[16:32],AES.MODE_CBC,edclist[2].decode('base64')).decrypt(pdrlpol)
- if ord(pdrlpol[-1]) < 1 or ord(pdrlpol[-1]) > 16:
- raise ADEPTError('Could not decrypt PDRLPol, aborting ...')
- else:
- cutter = -1 * ord(pdrlpol[-1])
- pdrlpol = pdrlpol[:cutter]
- return plaintext[:16]
-
- PASSWORD_PADDING = '(\xbfN^Nu\x8aAd\x00NV\xff\xfa\x01\x08..' \
- '\x00\xb6\xd0h>\x80/\x0c\xa9\xfedSiz'
- # experimental aes pw support
- def initialize_standard(self, password, docid, param):
- # copy from a global variable
- V = int_value(param.get('V', 0))
- if (V <=0 or V > 4):
- raise PDFEncryptionError('Unknown algorithm: param=%r' % param)
- length = int_value(param.get('Length', 40)) # Key length (bits)
- O = str_value(param['O'])
- R = int_value(param['R']) # Revision
- if 5 <= R:
- raise PDFEncryptionError('Unknown revision: %r' % R)
- U = str_value(param['U'])
- P = int_value(param['P'])
- try:
- EncMetadata = str_value(param['EncryptMetadata'])
- except:
- EncMetadata = 'True'
- self.is_printable = bool(P & 4)
- self.is_modifiable = bool(P & 8)
- self.is_extractable = bool(P & 16)
- self.is_annotationable = bool(P & 32)
- self.is_formsenabled = bool(P & 256)
- self.is_textextractable = bool(P & 512)
- self.is_assemblable = bool(P & 1024)
- self.is_formprintable = bool(P & 2048)
- # Algorithm 3.2
- password = (password+self.PASSWORD_PADDING)[:32] # 1
- hash = hashlib.md5(password) # 2
- hash.update(O) # 3
- hash.update(struct.pack('= 3:
- # Algorithm 3.5
- hash = hashlib.md5(self.PASSWORD_PADDING) # 2
- hash.update(docid[0]) # 3
- x = ARC4.new(key).decrypt(hash.digest()[:16]) # 4
- for i in xrange(1,19+1):
- k = ''.join( chr(ord(c) ^ i) for c in key )
- x = ARC4.new(k).decrypt(x)
- u1 = x+x # 32bytes total
- if R == 2:
- is_authenticated = (u1 == U)
- else:
- is_authenticated = (u1[:16] == U[:16])
- if not is_authenticated:
- raise ADEPTError('Password is not correct.')
- self.decrypt_key = key
- # genkey method
- if V == 1 or V == 2:
- self.genkey = self.genkey_v2
- elif V == 3:
- self.genkey = self.genkey_v3
- elif V == 4:
- self.genkey = self.genkey_v2
- #self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
- # rc4
- if V != 4:
- self.decipher = self.decipher_rc4 # XXX may be AES
- # aes
- elif V == 4 and Length == 128:
- elf.decipher = self.decipher_aes
- elif V == 4 and Length == 256:
- raise PDFNotImplementedError('AES256 encryption is currently unsupported')
- self.ready = True
- return
-
- def initialize_ebx(self, password, docid, param):
- self.is_printable = self.is_modifiable = self.is_extractable = True
- rsa = RSA(password)
- length = int_value(param.get('Length', 0)) / 8
- rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
- rights = zlib.decompress(rights, -15)
- rights = etree.fromstring(rights)
- expr = './/{http://ns.adobe.com/adept}encryptedKey'
- bookkey = ''.join(rights.findtext(expr)).decode('base64')
- bookkey = rsa.decrypt(bookkey)
- if bookkey[0] != '\x02':
- raise ADEPTError('error decrypting book session key')
- index = bookkey.index('\0') + 1
- bookkey = bookkey[index:]
- ebx_V = int_value(param.get('V', 4))
- ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
- # added because of improper booktype / decryption book session key errors
- if length > 0:
- if len(bookkey) == length:
- if ebx_V == 3:
- V = 3
- else:
- V = 2
- elif len(bookkey) == length + 1:
- V = ord(bookkey[0])
- bookkey = bookkey[1:]
- else:
- 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 "bookkey[0] is %d" % ord(bookkey[0])
- raise ADEPTError('error decrypting book session key - mismatched length')
- else:
- # proper length unknown try with whatever you have
- 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 "bookkey[0] is %d" % ord(bookkey[0])
- if ebx_V == 3:
- V = 3
- else:
- V = 2
- self.decrypt_key = bookkey
- self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2
- self.decipher = self.decrypt_rc4
- self.ready = True
- return
-
- # genkey functions
- def genkey_v2(self, objid, genno):
- objid = struct.pack(' PDFObjStmRef.maxindex:
- PDFObjStmRef.maxindex = index
-
-
-## PDFParser
-##
-class PDFParser(PSStackParser):
-
- def __init__(self, doc, fp):
- PSStackParser.__init__(self, fp)
- self.doc = doc
- self.doc.set_parser(self)
- return
-
- def __repr__(self):
- return ''
-
- KEYWORD_R = PSKeywordTable.intern('R')
- KEYWORD_ENDOBJ = PSKeywordTable.intern('endobj')
- KEYWORD_STREAM = PSKeywordTable.intern('stream')
- KEYWORD_XREF = PSKeywordTable.intern('xref')
- KEYWORD_STARTXREF = PSKeywordTable.intern('startxref')
- def do_keyword(self, pos, token):
- if token in (self.KEYWORD_XREF, self.KEYWORD_STARTXREF):
- self.add_results(*self.pop(1))
- return
- if token is self.KEYWORD_ENDOBJ:
- self.add_results(*self.pop(4))
- return
-
- if token is self.KEYWORD_R:
- # reference to indirect object
- try:
- ((_,objid), (_,genno)) = self.pop(2)
- (objid, genno) = (int(objid), int(genno))
- obj = PDFObjRef(self.doc, objid, genno)
- self.push((pos, obj))
- except PSSyntaxError:
- pass
- return
-
- if token is self.KEYWORD_STREAM:
- # stream object
- ((_,dic),) = self.pop(1)
- dic = dict_value(dic)
- try:
- objlen = int_value(dic['Length'])
- except KeyError:
- if STRICT:
- raise PDFSyntaxError('/Length is undefined: %r' % dic)
- objlen = 0
- self.seek(pos)
- try:
- (_, line) = self.nextline() # 'stream'
- except PSEOF:
- if STRICT:
- raise PDFSyntaxError('Unexpected EOF')
- return
- pos += len(line)
- self.fp.seek(pos)
- data = self.fp.read(objlen)
- self.seek(pos+objlen)
- while 1:
- try:
- (linepos, line) = self.nextline()
- except PSEOF:
- if STRICT:
- raise PDFSyntaxError('Unexpected EOF')
- break
- if 'endstream' in line:
- i = line.index('endstream')
- objlen += i
- data += line[:i]
- break
- objlen += len(line)
- data += line
- self.seek(pos+objlen)
- obj = PDFStream(dic, data, self.doc.decipher)
- self.push((pos, obj))
- return
-
- # others
- self.push((pos, token))
- return
-
- def find_xref(self):
- # search the last xref table by scanning the file backwards.
- prev = None
- for line in self.revreadlines():
- line = line.strip()
- if line == 'startxref': break
- if line:
- prev = line
- else:
- raise PDFNoValidXRef('Unexpected EOF')
- return int(prev)
-
- # read xref table
- def read_xref_from(self, start, xrefs):
- self.seek(start)
- self.reset()
- try:
- (pos, token) = self.nexttoken()
- except PSEOF:
- raise PDFNoValidXRef('Unexpected EOF')
- if isinstance(token, int):
- # XRefStream: PDF-1.5
- if GEN_XREF_STM == 1:
- global gen_xref_stm
- gen_xref_stm = True
- self.seek(pos)
- self.reset()
- xref = PDFXRefStream()
- xref.load(self)
- else:
- if token is not self.KEYWORD_XREF:
- raise PDFNoValidXRef('xref not found: pos=%d, token=%r' %
- (pos, token))
- self.nextline()
- xref = PDFXRef()
- xref.load(self)
- xrefs.append(xref)
- trailer = xref.trailer
- if 'XRefStm' in trailer:
- pos = int_value(trailer['XRefStm'])
- self.read_xref_from(pos, xrefs)
- if 'Prev' in trailer:
- # find previous xref
- pos = int_value(trailer['Prev'])
- self.read_xref_from(pos, xrefs)
- return
-
- # read xref tables and trailers
- def read_xref(self):
- xrefs = []
- trailerpos = None
- try:
- pos = self.find_xref()
- self.read_xref_from(pos, xrefs)
- except PDFNoValidXRef:
- # fallback
- self.seek(0)
- pat = re.compile(r'^(\d+)\s+(\d+)\s+obj\b')
- offsets = {}
- xref = PDFXRef()
- while 1:
- try:
- (pos, line) = self.nextline()
- except PSEOF:
- break
- if line.startswith('trailer'):
- trailerpos = pos # remember last trailer
- m = pat.match(line)
- if not m: continue
- (objid, genno) = m.groups()
- offsets[int(objid)] = (0, pos)
- if not offsets: raise
- xref.offsets = offsets
- if trailerpos:
- self.seek(trailerpos)
- xref.load_trailer(self)
- xrefs.append(xref)
- return xrefs
-
-## PDFObjStrmParser
-##
-class PDFObjStrmParser(PDFParser):
-
- def __init__(self, data, doc):
- PSStackParser.__init__(self, StringIO(data))
- self.doc = doc
- return
-
- def flush(self):
- self.add_results(*self.popall())
- return
-
- KEYWORD_R = KWD('R')
- def do_keyword(self, pos, token):
- if token is self.KEYWORD_R:
- # reference to indirect object
- try:
- ((_,objid), (_,genno)) = self.pop(2)
- (objid, genno) = (int(objid), int(genno))
- obj = PDFObjRef(self.doc, objid, genno)
- self.push((pos, obj))
- except PSSyntaxError:
- pass
- return
- # others
- self.push((pos, token))
- return
-
-###
-### My own code, for which there is none else to blame
-
-class PDFSerializer(object):
- def __init__(self, inf, userkey):
- global GEN_XREF_STM, gen_xref_stm
- gen_xref_stm = GEN_XREF_STM > 1
- self.version = inf.read(8)
- inf.seek(0)
- self.doc = doc = PDFDocument()
- parser = PDFParser(doc, inf)
- doc.initialize(userkey)
- self.objids = objids = set()
- for xref in reversed(doc.xrefs):
- trailer = xref.trailer
- for objid in xref.objids():
- objids.add(objid)
- trailer = dict(trailer)
- trailer.pop('Prev', None)
- trailer.pop('XRefStm', None)
- if 'Encrypt' in trailer:
- objids.remove(trailer.pop('Encrypt').objid)
- self.trailer = trailer
-
- def dump(self, outf):
- self.outf = outf
- self.write(self.version)
- self.write('\n%\xe2\xe3\xcf\xd3\n')
- doc = self.doc
- objids = self.objids
- xrefs = {}
- maxobj = max(objids)
- trailer = dict(self.trailer)
- trailer['Size'] = maxobj + 1
- for objid in objids:
- obj = doc.getobj(objid)
- if isinstance(obj, PDFObjStmRef):
- xrefs[objid] = obj
- continue
- if obj is not None:
- try:
- genno = obj.genno
- except AttributeError:
- genno = 0
- xrefs[objid] = (self.tell(), genno)
- self.serialize_indirect(objid, obj)
- startxref = self.tell()
-
- if not gen_xref_stm:
- self.write('xref\n')
- self.write('0 %d\n' % (maxobj + 1,))
- for objid in xrange(0, maxobj + 1):
- if objid in xrefs:
- # force the genno to be 0
- self.write("%010d 00000 n \n" % xrefs[objid][0])
- else:
- self.write("%010d %05d f \n" % (0, 65535))
-
- self.write('trailer\n')
- self.serialize_object(trailer)
- self.write('\nstartxref\n%d\n%%%%EOF' % startxref)
-
- else: # Generate crossref stream.
-
- # Calculate size of entries
- maxoffset = max(startxref, maxobj)
- maxindex = PDFObjStmRef.maxindex
- fl2 = 2
- power = 65536
- while maxoffset >= power:
- fl2 += 1
- power *= 256
- fl3 = 1
- power = 256
- while maxindex >= power:
- fl3 += 1
- power *= 256
-
- index = []
- first = None
- prev = None
- data = []
- # Put the xrefstream's reference in itself
- startxref = self.tell()
- maxobj += 1
- xrefs[maxobj] = (startxref, 0)
- for objid in sorted(xrefs):
- if first is None:
- first = objid
- elif objid != prev + 1:
- index.extend((first, prev - first + 1))
- first = objid
- prev = objid
- objref = xrefs[objid]
- if isinstance(objref, PDFObjStmRef):
- f1 = 2
- f2 = objref.stmid
- f3 = objref.index
- else:
- f1 = 1
- f2 = objref[0]
- # we force all generation numbers to be 0
- # f3 = objref[1]
- f3 = 0
-
- data.append(struct.pack('>B', f1))
- data.append(struct.pack('>L', f2)[-fl2:])
- data.append(struct.pack('>L', f3)[-fl3:])
- index.extend((first, prev - first + 1))
- data = zlib.compress(''.join(data))
- dic = {'Type': LITERAL_XREF, 'Size': prev + 1, 'Index': index,
- 'W': [1, fl2, fl3], 'Length': len(data),
- 'Filter': LITERALS_FLATE_DECODE[0],
- 'Root': trailer['Root'],}
- if 'Info' in trailer:
- dic['Info'] = trailer['Info']
- xrefstm = PDFStream(dic, data)
- self.serialize_indirect(maxobj, xrefstm)
- self.write('startxref\n%d\n%%%%EOF' % startxref)
- def write(self, data):
- self.outf.write(data)
- self.last = data[-1:]
-
- def tell(self):
- return self.outf.tell()
-
- def escape_string(self, string):
- string = string.replace('\\', '\\\\')
- string = string.replace('\n', r'\n')
- string = string.replace('(', r'\(')
- string = string.replace(')', r'\)')
- # get rid of ciando id
- regularexp = re.compile(r'http://www.ciando.com/index.cfm/intRefererID/\d{5}')
- if regularexp.match(string): return ('http://www.ciando.com')
- return string
-
- def serialize_object(self, obj):
- if isinstance(obj, dict):
- # Correct malformed Mac OS resource forks for Stanza
- if 'ResFork' in obj and 'Type' in obj and 'Subtype' not in obj \
- and isinstance(obj['Type'], int):
- obj['Subtype'] = obj['Type']
- del obj['Type']
- # end - hope this doesn't have bad effects
- self.write('<<')
- for key, val in obj.items():
- self.write('/%s' % key)
- self.serialize_object(val)
- self.write('>>')
- elif isinstance(obj, list):
- self.write('[')
- for val in obj:
- self.serialize_object(val)
- self.write(']')
- elif isinstance(obj, str):
- self.write('(%s)' % self.escape_string(obj))
- elif isinstance(obj, bool):
- if self.last.isalnum():
- self.write(' ')
- self.write(str(obj).lower())
- elif isinstance(obj, (int, long)):
- if self.last.isalnum():
- self.write(' ')
- self.write(str(obj))
- elif isinstance(obj, Decimal):
- if self.last.isalnum():
- self.write(' ')
- self.write(str(obj))
- elif isinstance(obj, PDFObjRef):
- if self.last.isalnum():
- self.write(' ')
- self.write('%d %d R' % (obj.objid, 0))
- elif isinstance(obj, PDFStream):
- ### If we don't generate cross ref streams the object streams
- ### are no longer useful, as we have extracted all objects from
- ### them. Therefore leave them out from the output.
- if obj.dic.get('Type') == LITERAL_OBJSTM and not gen_xref_stm:
- self.write('(deleted)')
- else:
- data = obj.get_decdata()
- self.serialize_object(obj.dic)
- self.write('stream\n')
- self.write(data)
- self.write('\nendstream')
- else:
- data = str(obj)
- if data[0].isalnum() and self.last.isalnum():
- self.write(' ')
- self.write(data)
-
- def serialize_indirect(self, objid, obj):
- self.write('%d 0 obj' % (objid,))
- self.serialize_object(obj)
- if self.last.isalnum():
- self.write('\n')
- self.write('endobj\n')
-
-
-
-
-def decryptBook(userkey, inpath, outpath):
- if RSA is None:
- raise ADEPTError(u"PyCrypto or OpenSSL must be installed.")
- with open(inpath, 'rb') as inf:
- #try:
- serializer = PDFSerializer(inf, userkey)
- #except:
- # print u"Error serializing pdf {0}. Probably wrong key.".format(os.path.basename(inpath))
- # return 2
- # hope this will fix the 'bad file descriptor' problem
- with open(outpath, 'wb') as outf:
- # help construct to make sure the method runs to the end
- try:
- serializer.dump(outf)
- except Exception, e:
- print u"error writing pdf: {0}".format(e.args[0])
- return 2
- return 0
-
-
-def cli_main():
- sys.stdout=SafeUnbuffered(sys.stdout)
- sys.stderr=SafeUnbuffered(sys.stderr)
- argv=unicode_argv()
- progname = os.path.basename(argv[0])
- if len(argv) != 4:
- print u"usage: {0} ".format(progname)
- return 1
- keypath, inpath, outpath = argv[1:]
- userkey = open(keypath,'rb').read()
- result = decryptBook(userkey, inpath, outpath)
- if result == 0:
- print u"Successfully decrypted {0:s} as {1:s}".format(os.path.basename(inpath),os.path.basename(outpath))
- return result
-
-
-def gui_main():
- try:
- import Tkinter
- import Tkconstants
- import tkFileDialog
- import tkMessageBox
- import traceback
- except:
- return cli_main()
-
- class DecryptionDialog(Tkinter.Frame):
- def __init__(self, root):
- Tkinter.Frame.__init__(self, root, border=5)
- self.status = Tkinter.Label(self, text=u"Select files for decryption")
- self.status.pack(fill=Tkconstants.X, expand=1)
- body = Tkinter.Frame(self)
- body.pack(fill=Tkconstants.X, expand=1)
- sticky = Tkconstants.E + Tkconstants.W
- body.grid_columnconfigure(1, weight=2)
- Tkinter.Label(body, text=u"Key file").grid(row=0)
- self.keypath = Tkinter.Entry(body, width=30)
- self.keypath.grid(row=0, column=1, sticky=sticky)
- if os.path.exists(u"adeptkey.der"):
- self.keypath.insert(0, u"adeptkey.der")
- button = Tkinter.Button(body, text=u"...", command=self.get_keypath)
- button.grid(row=0, column=2)
- Tkinter.Label(body, text=u"Input file").grid(row=1)
- self.inpath = Tkinter.Entry(body, width=30)
- self.inpath.grid(row=1, column=1, sticky=sticky)
- button = Tkinter.Button(body, text=u"...", command=self.get_inpath)
- button.grid(row=1, column=2)
- Tkinter.Label(body, text=u"Output file").grid(row=2)
- self.outpath = Tkinter.Entry(body, width=30)
- self.outpath.grid(row=2, column=1, sticky=sticky)
- button = Tkinter.Button(body, text=u"...", command=self.get_outpath)
- button.grid(row=2, column=2)
- buttons = Tkinter.Frame(self)
- buttons.pack()
- botton = Tkinter.Button(
- buttons, text=u"Decrypt", width=10, command=self.decrypt)
- botton.pack(side=Tkconstants.LEFT)
- Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
- button = Tkinter.Button(
- buttons, text=u"Quit", width=10, command=self.quit)
- button.pack(side=Tkconstants.RIGHT)
-
- def get_keypath(self):
- keypath = tkFileDialog.askopenfilename(
- parent=None, title=u"Select Adobe Adept \'.der\' key file",
- defaultextension=u".der",
- filetypes=[('Adobe Adept DER-encoded files', '.der'),
- ('All Files', '.*')])
- if keypath:
- keypath = os.path.normpath(keypath)
- self.keypath.delete(0, Tkconstants.END)
- self.keypath.insert(0, keypath)
- return
-
- def get_inpath(self):
- inpath = tkFileDialog.askopenfilename(
- parent=None, title=u"Select ADEPT-encrypted PDF file to decrypt",
- defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
- if inpath:
- inpath = os.path.normpath(inpath)
- self.inpath.delete(0, Tkconstants.END)
- self.inpath.insert(0, inpath)
- return
-
- def get_outpath(self):
- outpath = tkFileDialog.asksaveasfilename(
- parent=None, title=u"Select unencrypted PDF file to produce",
- defaultextension=u".pdf", filetypes=[('PDF files', '.pdf')])
- if outpath:
- outpath = os.path.normpath(outpath)
- self.outpath.delete(0, Tkconstants.END)
- self.outpath.insert(0, outpath)
- return
-
- def decrypt(self):
- keypath = self.keypath.get()
- inpath = self.inpath.get()
- outpath = self.outpath.get()
- if not keypath or not os.path.exists(keypath):
- self.status['text'] = u"Specified key file does not exist"
- return
- if not inpath or not os.path.exists(inpath):
- self.status['text'] = u"Specified input file does not exist"
- return
- if not outpath:
- self.status['text'] = u"Output file not specified"
- return
- if inpath == outpath:
- self.status['text'] = u"Must have different input and output files"
- return
- userkey = open(keypath,'rb').read()
- self.status['text'] = u"Decrypting..."
- try:
- decrypt_status = decryptBook(userkey, inpath, outpath)
- except Exception, e:
- self.status['text'] = u"Error; {0}".format(e.args[0])
- return
- if decrypt_status == 0:
- self.status['text'] = u"File successfully decrypted"
- else:
- self.status['text'] = u"The was an error decrypting the file."
-
-
- root = Tkinter.Tk()
- if RSA is None:
- root.withdraw()
- tkMessageBox.showerror(
- "INEPT PDF",
- "This script requires OpenSSL or PyCrypto, which must be installed "
- "separately. Read the top-of-script comment for details.")
- return 1
- root.title(u"Adobe Adept PDF Decrypter v.{0}".format(__version__))
- root.resizable(True, False)
- root.minsize(370, 0)
- DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
- root.mainloop()
- return 0
-
-
-if __name__ == '__main__':
- if len(sys.argv) > 1:
- sys.exit(cli_main())
- sys.exit(gui_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ion.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ion.py
deleted file mode 100644
index 40433ca..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/ion.py
+++ /dev/null
@@ -1,985 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Pascal implementation by lulzkabulz. Python translation by apprenticenaomi. DeDRM integration by anon.
-# BinaryIon.pas + DrmIon.pas + IonSymbols.pas
-
-from __future__ import with_statement
-
-import collections
-import hashlib
-import hmac
-import os
-import os.path
-import struct
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-from Crypto.Cipher import AES
-from Crypto.Util.py3compat import bchr, bord
-
-try:
- # lzma library from calibre 2.35.0 or later
- import lzma.lzma1 as calibre_lzma
-except ImportError:
- calibre_lzma = None
- try:
- import lzma
- except ImportError:
- # Need pip backports.lzma on Python <3.3
- try:
- from backports import lzma
- except ImportError:
- # Windows-friendly choice: pylzma wheels
- import pylzma as lzma
-
-
-TID_NULL = 0
-TID_BOOLEAN = 1
-TID_POSINT = 2
-TID_NEGINT = 3
-TID_FLOAT = 4
-TID_DECIMAL = 5
-TID_TIMESTAMP = 6
-TID_SYMBOL = 7
-TID_STRING = 8
-TID_CLOB = 9
-TID_BLOB = 0xA
-TID_LIST = 0xB
-TID_SEXP = 0xC
-TID_STRUCT = 0xD
-TID_TYPEDECL = 0xE
-TID_UNUSED = 0xF
-
-
-SID_UNKNOWN = -1
-SID_ION = 1
-SID_ION_1_0 = 2
-SID_ION_SYMBOL_TABLE = 3
-SID_NAME = 4
-SID_VERSION = 5
-SID_IMPORTS = 6
-SID_SYMBOLS = 7
-SID_MAX_ID = 8
-SID_ION_SHARED_SYMBOL_TABLE = 9
-SID_ION_1_0_MAX = 10
-
-
-LEN_IS_VAR_LEN = 0xE
-LEN_IS_NULL = 0xF
-
-
-VERSION_MARKER = b"\x01\x00\xEA"
-
-
-# asserts must always raise exceptions for proper functioning
-def _assert(test, msg="Exception"):
- if not test:
- raise Exception(msg)
-
-
-class SystemSymbols(object):
- ION = '$ion'
- ION_1_0 = '$ion_1_0'
- ION_SYMBOL_TABLE = '$ion_symbol_table'
- NAME = 'name'
- VERSION = 'version'
- IMPORTS = 'imports'
- SYMBOLS = 'symbols'
- MAX_ID = 'max_id'
- ION_SHARED_SYMBOL_TABLE = '$ion_shared_symbol_table'
-
-
-class IonCatalogItem(object):
- name = ""
- version = 0
- symnames = []
-
- def __init__(self, name, version, symnames):
- self.name = name
- self.version = version
- self.symnames = symnames
-
-
-class SymbolToken(object):
- text = ""
- sid = 0
-
- def __init__(self, text, sid):
- if text == "" and sid == 0:
- raise ValueError("Symbol token must have Text or SID")
-
- self.text = text
- self.sid = sid
-
-
-class SymbolTable(object):
- table = None
-
- def __init__(self):
- self.table = [None] * SID_ION_1_0_MAX
- self.table[SID_ION] = SystemSymbols.ION
- self.table[SID_ION_1_0] = SystemSymbols.ION_1_0
- self.table[SID_ION_SYMBOL_TABLE] = SystemSymbols.ION_SYMBOL_TABLE
- self.table[SID_NAME] = SystemSymbols.NAME
- self.table[SID_VERSION] = SystemSymbols.VERSION
- self.table[SID_IMPORTS] = SystemSymbols.IMPORTS
- self.table[SID_SYMBOLS] = SystemSymbols.SYMBOLS
- self.table[SID_MAX_ID] = SystemSymbols.MAX_ID
- self.table[SID_ION_SHARED_SYMBOL_TABLE] = SystemSymbols.ION_SHARED_SYMBOL_TABLE
-
- def findbyid(self, sid):
- if sid < 1:
- raise ValueError("Invalid symbol id")
-
- if sid < len(self.table):
- return self.table[sid]
- else:
- return ""
-
- def import_(self, table, maxid):
- for i in range(maxid):
- self.table.append(table.symnames[i])
-
- def importunknown(self, name, maxid):
- for i in range(maxid):
- self.table.append("%s#%d" % (name, i + 1))
-
-
-class ParserState:
- Invalid,BeforeField,BeforeTID,BeforeValue,AfterValue,EOF = 1,2,3,4,5,6
-
-ContainerRec = collections.namedtuple("ContainerRec", "nextpos, tid, remaining")
-
-
-class BinaryIonParser(object):
- eof = False
- state = None
- localremaining = 0
- needhasnext = False
- isinstruct = False
- valuetid = 0
- valuefieldid = 0
- parenttid = 0
- valuelen = 0
- valueisnull = False
- valueistrue = False
- value = None
- didimports = False
-
- def __init__(self, stream):
- self.annotations = []
- self.catalog = []
-
- self.stream = stream
- self.initpos = stream.tell()
- self.reset()
- self.symbols = SymbolTable()
-
- def reset(self):
- self.state = ParserState.BeforeTID
- self.needhasnext = True
- self.localremaining = -1
- self.eof = False
- self.isinstruct = False
- self.containerstack = []
- self.stream.seek(self.initpos)
-
- def addtocatalog(self, name, version, symbols):
- self.catalog.append(IonCatalogItem(name, version, symbols))
-
- def hasnext(self):
- while self.needhasnext and not self.eof:
- self.hasnextraw()
- if len(self.containerstack) == 0 and not self.valueisnull:
- if self.valuetid == TID_SYMBOL:
- if self.value == SID_ION_1_0:
- self.needhasnext = True
- elif self.valuetid == TID_STRUCT:
- for a in self.annotations:
- if a == SID_ION_SYMBOL_TABLE:
- self.parsesymboltable()
- self.needhasnext = True
- break
- return not self.eof
-
- def hasnextraw(self):
- self.clearvalue()
- while self.valuetid == -1 and not self.eof:
- self.needhasnext = False
- if self.state == ParserState.BeforeField:
- _assert(self.valuefieldid == SID_UNKNOWN)
-
- self.valuefieldid = self.readfieldid()
- if self.valuefieldid != SID_UNKNOWN:
- self.state = ParserState.BeforeTID
- else:
- self.eof = True
-
- elif self.state == ParserState.BeforeTID:
- self.state = ParserState.BeforeValue
- self.valuetid = self.readtypeid()
- if self.valuetid == -1:
- self.state = ParserState.EOF
- self.eof = True
- break
-
- if self.valuetid == TID_TYPEDECL:
- if self.valuelen == 0:
- self.checkversionmarker()
- else:
- self.loadannotations()
-
- elif self.state == ParserState.BeforeValue:
- self.skip(self.valuelen)
- self.state = ParserState.AfterValue
-
- elif self.state == ParserState.AfterValue:
- if self.isinstruct:
- self.state = ParserState.BeforeField
- else:
- self.state = ParserState.BeforeTID
-
- else:
- _assert(self.state == ParserState.EOF)
-
- def next(self):
- if self.hasnext():
- self.needhasnext = True
- return self.valuetid
- else:
- return -1
-
- def push(self, typeid, nextposition, nextremaining):
- self.containerstack.append(ContainerRec(nextpos=nextposition, tid=typeid, remaining=nextremaining))
-
- def stepin(self):
- _assert(self.valuetid in [TID_STRUCT, TID_LIST, TID_SEXP] and not self.eof,
- "valuetid=%s eof=%s" % (self.valuetid, self.eof))
- _assert((not self.valueisnull or self.state == ParserState.AfterValue) and
- (self.valueisnull or self.state == ParserState.BeforeValue))
-
- nextrem = self.localremaining
- if nextrem != -1:
- nextrem -= self.valuelen
- if nextrem < 0:
- nextrem = 0
- self.push(self.parenttid, self.stream.tell() + self.valuelen, nextrem)
-
- self.isinstruct = (self.valuetid == TID_STRUCT)
- if self.isinstruct:
- self.state = ParserState.BeforeField
- else:
- self.state = ParserState.BeforeTID
-
- self.localremaining = self.valuelen
- self.parenttid = self.valuetid
- self.clearvalue()
- self.needhasnext = True
-
- def stepout(self):
- rec = self.containerstack.pop()
-
- self.eof = False
- self.parenttid = rec.tid
- if self.parenttid == TID_STRUCT:
- self.isinstruct = True
- self.state = ParserState.BeforeField
- else:
- self.isinstruct = False
- self.state = ParserState.BeforeTID
- self.needhasnext = True
-
- self.clearvalue()
- curpos = self.stream.tell()
- if rec.nextpos > curpos:
- self.skip(rec.nextpos - curpos)
- else:
- _assert(rec.nextpos == curpos)
-
- self.localremaining = rec.remaining
-
- def read(self, count=1):
- if self.localremaining != -1:
- self.localremaining -= count
- _assert(self.localremaining >= 0)
-
- result = self.stream.read(count)
- if len(result) == 0:
- raise EOFError()
- return result
-
- def readfieldid(self):
- if self.localremaining != -1 and self.localremaining < 1:
- return -1
-
- try:
- return self.readvaruint()
- except EOFError:
- return -1
-
- def readtypeid(self):
- if self.localremaining != -1:
- if self.localremaining < 1:
- return -1
- self.localremaining -= 1
-
- b = self.stream.read(1)
- if len(b) < 1:
- return -1
- b = bord(b)
- result = b >> 4
- ln = b & 0xF
-
- if ln == LEN_IS_VAR_LEN:
- ln = self.readvaruint()
- elif ln == LEN_IS_NULL:
- ln = 0
- self.state = ParserState.AfterValue
- elif result == TID_NULL:
- # Must have LEN_IS_NULL
- _assert(False)
- elif result == TID_BOOLEAN:
- _assert(ln <= 1)
- self.valueistrue = (ln == 1)
- ln = 0
- self.state = ParserState.AfterValue
- elif result == TID_STRUCT:
- if ln == 1:
- ln = self.readvaruint()
-
- self.valuelen = ln
- return result
-
- def readvarint(self):
- b = bord(self.read())
- negative = ((b & 0x40) != 0)
- result = (b & 0x3F)
-
- i = 0
- while (b & 0x80) == 0 and i < 4:
- b = bord(self.read())
- result = (result << 7) | (b & 0x7F)
- i += 1
-
- _assert(i < 4 or (b & 0x80) != 0, "int overflow")
-
- if negative:
- return -result
- return result
-
- def readvaruint(self):
- b = bord(self.read())
- result = (b & 0x7F)
-
- i = 0
- while (b & 0x80) == 0 and i < 4:
- b = bord(self.read())
- result = (result << 7) | (b & 0x7F)
- i += 1
-
- _assert(i < 4 or (b & 0x80) != 0, "int overflow")
-
- return result
-
- def readdecimal(self):
- if self.valuelen == 0:
- return 0.
-
- rem = self.localremaining - self.valuelen
- self.localremaining = self.valuelen
- exponent = self.readvarint()
-
- _assert(self.localremaining > 0, "Only exponent in ReadDecimal")
- _assert(self.localremaining <= 8, "Decimal overflow")
-
- signed = False
- b = [bord(x) for x in self.read(self.localremaining)]
- if (b[0] & 0x80) != 0:
- b[0] = b[0] & 0x7F
- signed = True
-
- # Convert variably sized network order integer into 64-bit little endian
- j = 0
- vb = [0] * 8
- for i in range(len(b), -1, -1):
- vb[i] = b[j]
- j += 1
-
- v = struct.unpack(" 0:
- result = result[:-1]
- return result
-
- def ionwalk(self, supert, indent, lst):
- while self.hasnext():
- if supert == TID_STRUCT:
- L = self.getfieldname() + ":"
- else:
- L = ""
-
- t = self.next()
- if t in [TID_STRUCT, TID_LIST]:
- if L != "":
- lst.append(indent + L)
- L = self.gettypename()
- if L != "":
- lst.append(indent + L + "::")
- if t == TID_STRUCT:
- lst.append(indent + "{")
- else:
- lst.append(indent + "[")
-
- self.stepin()
- self.ionwalk(t, indent + " ", lst)
- self.stepout()
-
- if t == TID_STRUCT:
- lst.append(indent + "}")
- else:
- lst.append(indent + "]")
-
- else:
- if t == TID_STRING:
- L += ('"%s"' % self.stringvalue())
- elif t in [TID_CLOB, TID_BLOB]:
- L += ("{%s}" % self.printlob(self.lobvalue()))
- elif t == TID_POSINT:
- L += str(self.intvalue())
- elif t == TID_SYMBOL:
- tn = self.gettypename()
- if tn != "":
- tn += "::"
- L += tn + self.symbolvalue()
- elif t == TID_DECIMAL:
- L += str(self.decimalvalue())
- else:
- L += ("TID %d" % t)
- lst.append(indent + L)
-
- def print_(self, lst):
- self.reset()
- self.ionwalk(-1, "", lst)
-
-
-SYM_NAMES = [ 'com.amazon.drm.Envelope@1.0',
- 'com.amazon.drm.EnvelopeMetadata@1.0', 'size', 'page_size',
- 'encryption_key', 'encryption_transformation',
- 'encryption_voucher', 'signing_key', 'signing_algorithm',
- 'signing_voucher', 'com.amazon.drm.EncryptedPage@1.0',
- 'cipher_text', 'cipher_iv', 'com.amazon.drm.Signature@1.0',
- 'data', 'com.amazon.drm.EnvelopeIndexTable@1.0', 'length',
- 'offset', 'algorithm', 'encoded', 'encryption_algorithm',
- 'hashing_algorithm', 'expires', 'format', 'id',
- 'lock_parameters', 'strategy', 'com.amazon.drm.Key@1.0',
- 'com.amazon.drm.KeySet@1.0', 'com.amazon.drm.PIDv3@1.0',
- 'com.amazon.drm.PlainTextPage@1.0',
- 'com.amazon.drm.PlainText@1.0', 'com.amazon.drm.PrivateKey@1.0',
- 'com.amazon.drm.PublicKey@1.0', 'com.amazon.drm.SecretKey@1.0',
- 'com.amazon.drm.Voucher@1.0', 'public_key', 'private_key',
- 'com.amazon.drm.KeyPair@1.0', 'com.amazon.drm.ProtectedData@1.0',
- 'doctype', 'com.amazon.drm.EnvelopeIndexTableOffset@1.0',
- 'enddoc', 'license_type', 'license', 'watermark', 'key', 'value',
- 'com.amazon.drm.License@1.0', 'category', 'metadata',
- 'categorized_metadata', 'com.amazon.drm.CategorizedMetadata@1.0',
- 'com.amazon.drm.VoucherEnvelope@1.0', 'mac', 'voucher',
- 'com.amazon.drm.ProtectedData@2.0',
- 'com.amazon.drm.Envelope@2.0',
- 'com.amazon.drm.EnvelopeMetadata@2.0',
- 'com.amazon.drm.EncryptedPage@2.0',
- 'com.amazon.drm.PlainText@2.0', 'compression_algorithm',
- 'com.amazon.drm.Compressed@1.0', 'priority', 'refines']
-
-def addprottable(ion):
- ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
-
-
-def pkcs7pad(msg, blocklen):
- paddinglen = blocklen - len(msg) % blocklen
- padding = bchr(paddinglen) * paddinglen
- return msg + padding
-
-
-def pkcs7unpad(msg, blocklen):
- _assert(len(msg) % blocklen == 0)
-
- paddinglen = bord(msg[-1])
- _assert(paddinglen > 0 and paddinglen <= blocklen, "Incorrect padding - Wrong key")
- _assert(msg[-paddinglen:] == bchr(paddinglen) * paddinglen, "Incorrect padding - Wrong key")
-
- return msg[:-paddinglen]
-
-
-class DrmIonVoucher(object):
- envelope = None
- voucher = None
- drmkey = None
- license_type = "Unknown"
-
- encalgorithm = ""
- enctransformation = ""
- hashalgorithm = ""
-
- lockparams = None
-
- ciphertext = b""
- cipheriv = b""
- secretkey = b""
-
- def __init__(self, voucherenv, dsn, secret):
- self.dsn,self.secret = dsn,secret
-
- self.lockparams = []
-
- self.envelope = BinaryIonParser(voucherenv)
- addprottable(self.envelope)
-
- def decryptvoucher(self):
- shared = "PIDv3" + self.encalgorithm + self.enctransformation + self.hashalgorithm
-
- self.lockparams.sort()
- for param in self.lockparams:
- if param == "ACCOUNT_SECRET":
- shared += param + self.secret
- elif param == "CLIENT_ID":
- shared += param + self.dsn
- else:
- _assert(False, "Unknown lock parameter: %s" % param)
-
- sharedsecret = shared.encode("UTF-8")
-
- key = hmac.new(sharedsecret, sharedsecret[:5], digestmod=hashlib.sha256).digest()
- aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
- b = aes.decrypt(self.ciphertext)
- b = pkcs7unpad(b, 16)
-
- self.drmkey = BinaryIonParser(StringIO(b))
- addprottable(self.drmkey)
-
- _assert(self.drmkey.hasnext() and self.drmkey.next() == TID_LIST and self.drmkey.gettypename() == "com.amazon.drm.KeySet@1.0",
- "Expected KeySet, got %s" % self.drmkey.gettypename())
-
- self.drmkey.stepin()
- while self.drmkey.hasnext():
- self.drmkey.next()
- if self.drmkey.gettypename() != "com.amazon.drm.SecretKey@1.0":
- continue
-
- self.drmkey.stepin()
- while self.drmkey.hasnext():
- self.drmkey.next()
- if self.drmkey.getfieldname() == "algorithm":
- _assert(self.drmkey.stringvalue() == "AES", "Unknown cipher algorithm: %s" % self.drmkey.stringvalue())
- elif self.drmkey.getfieldname() == "format":
- _assert(self.drmkey.stringvalue() == "RAW", "Unknown key format: %s" % self.drmkey.stringvalue())
- elif self.drmkey.getfieldname() == "encoded":
- self.secretkey = self.drmkey.lobvalue()
-
- self.drmkey.stepout()
- break
-
- self.drmkey.stepout()
-
- def parse(self):
- self.envelope.reset()
- _assert(self.envelope.hasnext(), "Envelope is empty")
- _assert(self.envelope.next() == TID_STRUCT and self.envelope.gettypename() == "com.amazon.drm.VoucherEnvelope@1.0",
- "Unknown type encountered in envelope, expected VoucherEnvelope")
-
- self.envelope.stepin()
- while self.envelope.hasnext():
- self.envelope.next()
- field = self.envelope.getfieldname()
- if field == "voucher":
- self.voucher = BinaryIonParser(StringIO(self.envelope.lobvalue()))
- addprottable(self.voucher)
- continue
- elif field != "strategy":
- continue
-
- _assert(self.envelope.gettypename() == "com.amazon.drm.PIDv3@1.0", "Unknown strategy: %s" % self.envelope.gettypename())
-
- self.envelope.stepin()
- while self.envelope.hasnext():
- self.envelope.next()
- field = self.envelope.getfieldname()
- if field == "encryption_algorithm":
- self.encalgorithm = self.envelope.stringvalue()
- elif field == "encryption_transformation":
- self.enctransformation = self.envelope.stringvalue()
- elif field == "hashing_algorithm":
- self.hashalgorithm = self.envelope.stringvalue()
- elif field == "lock_parameters":
- self.envelope.stepin()
- while self.envelope.hasnext():
- _assert(self.envelope.next() == TID_STRING, "Expected string list for lock_parameters")
- self.lockparams.append(self.envelope.stringvalue())
- self.envelope.stepout()
-
- self.envelope.stepout()
-
- self.parsevoucher()
-
- def parsevoucher(self):
- _assert(self.voucher.hasnext(), "Voucher is empty")
- _assert(self.voucher.next() == TID_STRUCT and self.voucher.gettypename() == "com.amazon.drm.Voucher@1.0",
- "Unknown type, expected Voucher")
-
- self.voucher.stepin()
- while self.voucher.hasnext():
- self.voucher.next()
-
- if self.voucher.getfieldname() == "cipher_iv":
- self.cipheriv = self.voucher.lobvalue()
- elif self.voucher.getfieldname() == "cipher_text":
- self.ciphertext = self.voucher.lobvalue()
- elif self.voucher.getfieldname() == "license":
- _assert(self.voucher.gettypename() == "com.amazon.drm.License@1.0",
- "Unknown license: %s" % self.voucher.gettypename())
- self.voucher.stepin()
- while self.voucher.hasnext():
- self.voucher.next()
- if self.voucher.getfieldname() == "license_type":
- self.license_type = self.voucher.stringvalue()
- self.voucher.stepout()
-
- def printenvelope(self, lst):
- self.envelope.print_(lst)
-
- def printkey(self, lst):
- if self.voucher is None:
- self.parse()
- if self.drmkey is None:
- self.decryptvoucher()
-
- self.drmkey.print_(lst)
-
- def printvoucher(self, lst):
- if self.voucher is None:
- self.parse()
-
- self.voucher.print_(lst)
-
- def getlicensetype(self):
- return self.license_type
-
-
-class DrmIon(object):
- ion = None
- voucher = None
- vouchername = ""
- key = b""
- onvoucherrequired = None
-
- def __init__(self, ionstream, onvoucherrequired):
- self.ion = BinaryIonParser(ionstream)
- addprottable(self.ion)
- self.onvoucherrequired = onvoucherrequired
-
- def parse(self, outpages):
- self.ion.reset()
-
- _assert(self.ion.hasnext(), "DRMION envelope is empty")
- _assert(self.ion.next() == TID_SYMBOL and self.ion.gettypename() == "doctype", "Expected doctype symbol")
- _assert(self.ion.next() == TID_LIST and self.ion.gettypename() in ["com.amazon.drm.Envelope@1.0", "com.amazon.drm.Envelope@2.0"],
- "Unknown type encountered in DRMION envelope, expected Envelope, got %s" % self.ion.gettypename())
-
- while True:
- if self.ion.gettypename() == "enddoc":
- break
-
- self.ion.stepin()
- while self.ion.hasnext():
- self.ion.next()
-
- if self.ion.gettypename() in ["com.amazon.drm.EnvelopeMetadata@1.0", "com.amazon.drm.EnvelopeMetadata@2.0"]:
- self.ion.stepin()
- while self.ion.hasnext():
- self.ion.next()
- if self.ion.getfieldname() != "encryption_voucher":
- continue
-
- if self.vouchername == "":
- self.vouchername = self.ion.stringvalue()
- self.voucher = self.onvoucherrequired(self.vouchername)
- self.key = self.voucher.secretkey
- _assert(self.key is not None, "Unable to obtain secret key from voucher")
- else:
- _assert(self.vouchername == self.ion.stringvalue(),
- "Unexpected: Different vouchers required for same file?")
-
- self.ion.stepout()
-
- elif self.ion.gettypename() in ["com.amazon.drm.EncryptedPage@1.0", "com.amazon.drm.EncryptedPage@2.0"]:
- decompress = False
- ct = None
- civ = None
- self.ion.stepin()
- while self.ion.hasnext():
- self.ion.next()
- if self.ion.gettypename() == "com.amazon.drm.Compressed@1.0":
- decompress = True
- if self.ion.getfieldname() == "cipher_text":
- ct = self.ion.lobvalue()
- elif self.ion.getfieldname() == "cipher_iv":
- civ = self.ion.lobvalue()
-
- if ct is not None and civ is not None:
- self.processpage(ct, civ, outpages, decompress)
- self.ion.stepout()
-
- self.ion.stepout()
- if not self.ion.hasnext():
- break
- self.ion.next()
-
- def print_(self, lst):
- self.ion.print_(lst)
-
- def processpage(self, ct, civ, outpages, decompress):
- aes = AES.new(self.key[:16], AES.MODE_CBC, civ[:16])
- msg = pkcs7unpad(aes.decrypt(ct), 16)
-
- if not decompress:
- outpages.write(msg)
- return
-
- _assert(msg[0] == b"\x00", "LZMA UseFilter not supported")
-
- if calibre_lzma is not None:
- with calibre_lzma.decompress(msg[1:], bufsize=0x1000000) as f:
- f.seek(0)
- outpages.write(f.read())
- return
-
- decomp = lzma.LZMADecompressor(format=lzma.FORMAT_ALONE)
- while not decomp.eof:
- segment = decomp.decompress(msg[1:])
- msg = b"" # Contents were internally buffered after the first call
- outpages.write(segment)
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py
deleted file mode 100644
index 1ce1f35..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/k4mobidedrm.py
+++ /dev/null
@@ -1,353 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-# k4mobidedrm.py
-# Copyright © 2008-2017 by Apprentice Harper et al.
-
-__license__ = 'GPL v3'
-__version__ = '5.5'
-
-# Engine to remove drm from Kindle and Mobipocket ebooks
-# for personal use for archiving and converting your ebooks
-
-# PLEASE DO NOT PIRATE EBOOKS!
-
-# We want all authors and publishers, and ebook stores to live
-# long and prosperous lives but at the same time we just want to
-# be able to read OUR books on whatever device we want and to keep
-# readable for a long, long time
-
-# This borrows very heavily from works by CMBDTC, IHeartCabbages, skindle,
-# unswindle, DarkReverser, ApprenticeAlf, and many many others
-
-# Special thanks to The Dark Reverser for MobiDeDrm and CMBDTC for cmbdtc_dump
-# from which this script borrows most unashamedly.
-
-# Changelog
-# 1.0 - Name change to k4mobidedrm. Adds Mac support, Adds plugin code
-# 1.1 - Adds support for additional kindle.info files
-# 1.2 - Better error handling for older Mobipocket
-# 1.3 - Don't try to decrypt Topaz books
-# 1.7 - Add support for Topaz books and Kindle serial numbers. Split code.
-# 1.9 - Tidy up after Topaz, minor exception changes
-# 2.1 - Topaz fix and filename sanitizing
-# 2.2 - Topaz Fix and minor Mac code fix
-# 2.3 - More Topaz fixes
-# 2.4 - K4PC/Mac key generation fix
-# 2.6 - Better handling of non-K4PC/Mac ebooks
-# 2.7 - Better trailing bytes handling in mobidedrm
-# 2.8 - Moved parsing of kindle.info files to mac & pc util files.
-# 3.1 - Updated for new calibre interface. Now __init__ in plugin.
-# 3.5 - Now support Kindle for PC/Mac 1.6
-# 3.6 - Even better trailing bytes handling in mobidedrm
-# 3.7 - Add support for Amazon Print Replica ebooks.
-# 3.8 - Improved Topaz support
-# 4.1 - Improved Topaz support and faster decryption with alfcrypto
-# 4.2 - Added support for Amazon's KF8 format ebooks
-# 4.4 - Linux calls to Wine added, and improved configuration dialog
-# 4.5 - Linux works again without Wine. Some Mac key file search changes
-# 4.6 - First attempt to handle unicode properly
-# 4.7 - Added timing reports, and changed search for Mac key files
-# 4.8 - Much better unicode handling, matching the updated inept and ignoble scripts
-# - Moved back into plugin, __init__ in plugin now only contains plugin code.
-# 4.9 - Missed some invalid characters in cleanup_name
-# 5.0 - Extraction of info from Kindle for PC/Mac moved into kindlekey.py
-# - tweaked GetDecryptedBook interface to leave passed parameters unchanged
-# 5.1 - moved unicode_argv call inside main for Windows DeDRM compatibility
-# 5.2 - Fixed error in command line processing of unicode arguments
-# 5.3 - Changed Android support to allow passing of backup .ab files
-# 5.4 - Recognise KFX files masquerading as azw, even if we can't decrypt them yet.
-# 5.5 - Added GPL v3 licence explicitly.
-# 5.x - Invoke KFXZipBook to handle zipped KFX files
-
-import sys, os, re
-import csv
-import getopt
-import re
-import traceback
-import time
-import htmlentitydefs
-import json
-
-class DrmException(Exception):
- pass
-
-if 'calibre' in sys.modules:
- inCalibre = True
-else:
- inCalibre = False
-
-if inCalibre:
- from calibre_plugins.dedrm import mobidedrm
- from calibre_plugins.dedrm import topazextract
- from calibre_plugins.dedrm import kgenpids
- from calibre_plugins.dedrm import androidkindlekey
- from calibre_plugins.dedrm import kfxdedrm
-else:
- import mobidedrm
- import topazextract
- import kgenpids
- import androidkindlekey
- import kfxdedrm
-
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
- def __init__(self, stream):
- self.stream = stream
- self.encoding = stream.encoding
- if self.encoding == None:
- self.encoding = "utf-8"
- def write(self, data):
- if isinstance(data,unicode):
- data = data.encode(self.encoding,"replace")
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-iswindows = sys.platform.startswith('win')
-isosx = sys.platform.startswith('darwin')
-
-def unicode_argv():
- if iswindows:
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'.
-
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- # if we don't have any arguments at all, just pass back script name
- # this should never happen
- return [u"mobidedrm.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-# cleanup unicode filenames
-# borrowed from calibre from calibre/src/calibre/__init__.py
-# added in removal of control (<32) chars
-# and removal of . at start and end
-# and with some (heavily edited) code from Paul Durrant's kindlenamer.py
-def cleanup_name(name):
- # substitute filename unfriendly characters
- name = name.replace(u"<",u"[").replace(u">",u"]").replace(u" : ",u" – ").replace(u": ",u" – ").replace(u":",u"—").replace(u"/",u"_").replace(u"\\",u"_").replace(u"|",u"_").replace(u"\"",u"\'").replace(u"*",u"_").replace(u"?",u"")
- # delete control characters
- name = u"".join(char for char in name if ord(char)>=32)
- # white space to single space, delete leading and trailing while space
- name = re.sub(ur"\s", u" ", name).strip()
- # remove leading dots
- while len(name)>0 and name[0] == u".":
- name = name[1:]
- # remove trailing dots (Windows doesn't like them)
- if name.endswith(u'.'):
- name = name[:-1]
- return name
-
-# must be passed unicode
-def unescape(text):
- def fixup(m):
- text = m.group(0)
- if text[:2] == u"":
- # character reference
- try:
- if text[:3] == u"":
- return unichr(int(text[3:-1], 16))
- else:
- return unichr(int(text[2:-1]))
- except ValueError:
- pass
- else:
- # named entity
- try:
- text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
- except KeyError:
- pass
- return text # leave as is
- return re.sub(u"?\w+;", fixup, text)
-
-def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime = time.time()):
- # handle the obvious cases at the beginning
- if not os.path.isfile(infile):
- raise DrmException(u"Input file does not exist.")
-
- mobi = True
- magic8 = open(infile,'rb').read(8)
- if magic8 == '\xeaDRMION\xee':
- 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]
- if magic3 == 'TPZ':
- mobi = False
-
- if magic8[:4] == 'PK\x03\x04':
- mb = kfxdedrm.KFXZipBook(infile)
- elif mobi:
- mb = mobidedrm.MobiBook(infile)
- else:
- mb = topazextract.TopazBook(infile)
-
- bookname = unescape(mb.getBookTitle())
- print u"Decrypting {1} ebook: {0}".format(bookname, mb.getBookType())
-
- # copy list of pids
- totalpids = list(pids)
- # extend list of serials with serials from android databases
- for aFile in androidFiles:
- serials.extend(androidkindlekey.get_serials(aFile))
- # extend PID list with book-specific PIDs from seriala and kDatabases
- md1, md2 = mb.getPIDMetaInfo()
- totalpids.extend(kgenpids.getPidList(md1, md2, serials, kDatabases))
- # remove any duplicates
- totalpids = list(set(totalpids))
- print u"Found {1:d} keys to try after {0:.1f} seconds".format(time.time()-starttime, len(totalpids))
- #print totalpids
-
- try:
- mb.processBook(totalpids)
- except:
- mb.cleanup
- raise
-
- print u"Decryption succeeded after {0:.1f} seconds".format(time.time()-starttime)
- return mb
-
-
-# kDatabaseFiles is a list of files created by kindlekey
-def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
- starttime = time.time()
- kDatabases = []
- for dbfile in kDatabaseFiles:
- kindleDatabase = {}
- try:
- with open(dbfile, 'r') as keyfilein:
- kindleDatabase = json.loads(keyfilein.read())
- kDatabases.append([dbfile,kindleDatabase])
- except Exception, e:
- print u"Error getting database from file {0:s}: {1:s}".format(dbfile,e)
- traceback.print_exc()
-
-
-
- try:
- book = GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime)
- except Exception, e:
- print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
- traceback.print_exc()
- return 1
-
- # Try to infer a reasonable name
- orig_fn_root = os.path.splitext(os.path.basename(infile))[0]
- if (
- re.match('^B[A-Z0-9]{9}(_EBOK|_EBSP|_sample)?$', orig_fn_root) or
- re.match('^{0-9A-F-}{36}$', orig_fn_root)
- ): # Kindle for PC / Mac / Android / Fire / iOS
- clean_title = cleanup_name(book.getBookTitle())
- outfilename = '{}_{}'.format(orig_fn_root, clean_title)
- else: # E Ink Kindle, which already uses a reasonable name
- outfilename = orig_fn_root
-
- # avoid excessively long file names
- if len(outfilename)>150:
- outfilename = outfilename[:99]+"--"+outfilename[-49:]
-
- outfilename = outfilename+u"_nodrm"
- outfile = os.path.join(outdir, outfilename + book.getBookExtension())
-
- book.getFile(outfile)
- print u"Saved decrypted book {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
-
- if book.getBookType()==u"Topaz":
- zipname = os.path.join(outdir, outfilename + u"_SVG.zip")
- book.getSVGZip(zipname)
- print u"Saved SVG ZIP Archive for {1:s} after {0:.1f} seconds".format(time.time()-starttime, outfilename)
-
- # remove internal temporary directory of Topaz pieces
- book.cleanup()
- return 0
-
-
-def usage(progname):
- print u"Removes DRM protection from Mobipocket, Amazon KF8, Amazon Print Replica and Amazon Topaz ebooks"
- print u"Usage:"
- print u" {0} [-k ] [-p ] [-s ] [ -a ] ".format(progname)
-
-#
-# Main
-#
-def cli_main():
- argv=unicode_argv()
- progname = os.path.basename(argv[0])
- print u"K4MobiDeDrm v{0}.\nCopyright © 2008-2017 Apprentice Harper et al.".format(__version__)
-
- try:
- opts, args = getopt.getopt(argv[1:], "k:p:s:a:")
- except getopt.GetoptError, err:
- print u"Error in options or arguments: {0}".format(err.args[0])
- usage(progname)
- sys.exit(2)
- if len(args)<2:
- usage(progname)
- sys.exit(2)
-
- infile = args[0]
- outdir = args[1]
- kDatabaseFiles = []
- androidFiles = []
- serials = []
- pids = []
-
- for o, a in opts:
- if o == "-k":
- if a == None :
- raise DrmException("Invalid parameter for -k")
- kDatabaseFiles.append(a)
- if o == "-p":
- if a == None :
- raise DrmException("Invalid parameter for -p")
- pids = a.split(',')
- if o == "-s":
- if a == None :
- raise DrmException("Invalid parameter for -s")
- serials = a.split(',')
- if o == '-a':
- if a == None:
- raise DrmException("Invalid parameter for -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)
-
-
-if __name__ == '__main__':
- sys.stdout=SafeUnbuffered(sys.stdout)
- sys.stderr=SafeUnbuffered(sys.stderr)
- sys.exit(cli_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kfxdedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kfxdedrm.py
deleted file mode 100644
index c2b9bb1..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kfxdedrm.py
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-# Engine to remove drm from Kindle KFX ebooks
-
-import os
-import shutil
-import zipfile
-
-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'
-__version__ = '1.0'
-
-
-class KFXZipBook:
- def __init__(self, infile):
- self.infile = infile
- self.voucher = None
- self.decrypted = {}
-
- def getPIDMetaInfo(self):
- return (None, None)
-
- def processBook(self, totalpids):
- with zipfile.ZipFile(self.infile, 'r') as zf:
- for filename in zf.namelist():
- data = zf.read(filename)
- if data.startswith('\xeaDRMION\xee'):
- if self.voucher is None:
- self.decrypt_voucher(totalpids)
- print u'Decrypting KFX DRMION: {0}'.format(filename)
- outfile = StringIO()
- ion.DrmIon(StringIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
- self.decrypted[filename] = outfile.getvalue()
-
- if not self.decrypted:
- print(u'The .kfx-zip archive does not contain an encrypted DRMION file')
-
- def decrypt_voucher(self, totalpids):
- with zipfile.ZipFile(self.infile, 'r') as zf:
- for info in zf.infolist():
- if info.file_size < 0x10000:
- data = zf.read(info.filename)
- if data.startswith('\xe0\x01\x00\xea') and 'ProtectedData' in data:
- break # found DRM voucher
- else:
- raise Exception(u'The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher')
-
- print u'Decrypting KFX DRM voucher: {0}'.format(info.filename)
-
- for pid in [''] + totalpids:
- for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,40), (40,40)]:
- if len(pid) == dsn_len + secret_len:
- break # split pid into DSN and account secret
- else:
- continue
-
- try:
- voucher = ion.DrmIonVoucher(StringIO(data), pid[:dsn_len], pid[dsn_len:])
- voucher.parse()
- voucher.decryptvoucher()
- break
- except:
- pass
- else:
- raise Exception(u'Failed to decrypt KFX DRM voucher with any key')
-
- print u'KFX DRM voucher successfully decrypted'
-
- license_type = voucher.getlicensetype()
- if license_type != "Purchase":
- raise Exception((u'This book is licensed as {0}. '
- 'These tools are intended for use on purchased books.').format(license_type))
-
- self.voucher = voucher
-
- def getBookTitle(self):
- return os.path.splitext(os.path.split(self.infile)[1])[0]
-
- def getBookExtension(self):
- return '.kfx-zip'
-
- def getBookType(self):
- return 'KFX-ZIP'
-
- def cleanup(self):
- pass
-
- def getFile(self, outpath):
- if not self.decrypted:
- shutil.copyfile(self.infile, outpath)
- else:
- with zipfile.ZipFile(self.infile, 'r') as zif:
- with zipfile.ZipFile(outpath, 'w') as zof:
- for info in zif.infolist():
- zof.writestr(info, self.decrypted.get(info.filename, zif.read(info.filename)))
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py
deleted file mode 100644
index 9b4373e..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kgenpids.py
+++ /dev/null
@@ -1,310 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-# kgenpids.py
-# Copyright © 2008-2017 Apprentice Harper et al.
-
-__license__ = 'GPL v3'
-__version__ = '2.1'
-
-# Revision history:
-# 2.0 - Fix for non-ascii Windows user names
-# 2.1 - Actual fix for non-ascii WIndows user names.
-# x.x - Return information needed for KFX decryption
-
-import sys
-import os, csv
-import binascii
-import zlib
-import re
-from struct import pack, unpack, unpack_from
-import traceback
-
-class DrmException(Exception):
- pass
-
-global charMap1
-global charMap3
-global charMap4
-
-
-charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
-charMap3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
-charMap4 = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
-
-# crypto digestroutines
-import hashlib
-
-def MD5(message):
- ctx = hashlib.md5()
- ctx.update(message)
- return ctx.digest()
-
-def SHA1(message):
- ctx = hashlib.sha1()
- ctx.update(message)
- return ctx.digest()
-
-
-# Encode the bytes in data with the characters in map
-def encode(data, map):
- result = ''
- for char in data:
- value = ord(char)
- Q = (value ^ 0x80) // len(map)
- R = value % len(map)
- result += map[Q]
- result += map[R]
- return result
-
-# Hash the bytes in data and then encode the digest with the characters in map
-def encodeHash(data,map):
- return encode(MD5(data),map)
-
-# Decode the string in data with the characters in map. Returns the decoded bytes
-def decode(data,map):
- result = ''
- for i in range (0,len(data)-1,2):
- high = map.find(data[i])
- low = map.find(data[i+1])
- if (high == -1) or (low == -1) :
- break
- value = (((high * len(map)) ^ 0x80) & 0xFF) + low
- result += pack('B',value)
- return result
-
-#
-# PID generation routines
-#
-
-# Returns two bit at offset from a bit field
-def getTwoBitsFromBitField(bitField,offset):
- byteNumber = offset // 4
- bitPosition = 6 - 2*(offset % 4)
- return ord(bitField[byteNumber]) >> bitPosition & 3
-
-# Returns the six bits at offset from a bit field
-def getSixBitsFromBitField(bitField,offset):
- offset *= 3
- value = (getTwoBitsFromBitField(bitField,offset) <<4) + (getTwoBitsFromBitField(bitField,offset+1) << 2) +getTwoBitsFromBitField(bitField,offset+2)
- return value
-
-# 8 bits to six bits encoding from hash to generate PID string
-def encodePID(hash):
- global charMap3
- PID = ''
- for position in range (0,8):
- PID += charMap3[getSixBitsFromBitField(hash,position)]
- return PID
-
-# Encryption table used to generate the device PID
-def generatePidEncryptionTable() :
- table = []
- for counter1 in range (0,0x100):
- value = counter1
- for counter2 in range (0,8):
- if (value & 1 == 0) :
- value = value >> 1
- else :
- value = value >> 1
- value = value ^ 0xEDB88320
- table.append(value)
- return table
-
-# Seed value used to generate the device PID
-def generatePidSeed(table,dsn) :
- value = 0
- for counter in range (0,4) :
- index = (ord(dsn[counter]) ^ value) &0xFF
- value = (value >> 8) ^ table[index]
- return value
-
-# Generate the device PID
-def generateDevicePID(table,dsn,nbRoll):
- global charMap4
- seed = generatePidSeed(table,dsn)
- pidAscii = ''
- pid = [(seed >>24) &0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF,(seed>>24) & 0xFF,(seed >> 16) &0xff,(seed >> 8) &0xFF,(seed) & 0xFF]
- index = 0
- for counter in range (0,nbRoll):
- pid[index] = pid[index] ^ ord(dsn[counter])
- index = (index+1) %8
- for counter in range (0,8):
- index = ((((pid[counter] >>5) & 3) ^ pid[counter]) & 0x1f) + (pid[counter] >> 7)
- pidAscii += charMap4[index]
- return pidAscii
-
-def crc32(s):
- return (~binascii.crc32(s,-1))&0xFFFFFFFF
-
-# convert from 8 digit PID to 10 digit PID with checksum
-def checksumPid(s):
- global charMap4
- crc = crc32(s)
- crc = crc ^ (crc >> 16)
- res = s
- l = len(charMap4)
- for i in (0,1):
- b = crc & 0xff
- pos = (b // l) ^ (b % l)
- res += charMap4[pos%l]
- crc >>= 8
- return res
-
-
-# old kindle serial number to fixed pid
-def pidFromSerial(s, l):
- global charMap4
- crc = crc32(s)
- arr1 = [0]*l
- for i in xrange(len(s)):
- arr1[i%l] ^= ord(s[i])
- crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
- for i in xrange(l):
- arr1[i] ^= crc_bytes[i&3]
- pid = ""
- for i in xrange(l):
- b = arr1[i] & 0xff
- pid+=charMap4[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
- return pid
-
-
-# Parse the EXTH header records and use the Kindle serial number to calculate the book pid.
-def getKindlePids(rec209, token, serialnum):
- if rec209 is None:
- return [serialnum]
-
- pids=[]
-
- if isinstance(serialnum,unicode):
- serialnum = serialnum.encode('utf-8')
-
- # Compute book PID
- pidHash = SHA1(serialnum+rec209+token)
- bookPID = encodePID(pidHash)
- bookPID = checksumPid(bookPID)
- pids.append(bookPID)
-
- # compute fixed pid for old pre 2.5 firmware update pid as well
- kindlePID = pidFromSerial(serialnum, 7) + "*"
- kindlePID = checksumPid(kindlePID)
- pids.append(kindlePID)
-
- return pids
-
-
-# parse the Kindleinfo file to calculate the book pid.
-
-keynames = ['kindle.account.tokens','kindle.cookie.item','eulaVersionAccepted','login_date','kindle.token.item','login','kindle.key.item','kindle.name.info','kindle.device.info', 'MazamaRandomNumber']
-
-def getK4Pids(rec209, token, kindleDatabase):
- global charMap1
- pids = []
-
- try:
- # Get the kindle account token, if present
- kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
-
- except KeyError:
- kindleAccountToken=""
- pass
-
- try:
- # Get the DSN token, if present
- DSN = (kindleDatabase[1])['DSN'].decode('hex')
- print u"Got DSN key from database {0}".format(kindleDatabase[0])
- except KeyError:
- # See if we have the info to generate the DSN
- try:
- # Get the Mazama Random number
- MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
- #print u"Got MazamaRandomNumber from database {0}".format(kindleDatabase[0])
-
- try:
- # Get the SerialNumber token, if present
- IDString = (kindleDatabase[1])['SerialNumber'].decode('hex')
- print u"Got SerialNumber from database {0}".format(kindleDatabase[0])
- except KeyError:
- # Get the IDString we added
- IDString = (kindleDatabase[1])['IDString'].decode('hex')
-
- try:
- # Get the UsernameHash token, if present
- encodedUsername = (kindleDatabase[1])['UsernameHash'].decode('hex')
- print u"Got UsernameHash from database {0}".format(kindleDatabase[0])
- except KeyError:
- # Get the UserName we added
- UserName = (kindleDatabase[1])['UserName'].decode('hex')
- # encode it
- encodedUsername = encodeHash(UserName,charMap1)
- #print u"encodedUsername",encodedUsername.encode('hex')
- except KeyError:
- print u"Keys not found in the database {0}.".format(kindleDatabase[0])
- return pids
-
- # Get the ID string used
- encodedIDString = encodeHash(IDString,charMap1)
- #print u"encodedIDString",encodedIDString.encode('hex')
-
- # concat, hash and encode to calculate the DSN
- DSN = encode(SHA1(MazamaRandomNumber+encodedIDString+encodedUsername),charMap1)
- #print u"DSN",DSN.encode('hex')
- pass
-
- if rec209 is None:
- pids.append(DSN+kindleAccountToken)
- return pids
-
- # Compute the device PID (for which I can tell, is used for nothing).
- table = generatePidEncryptionTable()
- devicePID = generateDevicePID(table,DSN,4)
- devicePID = checksumPid(devicePID)
- pids.append(devicePID)
-
- # Compute book PIDs
-
- # book pid
- pidHash = SHA1(DSN+kindleAccountToken+rec209+token)
- bookPID = encodePID(pidHash)
- bookPID = checksumPid(bookPID)
- pids.append(bookPID)
-
- # variant 1
- pidHash = SHA1(kindleAccountToken+rec209+token)
- bookPID = encodePID(pidHash)
- bookPID = checksumPid(bookPID)
- pids.append(bookPID)
-
- # variant 2
- pidHash = SHA1(DSN+rec209+token)
- bookPID = encodePID(pidHash)
- bookPID = checksumPid(bookPID)
- pids.append(bookPID)
-
- return pids
-
-def getPidList(md1, md2, serials=[], kDatabases=[]):
- pidlst = []
-
- if kDatabases is None:
- kDatabases = []
- if serials is None:
- serials = []
-
- for kDatabase in kDatabases:
- try:
- pidlst.extend(getK4Pids(md1, md2, kDatabase))
- except Exception, e:
- print u"Error getting PIDs from database {0}: {1}".format(kDatabase[0],e.args[0])
- traceback.print_exc()
-
- for serialnum in serials:
- try:
- pidlst.extend(getKindlePids(md1, md2, serialnum))
- except Exception, e:
- print u"Error getting PIDs from serial number {0}: {1}".format(serialnum ,e.args[0])
- traceback.print_exc()
-
- return pidlst
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlekey.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlekey.py
deleted file mode 100644
index e20b7c9..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlekey.py
+++ /dev/null
@@ -1,1727 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-# kindlekey.py
-# Copyright © 2008-2017 Apprentice Harper et al.
-
-__license__ = 'GPL v3'
-__version__ = '2.5'
-
-# Revision history:
-# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
-# 1.1 - Added Tkinter to match adobekey.py
-# 1.2 - Fixed testing of successful retrieval on Mac
-# 1.3 - Added getkey interface for Windows DeDRM application
-# Simplified some of the Kindle for Mac code.
-# 1.4 - Remove dependency on alfcrypto
-# 1.5 - moved unicode_argv call inside main for Windows DeDRM compatibility
-# 1.6 - Fixed a problem getting the disk serial numbers
-# 1.7 - Work if TkInter is missing
-# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names
-# 1.9 - Fixes for Unicode in Windows user names
-# 2.0 - Added comments and extra fix for non-ascii Windows user names
-# 2.1 - Fixed Kindle for PC encryption changes March 2016
-# 2.2 - Fixes for Macs with bonded ethernet ports
-# Also removed old .kinfo file support (pre-2011)
-# 2.3 - Added more field names thanks to concavegit's KFX code.
-# 2.4 - Fix for complex Mac disk setups, thanks to Tibs
-# 2.5 - Final Fix for Windows user names with non-ascii characters, thanks to oneofusoneofus
-
-
-"""
-Retrieve Kindle for PC/Mac user key.
-"""
-
-import sys, os, re
-from struct import pack, unpack, unpack_from
-import json
-import getopt
-
-# Routines common to Mac and PC
-
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
- def __init__(self, stream):
- self.stream = stream
- self.encoding = stream.encoding
- if self.encoding == None:
- self.encoding = "utf-8"
- def write(self, data):
- if isinstance(data,unicode):
- data = data.encode(self.encoding,"replace")
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-try:
- from calibre.constants import iswindows, isosx
-except:
- iswindows = sys.platform.startswith('win')
- isosx = sys.platform.startswith('darwin')
-
-def unicode_argv():
- if iswindows:
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
- # as a list of Unicode strings and encode them as utf-8
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- # if we don't have any arguments at all, just pass back script name
- # this should never happen
- return [u"kindlekey.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-class DrmException(Exception):
- pass
-
-# crypto digestroutines
-import hashlib
-
-def MD5(message):
- ctx = hashlib.md5()
- ctx.update(message)
- return ctx.digest()
-
-def SHA1(message):
- ctx = hashlib.sha1()
- ctx.update(message)
- return ctx.digest()
-
-def SHA256(message):
- ctx = hashlib.sha256()
- ctx.update(message)
- return ctx.digest()
-
-# For K4M/PC 1.6.X and later
-# generate table of prime number less than or equal to int n
-def primes(n):
- if n==2: return [2]
- elif n<2: return []
- s=range(3,n+1,2)
- mroot = n ** 0.5
- half=(n+1)/2-1
- i=0
- m=3
- while m <= mroot:
- if s[i]:
- j=(m*m-3)/2
- s[j]=0
- while j 0: # save any bytes that are not block aligned
- self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
- else:
- self.bytesToEncrypt = ''
-
- if more == None: # no more data expected from caller
- finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize)
- if len(finalBytes) > 0:
- ctBlock = self.encryptBlock(finalBytes)
- self.encryptBlockCount += 1
- cipherText += ctBlock
- self.resetEncrypt()
- return cipherText
-
- def decrypt(self, cipherText, more = None):
- """ Decrypt a string and return a string """
- self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt
-
- numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
- if more == None: # no more calls to decrypt, should have all the data
- if numExtraBytes != 0:
- raise DecryptNotBlockAlignedError, 'Data not block aligned on decrypt'
-
- # hold back some bytes in case last decrypt has zero len
- if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
- numBlocks -= 1
- numExtraBytes = self.blockSize
-
- plainText = ''
- for i in range(numBlocks):
- bStart = i*self.blockSize
- ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize])
- self.decryptBlockCount += 1
- plainText += ptBlock
-
- if numExtraBytes > 0: # save any bytes that are not block aligned
- self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
- else:
- self.bytesToEncrypt = ''
-
- if more == None: # last decrypt remove padding
- plainText = self.padding.removePad(plainText, self.blockSize)
- self.resetDecrypt()
- return plainText
-
-
- class Pad:
- def __init__(self):
- pass # eventually could put in calculation of min and max size extension
-
- class padWithPadLen(Pad):
- """ Pad a binary string with the length of the padding """
-
- def addPad(self, extraBytes, blockSize):
- """ Add padding to a binary string to make it an even multiple
- of the block size """
- blocks, numExtraBytes = divmod(len(extraBytes), blockSize)
- padLength = blockSize - numExtraBytes
- return extraBytes + padLength*chr(padLength)
-
- def removePad(self, paddedBinaryString, blockSize):
- """ Remove padding from a binary string """
- if not(0 6 and i%Nk == 4 :
- temp = [ Sbox[byte] for byte in temp ] # SubWord(temp)
- w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] )
- return w
-
- Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!!
- 0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,
- 0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91)
-
- #-------------------------------------
- def AddRoundKey(algInstance, keyBlock):
- """ XOR the algorithm state with a block of key material """
- for column in range(algInstance.Nb):
- for row in range(4):
- algInstance.state[column][row] ^= keyBlock[column][row]
- #-------------------------------------
-
- def SubBytes(algInstance):
- for column in range(algInstance.Nb):
- for row in range(4):
- algInstance.state[column][row] = Sbox[algInstance.state[column][row]]
-
- def InvSubBytes(algInstance):
- for column in range(algInstance.Nb):
- for row in range(4):
- algInstance.state[column][row] = InvSbox[algInstance.state[column][row]]
-
- Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,
- 0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
- 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,
- 0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
- 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,
- 0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
- 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,
- 0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
- 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,
- 0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
- 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,
- 0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
- 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,
- 0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
- 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,
- 0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
- 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,
- 0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
- 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,
- 0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
- 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,
- 0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
- 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,
- 0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
- 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,
- 0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
- 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,
- 0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
- 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,
- 0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
- 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,
- 0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)
-
- InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,
- 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
- 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,
- 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
- 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,
- 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
- 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,
- 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
- 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,
- 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
- 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,
- 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
- 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,
- 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
- 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,
- 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
- 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,
- 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
- 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,
- 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
- 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,
- 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
- 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,
- 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
- 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,
- 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
- 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,
- 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
- 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,
- 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
- 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,
- 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d)
-
- #-------------------------------------
- """ For each block size (Nb), the ShiftRow operation shifts row i
- by the amount Ci. Note that row 0 is not shifted.
- Nb C1 C2 C3
- ------------------- """
- shiftOffset = { 4 : ( 0, 1, 2, 3),
- 5 : ( 0, 1, 2, 3),
- 6 : ( 0, 1, 2, 3),
- 7 : ( 0, 1, 2, 4),
- 8 : ( 0, 1, 3, 4) }
- def ShiftRows(algInstance):
- tmp = [0]*algInstance.Nb # list of size Nb
- for r in range(1,4): # row 0 reamains unchanged and can be skipped
- for c in range(algInstance.Nb):
- tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
- for c in range(algInstance.Nb):
- algInstance.state[c][r] = tmp[c]
- def InvShiftRows(algInstance):
- tmp = [0]*algInstance.Nb # list of size Nb
- for r in range(1,4): # row 0 reamains unchanged and can be skipped
- for c in range(algInstance.Nb):
- tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
- for c in range(algInstance.Nb):
- algInstance.state[c][r] = tmp[c]
- #-------------------------------------
- def MixColumns(a):
- Sprime = [0,0,0,0]
- for j in range(a.Nb): # for each column
- Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3])
- Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3])
- Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3])
- Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3])
- for i in range(4):
- a.state[j][i] = Sprime[i]
-
- def InvMixColumns(a):
- """ Mix the four bytes of every column in a linear way
- This is the opposite operation of Mixcolumn """
- Sprime = [0,0,0,0]
- for j in range(a.Nb): # for each column
- Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3])
- Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3])
- Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3])
- Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3])
- for i in range(4):
- a.state[j][i] = Sprime[i]
-
- #-------------------------------------
- def mul(a, b):
- """ Multiply two elements of GF(2^m)
- needed for MixColumn and InvMixColumn """
- if (a !=0 and b!=0):
- return Alogtable[(Logtable[a] + Logtable[b])%255]
- else:
- return 0
-
- Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3,
- 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193,
- 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120,
- 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142,
- 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56,
- 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16,
- 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186,
- 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87,
- 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232,
- 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160,
- 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183,
- 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157,
- 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209,
- 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171,
- 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165,
- 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7)
-
- Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53,
- 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170,
- 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49,
- 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205,
- 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136,
- 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154,
- 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163,
- 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160,
- 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65,
- 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117,
- 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128,
- 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84,
- 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202,
- 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14,
- 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23,
- 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1)
-
-
-
-
- """
- AES Encryption Algorithm
- The AES algorithm is just Rijndael algorithm restricted to the default
- blockSize of 128 bits.
- """
-
- class AES(Rijndael):
- """ The AES algorithm is the Rijndael block cipher restricted to block
- sizes of 128 bits and key sizes of 128, 192 or 256 bits
- """
- def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
- """ Initialize AES, keySize is in bytes """
- if not (keySize == 16 or keySize == 24 or keySize == 32) :
- raise BadKeySizeError, 'Illegal AES key size, must be 16, 24, or 32 bytes'
-
- Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
-
- self.name = 'AES'
-
-
- """
- CBC mode of encryption for block ciphers.
- This algorithm mode wraps any BlockCipher to make a
- Cipher Block Chaining mode.
- """
- from random import Random # should change to crypto.random!!!
-
-
- class CBC(BlockCipher):
- """ The CBC class wraps block ciphers to make cipher block chaining (CBC) mode
- algorithms. The initialization (IV) is automatic if set to None. Padding
- is also automatic based on the Pad class used to initialize the algorithm
- """
- def __init__(self, blockCipherInstance, padding = padWithPadLen()):
- """ CBC algorithms are created by initializing with a BlockCipher instance """
- self.baseCipher = blockCipherInstance
- self.name = self.baseCipher.name + '_CBC'
- self.blockSize = self.baseCipher.blockSize
- self.keySize = self.baseCipher.keySize
- self.padding = padding
- self.baseCipher.padding = noPadding() # baseCipher should NOT pad!!
- self.r = Random() # for IV generation, currently uses
- # mediocre standard distro version <----------------
- import time
- newSeed = time.ctime()+str(self.r) # seed with instance location
- self.r.seed(newSeed) # to make unique
- self.reset()
-
- def setKey(self, key):
- self.baseCipher.setKey(key)
-
- # Overload to reset both CBC state and the wrapped baseCipher
- def resetEncrypt(self):
- BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class)
- self.baseCipher.resetEncrypt() # reset base cipher encrypt state
-
- def resetDecrypt(self):
- BlockCipher.resetDecrypt(self) # reset CBC state (super class)
- self.baseCipher.resetDecrypt() # reset base cipher decrypt state
-
- def encrypt(self, plainText, iv=None, more=None):
- """ CBC encryption - overloads baseCipher to allow optional explicit IV
- when iv=None, iv is auto generated!
- """
- if self.encryptBlockCount == 0:
- self.iv = iv
- else:
- assert(iv==None), 'IV used only on first call to encrypt'
-
- return BlockCipher.encrypt(self,plainText, more=more)
-
- def decrypt(self, cipherText, iv=None, more=None):
- """ CBC decryption - overloads baseCipher to allow optional explicit IV
- when iv=None, iv is auto generated!
- """
- if self.decryptBlockCount == 0:
- self.iv = iv
- else:
- assert(iv==None), 'IV used only on first call to decrypt'
-
- return BlockCipher.decrypt(self, cipherText, more=more)
-
- def encryptBlock(self, plainTextBlock):
- """ CBC block encryption, IV is set with 'encrypt' """
- auto_IV = ''
- if self.encryptBlockCount == 0:
- if self.iv == None:
- # generate IV and use
- self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)])
- self.prior_encr_CT_block = self.iv
- auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic
- else: # application provided IV
- assert(len(self.iv) == self.blockSize ),'IV must be same length as block'
- self.prior_encr_CT_block = self.iv
- """ encrypt the prior CT XORed with the PT """
- ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) )
- self.prior_encr_CT_block = ct
- return auto_IV+ct
-
- def decryptBlock(self, encryptedBlock):
- """ Decrypt a single block """
-
- if self.decryptBlockCount == 0: # first call, process IV
- if self.iv == None: # auto decrypt IV?
- self.prior_CT_block = encryptedBlock
- return ''
- else:
- assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption"
- self.prior_CT_block = self.iv
-
- dct = self.baseCipher.decryptBlock(encryptedBlock)
- """ XOR the prior decrypted CT with the prior CT """
- dct_XOR_priorCT = xor( self.prior_CT_block, dct )
-
- self.prior_CT_block = encryptedBlock
-
- return dct_XOR_priorCT
-
-
- """
- AES_CBC Encryption Algorithm
- """
-
- class aescbc_AES_CBC(CBC):
- """ AES encryption in CBC feedback mode """
- def __init__(self, key=None, padding=padWithPadLen(), keySize=16):
- CBC.__init__( self, AES(key, noPadding(), keySize), padding)
- self.name = 'AES_CBC'
-
- class AES_CBC(object):
- def __init__(self):
- self._key = None
- self._iv = None
- self.aes = None
-
- def set_decrypt_key(self, userkey, iv):
- self._key = userkey
- self._iv = iv
- self.aes = aescbc_AES_CBC(userkey, noPadding(), len(userkey))
-
- def decrypt(self, data):
- iv = self._iv
- cleartext = self.aes.decrypt(iv + data)
- return cleartext
-
- import hmac
-
- class KeyIVGen(object):
- # this only exists in openssl so we will use pure python implementation instead
- # PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
- # [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
- def pbkdf2(self, passwd, salt, iter, keylen):
-
- def xorstr( a, b ):
- if len(a) != len(b):
- raise Exception("xorstr(): lengths differ")
- return ''.join((chr(ord(x)^ord(y)) for x, y in zip(a, b)))
-
- def prf( h, data ):
- hm = h.copy()
- hm.update( data )
- return hm.digest()
-
- def pbkdf2_F( h, salt, itercount, blocknum ):
- U = prf( h, salt + pack('>i',blocknum ) )
- T = U
- for i in range(2, itercount+1):
- U = prf( h, U )
- T = xorstr( T, U )
- return T
-
- sha = hashlib.sha1
- digest_size = sha().digest_size
- # l - number of output blocks to produce
- l = keylen / digest_size
- if keylen % digest_size != 0:
- l += 1
- h = hmac.new( passwd, None, sha )
- T = ""
- for i in range(1, l+1):
- T += pbkdf2_F( h, salt, iter, i )
- return T[0: keylen]
-
- def UnprotectHeaderData(encryptedData):
- passwdData = 'header_key_data'
- salt = 'HEADER.2011'
- iter = 0x80
- keylen = 0x100
- key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen)
- key = key_iv[0:32]
- iv = key_iv[32:48]
- aes=AES_CBC()
- aes.set_decrypt_key(key, iv)
- cleartext = aes.decrypt(encryptedData)
- return cleartext
-
- # Various character maps used to decrypt kindle info values.
- # Probably supposed to act as obfuscation
- charMap2 = "AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
- charMap5 = "AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE"
- # New maps in K4PC 1.9.0
- testMap1 = "n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M"
- testMap6 = "9YzAb0Cd1Ef2n5Pr6St7Uvh3Jk4M8WxG"
- testMap8 = "YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD"
-
- # interface with Windows OS Routines
- class DataBlob(Structure):
- _fields_ = [('cbData', c_uint),
- ('pbData', c_void_p)]
- DataBlob_p = POINTER(DataBlob)
-
-
- def GetSystemDirectory():
- GetSystemDirectoryW = kernel32.GetSystemDirectoryW
- GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
- GetSystemDirectoryW.restype = c_uint
- def GetSystemDirectory():
- buffer = create_unicode_buffer(MAX_PATH + 1)
- GetSystemDirectoryW(buffer, len(buffer))
- return buffer.value
- return GetSystemDirectory
- GetSystemDirectory = GetSystemDirectory()
-
- def GetVolumeSerialNumber():
- GetVolumeInformationW = kernel32.GetVolumeInformationW
- GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
- POINTER(c_uint), POINTER(c_uint),
- POINTER(c_uint), c_wchar_p, c_uint]
- GetVolumeInformationW.restype = c_uint
- def GetVolumeSerialNumber(path = GetSystemDirectory().split('\\')[0] + '\\'):
- vsn = c_uint(0)
- GetVolumeInformationW(path, None, 0, byref(vsn), None, None, None, 0)
- return str(vsn.value)
- return GetVolumeSerialNumber
- GetVolumeSerialNumber = GetVolumeSerialNumber()
-
- def GetIDString():
- vsn = GetVolumeSerialNumber()
- #print('Using Volume Serial Number for ID: '+vsn)
- return vsn
-
- def getLastError():
- GetLastError = kernel32.GetLastError
- GetLastError.argtypes = None
- GetLastError.restype = c_uint
- def getLastError():
- return GetLastError()
- return getLastError
- getLastError = getLastError()
-
- def GetUserName():
- GetUserNameW = advapi32.GetUserNameW
- GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
- GetUserNameW.restype = c_uint
- def GetUserName():
- buffer = create_unicode_buffer(2)
- size = c_uint(len(buffer))
- while not GetUserNameW(buffer, byref(size)):
- errcd = getLastError()
- if errcd == 234:
- # bad wine implementation up through wine 1.3.21
- return "AlternateUserName"
- # double the buffer size
- buffer = create_unicode_buffer(len(buffer) * 2)
- size.value = len(buffer)
-
- # replace any non-ASCII values with 0xfffd
- for i in xrange(0,len(buffer)):
- if buffer[i]>u"\u007f":
- #print u"swapping char "+str(i)+" ("+buffer[i]+")"
- buffer[i] = u"\ufffd"
- # return utf-8 encoding of modified username
- #print u"modified username:"+buffer.value
- return buffer.value.encode('utf-8')
- return GetUserName
- GetUserName = GetUserName()
-
- def CryptUnprotectData():
- _CryptUnprotectData = crypt32.CryptUnprotectData
- _CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
- c_void_p, c_void_p, c_uint, DataBlob_p]
- _CryptUnprotectData.restype = c_uint
- def CryptUnprotectData(indata, entropy, flags):
- indatab = create_string_buffer(indata)
- indata = DataBlob(len(indata), cast(indatab, c_void_p))
- entropyb = create_string_buffer(entropy)
- entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
- outdata = DataBlob()
- if not _CryptUnprotectData(byref(indata), None, byref(entropy),
- None, None, flags, byref(outdata)):
- # raise DrmException("Failed to Unprotect Data")
- return 'failed'
- return string_at(outdata.pbData, outdata.cbData)
- return CryptUnprotectData
- CryptUnprotectData = CryptUnprotectData()
-
- # Returns Environmental Variables that contain unicode
- def getEnvironmentVariable(name):
- import ctypes
- name = unicode(name) # make sure string argument is unicode
- n = ctypes.windll.kernel32.GetEnvironmentVariableW(name, None, 0)
- if n == 0:
- return None
- buf = ctypes.create_unicode_buffer(u'\0'*n)
- ctypes.windll.kernel32.GetEnvironmentVariableW(name, buf, n)
- return buf.value
-
- # Locate all of the kindle-info style files and return as list
- def getKindleInfoFiles():
- kInfoFiles = []
- # some 64 bit machines do not have the proper registry key for some reason
- # or the python interface to the 32 vs 64 bit registry is broken
- path = ""
- if 'LOCALAPPDATA' in os.environ.keys():
- # Python 2.x does not return unicode env. Use Python 3.x
- path = winreg.ExpandEnvironmentStrings(u"%LOCALAPPDATA%")
- # this is just another alternative.
- # path = getEnvironmentVariable('LOCALAPPDATA')
- if not os.path.isdir(path):
- path = ""
- else:
- # User Shell Folders show take precedent over Shell Folders if present
- try:
- # this will still break
- regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\")
- path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
- if not os.path.isdir(path):
- path = ""
- try:
- regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
- path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
- if not os.path.isdir(path):
- path = ""
- except RegError:
- pass
- except RegError:
- pass
-
- found = False
- if path == "":
- print ('Could not find the folder in which to look for kinfoFiles.')
- else:
- # Probably not the best. To Fix (shouldn't ignore in encoding) or use utf-8
- print(u'searching for kinfoFiles in ' + path.encode('ascii', 'ignore'))
-
- # look for (K4PC 1.9.0 and later) .kinf2011 file
- kinfopath = path +'\\Amazon\\Kindle\\storage\\.kinf2011'
- if os.path.isfile(kinfopath):
- found = True
- print('Found K4PC 1.9+ kinf2011 file: ' + kinfopath.encode('ascii','ignore'))
- kInfoFiles.append(kinfopath)
-
- # look for (K4PC 1.6.0 and later) rainier.2.1.1.kinf file
- kinfopath = path +'\\Amazon\\Kindle\\storage\\rainier.2.1.1.kinf'
- if os.path.isfile(kinfopath):
- found = True
- print('Found K4PC 1.6-1.8 kinf file: ' + kinfopath)
- kInfoFiles.append(kinfopath)
-
- # look for (K4PC 1.5.0 and later) rainier.2.1.1.kinf file
- kinfopath = path +'\\Amazon\\Kindle For PC\\storage\\rainier.2.1.1.kinf'
- if os.path.isfile(kinfopath):
- found = True
- print('Found K4PC 1.5 kinf file: ' + kinfopath)
- kInfoFiles.append(kinfopath)
-
- # look for original (earlier than K4PC 1.5.0) kindle-info files
- kinfopath = path +'\\Amazon\\Kindle For PC\\{AMAwzsaPaaZAzmZzZQzgZCAkZ3AjA_AY}\\kindle.info'
- if os.path.isfile(kinfopath):
- found = True
- print('Found K4PC kindle.info file: ' + kinfopath)
- kInfoFiles.append(kinfopath)
-
- if not found:
- print('No K4PC kindle.info/kinf/kinf2011 files have been found.')
- return kInfoFiles
-
-
- # determine type of kindle info provided and return a
- # database of keynames and values
- def getDBfromFile(kInfoFile):
- names = [\
- 'kindle.account.tokens',\
- 'kindle.cookie.item',\
- 'eulaVersionAccepted',\
- 'login_date',\
- 'kindle.token.item',\
- 'login',\
- 'kindle.key.item',\
- 'kindle.name.info',\
- 'kindle.device.info',\
- 'MazamaRandomNumber',\
- 'max_date',\
- 'SIGVERIF',\
- 'build_version',\
- 'SerialNumber',\
- 'UsernameHash',\
- 'kindle.directedid.info',\
- 'DSN',\
- 'kindle.accounttype.info',\
- 'krx.flashcardsplugin.data.encryption_key',\
- 'krx.notebookexportplugin.data.encryption_key',\
- 'proxy.http.password',\
- 'proxy.http.username'
- ]
- DB = {}
- with open(kInfoFile, 'rb') as infoReader:
- data = infoReader.read()
- # assume newest .kinf2011 style .kinf file
- # the .kinf file uses "/" to separate it into records
- # so remove the trailing "/" to make it easy to use split
- data = data[:-1]
- items = data.split('/')
-
- # starts with an encoded and encrypted header blob
- headerblob = items.pop(0)
- encryptedValue = decode(headerblob, testMap1)
- cleartext = UnprotectHeaderData(encryptedValue)
- #print "header cleartext:",cleartext
- # now extract the pieces that form the added entropy
- pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
- for m in re.finditer(pattern, cleartext):
- added_entropy = m.group(2) + m.group(4)
-
-
- # loop through the item records until all are processed
- while len(items) > 0:
-
- # get the first item record
- item = items.pop(0)
-
- # the first 32 chars of the first record of a group
- # is the MD5 hash of the key name encoded by charMap5
- keyhash = item[0:32]
-
- # the sha1 of raw keyhash string is used to create entropy along
- # with the added entropy provided above from the headerblob
- entropy = SHA1(keyhash) + added_entropy
-
- # the remainder of the first record when decoded with charMap5
- # has the ':' split char followed by the string representation
- # of the number of records that follow
- # and make up the contents
- srcnt = decode(item[34:],charMap5)
- rcnt = int(srcnt)
-
- # read and store in rcnt records of data
- # that make up the contents value
- edlst = []
- for i in xrange(rcnt):
- item = items.pop(0)
- edlst.append(item)
-
- # key names now use the new testMap8 encoding
- keyname = "unknown"
- for name in names:
- if encodeHash(name,testMap8) == keyhash:
- keyname = name
- #print "keyname found from hash:",keyname
- break
- if keyname == "unknown":
- keyname = keyhash
- #print "keyname not found, hash is:",keyname
-
- # the testMap8 encoded contents data has had a length
- # of chars (always odd) cut off of the front and moved
- # to the end to prevent decoding using testMap8 from
- # working properly, and thereby preventing the ensuing
- # CryptUnprotectData call from succeeding.
-
- # The offset into the testMap8 encoded contents seems to be:
- # len(contents)-largest prime number <= int(len(content)/3)
- # (in other words split "about" 2/3rds of the way through)
-
- # move first offsets chars to end to align for decode by testMap8
- # by moving noffset chars from the start of the
- # string to the end of the string
- encdata = "".join(edlst)
- #print "encrypted data:",encdata
- contlen = len(encdata)
- noffset = contlen - primes(int(contlen/3))[-1]
- pfx = encdata[0:noffset]
- encdata = encdata[noffset:]
- encdata = encdata + pfx
- #print "rearranged data:",encdata
-
-
- # decode using new testMap8 to get the original CryptProtect Data
- encryptedValue = decode(encdata,testMap8)
- #print "decoded data:",encryptedValue.encode('hex')
- cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
- if len(cleartext)>0:
- #print "cleartext data:",cleartext,":end data"
- DB[keyname] = cleartext
- #print keyname, cleartext
-
- if len(DB)>6:
- # store values used in decryption
- DB['IDString'] = GetIDString()
- DB['UserName'] = GetUserName()
- print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))
- else:
- print u"Couldn't decrypt file."
- DB = {}
- return DB
-elif isosx:
- import copy
- import subprocess
-
- # interface to needed routines in openssl's libcrypto
- def _load_crypto_libcrypto():
- from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
- Structure, c_ulong, create_string_buffer, addressof, string_at, cast
- from ctypes.util import find_library
-
- libcrypto = find_library('crypto')
- if libcrypto is None:
- raise DrmException(u"libcrypto not found")
- libcrypto = CDLL(libcrypto)
-
- # From OpenSSL's crypto aes header
- #
- # AES_ENCRYPT 1
- # AES_DECRYPT 0
- # AES_MAXNR 14 (in bytes)
- # AES_BLOCK_SIZE 16 (in bytes)
- #
- # struct aes_key_st {
- # unsigned long rd_key[4 *(AES_MAXNR + 1)];
- # int rounds;
- # };
- # typedef struct aes_key_st AES_KEY;
- #
- # int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
- #
- # note: the ivec string, and output buffer are both mutable
- # void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
- # const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc);
-
- AES_MAXNR = 14
- c_char_pp = POINTER(c_char_p)
- c_int_p = POINTER(c_int)
-
- class AES_KEY(Structure):
- _fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
- AES_KEY_p = POINTER(AES_KEY)
-
- def F(restype, name, argtypes):
- func = getattr(libcrypto, name)
- func.restype = restype
- func.argtypes = argtypes
- return func
-
- AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
-
- AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
-
- # From OpenSSL's Crypto evp/p5_crpt2.c
- #
- # int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
- # const unsigned char *salt, int saltlen, int iter,
- # int keylen, unsigned char *out);
-
- PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
- [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
-
- class LibCrypto(object):
- def __init__(self):
- self._blocksize = 0
- self._keyctx = None
- self._iv = 0
-
- def set_decrypt_key(self, userkey, iv):
- self._blocksize = len(userkey)
- if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
- raise DrmException(u"AES improper key used")
- return
- keyctx = self._keyctx = AES_KEY()
- self._iv = iv
- self._userkey = userkey
- rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
- if rv < 0:
- raise DrmException(u"Failed to initialize AES key")
-
- def decrypt(self, data):
- out = create_string_buffer(len(data))
- mutable_iv = create_string_buffer(self._iv, len(self._iv))
- keyctx = self._keyctx
- rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
- if rv == 0:
- raise DrmException(u"AES decryption failed")
- return out.raw
-
- def keyivgen(self, passwd, salt, iter, keylen):
- saltlen = len(salt)
- passlen = len(passwd)
- out = create_string_buffer(keylen)
- rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
- return out.raw
- return LibCrypto
-
- def _load_crypto():
- LibCrypto = None
- try:
- LibCrypto = _load_crypto_libcrypto()
- except (ImportError, DrmException):
- pass
- return LibCrypto
-
- LibCrypto = _load_crypto()
-
- # Various character maps used to decrypt books. Probably supposed to act as obfuscation
- charMap1 = 'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
- charMap2 = 'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
-
- # For kinf approach of K4Mac 1.6.X or later
- # On K4PC charMap5 = 'AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE'
- # For Mac they seem to re-use charMap2 here
- charMap5 = charMap2
-
- # new in K4M 1.9.X
- testMap8 = 'YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD'
-
- # uses a sub process to get the Hard Drive Serial Number using ioreg
- # returns serial numbers of all internal hard drive drives
- def GetVolumesSerialNumbers():
- sernums = []
- sernum = os.getenv('MYSERIALNUMBER')
- if sernum != None:
- sernums.append(sernum.strip())
- cmdline = '/usr/sbin/ioreg -w 0 -r -c AppleAHCIDiskDriver'
- cmdline = cmdline.encode(sys.getfilesystemencoding())
- p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
- out1, out2 = p.communicate()
- #print out1
- reslst = out1.split('\n')
- cnt = len(reslst)
- for j in xrange(cnt):
- resline = reslst[j]
- pp = resline.find('\"Serial Number\" = \"')
- if pp >= 0:
- sernum = resline[pp+19:-1]
- sernums.append(sernum.strip())
- return sernums
-
- def GetDiskPartitionNames():
- names = []
- cmdline = '/sbin/mount'
- cmdline = cmdline.encode(sys.getfilesystemencoding())
- p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
- out1, out2 = p.communicate()
- reslst = out1.split('\n')
- cnt = len(reslst)
- for j in xrange(cnt):
- resline = reslst[j]
- if resline.startswith('/dev'):
- (devpart, mpath) = resline.split(' on ')[:2]
- dpart = devpart[5:]
- names.append(dpart)
- return names
-
- # uses a sub process to get the UUID of all disk partitions
- def GetDiskPartitionUUIDs():
- uuids = []
- uuidnum = os.getenv('MYUUIDNUMBER')
- if uuidnum != None:
- uuids.append(strip(uuidnum))
- cmdline = '/usr/sbin/ioreg -l -S -w 0 -r -c AppleAHCIDiskDriver'
- cmdline = cmdline.encode(sys.getfilesystemencoding())
- p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
- out1, out2 = p.communicate()
- #print out1
- reslst = out1.split('\n')
- cnt = len(reslst)
- for j in xrange(cnt):
- resline = reslst[j]
- pp = resline.find('\"UUID\" = \"')
- if pp >= 0:
- uuidnum = resline[pp+10:-1]
- uuidnum = uuidnum.strip()
- uuids.append(uuidnum)
- return uuids
-
- def GetMACAddressesMunged():
- macnums = []
- macnum = os.getenv('MYMACNUM')
- if macnum != None:
- macnums.append(macnum)
- cmdline = 'networksetup -listallhardwareports' # en0'
- cmdline = cmdline.encode(sys.getfilesystemencoding())
- p = subprocess.Popen(cmdline, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=False)
- out1, out2 = p.communicate()
- reslst = out1.split('\n')
- cnt = len(reslst)
- for j in xrange(cnt):
- resline = reslst[j]
- pp = resline.find('Ethernet Address: ')
- if pp >= 0:
- #print resline
- macnum = resline[pp+18:]
- macnum = macnum.strip()
- maclst = macnum.split(':')
- n = len(maclst)
- if n != 6:
- continue
- #print 'original mac', macnum
- # now munge it up the way Kindle app does
- # by xoring it with 0xa5 and swapping elements 3 and 4
- for i in range(6):
- maclst[i] = int('0x' + maclst[i], 0)
- mlst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
- mlst[5] = maclst[5] ^ 0xa5
- mlst[4] = maclst[3] ^ 0xa5
- mlst[3] = maclst[4] ^ 0xa5
- mlst[2] = maclst[2] ^ 0xa5
- mlst[1] = maclst[1] ^ 0xa5
- mlst[0] = maclst[0] ^ 0xa5
- macnum = '%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x' % (mlst[0], mlst[1], mlst[2], mlst[3], mlst[4], mlst[5])
- #print 'munged mac', macnum
- macnums.append(macnum)
- return macnums
-
-
- # uses unix env to get username instead of using sysctlbyname
- def GetUserName():
- username = os.getenv('USER')
- #print "Username:",username
- return username
-
- def GetIDStrings():
- # Return all possible ID Strings
- strings = []
- strings.extend(GetMACAddressesMunged())
- strings.extend(GetVolumesSerialNumbers())
- strings.extend(GetDiskPartitionNames())
- strings.extend(GetDiskPartitionUUIDs())
- strings.append('9999999999')
- #print "ID Strings:\n",strings
- return strings
-
-
- # unprotect the new header blob in .kinf2011
- # used in Kindle for Mac Version >= 1.9.0
- def UnprotectHeaderData(encryptedData):
- passwdData = 'header_key_data'
- salt = 'HEADER.2011'
- iter = 0x80
- keylen = 0x100
- crp = LibCrypto()
- key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
- key = key_iv[0:32]
- iv = key_iv[32:48]
- crp.set_decrypt_key(key,iv)
- cleartext = crp.decrypt(encryptedData)
- return cleartext
-
-
- # implements an Pseudo Mac Version of Windows built-in Crypto routine
- class CryptUnprotectData(object):
- def __init__(self, entropy, IDString):
- sp = GetUserName() + '+@#$%+' + IDString
- passwdData = encode(SHA256(sp),charMap2)
- salt = entropy
- self.crp = LibCrypto()
- iter = 0x800
- keylen = 0x400
- key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
- self.key = key_iv[0:32]
- self.iv = key_iv[32:48]
- self.crp.set_decrypt_key(self.key, self.iv)
-
- def decrypt(self, encryptedData):
- cleartext = self.crp.decrypt(encryptedData)
- cleartext = decode(cleartext, charMap2)
- return cleartext
-
-
- # Locate the .kindle-info files
- def getKindleInfoFiles():
- # file searches can take a long time on some systems, so just look in known specific places.
- kInfoFiles=[]
- found = False
- home = os.getenv('HOME')
- # check for .kinf2011 file in new location (App Store Kindle for Mac)
- testpath = home + '/Library/Containers/com.amazon.Kindle/Data/Library/Application Support/Kindle/storage/.kinf2011'
- if os.path.isfile(testpath):
- kInfoFiles.append(testpath)
- print('Found k4Mac kinf2011 file: ' + testpath)
- found = True
- # check for .kinf2011 files from 1.10
- testpath = home + '/Library/Application Support/Kindle/storage/.kinf2011'
- if os.path.isfile(testpath):
- kInfoFiles.append(testpath)
- print('Found k4Mac kinf2011 file: ' + testpath)
- found = True
- # check for .rainier-2.1.1-kinf files from 1.6
- testpath = home + '/Library/Application Support/Kindle/storage/.rainier-2.1.1-kinf'
- if os.path.isfile(testpath):
- kInfoFiles.append(testpath)
- print('Found k4Mac rainier file: ' + testpath)
- found = True
- # check for .kindle-info files from 1.4
- testpath = home + '/Library/Application Support/Kindle/storage/.kindle-info'
- if os.path.isfile(testpath):
- kInfoFiles.append(testpath)
- print('Found k4Mac kindle-info file: ' + testpath)
- found = True
- # check for .kindle-info file from 1.2.2
- testpath = home + '/Library/Application Support/Amazon/Kindle/storage/.kindle-info'
- if os.path.isfile(testpath):
- kInfoFiles.append(testpath)
- print('Found k4Mac kindle-info file: ' + testpath)
- found = True
- # check for .kindle-info file from 1.0 beta 1 (27214)
- testpath = home + '/Library/Application Support/Amazon/Kindle for Mac/storage/.kindle-info'
- if os.path.isfile(testpath):
- kInfoFiles.append(testpath)
- print('Found k4Mac kindle-info file: ' + testpath)
- found = True
- if not found:
- print('No k4Mac kindle-info/rainier/kinf2011 files have been found.')
- return kInfoFiles
-
- # determine type of kindle info provided and return a
- # database of keynames and values
- def getDBfromFile(kInfoFile):
- names = [\
- 'kindle.account.tokens',\
- 'kindle.cookie.item',\
- 'eulaVersionAccepted',\
- 'login_date',\
- 'kindle.token.item',\
- 'login',\
- 'kindle.key.item',\
- 'kindle.name.info',\
- 'kindle.device.info',\
- 'MazamaRandomNumber',\
- 'max_date',\
- 'SIGVERIF',\
- 'build_version',\
- 'SerialNumber',\
- 'UsernameHash',\
- 'kindle.directedid.info',\
- 'DSN'
- ]
- with open(kInfoFile, 'rb') as infoReader:
- filedata = infoReader.read()
-
- data = filedata[:-1]
- items = data.split('/')
- IDStrings = GetIDStrings()
- for IDString in IDStrings:
- #print "trying IDString:",IDString
- try:
- DB = {}
- items = data.split('/')
-
- # the headerblob is the encrypted information needed to build the entropy string
- headerblob = items.pop(0)
- encryptedValue = decode(headerblob, charMap1)
- cleartext = UnprotectHeaderData(encryptedValue)
-
- # now extract the pieces in the same way
- # this version is different from K4PC it scales the build number by multipying by 735
- pattern = re.compile(r'''\[Version:(\d+)\]\[Build:(\d+)\]\[Cksum:([^\]]+)\]\[Guid:([\{\}a-z0-9\-]+)\]''', re.IGNORECASE)
- for m in re.finditer(pattern, cleartext):
- entropy = str(int(m.group(2)) * 0x2df) + m.group(4)
-
- cud = CryptUnprotectData(entropy,IDString)
-
- # loop through the item records until all are processed
- while len(items) > 0:
-
- # get the first item record
- item = items.pop(0)
-
- # the first 32 chars of the first record of a group
- # is the MD5 hash of the key name encoded by charMap5
- keyhash = item[0:32]
- keyname = 'unknown'
-
- # unlike K4PC the keyhash is not used in generating entropy
- # entropy = SHA1(keyhash) + added_entropy
- # entropy = added_entropy
-
- # the remainder of the first record when decoded with charMap5
- # has the ':' split char followed by the string representation
- # of the number of records that follow
- # and make up the contents
- srcnt = decode(item[34:],charMap5)
- rcnt = int(srcnt)
-
- # read and store in rcnt records of data
- # that make up the contents value
- edlst = []
- for i in xrange(rcnt):
- item = items.pop(0)
- edlst.append(item)
-
- keyname = 'unknown'
- for name in names:
- if encodeHash(name,testMap8) == keyhash:
- keyname = name
- break
- if keyname == 'unknown':
- keyname = keyhash
-
- # the testMap8 encoded contents data has had a length
- # of chars (always odd) cut off of the front and moved
- # to the end to prevent decoding using testMap8 from
- # working properly, and thereby preventing the ensuing
- # CryptUnprotectData call from succeeding.
-
- # The offset into the testMap8 encoded contents seems to be:
- # len(contents) - largest prime number less than or equal to int(len(content)/3)
- # (in other words split 'about' 2/3rds of the way through)
-
- # move first offsets chars to end to align for decode by testMap8
- encdata = ''.join(edlst)
- contlen = len(encdata)
-
- # now properly split and recombine
- # by moving noffset chars from the start of the
- # string to the end of the string
- noffset = contlen - primes(int(contlen/3))[-1]
- pfx = encdata[0:noffset]
- encdata = encdata[noffset:]
- encdata = encdata + pfx
-
- # decode using testMap8 to get the CryptProtect Data
- encryptedValue = decode(encdata,testMap8)
- cleartext = cud.decrypt(encryptedValue)
- # print keyname
- # print cleartext
- if len(cleartext) > 0:
- DB[keyname] = cleartext
-
- if len(DB)>6:
- break
- except:
- pass
- if len(DB)>6:
- # store values used in decryption
- print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(IDString, GetUserName())
- DB['IDString'] = IDString
- DB['UserName'] = GetUserName()
- else:
- print u"Couldn't decrypt file."
- DB = {}
- return DB
-else:
- def getDBfromFile(kInfoFile):
- raise DrmException(u"This script only runs under Windows or Mac OS X.")
- return {}
-
-def kindlekeys(files = []):
- keys = []
- if files == []:
- files = getKindleInfoFiles()
- for file in files:
- key = getDBfromFile(file)
- if key:
- # convert all values to hex, just in case.
- for keyname in key:
- key[keyname]=key[keyname].encode('hex')
- keys.append(key)
- return keys
-
-# interface for Python DeDRM
-# returns single key or multiple keys, depending on path or file passed in
-def getkey(outpath, files=[]):
- keys = kindlekeys(files)
- if len(keys) > 0:
- if not os.path.isdir(outpath):
- outfile = outpath
- with file(outfile, 'w') as keyfileout:
- keyfileout.write(json.dumps(keys[0]))
- print u"Saved a key to {0}".format(outfile)
- else:
- keycount = 0
- for key in keys:
- while True:
- keycount += 1
- outfile = os.path.join(outpath,u"kindlekey{0:d}.k4i".format(keycount))
- if not os.path.exists(outfile):
- break
- with file(outfile, 'w') as keyfileout:
- keyfileout.write(json.dumps(key))
- print u"Saved a key to {0}".format(outfile)
- return True
- return False
-
-def usage(progname):
- print u"Finds, decrypts and saves the default Kindle For Mac/PC encryption keys."
- print u"Keys are saved to the current directory, or a specified output directory."
- print u"If a file name is passed instead of a directory, only the first key is saved, in that file."
- print u"Usage:"
- print u" {0:s} [-h] [-k ] []".format(progname)
-
-
-def cli_main():
- sys.stdout=SafeUnbuffered(sys.stdout)
- sys.stderr=SafeUnbuffered(sys.stderr)
- argv=unicode_argv()
- progname = os.path.basename(argv[0])
- print u"{0} v{1}\nCopyright © 2010-2016 by some_updates, Apprentice Alf and Apprentice Harper".format(progname,__version__)
-
- try:
- opts, args = getopt.getopt(argv[1:], "hk:")
- except getopt.GetoptError, err:
- print u"Error in options or arguments: {0}".format(err.args[0])
- usage(progname)
- sys.exit(2)
-
- files = []
- for o, a in opts:
- if o == "-h":
- usage(progname)
- sys.exit(0)
- if o == "-k":
- files = [a]
-
- if len(args) > 1:
- usage(progname)
- sys.exit(2)
-
- if len(args) == 1:
- # save to the specified file or directory
- outpath = args[0]
- if not os.path.isabs(outpath):
- outpath = os.path.abspath(outpath)
- else:
- # save to the same directory as the script
- outpath = os.path.dirname(argv[0])
-
- # make sure the outpath is canonical
- outpath = os.path.realpath(os.path.normpath(outpath))
-
- if not getkey(outpath, files):
- print u"Could not retrieve Kindle for Mac/PC key."
- return 0
-
-
-def gui_main():
- try:
- import Tkinter
- import Tkconstants
- import tkMessageBox
- import traceback
- except:
- return cli_main()
-
- class ExceptionDialog(Tkinter.Frame):
- def __init__(self, root, text):
- Tkinter.Frame.__init__(self, root, border=5)
- label = Tkinter.Label(self, text=u"Unexpected error:",
- anchor=Tkconstants.W, justify=Tkconstants.LEFT)
- label.pack(fill=Tkconstants.X, expand=0)
- self.text = Tkinter.Text(self)
- self.text.pack(fill=Tkconstants.BOTH, expand=1)
-
- self.text.insert(Tkconstants.END, text)
-
-
- argv=unicode_argv()
- root = Tkinter.Tk()
- root.withdraw()
- progpath, progname = os.path.split(argv[0])
- success = False
- try:
- keys = kindlekeys()
- keycount = 0
- for key in keys:
- while True:
- keycount += 1
- outfile = os.path.join(progpath,u"kindlekey{0:d}.k4i".format(keycount))
- if not os.path.exists(outfile):
- break
-
- with file(outfile, 'w') as keyfileout:
- keyfileout.write(json.dumps(key))
- success = True
- tkMessageBox.showinfo(progname, u"Key successfully retrieved to {0}".format(outfile))
- except DrmException, e:
- tkMessageBox.showerror(progname, u"Error: {0}".format(str(e)))
- except Exception:
- root.wm_state('normal')
- root.title(progname)
- text = traceback.format_exc()
- ExceptionDialog(root, text).pack(fill=Tkconstants.BOTH, expand=1)
- root.mainloop()
- if not success:
- return 1
- return 0
-
-if __name__ == '__main__':
- if len(sys.argv) > 1:
- sys.exit(cli_main())
- sys.exit(gui_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlepid.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlepid.py
deleted file mode 100644
index 8bbcf69..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/kindlepid.py
+++ /dev/null
@@ -1,144 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Mobipocket PID calculator v0.4 for Amazon Kindle.
-# Copyright (c) 2007, 2009 Igor Skochinsky
-# History:
-# 0.1 Initial release
-# 0.2 Added support for generating PID for iPhone (thanks to mbp)
-# 0.3 changed to autoflush stdout, fixed return code usage
-# 0.3 updated for unicode
-# 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
-
-import sys
-import binascii
-
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
- def __init__(self, stream):
- self.stream = stream
- self.encoding = stream.encoding
- if self.encoding == None:
- self.encoding = "utf-8"
- def write(self, data):
- if isinstance(data,unicode):
- data = data.encode(self.encoding,"replace")
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-iswindows = sys.platform.startswith('win')
-isosx = sys.platform.startswith('darwin')
-
-def unicode_argv():
- if iswindows:
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'.
-
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- # if we don't have any arguments at all, just pass back script name
- # this should never happen
- return [u"kindlepid.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = "utf-8"
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-if sys.hexversion >= 0x3000000:
- print 'This script is incompatible with Python 3.x. Please install Python 2.7.x.'
- sys.exit(2)
-
-letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
-
-def crc32(s):
- return (~binascii.crc32(s,-1))&0xFFFFFFFF
-
-def checksumPid(s):
- crc = crc32(s)
- crc = crc ^ (crc >> 16)
- res = s
- l = len(letters)
- for i in (0,1):
- b = crc & 0xff
- pos = (b // l) ^ (b % l)
- res += letters[pos%l]
- crc >>= 8
-
- return res
-
-def pidFromSerial(s, l):
- crc = crc32(s)
-
- arr1 = [0]*l
- for i in xrange(len(s)):
- arr1[i%l] ^= ord(s[i])
-
- crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
- for i in xrange(l):
- arr1[i] ^= crc_bytes[i&3]
-
- pid = ''
- for i in xrange(l):
- b = arr1[i] & 0xff
- pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
-
- return pid
-
-def cli_main():
- print u"Mobipocket PID calculator for Amazon Kindle. Copyright © 2007, 2009 Igor Skochinsky"
- argv=unicode_argv()
- if len(argv)==2:
- serial = argv[1]
- else:
- print u"Usage: kindlepid.py /"
- return 1
- if len(serial)==16:
- if serial.startswith("B") or serial.startswith("9"):
- print u"Kindle serial number detected"
- else:
- print u"Warning: unrecognized serial number. Please recheck input."
- return 1
- pid = pidFromSerial(serial.encode("utf-8"),7)+'*'
- print u"Mobipocket PID for Kindle serial#{0} is {1}".format(serial,checksumPid(pid))
- return 0
- elif len(serial)==40:
- print u"iPhone serial number (UDID) detected"
- pid = pidFromSerial(serial.encode("utf-8"),8)
- print u"Mobipocket PID for iPhone serial#{0} is {1}".format(serial,checksumPid(pid))
- return 0
- print u"Warning: unrecognized serial number. Please recheck input."
- return 1
-
-
-if __name__ == "__main__":
- sys.stdout=SafeUnbuffered(sys.stdout)
- sys.stderr=SafeUnbuffered(sys.stderr)
- sys.exit(cli_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto.dylib b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto.dylib
deleted file mode 100644
index 01c348c..0000000
Binary files a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto.dylib and /dev/null differ
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto32.so b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto32.so
deleted file mode 100644
index 9a5a442..0000000
Binary files a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto32.so and /dev/null differ
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto64.so b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto64.so
deleted file mode 100644
index a08ac28..0000000
Binary files a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/libalfcrypto64.so and /dev/null differ
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py
deleted file mode 100644
index 501aa2d..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/mobidedrm.py
+++ /dev/null
@@ -1,543 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# mobidedrm.py
-# Copyright © 2008 The Dark Reverser
-# Portions © 2008–2017 Apprentice Harper et al.
-
-__license__ = 'GPL v3'
-__version__ = u"0.42"
-
-# This is a python script. You need a Python interpreter to run it.
-# For example, ActiveState Python, which exists for windows.
-#
-# Changelog
-# 0.01 - Initial version
-# 0.02 - Huffdic compressed books were not properly decrypted
-# 0.03 - Wasn't checking MOBI header length
-# 0.04 - Wasn't sanity checking size of data record
-# 0.05 - It seems that the extra data flags take two bytes not four
-# 0.06 - And that low bit does mean something after all :-)
-# 0.07 - The extra data flags aren't present in MOBI header < 0xE8 in size
-# 0.08 - ...and also not in Mobi header version < 6
-# 0.09 - ...but they are there with Mobi header version 6, header size 0xE4!
-# 0.10 - Outputs unencrypted files as-is, so that when run as a Calibre
-# import filter it works when importing unencrypted files.
-# Also now handles encrypted files that don't need a specific PID.
-# 0.11 - use autoflushed stdout and proper return values
-# 0.12 - Fix for problems with metadata import as Calibre plugin, report errors
-# 0.13 - Formatting fixes: retabbed file, removed trailing whitespace
-# and extra blank lines, converted CR/LF pairs at ends of each line,
-# and other cosmetic fixes.
-# 0.14 - Working out when the extra data flags are present has been problematic
-# Versions 7 through 9 have tried to tweak the conditions, but have been
-# only partially successful. Closer examination of lots of sample
-# files reveals that a confusion has arisen because trailing data entries
-# are not encrypted, but it turns out that the multibyte entries
-# in utf8 file are encrypted. (Although neither kind gets compressed.)
-# This knowledge leads to a simplification of the test for the
-# trailing data byte flags - version 5 and higher AND header size >= 0xE4.
-# 0.15 - Now outputs 'heartbeat', and is also quicker for long files.
-# 0.16 - And reverts to 'done' not 'done.' at the end for unswindle compatibility.
-# 0.17 - added modifications to support its use as an imported python module
-# both inside calibre and also in other places (ie K4DeDRM tools)
-# 0.17a- disabled the standalone plugin feature since a plugin can not import
-# a plugin
-# 0.18 - It seems that multibyte entries aren't encrypted in a v7 file...
-# Removed the disabled Calibre plug-in code
-# Permit use of 8-digit PIDs
-# 0.19 - It seems that multibyte entries aren't encrypted in a v6 file either.
-# 0.20 - Correction: It seems that multibyte entries are encrypted in a v6 file.
-# 0.21 - Added support for multiple pids
-# 0.22 - revised structure to hold MobiBook as a class to allow an extended interface
-# 0.23 - fixed problem with older files with no EXTH section
-# 0.24 - add support for type 1 encryption and 'TEXtREAd' books as well
-# 0.25 - Fixed support for 'BOOKMOBI' type 1 encryption
-# 0.26 - Now enables Text-To-Speech flag and sets clipping limit to 100%
-# 0.27 - Correct pid metadata token generation to match that used by skindle (Thank You Bart!)
-# 0.28 - slight additional changes to metadata token generation (None -> '')
-# 0.29 - It seems that the ideas about when multibyte trailing characters were
-# included in the encryption were wrong. They are for DOC compressed
-# files, but they are not for HUFF/CDIC compress files!
-# 0.30 - Modified interface slightly to work better with new calibre plugin style
-# 0.31 - The multibyte encrytion info is true for version 7 files too.
-# 0.32 - Added support for "Print Replica" Kindle ebooks
-# 0.33 - Performance improvements for large files (concatenation)
-# 0.34 - Performance improvements in decryption (libalfcrypto)
-# 0.35 - add interface to get mobi_version
-# 0.36 - fixed problem with TEXtREAd and getBookTitle interface
-# 0.37 - Fixed double announcement for stand-alone operation
-# 0.38 - Unicode used wherever possible, cope with absent alfcrypto
-# 0.39 - Fixed problem with TEXtREAd and getBookType interface
-# 0.40 - moved unicode_argv call inside main for Windows DeDRM compatibility
-# 0.41 - Fixed potential unicode problem in command line calls
-# 0.42 - Added GPL v3 licence. updated/removed some print statements
-
-import sys
-import os
-import struct
-import binascii
-try:
- from alfcrypto import Pukall_Cipher
-except:
- print u"AlfCrypto not found. Using python PC1 implementation."
-
-# Wrap a stream so that output gets flushed immediately
-# and also make sure that any unicode strings get
-# encoded using "replace" before writing them.
-class SafeUnbuffered:
- def __init__(self, stream):
- self.stream = stream
- self.encoding = stream.encoding
- if self.encoding == None:
- self.encoding = "utf-8"
- def write(self, data):
- if isinstance(data,unicode):
- data = data.encode(self.encoding,"replace")
- self.stream.write(data)
- self.stream.flush()
- def __getattr__(self, attr):
- return getattr(self.stream, attr)
-
-iswindows = sys.platform.startswith('win')
-isosx = sys.platform.startswith('darwin')
-
-def unicode_argv():
- if iswindows:
- # Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
- # strings.
-
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'.
-
-
- from ctypes import POINTER, byref, cdll, c_int, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- GetCommandLineW = cdll.kernel32.GetCommandLineW
- GetCommandLineW.argtypes = []
- GetCommandLineW.restype = LPCWSTR
-
- CommandLineToArgvW = windll.shell32.CommandLineToArgvW
- CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
- CommandLineToArgvW.restype = POINTER(LPWSTR)
-
- cmd = GetCommandLineW()
- argc = c_int(0)
- argv = CommandLineToArgvW(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in
- xrange(start, argc.value)]
- # if we don't have any arguments at all, just pass back script name
- # this should never happen
- return [u"mobidedrm.py"]
- else:
- argvencoding = sys.stdin.encoding
- if argvencoding == None:
- argvencoding = 'utf-8'
- return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
-
-
-class DrmException(Exception):
- pass
-
-
-#
-# MobiBook Utility Routines
-#
-
-# Implementation of Pukall Cipher 1
-def PC1(key, src, decryption=True):
- # if we can get it from alfcrypto, use that
- try:
- return Pukall_Cipher().PC1(key,src,decryption)
- except NameError:
- pass
- except TypeError:
- pass
-
- # use slow python version, since Pukall_Cipher didn't load
- sum1 = 0;
- sum2 = 0;
- keyXorVal = 0;
- if len(key)!=16:
- DrmException (u"PC1: Bad key length")
- wkey = []
- for i in xrange(8):
- wkey.append(ord(key[i*2])<<8 | ord(key[i*2+1]))
- dst = ""
- for i in xrange(len(src)):
- temp1 = 0;
- byteXorVal = 0;
- for j in xrange(8):
- temp1 ^= wkey[j]
- sum2 = (sum2+j)*20021 + sum1
- sum1 = (temp1*346)&0xFFFF
- sum2 = (sum2+sum1)&0xFFFF
- temp1 = (temp1*20021+1)&0xFFFF
- byteXorVal ^= temp1 ^ sum2
- curByte = ord(src[i])
- if not decryption:
- keyXorVal = curByte * 257;
- curByte = ((curByte ^ (byteXorVal >> 8)) ^ byteXorVal) & 0xFF
- if decryption:
- keyXorVal = curByte * 257;
- for j in xrange(8):
- wkey[j] ^= keyXorVal;
- dst+=chr(curByte)
- return dst
-
-def checksumPid(s):
- letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
- crc = (~binascii.crc32(s,-1))&0xFFFFFFFF
- crc = crc ^ (crc >> 16)
- res = s
- l = len(letters)
- for i in (0,1):
- b = crc & 0xff
- pos = (b // l) ^ (b % l)
- res += letters[pos%l]
- crc >>= 8
- return res
-
-def getSizeOfTrailingDataEntries(ptr, size, flags):
- def getSizeOfTrailingDataEntry(ptr, size):
- bitpos, result = 0, 0
- if size <= 0:
- return result
- while True:
- v = ord(ptr[size-1])
- result |= (v & 0x7F) << bitpos
- bitpos += 7
- size -= 1
- if (v & 0x80) != 0 or (bitpos >= 28) or (size == 0):
- return result
- num = 0
- testflags = flags >> 1
- while testflags:
- if testflags & 1:
- num += getSizeOfTrailingDataEntry(ptr, size - num)
- testflags >>= 1
- # Check the low bit to see if there's multibyte data present.
- # if multibyte data is included in the encryped data, we'll
- # have already cleared this flag.
- if flags & 1:
- num += (ord(ptr[size - num - 1]) & 0x3) + 1
- return num
-
-
-
-class MobiBook:
- def loadSection(self, section):
- if (section + 1 == self.num_sections):
- endoff = len(self.data_file)
- else:
- endoff = self.sections[section + 1][0]
- off = self.sections[section][0]
- return self.data_file[off:endoff]
-
- def cleanup(self):
- # to match function in Topaz book
- pass
-
- def __init__(self, infile):
- print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)
-
- try:
- from alfcrypto import Pukall_Cipher
- except:
- print u"AlfCrypto not found. Using python PC1 implementation."
-
- # initial sanity check on file
- self.data_file = file(infile, 'rb').read()
- self.mobi_data = ''
- self.header = self.data_file[0:78]
- if self.header[0x3C:0x3C+8] != 'BOOKMOBI' and self.header[0x3C:0x3C+8] != 'TEXtREAd':
- raise DrmException(u"Invalid file format")
- self.magic = self.header[0x3C:0x3C+8]
- self.crypto_type = -1
-
- # build up section offset and flag info
- self.num_sections, = struct.unpack('>H', self.header[76:78])
- self.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])
- flags, val = a1, a2<<16|a3<<8|a4
- self.sections.append( (offset, flags, val) )
-
- # parse information from section 0
- self.sect = self.loadSection(0)
- self.records, = struct.unpack('>H', self.sect[0x8:0x8+2])
- self.compression, = struct.unpack('>H', self.sect[0x0:0x0+2])
-
- # det default values before PalmDoc test
- self.print_replica = False
- self.extra_data_flags = 0
- self.meta_array = {}
- self.mobi_length = 0
- self.mobi_codepage = 1252
- self.mobi_version = -1
-
- if self.magic == 'TEXtREAd':
- print u"PalmDoc format book detected."
- return
-
- self.mobi_length, = struct.unpack('>L',self.sect[0x14:0x18])
- self.mobi_codepage, = struct.unpack('>L',self.sect[0x1c:0x20])
- self.mobi_version, = struct.unpack('>L',self.sect[0x68:0x6C])
- #print 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):
- self.extra_data_flags, = struct.unpack('>H', self.sect[0xF2:0xF4])
- #print u"Extra Data Flags: {0:d}".format(self.extra_data_flags)
- if (self.compression != 17480):
- # multibyte utf8 data is included in the encryption for PalmDoc compression
- # so clear that byte so that we leave it to be decrypted.
- self.extra_data_flags &= 0xFFFE
-
- # if exth region exists parse it for metadata array
- try:
- exth_flag, = struct.unpack('>L', self.sect[0x80:0x84])
- exth = ''
- if exth_flag & 0x40:
- exth = self.sect[16 + self.mobi_length:]
- if (len(exth) >= 12) and (exth[:4] == 'EXTH'):
- nitems, = struct.unpack('>I', exth[8:12])
- pos = 12
- for i in xrange(nitems):
- type, size = struct.unpack('>II', exth[pos: pos + 8])
- content = exth[pos + 8: pos + size]
- self.meta_array[type] = content
- # reset the text to speech flag and clipping limit, if present
- if type == 401 and size == 9:
- # set clipping limit to 100%
- self.patchSection(0, '\144', 16 + self.mobi_length + pos + 8)
- elif type == 404 and size == 9:
- # make sure text to speech is enabled
- self.patchSection(0, '\0', 16 + self.mobi_length + pos + 8)
- # print type, size, content, content.encode('hex')
- pos += size
- except:
- pass
-
- def getBookTitle(self):
- codec_map = {
- 1252 : 'windows-1252',
- 65001 : 'utf-8',
- }
- title = ''
- codec = 'windows-1252'
- if self.magic == 'BOOKMOBI':
- if 503 in self.meta_array:
- title = self.meta_array[503]
- else:
- toff, tlen = struct.unpack('>II', self.sect[0x54:0x5c])
- tend = toff + tlen
- title = self.sect[toff:tend]
- if self.mobi_codepage in codec_map.keys():
- codec = codec_map[self.mobi_codepage]
- if title == '':
- title = self.header[:32]
- title = title.split('\0')[0]
- return unicode(title, codec)
-
- def getPIDMetaInfo(self):
- rec209 = ''
- token = ''
- if 209 in self.meta_array:
- rec209 = self.meta_array[209]
- data = rec209
- # 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
- # if that key exists in the meta_array, append its contents to the token
- for i in xrange(0,len(data),5):
- val, = struct.unpack('>I',data[i+1:i+5])
- sval = self.meta_array.get(val,'')
- token += sval
- return rec209, token
-
- def patch(self, off, new):
- self.data_file = self.data_file[:off] + new + self.data_file[off+len(new):]
-
- def patchSection(self, section, new, in_off = 0):
- if (section + 1 == self.num_sections):
- endoff = len(self.data_file)
- else:
- endoff = self.sections[section + 1][0]
- off = self.sections[section][0]
- assert off + in_off + len(new) <= endoff
- self.patch(off + in_off, new)
-
- def parseDRM(self, data, count, pidlist):
- found_key = None
- keyvec1 = '\x72\x38\x33\xB0\xB4\xF2\xE3\xCA\xDF\x09\x01\xD6\xE2\xE0\x3F\x96'
- for pid in pidlist:
- bigpid = pid.ljust(16,'\0')
- temp_key = PC1(keyvec1, bigpid, False)
- temp_key_sum = sum(map(ord,temp_key)) & 0xff
- found_key = None
- for i in xrange(count):
- verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
- if cksum == temp_key_sum:
- cookie = PC1(temp_key, cookie)
- ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
- if verification == ver and (flags & 0x1F) == 1:
- found_key = finalkey
- break
- if found_key != None:
- break
- if not found_key:
- # Then try the default encoding that doesn't require a PID
- pid = '00000000'
- temp_key = keyvec1
- temp_key_sum = sum(map(ord,temp_key)) & 0xff
- for i in xrange(count):
- verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
- if cksum == temp_key_sum:
- cookie = PC1(temp_key, cookie)
- ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
- if verification == ver:
- found_key = finalkey
- break
- return [found_key,pid]
-
- def getFile(self, outpath):
- file(outpath,'wb').write(self.mobi_data)
-
- def getBookType(self):
- if self.print_replica:
- return u"Print Replica"
- if self.mobi_version >= 8:
- return u"Kindle Format 8"
- if self.mobi_version >= 0:
- return u"Mobipocket {0:d}".format(self.mobi_version)
- return u"PalmDoc"
-
- def getBookExtension(self):
- if self.print_replica:
- return u".azw4"
- if self.mobi_version >= 8:
- return u".azw3"
- return u".mobi"
-
- def processBook(self, pidlist):
- crypto_type, = struct.unpack('>H', self.sect[0xC:0xC+2])
- print u"Crypto Type is: {0:d}".format(crypto_type)
- self.crypto_type = crypto_type
- if crypto_type == 0:
- print u"This book is not encrypted."
- # we must still check for Print Replica
- self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
- self.mobi_data = self.data_file
- return
- if crypto_type != 2 and crypto_type != 1:
- raise DrmException(u"Cannot decode unknown Mobipocket encryption type {0:d}".format(crypto_type))
- if 406 in self.meta_array:
- data406 = self.meta_array[406]
- val406, = struct.unpack('>Q',data406)
- if val406 != 0:
- raise DrmException(u"Cannot decode library or rented ebooks.")
-
- goodpids = []
- for pid in pidlist:
- if len(pid)==10:
- if checksumPid(pid[0:-2]) != pid:
- print u"Warning: PID {0} has incorrect checksum, should have been {1}".format(pid,checksumPid(pid[0:-2]))
- goodpids.append(pid[0:-2])
- elif len(pid)==8:
- goodpids.append(pid)
- else:
- print u"Warning: PID {0} has wrong number of digits".format(pid)
-
- if self.crypto_type == 1:
- t1_keyvec = 'QDCVEPMU675RUBSZ'
- if self.magic == 'TEXtREAd':
- bookkey_data = self.sect[0x0E:0x0E+16]
- elif self.mobi_version < 0:
- bookkey_data = self.sect[0x90:0x90+16]
- else:
- bookkey_data = self.sect[self.mobi_length+16:self.mobi_length+32]
- pid = '00000000'
- found_key = PC1(t1_keyvec, bookkey_data)
- else :
- # calculate the keys
- drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', self.sect[0xA8:0xA8+16])
- if drm_count == 0:
- raise DrmException(u"Encryption not initialised. Must be opened with Mobipocket Reader first.")
- found_key, pid = self.parseDRM(self.sect[drm_ptr:drm_ptr+drm_size], drm_count, goodpids)
- if not found_key:
- raise DrmException(u"No key found in {0:d} keys tried.".format(len(goodpids)))
- # kill the drm keys
- self.patchSection(0, '\0' * drm_size, drm_ptr)
- # kill the drm pointers
- self.patchSection(0, '\xff' * 4 + '\0' * 12, 0xA8)
-
- if pid=='00000000':
- print u"File has default encryption, no specific key needed."
- else:
- print u"File is encoded with PID {0}.".format(checksumPid(pid))
-
- # clear the crypto type
- self.patchSection(0, "\0" * 2, 0xC)
-
- # decrypt sections
- print u"Decrypting. Please wait . . .",
- mobidataList = []
- mobidataList.append(self.data_file[:self.sections[1][0]])
- for i in xrange(1, self.records+1):
- data = self.loadSection(i)
- extra_size = getSizeOfTrailingDataEntries(data, len(data), self.extra_data_flags)
- if i%100 == 0:
- print u".",
- # print "record %d, extra_size %d" %(i,extra_size)
- decoded_data = PC1(found_key, data[0:len(data) - extra_size])
- if i==1:
- self.print_replica = (decoded_data[0:4] == '%MOP')
- mobidataList.append(decoded_data)
- if extra_size > 0:
- mobidataList.append(data[-extra_size:])
- if self.num_sections > self.records+1:
- mobidataList.append(self.data_file[self.sections[self.records+1][0]:])
- self.mobi_data = "".join(mobidataList)
- print u"done"
- return
-
-def getUnencryptedBook(infile,pidlist):
- if not os.path.isfile(infile):
- raise DrmException(u"Input File Not Found.")
- book = MobiBook(infile)
- book.processBook(pidlist)
- return book.mobi_data
-
-
-def cli_main():
- argv=unicode_argv()
- progname = os.path.basename(argv[0])
- if len(argv)<3 or len(argv)>4:
- print u"MobiDeDrm v{0:s}.\nCopyright © 2008-2017 The Dark Reverser, Apprentice Harper et al.".format(__version__)
- print u"Removes protection from Kindle/Mobipocket, Kindle/KF8 and Kindle/Print Replica ebooks"
- print u"Usage:"
- print u" {0} []".format(progname)
- return 1
- else:
- infile = argv[1]
- outfile = argv[2]
- if len(argv) is 4:
- pidlist = argv[3].split(',')
- else:
- pidlist = []
- try:
- stripped_file = getUnencryptedBook(infile, pidlist)
- file(outfile, 'wb').write(stripped_file)
- except DrmException, e:
- print u"MobiDeDRM v{0} Error: {1:s}".format(__version__,e.args[0])
- return 1
- return 0
-
-
-if __name__ == '__main__':
- sys.stdout=SafeUnbuffered(sys.stdout)
- sys.stderr=SafeUnbuffered(sys.stderr)
- sys.exit(cli_main())
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/openssl_des.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/openssl_des.py
deleted file mode 100644
index 9a84e58..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/openssl_des.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-
-# implement just enough of des from openssl to make erdr2pml.py happy
-
-def load_libcrypto():
- from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_char, c_int, c_long, \
- Structure, c_ulong, create_string_buffer, cast
- from ctypes.util import find_library
- import sys
-
- if sys.platform.startswith('win'):
- libcrypto = find_library('libeay32')
- else:
- libcrypto = find_library('crypto')
-
- if libcrypto is None:
- return None
-
- libcrypto = CDLL(libcrypto)
-
- # typedef struct DES_ks
- # {
- # union
- # {
- # DES_cblock cblock;
- # /* make sure things are correct size on machines with
- # * 8 byte longs */
- # DES_LONG deslong[2];
- # } ks[16];
- # } DES_key_schedule;
-
- # just create a big enough place to hold everything
- # it will have alignment of structure so we should be okay (16 byte aligned?)
- class DES_KEY_SCHEDULE(Structure):
- _fields_ = [('DES_cblock1', c_char * 16),
- ('DES_cblock2', c_char * 16),
- ('DES_cblock3', c_char * 16),
- ('DES_cblock4', c_char * 16),
- ('DES_cblock5', c_char * 16),
- ('DES_cblock6', c_char * 16),
- ('DES_cblock7', c_char * 16),
- ('DES_cblock8', c_char * 16),
- ('DES_cblock9', c_char * 16),
- ('DES_cblock10', c_char * 16),
- ('DES_cblock11', c_char * 16),
- ('DES_cblock12', c_char * 16),
- ('DES_cblock13', c_char * 16),
- ('DES_cblock14', c_char * 16),
- ('DES_cblock15', c_char * 16),
- ('DES_cblock16', c_char * 16)]
-
- DES_KEY_SCHEDULE_p = POINTER(DES_KEY_SCHEDULE)
-
- def F(restype, name, argtypes):
- func = getattr(libcrypto, name)
- func.restype = restype
- func.argtypes = argtypes
- return func
-
- DES_set_key = F(None, 'DES_set_key',[c_char_p, DES_KEY_SCHEDULE_p])
- DES_ecb_encrypt = F(None, 'DES_ecb_encrypt',[c_char_p, c_char_p, DES_KEY_SCHEDULE_p, c_int])
-
-
- class DES(object):
- def __init__(self, key):
- if len(key) != 8 :
- raise Exception('DES improper key used')
- return
- self.key = key
- self.keyschedule = DES_KEY_SCHEDULE()
- DES_set_key(self.key, self.keyschedule)
- def desdecrypt(self, data):
- ob = create_string_buffer(len(data))
- DES_ecb_encrypt(data, ob, self.keyschedule, 0)
- return ob.raw
- def decrypt(self, data):
- if not data:
- return ''
- i = 0
- result = []
- while i < len(data):
- block = data[i:i+8]
- processed_block = self.desdecrypt(block)
- result.append(processed_block)
- i += 8
- return ''.join(result)
-
- return DES
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/plugin-import-name-dedrm.txt b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/plugin-import-name-dedrm.txt
deleted file mode 100644
index e69de29..0000000
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/prefs.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/prefs.py
deleted file mode 100644
index c1bfcb9..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/prefs.py
+++ /dev/null
@@ -1,295 +0,0 @@
-#!/usr/bin/env python
-# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
-
-from __future__ import with_statement
-__license__ = 'GPL v3'
-
-# Standard Python modules.
-import os, sys, re, hashlib
-import json
-import traceback
-
-from calibre.utils.config import dynamic, config_dir, JSONConfig
-from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
-from calibre.constants import iswindows, isosx
-
-class DeDRM_Prefs():
- def __init__(self):
- JSON_PATH = os.path.join(u"plugins", PLUGIN_NAME.strip().lower().replace(' ', '_') + '.json')
- self.dedrmprefs = JSONConfig(JSON_PATH)
-
- self.dedrmprefs.defaults['configured'] = False
- self.dedrmprefs.defaults['bandnkeys'] = {}
- self.dedrmprefs.defaults['adeptkeys'] = {}
- self.dedrmprefs.defaults['ereaderkeys'] = {}
- self.dedrmprefs.defaults['kindlekeys'] = {}
- self.dedrmprefs.defaults['androidkeys'] = {}
- self.dedrmprefs.defaults['pids'] = []
- self.dedrmprefs.defaults['serials'] = []
- self.dedrmprefs.defaults['adobewineprefix'] = ""
- self.dedrmprefs.defaults['kindlewineprefix'] = ""
-
- # initialise
- # we must actually set the prefs that are dictionaries and lists
- # to empty dictionaries and lists, otherwise we are unable to add to them
- # as then it just adds to the (memory only) dedrmprefs.defaults versions!
- if self.dedrmprefs['bandnkeys'] == {}:
- self.dedrmprefs['bandnkeys'] = {}
- if self.dedrmprefs['adeptkeys'] == {}:
- self.dedrmprefs['adeptkeys'] = {}
- if self.dedrmprefs['ereaderkeys'] == {}:
- self.dedrmprefs['ereaderkeys'] = {}
- if self.dedrmprefs['kindlekeys'] == {}:
- self.dedrmprefs['kindlekeys'] = {}
- if self.dedrmprefs['androidkeys'] == {}:
- self.dedrmprefs['androidkeys'] = {}
- if self.dedrmprefs['pids'] == []:
- self.dedrmprefs['pids'] = []
- if self.dedrmprefs['serials'] == []:
- self.dedrmprefs['serials'] = []
-
- def __getitem__(self,kind = None):
- if kind is not None:
- return self.dedrmprefs[kind]
- return self.dedrmprefs
-
- def set(self, kind, value):
- self.dedrmprefs[kind] = value
-
- def writeprefs(self,value = True):
- self.dedrmprefs['configured'] = value
-
- def addnamedvaluetoprefs(self, prefkind, keyname, keyvalue):
- try:
- if keyvalue not in self.dedrmprefs[prefkind].values():
- # ensure that the keyname is unique
- # by adding a number (starting with 2) to the name if it is not
- namecount = 1
- newname = keyname
- while newname in self.dedrmprefs[prefkind]:
- namecount += 1
- newname = "{0:s}_{1:d}".format(keyname,namecount)
- # add to the preferences
- self.dedrmprefs[prefkind][newname] = keyvalue
- return (True, newname)
- except:
- traceback.print_exc()
- pass
- return (False, keyname)
-
- def addvaluetoprefs(self, prefkind, prefsvalue):
- # ensure the keyvalue isn't already in the preferences
- try:
- if prefsvalue not in self.dedrmprefs[prefkind]:
- self.dedrmprefs[prefkind].append(prefsvalue)
- return True
- except:
- traceback.print_exc()
- return False
-
-
-def convertprefs(always = False):
-
- def parseIgnobleString(keystuff):
- from calibre_plugins.dedrm.ignoblekeygen import generate_key
- userkeys = []
- ar = keystuff.split(':')
- for keystring in ar:
- try:
- name, ccn = keystring.split(',')
- # Generate Barnes & Noble EPUB user key from name and credit card number.
- keyname = u"{0}_{1}".format(name.strip(),ccn.strip()[-4:])
- keyvalue = generate_key(name, ccn)
- userkeys.append([keyname,keyvalue])
- except Exception, e:
- traceback.print_exc()
- print e.args[0]
- pass
- return userkeys
-
- def parseeReaderString(keystuff):
- from calibre_plugins.dedrm.erdr2pml import getuser_key
- userkeys = []
- ar = keystuff.split(':')
- for keystring in ar:
- try:
- name, cc = keystring.split(',')
- # Generate eReader user key from name and credit card number.
- keyname = u"{0}_{1}".format(name.strip(),cc.strip()[-4:])
- keyvalue = getuser_key(name,cc).encode('hex')
- userkeys.append([keyname,keyvalue])
- except Exception, e:
- traceback.print_exc()
- print e.args[0]
- pass
- return userkeys
-
- def parseKindleString(keystuff):
- pids = []
- serials = []
- ar = keystuff.split(',')
- for keystring in ar:
- keystring = str(keystring).strip().replace(" ","")
- if len(keystring) == 10 or len(keystring) == 8 and keystring not in pids:
- pids.append(keystring)
- elif len(keystring) == 16 and keystring[0] == 'B' and keystring not in serials:
- serials.append(keystring)
- return (pids,serials)
-
- def getConfigFiles(extension, encoding = None):
- # get any files with extension 'extension' in the config dir
- userkeys = []
- files = [f for f in os.listdir(config_dir) if f.endswith(extension)]
- for filename in files:
- try:
- fpath = os.path.join(config_dir, filename)
- key = os.path.splitext(filename)[0]
- value = open(fpath, 'rb').read()
- if encoding is not None:
- value = value.encode(encoding)
- userkeys.append([key,value])
- except:
- traceback.print_exc()
- pass
- return userkeys
-
- dedrmprefs = DeDRM_Prefs()
-
- if (not always) and dedrmprefs['configured']:
- # We've already converted old preferences,
- # and we're not being forced to do it again, so just return
- return
-
-
- print u"{0} v{1}: Importing configuration data from old DeDRM plugins".format(PLUGIN_NAME, PLUGIN_VERSION)
-
- IGNOBLEPLUGINNAME = "Ignoble Epub DeDRM"
- EREADERPLUGINNAME = "eReader PDB 2 PML"
- OLDKINDLEPLUGINNAME = "K4PC, K4Mac, Kindle Mobi and Topaz DeDRM"
-
- # get prefs from older tools
- kindleprefs = JSONConfig(os.path.join(u"plugins", u"K4MobiDeDRM"))
- ignobleprefs = JSONConfig(os.path.join(u"plugins", u"ignoble_epub_dedrm"))
-
- # Handle the old ignoble plugin's customization string by converting the
- # old string to stored keys... get that personal data out of plain sight.
- from calibre.customize.ui import config
- sc = config['plugin_customization']
- val = sc.pop(IGNOBLEPLUGINNAME, None)
- if val is not None:
- print u"{0} v{1}: Converting old Ignoble plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
- priorkeycount = len(dedrmprefs['bandnkeys'])
- userkeys = parseIgnobleString(str(val))
- for keypair in userkeys:
- name = keypair[0]
- value = keypair[1]
- dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
- addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
- print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from old Ignoble plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
- # Make the json write all the prefs to disk
- dedrmprefs.writeprefs(False)
-
- # Handle the old eReader plugin's customization string by converting the
- # old string to stored keys... get that personal data out of plain sight.
- val = sc.pop(EREADERPLUGINNAME, None)
- if val is not None:
- print u"{0} v{1}: Converting old eReader plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
- priorkeycount = len(dedrmprefs['ereaderkeys'])
- userkeys = parseeReaderString(str(val))
- for keypair in userkeys:
- name = keypair[0]
- value = keypair[1]
- dedrmprefs.addnamedvaluetoprefs('ereaderkeys', name, value)
- addedkeycount = len(dedrmprefs['ereaderkeys'])-priorkeycount
- print u"{0} v{1}: {2:d} eReader {3} imported from old eReader plugin configuration string".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
- # Make the json write all the prefs to disk
- dedrmprefs.writeprefs(False)
-
- # get old Kindle plugin configuration string
- val = sc.pop(OLDKINDLEPLUGINNAME, None)
- if val is not None:
- print u"{0} v{1}: Converting old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION)
- priorpidcount = len(dedrmprefs['pids'])
- priorserialcount = len(dedrmprefs['serials'])
- pids, serials = parseKindleString(val)
- for pid in pids:
- dedrmprefs.addvaluetoprefs('pids',pid)
- for serial in serials:
- dedrmprefs.addvaluetoprefs('serials',serial)
- addedpidcount = len(dedrmprefs['pids']) - priorpidcount
- addedserialcount = len(dedrmprefs['serials']) - priorserialcount
- print u"{0} v{1}: {2:d} {3} and {4:d} {5} imported from old Kindle plugin configuration string.".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs", addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
- # Make the json write all the prefs to disk
- dedrmprefs.writeprefs(False)
-
- # copy the customisations back into calibre preferences, as we've now removed the nasty plaintext
- config['plugin_customization'] = sc
-
- # get any .b64 files in the config dir
- priorkeycount = len(dedrmprefs['bandnkeys'])
- bandnfilekeys = getConfigFiles('.b64')
- for keypair in bandnfilekeys:
- name = keypair[0]
- value = keypair[1]
- dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
- addedkeycount = len(dedrmprefs['bandnkeys'])-priorkeycount
- if addedkeycount > 0:
- print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key file" if addedkeycount==1 else u"key files")
- # Make the json write all the prefs to disk
- dedrmprefs.writeprefs(False)
-
- # get any .der files in the config dir
- priorkeycount = len(dedrmprefs['adeptkeys'])
- adeptfilekeys = getConfigFiles('.der','hex')
- for keypair in adeptfilekeys:
- name = keypair[0]
- value = keypair[1]
- dedrmprefs.addnamedvaluetoprefs('adeptkeys', name, value)
- addedkeycount = len(dedrmprefs['adeptkeys'])-priorkeycount
- if addedkeycount > 0:
- print u"{0} v{1}: {2:d} Adobe Adept {3} imported from config folder.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"keyfile" if addedkeycount==1 else u"keyfiles")
- # Make the json write all the prefs to disk
- dedrmprefs.writeprefs(False)
-
- # get ignoble json prefs
- if 'keys' in ignobleprefs:
- priorkeycount = len(dedrmprefs['bandnkeys'])
- for name in ignobleprefs['keys']:
- value = ignobleprefs['keys'][name]
- dedrmprefs.addnamedvaluetoprefs('bandnkeys', name, value)
- addedkeycount = len(dedrmprefs['bandnkeys']) - priorkeycount
- # no need to delete old prefs, since they contain no recoverable private data
- if addedkeycount > 0:
- print u"{0} v{1}: {2:d} Barnes and Noble {3} imported from Ignoble plugin preferences.".format(PLUGIN_NAME, PLUGIN_VERSION, addedkeycount, u"key" if addedkeycount==1 else u"keys")
- # Make the json write all the prefs to disk
- dedrmprefs.writeprefs(False)
-
- # get kindle json prefs
- priorpidcount = len(dedrmprefs['pids'])
- priorserialcount = len(dedrmprefs['serials'])
- if 'pids' in kindleprefs:
- pids, serials = parseKindleString(kindleprefs['pids'])
- for pid in pids:
- dedrmprefs.addvaluetoprefs('pids',pid)
- if 'serials' in kindleprefs:
- pids, serials = parseKindleString(kindleprefs['serials'])
- for serial in serials:
- dedrmprefs.addvaluetoprefs('serials',serial)
- addedpidcount = len(dedrmprefs['pids']) - priorpidcount
- if addedpidcount > 0:
- print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedpidcount, u"PID" if addedpidcount==1 else u"PIDs")
- addedserialcount = len(dedrmprefs['serials']) - priorserialcount
- if addedserialcount > 0:
- print u"{0} v{1}: {2:d} {3} imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, addedserialcount, u"serial number" if addedserialcount==1 else u"serial numbers")
- try:
- if 'wineprefix' in kindleprefs and kindleprefs['wineprefix'] != "":
- dedrmprefs.set('adobewineprefix',kindleprefs['wineprefix'])
- dedrmprefs.set('kindlewineprefix',kindleprefs['wineprefix'])
- print u"{0} v{1}: WINEPREFIX ‘(2)’ imported from Kindle plugin preferences".format(PLUGIN_NAME, PLUGIN_VERSION, kindleprefs['wineprefix'])
- except:
- traceback.print_exc()
-
-
- # Make the json write all the prefs to disk
- dedrmprefs.writeprefs()
- print u"{0} v{1}: Finished setting up configuration data.".format(PLUGIN_NAME, PLUGIN_VERSION)
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/pycrypto_des.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/pycrypto_des.py
deleted file mode 100644
index 80d7d65..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/pycrypto_des.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-
-
-def load_pycrypto():
- try :
- from Crypto.Cipher import DES as _DES
- except:
- return None
-
- class DES(object):
- def __init__(self, key):
- if len(key) != 8 :
- raise Error('DES improper key used')
- self.key = key
- self._des = _DES.new(key,_DES.MODE_ECB)
- def desdecrypt(self, data):
- return self._des.decrypt(data)
- def decrypt(self, data):
- if not data:
- return ''
- i = 0
- result = []
- while i < len(data):
- block = data[i:i+8]
- processed_block = self.desdecrypt(block)
- result.append(processed_block)
- i += 8
- return ''.join(result)
- return DES
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/python_des.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/python_des.py
deleted file mode 100644
index bd02904..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/python_des.py
+++ /dev/null
@@ -1,220 +0,0 @@
-#!/usr/bin/env python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-import sys
-
-ECB = 0
-CBC = 1
-class Des(object):
- __pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
- 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
- 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
- 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3]
- __left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
- __pc2 = [13, 16, 10, 23, 0, 4,2, 27, 14, 5, 20, 9,
- 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
- 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
- 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31]
- __ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
- 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
- 56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2,
- 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
- __expansion_table = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8,
- 7, 8, 9, 10, 11, 12,11, 12, 13, 14, 15, 16,
- 15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
- 23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31, 0]
- __sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
- 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
- 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
- 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
- [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
- 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
- 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
- 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
- [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
- 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
- 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
- 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
- [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
- 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
- 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
- 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
- [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
- 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
- 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
- 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
- [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
- 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
- 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
- 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
- [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
- 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
- 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
- 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
- [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
- 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
- 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
- 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
- __p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
- 4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
- __fp = [39, 7, 47, 15, 55, 23, 63, 31,38, 6, 46, 14, 54, 22, 62, 30,
- 37, 5, 45, 13, 53, 21, 61, 29,36, 4, 44, 12, 52, 20, 60, 28,
- 35, 3, 43, 11, 51, 19, 59, 27,34, 2, 42, 10, 50, 18, 58, 26,
- 33, 1, 41, 9, 49, 17, 57, 25,32, 0, 40, 8, 48, 16, 56, 24]
- # Type of crypting being done
- ENCRYPT = 0x00
- DECRYPT = 0x01
- def __init__(self, key, mode=ECB, IV=None):
- if len(key) != 8:
- raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
- self.block_size = 8
- self.key_size = 8
- self.__padding = ''
- self.setMode(mode)
- if IV:
- self.setIV(IV)
- self.L = []
- self.R = []
- self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
- self.final = []
- self.setKey(key)
- def getKey(self):
- return self.__key
- def setKey(self, key):
- self.__key = key
- self.__create_sub_keys()
- def getMode(self):
- return self.__mode
- def setMode(self, mode):
- self.__mode = mode
- def getIV(self):
- return self.__iv
- def setIV(self, IV):
- if not IV or len(IV) != self.block_size:
- raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
- self.__iv = IV
- def getPadding(self):
- return self.__padding
- def __String_to_BitList(self, data):
- l = len(data) * 8
- result = [0] * l
- pos = 0
- for c in data:
- i = 7
- ch = ord(c)
- while i >= 0:
- if ch & (1 << i) != 0:
- result[pos] = 1
- else:
- result[pos] = 0
- pos += 1
- i -= 1
- return result
- def __BitList_to_String(self, data):
- result = ''
- pos = 0
- c = 0
- while pos < len(data):
- c += data[pos] << (7 - (pos % 8))
- if (pos % 8) == 7:
- result += chr(c)
- c = 0
- pos += 1
- return result
- def __permutate(self, table, block):
- return [block[x] for x in table]
- def __create_sub_keys(self):
- key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
- i = 0
- self.L = key[:28]
- self.R = key[28:]
- while i < 16:
- j = 0
- while j < Des.__left_rotations[i]:
- self.L.append(self.L[0])
- del self.L[0]
- self.R.append(self.R[0])
- del self.R[0]
- j += 1
- self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
- i += 1
- def __des_crypt(self, block, crypt_type):
- block = self.__permutate(Des.__ip, block)
- self.L = block[:32]
- self.R = block[32:]
- if crypt_type == Des.ENCRYPT:
- iteration = 0
- iteration_adjustment = 1
- else:
- iteration = 15
- iteration_adjustment = -1
- i = 0
- while i < 16:
- tempR = self.R[:]
- self.R = self.__permutate(Des.__expansion_table, self.R)
- self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
- B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
- j = 0
- Bn = [0] * 32
- pos = 0
- while j < 8:
- m = (B[j][0] << 1) + B[j][5]
- n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
- v = Des.__sbox[j][(m << 4) + n]
- Bn[pos] = (v & 8) >> 3
- Bn[pos + 1] = (v & 4) >> 2
- Bn[pos + 2] = (v & 2) >> 1
- Bn[pos + 3] = v & 1
- pos += 4
- j += 1
- self.R = self.__permutate(Des.__p, Bn)
- self.R = [x ^ y for x, y in zip(self.R, self.L)]
- self.L = tempR
- i += 1
- iteration += iteration_adjustment
- self.final = self.__permutate(Des.__fp, self.R + self.L)
- return self.final
- def crypt(self, data, crypt_type):
- if not data:
- return ''
- if len(data) % self.block_size != 0:
- if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
- raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
- if not self.getPadding():
- raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
- else:
- data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
- if self.getMode() == CBC:
- if self.getIV():
- iv = self.__String_to_BitList(self.getIV())
- else:
- raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
- i = 0
- dict = {}
- result = []
- while i < len(data):
- block = self.__String_to_BitList(data[i:i+8])
- if self.getMode() == CBC:
- if crypt_type == Des.ENCRYPT:
- block = [x ^ y for x, y in zip(block, iv)]
- processed_block = self.__des_crypt(block, crypt_type)
- if crypt_type == Des.DECRYPT:
- processed_block = [x ^ y for x, y in zip(processed_block, iv)]
- iv = block
- else:
- iv = processed_block
- else:
- processed_block = self.__des_crypt(block, crypt_type)
- result.append(self.__BitList_to_String(processed_block))
- i += 8
- if crypt_type == Des.DECRYPT and self.getPadding():
- s = result[-1]
- while s[-1] == self.getPadding():
- s = s[:-1]
- result[-1] = s
- return ''.join(result)
- def encrypt(self, data, pad=''):
- self.__padding = pad
- return self.crypt(data, Des.ENCRYPT)
- def decrypt(self, data, pad=''):
- self.__padding = pad
- return self.crypt(data, Des.DECRYPT)
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scriptinterface.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scriptinterface.py
deleted file mode 100644
index ec86b13..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scriptinterface.py
+++ /dev/null
@@ -1,198 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-
-import sys
-import os
-import re
-import ineptepub
-import ignobleepub
-import epubtest
-import zipfix
-import ineptpdf
-import erdr2pml
-import k4mobidedrm
-import traceback
-
-def decryptepub(infile, outdir, rscpath):
- errlog = ''
-
- # first fix the epub to make sure we do not get errors
- name, ext = os.path.splitext(os.path.basename(infile))
- bpath = os.path.dirname(infile)
- zippath = os.path.join(bpath,name + '_temp.zip')
- rv = zipfix.repairBook(infile, zippath)
- if rv != 0:
- print "Error while trying to fix epub"
- return rv
-
- # determine a good name for the output file
- outfile = os.path.join(outdir, name + '_nodrm.epub')
-
- rv = 1
- # first try with the Adobe adept epub
- if ineptepub.adeptBook(zippath):
- # try with any keyfiles (*.der) in the rscpath
- files = os.listdir(rscpath)
- filefilter = re.compile("\.der$", re.IGNORECASE)
- files = filter(filefilter.search, files)
- if files:
- for filename in files:
- keypath = os.path.join(rscpath, filename)
- userkey = open(keypath,'rb').read()
- try:
- rv = ineptepub.decryptBook(userkey, zippath, outfile)
- if rv == 0:
- print "Decrypted Adobe ePub with key file {0}".format(filename)
- break
- except Exception, e:
- errlog += traceback.format_exc()
- errlog += str(e)
- rv = 1
- # now try with ignoble epub
- elif ignobleepub.ignobleBook(zippath):
- # try with any keyfiles (*.b64) in the rscpath
- files = os.listdir(rscpath)
- filefilter = re.compile("\.b64$", re.IGNORECASE)
- files = filter(filefilter.search, files)
- if files:
- for filename in files:
- keypath = os.path.join(rscpath, filename)
- userkey = open(keypath,'r').read()
- #print userkey
- try:
- rv = ignobleepub.decryptBook(userkey, zippath, outfile)
- if rv == 0:
- print "Decrypted B&N ePub with key file {0}".format(filename)
- break
- except Exception, e:
- errlog += traceback.format_exc()
- errlog += str(e)
- rv = 1
- else:
- encryption = epubtest.encryption(zippath)
- if encryption == "Unencrypted":
- print "{0} is not DRMed.".format(name)
- rv = 0
- else:
- print "{0} has an unknown encryption.".format(name)
-
- os.remove(zippath)
- if rv != 0:
- print errlog
- return rv
-
-
-def decryptpdf(infile, outdir, rscpath):
- errlog = ''
- rv = 1
-
- # determine a good name for the output file
- name, ext = os.path.splitext(os.path.basename(infile))
- outfile = os.path.join(outdir, name + '_nodrm.pdf')
-
- # try with any keyfiles (*.der) in the rscpath
- files = os.listdir(rscpath)
- filefilter = re.compile("\.der$", re.IGNORECASE)
- files = filter(filefilter.search, files)
- if files:
- for filename in files:
- keypath = os.path.join(rscpath, filename)
- userkey = open(keypath,'rb').read()
- try:
- rv = ineptpdf.decryptBook(userkey, infile, outfile)
- if rv == 0:
- break
- except Exception, e:
- errlog += traceback.format_exc()
- errlog += str(e)
- rv = 1
-
- if rv != 0:
- print errlog
- return rv
-
-
-def decryptpdb(infile, outdir, rscpath):
- outname = os.path.splitext(os.path.basename(infile))[0] + ".pmlz"
- outpath = os.path.join(outdir, outname)
- rv = 1
- socialpath = os.path.join(rscpath,'sdrmlist.txt')
- if os.path.exists(socialpath):
- keydata = file(socialpath,'r').read()
- keydata = keydata.rstrip(os.linesep)
- ar = keydata.split(',')
- for i in ar:
- try:
- name, cc8 = i.split(':')
- except ValueError:
- print ' Error parsing user supplied social drm data.'
- return 1
- try:
- rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8))
- except Exception, e:
- errlog += traceback.format_exc()
- errlog += str(e)
- rv = 1
-
- if rv == 0:
- break
- return rv
-
-
-def decryptk4mobi(infile, outdir, rscpath):
- rv = 1
- pidnums = []
- pidspath = os.path.join(rscpath,'pidlist.txt')
- if os.path.exists(pidspath):
- pidstr = file(pidspath,'r').read()
- pidstr = pidstr.rstrip(os.linesep)
- pidstr = pidstr.strip()
- if pidstr != '':
- pidnums = pidstr.split(',')
- serialnums = []
- serialnumspath = os.path.join(rscpath,'seriallist.txt')
- if os.path.exists(serialnumspath):
- serialstr = file(serialnumspath,'r').read()
- serialstr = serialstr.rstrip(os.linesep)
- serialstr = serialstr.strip()
- if serialstr != '':
- serialnums = serialstr.split(',')
- kDatabaseFiles = []
- files = os.listdir(rscpath)
- filefilter = re.compile("\.k4i$", re.IGNORECASE)
- files = filter(filefilter.search, files)
- if files:
- for filename in files:
- dpath = os.path.join(rscpath,filename)
- kDatabaseFiles.append(dpath)
- androidFiles = []
- files = os.listdir(rscpath)
- filefilter = re.compile("\.ab$", re.IGNORECASE)
- files = filter(filefilter.search, files)
- if files:
- for filename in files:
- dpath = os.path.join(rscpath,filename)
- androidFiles.append(dpath)
- files = os.listdir(rscpath)
- filefilter = re.compile("\.db$", re.IGNORECASE)
- files = filter(filefilter.search, files)
- if files:
- for filename in files:
- dpath = os.path.join(rscpath,filename)
- androidFiles.append(dpath)
- files = os.listdir(rscpath)
- filefilter = re.compile("\.xml$", re.IGNORECASE)
- files = filter(filefilter.search, files)
- if files:
- for filename in files:
- dpath = os.path.join(rscpath,filename)
- androidFiles.append(dpath)
- try:
- rv = k4mobidedrm.decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serialnums, pidnums)
- except Exception, e:
- errlog += traceback.format_exc()
- errlog += str(e)
- rv = 1
-
- return rv
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scrolltextwidget.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scrolltextwidget.py
deleted file mode 100644
index 98b4147..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/scrolltextwidget.py
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-
-import Tkinter
-import Tkconstants
-
-# basic scrolled text widget
-class ScrolledText(Tkinter.Text):
- def __init__(self, master=None, **kw):
- self.frame = Tkinter.Frame(master)
- self.vbar = Tkinter.Scrollbar(self.frame)
- self.vbar.pack(side=Tkconstants.RIGHT, fill=Tkconstants.Y)
- kw.update({'yscrollcommand': self.vbar.set})
- Tkinter.Text.__init__(self, self.frame, **kw)
- self.pack(side=Tkconstants.LEFT, fill=Tkconstants.BOTH, expand=True)
- self.vbar['command'] = self.yview
- # Copy geometry methods of self.frame without overriding Text
- # methods = hack!
- text_meths = vars(Tkinter.Text).keys()
- methods = vars(Tkinter.Pack).keys() + vars(Tkinter.Grid).keys() + vars(Tkinter.Place).keys()
- methods = set(methods).difference(text_meths)
- for m in methods:
- if m[0] != '_' and m != 'config' and m != 'configure':
- setattr(self, m, getattr(self.frame, m))
-
- def __str__(self):
- return str(self.frame)
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/simpleprefs.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/simpleprefs.py
deleted file mode 100644
index 0809944..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/simpleprefs.py
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/env python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-
-import sys
-import os, os.path
-import shutil
-
-class SimplePrefsError(Exception):
- pass
-
-class SimplePrefs(object):
- def __init__(self, target, description):
- self.prefs = {}
- self.key2file={}
- self.file2key={}
- for keyfilemap in description:
- [key, filename] = keyfilemap
- self.key2file[key] = filename
- self.file2key[filename] = key
- self.target = target + 'Prefs'
- if sys.platform.startswith('win'):
- import _winreg as winreg
- regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\\")
- path = winreg.QueryValueEx(regkey, 'Local AppData')[0]
- prefdir = path + os.sep + self.target
- elif sys.platform.startswith('darwin'):
- home = os.getenv('HOME')
- prefdir = os.path.join(home,'Library','Preferences','org.' + self.target)
- else:
- # linux and various flavors of unix
- home = os.getenv('HOME')
- prefdir = os.path.join(home,'.' + self.target)
- if not os.path.exists(prefdir):
- os.makedirs(prefdir)
- self.prefdir = prefdir
- self.prefs['dir'] = self.prefdir
- self._loadPreferences()
-
- def _loadPreferences(self):
- filenames = os.listdir(self.prefdir)
- for filename in filenames:
- if filename in self.file2key:
- key = self.file2key[filename]
- filepath = os.path.join(self.prefdir,filename)
- if os.path.isfile(filepath):
- try :
- data = file(filepath,'rb').read()
- self.prefs[key] = data
- except Exception, e:
- pass
-
- def getPreferences(self):
- return self.prefs
-
- def setPreferences(self, newprefs={}):
- if 'dir' not in newprefs:
- raise SimplePrefsError('Error: Attempt to Set Preferences in unspecified directory')
- if newprefs['dir'] != self.prefs['dir']:
- raise SimplePrefsError('Error: Attempt to Set Preferences in unspecified directory')
- for key in newprefs:
- if key != 'dir':
- if key in self.key2file:
- filename = self.key2file[key]
- filepath = os.path.join(self.prefdir,filename)
- data = newprefs[key]
- if data != None:
- data = str(data)
- if data == None or data == '':
- if os.path.exists(filepath):
- os.remove(filepath)
- else:
- try:
- file(filepath,'wb').write(data)
- except Exception, e:
- pass
- self.prefs = newprefs
- return
diff --git a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/stylexml2css.py b/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/stylexml2css.py
deleted file mode 100644
index daa108a..0000000
--- a/DeDRM_Macintosh_Application/DeDRM.app/Contents/Resources/stylexml2css.py
+++ /dev/null
@@ -1,284 +0,0 @@
-#! /usr/bin/python
-# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
-# For use with Topaz Scripts Version 2.6
-
-import csv
-import sys
-import os
-import getopt
-import re
-from struct import pack
-from struct import unpack
-
-debug = False
-
-class DocParser(object):
- def __init__(self, flatxml, fontsize, ph, pw):
- self.flatdoc = flatxml.split('\n')
- self.fontsize = int(fontsize)
- self.ph = int(ph) * 1.0
- self.pw = int(pw) * 1.0
-
- stags = {
- 'paragraph' : 'p',
- 'graphic' : '.graphic'
- }
-
- attr_val_map = {
- 'hang' : 'text-indent: ',
- 'indent' : 'text-indent: ',
- 'line-space' : 'line-height: ',
- 'margin-bottom' : 'margin-bottom: ',
- 'margin-left' : 'margin-left: ',
- 'margin-right' : 'margin-right: ',
- 'margin-top' : 'margin-top: ',
- 'space-after' : 'padding-bottom: ',
- }
-
- attr_str_map = {
- 'align-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
- 'align-left' : 'text-align: left;',
- 'align-right' : 'text-align: right;',
- 'align-justify' : 'text-align: justify;',
- 'display-inline' : 'display: inline;',
- 'pos-left' : 'text-align: left;',
- 'pos-right' : 'text-align: right;',
- 'pos-center' : 'text-align: center; margin-left: auto; margin-right: auto;',
- }
-
-
- # find tag if within pos to end inclusive
- def findinDoc(self, tagpath, pos, end) :
- result = None
- docList = self.flatdoc
- cnt = len(docList)
- if end == -1 :
- end = cnt
- else:
- end = min(cnt,end)
- foundat = -1
- for j in xrange(pos, end):
- item = docList[j]
- if item.find('=') >= 0:
- (name, argres) = item.split('=',1)
- else :
- name = item
- argres = ''
- if name.endswith(tagpath) :
- result = argres
- foundat = j
- break
- return foundat, result
-
-
- # return list of start positions for the tagpath
- def posinDoc(self, tagpath):
- startpos = []
- pos = 0
- res = ""
- while res != None :
- (foundpos, res) = self.findinDoc(tagpath, pos, -1)
- if res != None :
- startpos.append(foundpos)
- pos = foundpos + 1
- return startpos
-
- # returns a vector of integers for the tagpath
- def getData(self, tagpath, pos, end, clean=False):
- if clean:
- digits_only = re.compile(r'''([0-9]+)''')
- argres=[]
- (foundat, argt) = self.findinDoc(tagpath, pos, end)
- if (argt != None) and (len(argt) > 0) :
- argList = argt.split('|')
- for strval in argList:
- if clean:
- m = re.search(digits_only, strval)
- if m != None:
- strval = m.group()
- argres.append(int(strval))
- return argres
-
- def process(self):
-
- classlst = ''
- csspage = '.cl-center { text-align: center; margin-left: auto; margin-right: auto; }\n'
- csspage += '.cl-right { text-align: right; }\n'
- csspage += '.cl-left { text-align: left; }\n'
- csspage += '.cl-justify { text-align: justify; }\n'
-
- # generate a list of each