mirror of
https://github.com/Leseratte10/acsm-calibre-plugin.git
synced 2024-12-22 17:29:56 +06:00
Beta 3: Support for importing from Wine
This commit is contained in:
parent
4c3ee827f0
commit
f7eb9e5d79
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/calibre-plugin/*.zip
|
||||||
|
/calibre-plugin/keyextract/*.exe
|
@ -7,7 +7,7 @@ It is a full Python reimplementation of libgourou by Grégory Soutadé (http://i
|
|||||||
|
|
||||||
1. Download the plugin and import it into Calibre
|
1. Download the plugin and import it into Calibre
|
||||||
2. Open the plugin settings, it should say "Not authorized for any ADE ID"
|
2. Open the plugin settings, it should say "Not authorized for any ADE ID"
|
||||||
3. If you have ADE installed on your machine (Windows+Mac only, no Linux/Wine), there will be a button "Import activation from ADE". Clicking that will automatically copy your account information from ADE over to the Calibre plugin without using up an activation.
|
3. If you have ADE installed on your machine, there will be a button "Import activation from ADE". Clicking that will automatically copy your account information from ADE over to the Calibre plugin without using up an activation.
|
||||||
4. If you don't have ADE installed, or you want to authorize a different account, or the automatic retrieval from ADE failed, click the "Link to ADE account" button to make a new clean authorization. You will then be asked to enter your AdobeID and password and to select an ADE version (ADE 2.0.1 recommended). A couple seconds later a success message should be displayed.
|
4. If you don't have ADE installed, or you want to authorize a different account, or the automatic retrieval from ADE failed, click the "Link to ADE account" button to make a new clean authorization. You will then be asked to enter your AdobeID and password and to select an ADE version (ADE 2.0.1 recommended). A couple seconds later a success message should be displayed.
|
||||||
5. The settings window should now say "Authorized with ADE ID X on device Y, emulating ADE version Z".
|
5. The settings window should now say "Authorized with ADE ID X on device Y, emulating ADE version Z".
|
||||||
6. Click the "Export account activation data" and "Export account encryption key" buttons to export / backup your keys. Do not skip this step. The first file (ZIP) can be used to re-authorize Calibre after a reset / reinstall without using up one of your Adobe authorizations. The second file (DER) can be imported into DeDRM.
|
6. Click the "Export account activation data" and "Export account encryption key" buttons to export / backup your keys. Do not skip this step. The first file (ZIP) can be used to re-authorize Calibre after a reset / reinstall without using up one of your Adobe authorizations. The second file (DER) can be imported into DeDRM.
|
||||||
@ -45,4 +45,5 @@ There's a bunch of features that could still be added, but most of them aren't i
|
|||||||
|
|
||||||
- Support for anonymous Adobe IDs
|
- Support for anonymous Adobe IDs
|
||||||
- Support for un-authorizing a machine
|
- Support for un-authorizing a machine
|
||||||
|
- Support to copy an authorization from the plugin to an ADE install
|
||||||
- ...
|
- ...
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
[ ! -f calibre-plugin/pyasn1.zip ] && ./package_modules.sh
|
[ ! -f calibre-plugin/pyasn1.zip ] && ./package_modules.sh
|
||||||
|
|
||||||
pushd calibre-plugin
|
pushd calibre-plugin
|
||||||
pushd key-wine
|
pushd keyextract
|
||||||
|
|
||||||
# Compile:
|
# Compile:
|
||||||
make
|
make
|
||||||
|
@ -20,9 +20,11 @@
|
|||||||
# fix bug that would block other FileTypePlugins
|
# fix bug that would block other FileTypePlugins
|
||||||
# v0.0.12: Fix Calibre Plugin index / updater
|
# v0.0.12: Fix Calibre Plugin index / updater
|
||||||
# v0.0.13: Add support for emulating multiple ADE versions (1.7.2, 2.0.1, 3.0.1, 4.0.3, 4.5.11),
|
# v0.0.13: Add support for emulating multiple ADE versions (1.7.2, 2.0.1, 3.0.1, 4.0.3, 4.5.11),
|
||||||
# add code to import existing activation from ADE (Windows+Mac only),
|
# add code to import existing activation from ADE (Windows, MacOS or Linux/Wine),
|
||||||
|
# add code to remove an existing activation from the plugin (Ctrl+Shift+D),
|
||||||
# fix race condition when importing multiple ACSMs simultaneously,
|
# fix race condition when importing multiple ACSMs simultaneously,
|
||||||
# fix authorization failing with certain non-ASCII characters in username.
|
# fix authorization failing with certain non-ASCII characters in username,
|
||||||
|
# add detailed logging toggle setting.
|
||||||
|
|
||||||
PLUGIN_NAME = "DeACSM"
|
PLUGIN_NAME = "DeACSM"
|
||||||
PLUGIN_VERSION_TUPLE = (0, 0, 13)
|
PLUGIN_VERSION_TUPLE = (0, 0, 13)
|
||||||
@ -33,6 +35,7 @@ __version__ = PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
|
|||||||
|
|
||||||
|
|
||||||
from calibre.utils.config import config_dir # type: ignore
|
from calibre.utils.config import config_dir # type: ignore
|
||||||
|
from calibre.constants import isosx, iswindows, islinux # type: ignore
|
||||||
|
|
||||||
import os, shutil, traceback, sys, time, io
|
import os, shutil, traceback, sys, time, io
|
||||||
import zipfile
|
import zipfile
|
||||||
@ -100,6 +103,16 @@ class DeACSM(FileTypePlugin):
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if islinux:
|
||||||
|
# Also extract EXE files needed for WINE ADE key extraction
|
||||||
|
names = [ "keyextract/decrypt_win32.exe", "keyextract/decrypt_win64.exe" ]
|
||||||
|
lib_dict = self.load_resources(names)
|
||||||
|
for entry, data in lib_dict.items():
|
||||||
|
file_path = os.path.join(self.moddir, entry.split('/')[1])
|
||||||
|
f = open(file_path, "wb")
|
||||||
|
f.write(data)
|
||||||
|
f.close()
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(self.moddir, "cryptography"))
|
sys.path.insert(0, os.path.join(self.moddir, "cryptography"))
|
||||||
sys.path.insert(0, os.path.join(self.moddir, "rsa"))
|
sys.path.insert(0, os.path.join(self.moddir, "rsa"))
|
||||||
sys.path.insert(0, os.path.join(self.moddir, "oscrypto"))
|
sys.path.insert(0, os.path.join(self.moddir, "oscrypto"))
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
# pyright: reportUndefinedVariable=false
|
# pyright: reportUndefinedVariable=false
|
||||||
|
|
||||||
import os, base64, traceback
|
import os, base64, traceback
|
||||||
|
from PyQt5.QtGui import QKeySequence
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ import time, datetime
|
|||||||
from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
|
from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
|
||||||
QGroupBox, QPushButton, QListWidget, QListWidgetItem, QInputDialog,
|
QGroupBox, QPushButton, QListWidget, QListWidgetItem, QInputDialog,
|
||||||
QLineEdit, QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
|
QLineEdit, QAbstractItemView, QIcon, QDialog, QDialogButtonBox, QUrl)
|
||||||
|
from PyQt5.QtWidgets import QShortcut
|
||||||
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
@ -25,7 +27,7 @@ from calibre.gui2 import (question_dialog, error_dialog, info_dialog, choose_sav
|
|||||||
from calibre_plugins.deacsm.__init__ import PLUGIN_NAME, PLUGIN_VERSION # type: ignore
|
from calibre_plugins.deacsm.__init__ import PLUGIN_NAME, PLUGIN_VERSION # type: ignore
|
||||||
import calibre_plugins.deacsm.prefs as prefs # type: ignore
|
import calibre_plugins.deacsm.prefs as prefs # type: ignore
|
||||||
from calibre.utils.config import config_dir # type: ignore
|
from calibre.utils.config import config_dir # type: ignore
|
||||||
from calibre.constants import isosx, iswindows # type: ignore
|
from calibre.constants import isosx, iswindows, islinux # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class ConfigWidget(QWidget):
|
class ConfigWidget(QWidget):
|
||||||
@ -42,6 +44,7 @@ class ConfigWidget(QWidget):
|
|||||||
self.tempdeacsmprefs['path_to_account_data'] = self.deacsmprefs['path_to_account_data']
|
self.tempdeacsmprefs['path_to_account_data'] = self.deacsmprefs['path_to_account_data']
|
||||||
|
|
||||||
self.tempdeacsmprefs['notify_fulfillment'] = self.deacsmprefs['notify_fulfillment']
|
self.tempdeacsmprefs['notify_fulfillment'] = self.deacsmprefs['notify_fulfillment']
|
||||||
|
self.tempdeacsmprefs['detailed_logging'] = self.deacsmprefs['detailed_logging']
|
||||||
|
|
||||||
self.tempdeacsmprefs['list_of_rented_books'] = self.deacsmprefs['list_of_rented_books']
|
self.tempdeacsmprefs['list_of_rented_books'] = self.deacsmprefs['list_of_rented_books']
|
||||||
|
|
||||||
@ -80,6 +83,12 @@ class ConfigWidget(QWidget):
|
|||||||
self.button_import_WinADE.clicked.connect(self.import_activation_from_Win)
|
self.button_import_WinADE.clicked.connect(self.import_activation_from_Win)
|
||||||
ua_group_box_layout.addWidget(self.button_import_WinADE)
|
ua_group_box_layout.addWidget(self.button_import_WinADE)
|
||||||
|
|
||||||
|
if islinux:
|
||||||
|
self.button_import_LinuxWineADE = QtGui.QPushButton(self)
|
||||||
|
self.button_import_LinuxWineADE.setText(_("Import activation from ADE (Wine)"))
|
||||||
|
self.button_import_LinuxWineADE.clicked.connect(self.import_activation_from_LinuxWine)
|
||||||
|
ua_group_box_layout.addWidget(self.button_import_LinuxWineADE)
|
||||||
|
|
||||||
self.button_import_activation = QtGui.QPushButton(self)
|
self.button_import_activation = QtGui.QPushButton(self)
|
||||||
self.button_import_activation.setText(_("Import existing activation backup (ZIP)"))
|
self.button_import_activation.setText(_("Import existing activation backup (ZIP)"))
|
||||||
self.button_import_activation.clicked.connect(self.import_activation_from_ZIP)
|
self.button_import_activation.clicked.connect(self.import_activation_from_ZIP)
|
||||||
@ -119,6 +128,17 @@ class ConfigWidget(QWidget):
|
|||||||
self.chkNotifyFulfillment.setChecked(self.tempdeacsmprefs["notify_fulfillment"])
|
self.chkNotifyFulfillment.setChecked(self.tempdeacsmprefs["notify_fulfillment"])
|
||||||
layout.addWidget(self.chkNotifyFulfillment)
|
layout.addWidget(self.chkNotifyFulfillment)
|
||||||
|
|
||||||
|
self.chkDetailedLogging = QtGui.QCheckBox("Enable detailed debug logging")
|
||||||
|
self.chkDetailedLogging.setToolTip("Default: False\n\nIf this is enabled, the plugin debug logs will be more verbose which might be helpful in case of errors.\nHowever, it will also mean that private data like encryption keys or account credentials might end up in the logfiles.")
|
||||||
|
self.chkDetailedLogging.setChecked(self.tempdeacsmprefs["detailed_logging"])
|
||||||
|
self.chkDetailedLogging.toggled.connect(self.toggle_logging)
|
||||||
|
layout.addWidget(self.chkDetailedLogging)
|
||||||
|
|
||||||
|
# Key shortcut Ctrl+Shift+D to remove authorization, just like in ADE.
|
||||||
|
self.deauthShortcut = QShortcut(QKeySequence("Ctrl+Shift+D"), self)
|
||||||
|
self.deauthShortcut.activated.connect(self.delete_ade_auth)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path, are_ade_version_lists_valid
|
from calibre_plugins.deacsm.libadobe import createDeviceKeyFile, update_account_path, are_ade_version_lists_valid
|
||||||
@ -135,25 +155,104 @@ class ConfigWidget(QWidget):
|
|||||||
update_account_path(self.deacsmprefs["path_to_account_data"])
|
update_account_path(self.deacsmprefs["path_to_account_data"])
|
||||||
self.resize(self.sizeHint())
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
if not are_ade_version_lists_valid():
|
try:
|
||||||
# Internal error, this should never happen
|
# Someone reported getting this error after upgrading the plugin.
|
||||||
if not activated:
|
# No idea why that happens - put a try/catch around just to be safe.
|
||||||
self.button_link_account.setEnabled(False)
|
if not are_ade_version_lists_valid():
|
||||||
self.button_import_activation.setEnabled(False)
|
# Internal error, this should never happen
|
||||||
if isosx:
|
if not activated:
|
||||||
self.button_import_MacADE.setEnabled(activated)
|
self.button_link_account.setEnabled(False)
|
||||||
if iswindows:
|
self.button_import_activation.setEnabled(False)
|
||||||
self.button_import_WinADE.setEnabled(activated)
|
if isosx:
|
||||||
else:
|
self.button_import_MacADE.setEnabled(activated)
|
||||||
self.button_switch_ade_version.setEnabled(False)
|
if iswindows:
|
||||||
self.button_export_key.setEnabled(False)
|
self.button_import_WinADE.setEnabled(activated)
|
||||||
self.button_export_activation.setEnabled(False)
|
if islinux:
|
||||||
self.button_rented_books.setEnabled(False)
|
self.button_import_LinuxWineADE.setEnabled(activated)
|
||||||
self.chkNotifyFulfillment.setEnabled(False)
|
else:
|
||||||
|
self.button_switch_ade_version.setEnabled(False)
|
||||||
|
self.button_export_key.setEnabled(False)
|
||||||
|
self.button_export_activation.setEnabled(False)
|
||||||
|
self.button_rented_books.setEnabled(False)
|
||||||
|
self.chkNotifyFulfillment.setEnabled(False)
|
||||||
|
|
||||||
error_dialog(None, "Internal error", "Version list mismatch. Please open a bug report.", show=True, show_copy_button=False)
|
error_dialog(None, "Internal error", "Version list mismatch. Please open a bug report.", show=True, show_copy_button=False)
|
||||||
|
except UnboundLocalError:
|
||||||
|
print("Verify function are_ade_version_lists_valid() not found - why?")
|
||||||
|
|
||||||
|
|
||||||
|
def toggle_logging(self):
|
||||||
|
if not self.chkDetailedLogging.isChecked():
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = "You have enabled detailed logging.\n"
|
||||||
|
msg += "This will cause various data to be included in the logfiles, like encryption keys, account keys and other confidential data.\n"
|
||||||
|
msg += "With this setting enabled, only share log files privately with the developer and don't make them publicly available."
|
||||||
|
|
||||||
|
info_dialog(None, "Warning", msg, show=True, show_copy_button=False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def delete_ade_auth(self):
|
||||||
|
# This function can only be triggered with the key combination Ctrl+Shift+D.
|
||||||
|
# There is no easy-to-access button to trigger that to prevent people from
|
||||||
|
# accidentally deleting their authorization.
|
||||||
|
|
||||||
|
info_string, activated, ade_mail = self.get_account_info()
|
||||||
|
|
||||||
|
if not activated:
|
||||||
|
# If there is no authorization, there's nothing to delete
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = "Are you sure you want to remove the ADE authorization?\n"
|
||||||
|
|
||||||
|
if ade_mail is None:
|
||||||
|
msg += "The current authorization is an anonymous login. It will be permanently lost if you proceed.\n\n"
|
||||||
|
else:
|
||||||
|
msg += "You will use up one of your six activations if you want to authorize your account again in the future.\n\n"
|
||||||
|
|
||||||
|
msg += "Click 'Yes' to delete the authorization or 'No' to cancel."
|
||||||
|
|
||||||
|
ok = question_dialog(None, "Remove ADE account", msg)
|
||||||
|
|
||||||
|
if (not ok):
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = "Do you want to create a backup of the current authorization?\n"
|
||||||
|
msg += "This backup can be imported again without using up one of your authorizations.\n\n"
|
||||||
|
msg += "Click 'Yes' to create a backup before deleting, click 'No' to delete without backup."
|
||||||
|
|
||||||
|
|
||||||
|
ok = question_dialog(None, "Remove ADE account", msg)
|
||||||
|
|
||||||
|
if (ok):
|
||||||
|
# Create a backup:
|
||||||
|
backup_success = self.export_activation()
|
||||||
|
if (not backup_success):
|
||||||
|
error_dialog(None, "Export failed", "The backup was unsuccessful - authorization will not be deleted.", show=True, show_copy_button=False)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Okay, once we are here, we can be pretty sure the user actually wants to delete their authorization.
|
||||||
|
try:
|
||||||
|
os.remove(os.path.join(self.deacsmprefs["path_to_account_data"], "activation.xml"))
|
||||||
|
os.remove(os.path.join(self.deacsmprefs["path_to_account_data"], "device.xml"))
|
||||||
|
os.remove(os.path.join(self.deacsmprefs["path_to_account_data"], "devicesalt"))
|
||||||
|
except:
|
||||||
|
error_dialog(None, "Remove ADE account", "There was an error while removing the authorization.", show=True, show_copy_button=False)
|
||||||
|
|
||||||
|
|
||||||
|
# Show success, then close:
|
||||||
|
info_dialog(None, "Remove ADE account", "ADE authorization successfully removed.", show=True, show_copy_button=False)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.button_switch_ade_version.setEnabled(False)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.button_export_activation.setEnabled(False)
|
||||||
|
self.button_export_key.setEnabled(False)
|
||||||
|
self.lblAccInfo.setText("Authorization deleted.\nClose and re-open this window to add a new authorization.")
|
||||||
|
|
||||||
|
|
||||||
def get_account_info(self):
|
def get_account_info(self):
|
||||||
|
|
||||||
@ -236,6 +335,7 @@ class ConfigWidget(QWidget):
|
|||||||
except:
|
except:
|
||||||
print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION))
|
print("{0} v{1}: Error while importing Account stuff".format(PLUGIN_NAME, PLUGIN_VERSION))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
update_account_path(self.deacsmprefs["path_to_account_data"])
|
update_account_path(self.deacsmprefs["path_to_account_data"])
|
||||||
@ -253,7 +353,7 @@ class ConfigWidget(QWidget):
|
|||||||
filters, all_files=False, initial_filename=export_filename)
|
filters, all_files=False, initial_filename=export_filename)
|
||||||
|
|
||||||
if (filename is None):
|
if (filename is None):
|
||||||
return
|
return False
|
||||||
|
|
||||||
print("{0} v{1}: Exporting activation data to {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename))
|
print("{0} v{1}: Exporting activation data to {2}".format(PLUGIN_NAME, PLUGIN_VERSION, filename))
|
||||||
|
|
||||||
@ -262,8 +362,97 @@ class ConfigWidget(QWidget):
|
|||||||
zipfile.write(os.path.join(self.deacsmprefs["path_to_account_data"], "device.xml"), "device.xml")
|
zipfile.write(os.path.join(self.deacsmprefs["path_to_account_data"], "device.xml"), "device.xml")
|
||||||
zipfile.write(os.path.join(self.deacsmprefs["path_to_account_data"], "activation.xml"), "activation.xml")
|
zipfile.write(os.path.join(self.deacsmprefs["path_to_account_data"], "activation.xml"), "activation.xml")
|
||||||
zipfile.write(os.path.join(self.deacsmprefs["path_to_account_data"], "devicesalt"), "devicesalt")
|
zipfile.write(os.path.join(self.deacsmprefs["path_to_account_data"], "devicesalt"), "devicesalt")
|
||||||
|
|
||||||
|
return True
|
||||||
except:
|
except:
|
||||||
return error_dialog(None, "Export failed", "Export failed.", show=True, show_copy_button=False)
|
error_dialog(None, "Export failed", "Export failed.", show=True, show_copy_button=False)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_ADE_registry(self, wineprefix):
|
||||||
|
# Gets a path to a WINEPREFIX and returns True if this is useable.
|
||||||
|
# Checks if the Wine registry contains an ADE activation.
|
||||||
|
|
||||||
|
try:
|
||||||
|
registry_file = open(os.path.join(wineprefix, "user.reg"))
|
||||||
|
while True:
|
||||||
|
line = registry_file.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
|
||||||
|
if line.strip().startswith("[Software\\\\Adobe\\\\Adept\\\\Activation\\\\0000"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
except:
|
||||||
|
print("Exception while validating WINEPREFIX:")
|
||||||
|
print(traceback.format_exc())
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def import_activation_from_LinuxWine(self):
|
||||||
|
# This will try to import the activation from Adobe Digital Editions on Linux / Wine ...
|
||||||
|
|
||||||
|
msg = "Trying to import existing activation from Adobe Digital Editions in WINE ...\n"
|
||||||
|
msg += "Note: Importing the activation can take up to 30 seconds, and Calibre will appear to be \"stuck\" during that time.\n\n"
|
||||||
|
msg += "Please enter the full, absolute path to your WINEPREFIX."
|
||||||
|
msg += "If there's already a path in the input box, it is usually (but not always) the correct one."
|
||||||
|
|
||||||
|
default_path = ""
|
||||||
|
|
||||||
|
if (default_path == ""):
|
||||||
|
# Check WINEPREFIX env variable
|
||||||
|
env_wineprefix = os.getenv("WINEPREFIX", None)
|
||||||
|
if (env_wineprefix is not None and os.path.isdir(env_wineprefix)):
|
||||||
|
if self.check_ADE_registry(env_wineprefix):
|
||||||
|
default_path = env_wineprefix
|
||||||
|
|
||||||
|
if (default_path == ""):
|
||||||
|
# Use default path ".wine" in HOME dir
|
||||||
|
home_wineprefix = os.path.join(os.path.expanduser("~"), ".wine")
|
||||||
|
if (os.path.isdir(home_wineprefix)):
|
||||||
|
if self.check_ADE_registry(home_wineprefix):
|
||||||
|
default_path = home_wineprefix
|
||||||
|
|
||||||
|
|
||||||
|
text, ok = QInputDialog.getText(self, "Importing authorization", msg, text=default_path)
|
||||||
|
|
||||||
|
if (not ok):
|
||||||
|
return
|
||||||
|
|
||||||
|
if (not os.path.isdir(text)):
|
||||||
|
return error_dialog(None, "Import failed", "The WINEPREFIX path you entered doesn't seem to exist.", show=True, show_copy_button=False)
|
||||||
|
|
||||||
|
if (not self.check_ADE_registry(text)):
|
||||||
|
return error_dialog(None, "Import failed", "The WINEPREFIX you entered doesn't seem to contain an authorized ADE.", show=True, show_copy_button=False)
|
||||||
|
|
||||||
|
|
||||||
|
from calibre_plugins.deacsm.libadobeImportAccount import importADEactivationLinuxWine
|
||||||
|
|
||||||
|
ret, msg = importADEactivationLinuxWine(text)
|
||||||
|
|
||||||
|
if (ret):
|
||||||
|
# update display
|
||||||
|
info_string, activated, ade_mail = self.get_account_info()
|
||||||
|
self.lblAccInfo.setText(info_string)
|
||||||
|
|
||||||
|
self.button_link_account.setEnabled(not activated)
|
||||||
|
self.button_import_activation.setEnabled(not activated)
|
||||||
|
self.button_import_LinuxWineADE.setEnabled(not activated)
|
||||||
|
self.button_export_key.setEnabled(activated)
|
||||||
|
self.button_export_activation.setEnabled(activated)
|
||||||
|
|
||||||
|
|
||||||
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
|
if (activated):
|
||||||
|
if ade_mail is None:
|
||||||
|
info_dialog(None, "Done", "Successfully imported an anonymous authorization", show=True, show_copy_button=False)
|
||||||
|
else:
|
||||||
|
info_dialog(None, "Done", "Successfully imported authorization for " + ade_mail, show=True, show_copy_button=False)
|
||||||
|
else:
|
||||||
|
error_dialog(None, "Import failed", "Import looks like it worked, but the resulting files seem to be corrupted ...", show=True, show_copy_button=False)
|
||||||
|
else:
|
||||||
|
error_dialog(None, "Import failed", "That didn't work:\n" + msg, show=True, show_copy_button=False)
|
||||||
|
|
||||||
|
|
||||||
def import_activation_from_Win(self):
|
def import_activation_from_Win(self):
|
||||||
# This will try to import the activation from Adobe Digital Editions on Windows ...
|
# This will try to import the activation from Adobe Digital Editions on Windows ...
|
||||||
@ -382,16 +571,14 @@ class ConfigWidget(QWidget):
|
|||||||
self.button_import_activation.setEnabled(not activated)
|
self.button_import_activation.setEnabled(not activated)
|
||||||
self.button_export_key.setEnabled(activated)
|
self.button_export_key.setEnabled(activated)
|
||||||
self.button_export_activation.setEnabled(activated)
|
self.button_export_activation.setEnabled(activated)
|
||||||
try:
|
if isosx:
|
||||||
self.button_import_MacADE.setEnabled(activated)
|
self.button_import_MacADE.setEnabled(not activated)
|
||||||
except:
|
if iswindows:
|
||||||
pass
|
self.button_import_WinADE.setEnabled(not activated)
|
||||||
|
if islinux:
|
||||||
|
self.button_import_LinuxWineADE.setEnabled(not activated)
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.button_import_WinADE.setEnabled(activated)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.resize(self.sizeHint())
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
if ade_mail is None:
|
if ade_mail is None:
|
||||||
@ -497,7 +684,7 @@ class ConfigWidget(QWidget):
|
|||||||
return error_dialog(None, "Failed", "Error while changing ADE version: " + msg, show=True, show_copy_button=False)
|
return error_dialog(None, "Failed", "Error while changing ADE version: " + msg, show=True, show_copy_button=False)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
return error_dialog(None, "Failed", "Error while changing ADE version.", show=True, det_msg=err, show_copy_button=False)
|
return error_dialog(None, "Failed", "Error while changing ADE version.", show=True, det_msg=traceback.format_exc(), show_copy_button=False)
|
||||||
|
|
||||||
|
|
||||||
def link_account(self):
|
def link_account(self):
|
||||||
@ -583,14 +770,12 @@ class ConfigWidget(QWidget):
|
|||||||
self.button_import_activation.setEnabled(False)
|
self.button_import_activation.setEnabled(False)
|
||||||
self.button_export_key.setEnabled(True)
|
self.button_export_key.setEnabled(True)
|
||||||
self.button_export_activation.setEnabled(True)
|
self.button_export_activation.setEnabled(True)
|
||||||
try:
|
if isosx:
|
||||||
self.button_import_MacADE.setEnabled(False)
|
self.button_import_MacADE.setEnabled(False)
|
||||||
except:
|
if iswindows:
|
||||||
pass
|
|
||||||
try:
|
|
||||||
self.button_import_WinADE.setEnabled(False)
|
self.button_import_WinADE.setEnabled(False)
|
||||||
except:
|
if islinux:
|
||||||
pass
|
self.button_import_LinuxWineADE.setEnabled(False)
|
||||||
|
|
||||||
self.resize(self.sizeHint())
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
@ -644,6 +829,7 @@ class ConfigWidget(QWidget):
|
|||||||
|
|
||||||
def save_settings(self):
|
def save_settings(self):
|
||||||
self.deacsmprefs.set('notify_fulfillment', self.chkNotifyFulfillment.isChecked())
|
self.deacsmprefs.set('notify_fulfillment', self.chkNotifyFulfillment.isChecked())
|
||||||
|
self.deacsmprefs.set('detailed_logging', self.chkDetailedLogging.isChecked())
|
||||||
self.deacsmprefs.writeprefs()
|
self.deacsmprefs.writeprefs()
|
||||||
|
|
||||||
def load_resource(self, name):
|
def load_resource(self, name):
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
from re import VERBOSE
|
||||||
|
|
||||||
|
|
||||||
def unfuck(user):
|
def unfuck(user):
|
||||||
# Wine uses a pretty nonstandard encoding in their registry file.
|
# Wine uses a pretty nonstandard encoding in their registry file.
|
||||||
# I haven't found any existing Python implementation for that,
|
# I haven't found any existing Python implementation for that,
|
||||||
@ -109,7 +112,19 @@ def GetMasterKey(path_to_wine_prefix):
|
|||||||
print("Hey! This is for Linux!")
|
print("Hey! This is for Linux!")
|
||||||
return
|
return
|
||||||
|
|
||||||
import cpuid
|
verbose_logging = False
|
||||||
|
try:
|
||||||
|
import calibre_plugins.deacsm.prefs as prefs
|
||||||
|
deacsmprefs = prefs.DeACSM_Prefs()
|
||||||
|
verbose_logging = deacsmprefs["detailed_logging"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
import cpuid
|
||||||
|
except:
|
||||||
|
import calibre_plugins.deacsm.cpuid as cpuid
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -119,21 +134,26 @@ def GetMasterKey(path_to_wine_prefix):
|
|||||||
serial_file.close()
|
serial_file.close()
|
||||||
serial = int(serial, 16)
|
serial = int(serial, 16)
|
||||||
except:
|
except:
|
||||||
# If this file is not present, Wine will use a default serial number of "0".
|
# If this file is not present, Wine will usually use a default serial number of "0".
|
||||||
|
# There are some edge cases where Wine uses a different serial number even when that
|
||||||
|
# .windows-serial file is not present.
|
||||||
serial = 0
|
serial = 0
|
||||||
|
|
||||||
print("Serial: " + str(serial))
|
if (verbose_logging):
|
||||||
|
print("Serial: " + str(serial))
|
||||||
|
|
||||||
cpu = cpuid.CPUID()
|
cpu = cpuid.CPUID()
|
||||||
_, b, c, d = cpu(0)
|
_, b, c, d = cpu(0)
|
||||||
vendor = struct.pack("III", b, d, c)
|
vendor = struct.pack("III", b, d, c)
|
||||||
|
|
||||||
print("Vendor: " + vendor.decode("utf-8"))
|
if (verbose_logging):
|
||||||
|
print("Vendor: " + vendor.decode("utf-8"))
|
||||||
|
|
||||||
signature, _, _, _ = cpu(1)
|
signature, _, _, _ = cpu(1)
|
||||||
signature = struct.pack('>I', signature)[1:]
|
signature = struct.pack('>I', signature)[1:]
|
||||||
|
|
||||||
print("Signature: " + str(signature.hex()))
|
if (verbose_logging):
|
||||||
|
print("Signature: " + str(signature.hex()))
|
||||||
|
|
||||||
# Search for the username in the registry:
|
# Search for the username in the registry:
|
||||||
user = None
|
user = None
|
||||||
@ -174,7 +194,8 @@ def GetMasterKey(path_to_wine_prefix):
|
|||||||
print("Error while determining username ...")
|
print("Error while determining username ...")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
print("Username: " + str(user))
|
if verbose_logging:
|
||||||
|
print("Username: " + str(user))
|
||||||
|
|
||||||
# Find the value we want to decrypt from the registry. loop through the Wine registry file to find the "key" attribute
|
# Find the value we want to decrypt from the registry. loop through the Wine registry file to find the "key" attribute
|
||||||
try:
|
try:
|
||||||
@ -213,12 +234,12 @@ def GetMasterKey(path_to_wine_prefix):
|
|||||||
raise
|
raise
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if key_line is None:
|
||||||
|
print("No ADE activation found ...")
|
||||||
|
return None
|
||||||
|
|
||||||
print("Encrypted key: " + str(key_line))
|
if verbose_logging:
|
||||||
|
print("Encrypted key: " + str(key_line))
|
||||||
import hexdump
|
|
||||||
|
|
||||||
hexdump.hexdump(key_line)
|
|
||||||
|
|
||||||
# These should all be "bytes" or "bytearray"
|
# These should all be "bytes" or "bytearray"
|
||||||
#print(type(vendor))
|
#print(type(vendor))
|
||||||
@ -227,29 +248,47 @@ def GetMasterKey(path_to_wine_prefix):
|
|||||||
|
|
||||||
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
|
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
|
||||||
|
|
||||||
print("Entropy: " + str(entropy))
|
if verbose_logging:
|
||||||
|
print("Entropy: " + str(entropy))
|
||||||
|
|
||||||
# We would now call CryptUnprotectData to decrypt the stuff,
|
# We would now call CryptUnprotectData to decrypt the stuff,
|
||||||
# but unfortunately there's no working Linux implementation
|
# but unfortunately there's no working Linux implementation
|
||||||
# for that. This means we have to call a Windows binary through
|
# for that.
|
||||||
|
#
|
||||||
|
# The plan was to handle everything in Python so we don't have
|
||||||
|
# to interact with Wine - that's why we're doing all the registry
|
||||||
|
# handling ourselves.
|
||||||
|
# Unfortunately, that doesn't work for the actual decryption.
|
||||||
|
#
|
||||||
|
# This means we have to call a Windows binary through
|
||||||
# Wine just for this one single decryption call ...
|
# Wine just for this one single decryption call ...
|
||||||
|
|
||||||
success, data = CryptUnprotectDataExecuteWine(path_to_wine_prefix, key_line, entropy)
|
success, data = CryptUnprotectDataExecuteWine(path_to_wine_prefix, key_line, entropy)
|
||||||
if (success):
|
if (success):
|
||||||
keykey = data
|
keykey = data
|
||||||
print(keykey)
|
if verbose_logging:
|
||||||
|
print("Key key: ")
|
||||||
|
print(keykey)
|
||||||
return keykey
|
return keykey
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Error number: " + str(data))
|
print("Error number: " + str(data))
|
||||||
if data == 13: # WINError ERROR_INVALID_DATA
|
if data == 13: # WINError ERROR_INVALID_DATA
|
||||||
print("Could not decrypt data with the given key. Did the Wine username change?")
|
print("Could not decrypt data with the given key. Did the entropy change?")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
|
def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
|
||||||
import subprocess, os, re
|
import subprocess, os, re
|
||||||
|
|
||||||
|
verbose_logging = False
|
||||||
|
try:
|
||||||
|
import calibre_plugins.deacsm.prefs as prefs
|
||||||
|
deacsmprefs = prefs.DeACSM_Prefs()
|
||||||
|
verbose_logging = deacsmprefs["detailed_logging"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
print("Asking WINE to decrypt encrypted key for us ...")
|
print("Asking WINE to decrypt encrypted key for us ...")
|
||||||
|
|
||||||
if wineprefix == "" or not os.path.exists(wineprefix):
|
if wineprefix == "" or not os.path.exists(wineprefix):
|
||||||
@ -282,10 +321,10 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
|
|||||||
env_dict = os.environ
|
env_dict = os.environ
|
||||||
env_dict["PYTHONPATH"] = ""
|
env_dict["PYTHONPATH"] = ""
|
||||||
env_dict["WINEPREFIX"] = wineprefix
|
env_dict["WINEPREFIX"] = wineprefix
|
||||||
env_dict["WINEDEBUG"] = "-all,+crypt"
|
#env_dict["WINEDEBUG"] = "-all,+crypt"
|
||||||
|
env_dict["WINEDEBUG"] = "+err,+fixme"
|
||||||
import base64
|
|
||||||
|
|
||||||
|
# Use environment variables to get the input data to the application.
|
||||||
env_dict["X_DECRYPT_DATA"] = data.hex()
|
env_dict["X_DECRYPT_DATA"] = data.hex()
|
||||||
env_dict["X_DECRYPT_ENTROPY"] = entropy.hex()
|
env_dict["X_DECRYPT_ENTROPY"] = entropy.hex()
|
||||||
|
|
||||||
@ -296,7 +335,7 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
|
|||||||
moddir = os.path.join(maindir,"modules")
|
moddir = os.path.join(maindir,"modules")
|
||||||
except:
|
except:
|
||||||
import os
|
import os
|
||||||
moddir = os.path.dirname(os.path.abspath(__file__))
|
moddir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "keyextract")
|
||||||
|
|
||||||
proc = subprocess.Popen(["wine", "decrypt_" + winearch + ".exe" ], shell=False, cwd=moddir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
proc = subprocess.Popen(["wine", "decrypt_" + winearch + ".exe" ], shell=False, cwd=moddir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
prog_output, prog_stderr = proc.communicate()
|
prog_output, prog_stderr = proc.communicate()
|
||||||
@ -305,22 +344,34 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
|
|||||||
|
|
||||||
if prog_output.decode("utf-8").startswith("PROGOUTPUT:0:"):
|
if prog_output.decode("utf-8").startswith("PROGOUTPUT:0:"):
|
||||||
key_string = prog_output.decode("utf-8").split(':')[2]
|
key_string = prog_output.decode("utf-8").split(':')[2]
|
||||||
print("Successfully got encryption key from WINE: " + key_string)
|
if verbose_logging:
|
||||||
|
print("Successfully got encryption key from WINE: " + key_string)
|
||||||
|
else:
|
||||||
|
print("Successfully got encryption key from WINE.")
|
||||||
master_key = bytes.fromhex(key_string)
|
master_key = bytes.fromhex(key_string)
|
||||||
return True, master_key
|
return True, master_key
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Huh. That didn't work. ")
|
print("Huh. That didn't work. ")
|
||||||
err = int(prog_output.decode("utf-8").split(':')[1])
|
try:
|
||||||
if err == -4:
|
err = int(prog_output.decode("utf-8").split(':')[1])
|
||||||
err = int(prog_output.decode("utf-8").split(':')[2])
|
if err == -4:
|
||||||
|
err = int(prog_output.decode("utf-8").split(':')[2])
|
||||||
|
new_serial = int(prog_output.decode("utf-8").split(':')[3])
|
||||||
|
if verbose_logging:
|
||||||
|
print("New serial: " + str(new_serial))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
print("Program reported: " + prog_output.decode("utf-8"))
|
if verbose_logging:
|
||||||
print("Debug log: ")
|
print("Program reported: " + prog_output.decode("utf-8"))
|
||||||
print(prog_stderr.decode("utf-8"))
|
print("Debug log: ")
|
||||||
|
print(prog_stderr.decode("utf-8"))
|
||||||
|
|
||||||
return False, err
|
return False, err
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
GetMasterKey()
|
print("Do not execute this directly!")
|
||||||
|
exit()
|
@ -25,14 +25,13 @@ def GetSystemDirectory():
|
|||||||
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
|
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
|
||||||
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
|
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
|
||||||
GetSystemDirectoryW.restype = 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():
|
buffer = create_unicode_buffer(MAX_PATH + 1)
|
||||||
|
GetSystemDirectoryW(buffer, len(buffer))
|
||||||
|
return buffer.value
|
||||||
|
|
||||||
|
|
||||||
|
def GetVolumeSerialNumber(path):
|
||||||
from ctypes import windll, c_wchar_p, c_uint, POINTER, byref
|
from ctypes import windll, c_wchar_p, c_uint, POINTER, byref
|
||||||
|
|
||||||
kernel32 = windll.kernel32
|
kernel32 = windll.kernel32
|
||||||
@ -41,13 +40,11 @@ def GetVolumeSerialNumber():
|
|||||||
POINTER(c_uint), POINTER(c_uint),
|
POINTER(c_uint), POINTER(c_uint),
|
||||||
POINTER(c_uint), c_wchar_p, c_uint]
|
POINTER(c_uint), c_wchar_p, c_uint]
|
||||||
GetVolumeInformationW.restype = c_uint
|
GetVolumeInformationW.restype = c_uint
|
||||||
def GetVolumeSerialNumber(path):
|
vsn = c_uint(0)
|
||||||
vsn = c_uint(0)
|
GetVolumeInformationW(
|
||||||
GetVolumeInformationW(
|
path, None, 0, byref(vsn), None, None, None, 0)
|
||||||
path, None, 0, byref(vsn), None, None, None, 0)
|
return vsn.value
|
||||||
return vsn.value
|
|
||||||
return GetVolumeSerialNumber
|
|
||||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
|
||||||
|
|
||||||
|
|
||||||
def GetUserNameWINAPI():
|
def GetUserNameWINAPI():
|
||||||
@ -113,23 +110,40 @@ def GetMasterKey():
|
|||||||
if os.name != 'nt':
|
if os.name != 'nt':
|
||||||
print("This script is for Windows!")
|
print("This script is for Windows!")
|
||||||
|
|
||||||
|
verbose_logging = False
|
||||||
|
try:
|
||||||
|
import calibre_plugins.deacsm.prefs as prefs
|
||||||
|
deacsmprefs = prefs.DeACSM_Prefs()
|
||||||
|
verbose_logging = deacsmprefs["detailed_logging"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# Get serial number of root drive
|
# Get serial number of root drive
|
||||||
root = GetSystemDirectory().split('\\')[0] + '\\'
|
root = GetSystemDirectory().split('\\')[0] + '\\'
|
||||||
serial = GetVolumeSerialNumber(root)
|
serial = GetVolumeSerialNumber(root)
|
||||||
print("Serial: " + str(serial))
|
if verbose_logging:
|
||||||
|
print("Serial: " + str(serial))
|
||||||
|
|
||||||
|
|
||||||
# Get CPU vendor:
|
# Get CPU vendor:
|
||||||
import cpuid, struct
|
try:
|
||||||
|
import cpuid
|
||||||
|
except:
|
||||||
|
import calibre_plugins.deacsm.cpuid as cpuid
|
||||||
|
|
||||||
|
import struct
|
||||||
cpu = cpuid.CPUID()
|
cpu = cpuid.CPUID()
|
||||||
_, b, c, d = cpu(0)
|
_, b, c, d = cpu(0)
|
||||||
vendor = struct.pack("III", b, d, c)
|
vendor = struct.pack("III", b, d, c)
|
||||||
print("Vendor: " + vendor.decode("utf-8"))
|
|
||||||
|
if verbose_logging:
|
||||||
|
print("Vendor: " + vendor.decode("utf-8"))
|
||||||
|
|
||||||
signature, _, _, _ = cpu(1)
|
signature, _, _, _ = cpu(1)
|
||||||
signature = struct.pack('>I', signature)[1:]
|
signature = struct.pack('>I', signature)[1:]
|
||||||
|
|
||||||
print("Signature: " + str(signature.hex()))
|
if verbose_logging:
|
||||||
|
print("Signature: " + str(signature.hex()))
|
||||||
|
|
||||||
# Search for the username in the registry:
|
# Search for the username in the registry:
|
||||||
user = None
|
user = None
|
||||||
@ -143,12 +157,13 @@ def GetMasterKey():
|
|||||||
else:
|
else:
|
||||||
user = current_user_name
|
user = current_user_name
|
||||||
|
|
||||||
if (user_from_registry is not None and user_from_registry != current_user_name):
|
if verbose_logging:
|
||||||
print("Username: {0}/{1} mismatch, using {0}".format(str(user_from_registry), str(current_user_name)))
|
if (user_from_registry is not None and user_from_registry != current_user_name):
|
||||||
elif (user_from_registry is not None):
|
print("Username: {0}/{1} mismatch, using {0}".format(str(user_from_registry), str(current_user_name)))
|
||||||
print("Username: {0} (Registry)".format(str(user_from_registry)))
|
elif (user_from_registry is not None):
|
||||||
else:
|
print("Username: {0} (Registry)".format(str(user_from_registry)))
|
||||||
print("Username: {0} (WinAPI)".format(str(current_user_name)))
|
else:
|
||||||
|
print("Username: {0} (WinAPI)".format(str(current_user_name)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -164,9 +179,10 @@ def GetMasterKey():
|
|||||||
device = winreg.QueryValueEx(regkey, 'key')[0]
|
device = winreg.QueryValueEx(regkey, 'key')[0]
|
||||||
except:
|
except:
|
||||||
print("Can't find encrypted device key.")
|
print("Can't find encrypted device key.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if verbose_logging:
|
||||||
print("Encrypted key: " + str(device))
|
print("Encrypted key: " + str(device))
|
||||||
|
|
||||||
# These three must all be bytes.
|
# These three must all be bytes.
|
||||||
#print(type(vendor))
|
#print(type(vendor))
|
||||||
@ -175,16 +191,19 @@ def GetMasterKey():
|
|||||||
|
|
||||||
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
|
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
|
||||||
|
|
||||||
|
if verbose_logging:
|
||||||
print("Entropy: " + str(entropy))
|
print("Entropy: " + str(entropy))
|
||||||
|
|
||||||
keykey = CryptUnprotectData(device, entropy)
|
keykey = CryptUnprotectData(device, entropy)
|
||||||
if (keykey is None):
|
if (keykey is None):
|
||||||
print("Couldn't decrypt key!")
|
print("Couldn't decrypt key!")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
print("Decrypted key: " + str(keykey))
|
if verbose_logging:
|
||||||
|
print("Decrypted key: " + str(keykey))
|
||||||
|
|
||||||
return keykey
|
return keykey
|
||||||
|
|
||||||
GetMasterKey()
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
GetMasterKey()
|
@ -4,6 +4,8 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <wincrypt.h>
|
#include <wincrypt.h>
|
||||||
#include <dpapi.h>
|
#include <dpapi.h>
|
||||||
|
#include <fileapi.h>
|
||||||
|
#include <direct.h>
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
#undef DEBUG
|
#undef DEBUG
|
||||||
@ -45,7 +47,7 @@ void hexDump (
|
|||||||
|
|
||||||
// Output description if given.
|
// Output description if given.
|
||||||
|
|
||||||
if (desc != NULL) printf ("%s:\n", desc);
|
if (desc != NULL) fprintf (stderr, "%s:\n", desc);
|
||||||
|
|
||||||
// Length checks.
|
// Length checks.
|
||||||
|
|
||||||
@ -66,7 +68,7 @@ void hexDump (
|
|||||||
if ((i % perLine) == 0) {
|
if ((i % perLine) == 0) {
|
||||||
// Only print previous-line ASCII buffer for lines beyond first.
|
// Only print previous-line ASCII buffer for lines beyond first.
|
||||||
|
|
||||||
if (i != 0) printf (" %s\n", buff);
|
if (i != 0) fprintf (stderr, " %s\n", buff);
|
||||||
|
|
||||||
// Output the offset of current line.
|
// Output the offset of current line.
|
||||||
|
|
||||||
@ -99,6 +101,15 @@ void hexDump (
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int get_serial() {
|
||||||
|
DWORD serial = 0;
|
||||||
|
int retval = GetVolumeInformation("c:\\\\", NULL, 0, &serial, NULL, NULL, NULL, 0);
|
||||||
|
if (retval == 0) {
|
||||||
|
fprintf(stderr, "Error with GetVolumeInformation: %d\n", GetLastError());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return serial;
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
char * var_data = "X_DECRYPT_DATA";
|
char * var_data = "X_DECRYPT_DATA";
|
||||||
@ -164,7 +175,16 @@ if (ret) {
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
printf("PROGOUTPUT:-4:%d", GetLastError());
|
|
||||||
|
// Apparently Wine has issues with the volume serial code sometimes
|
||||||
|
// so the code on the Linux side detects the wrong serial number.
|
||||||
|
// Thus, if the decryption fails, we read the serial number that Wine
|
||||||
|
// (and ADE) sees back to the Linux side for another attempt.
|
||||||
|
|
||||||
|
int err = GetLastError();
|
||||||
|
|
||||||
|
printf("PROGOUTPUT:-4:%d:%08x", err, get_serial());
|
||||||
|
|
||||||
exit(-4);
|
exit(-4);
|
||||||
}
|
}
|
||||||
|
|
@ -437,6 +437,14 @@ def activateDevice(useVersionIndex: int = 0):
|
|||||||
# ADE 1.7.2 or another version that authorization is disabled for
|
# ADE 1.7.2 or another version that authorization is disabled for
|
||||||
return False, "Authorization not supported for this build ID"
|
return False, "Authorization not supported for this build ID"
|
||||||
|
|
||||||
|
verbose_logging = False
|
||||||
|
try:
|
||||||
|
import calibre_plugins.deacsm.prefs as prefs
|
||||||
|
deacsmprefs = prefs.DeACSM_Prefs()
|
||||||
|
verbose_logging = deacsmprefs["detailed_logging"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
result, activate_req = buildActivateReq(useVersionIndex)
|
result, activate_req = buildActivateReq(useVersionIndex)
|
||||||
if (result is False):
|
if (result is False):
|
||||||
@ -455,8 +463,9 @@ def activateDevice(useVersionIndex: int = 0):
|
|||||||
|
|
||||||
etree.SubElement(req_xml, etree.QName(NSMAP["adept"], "signature")).text = signature
|
etree.SubElement(req_xml, etree.QName(NSMAP["adept"], "signature")).text = signature
|
||||||
|
|
||||||
#print ("final request:")
|
if verbose_logging:
|
||||||
#print(etree.tostring(req_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1"))
|
print ("Activation request:")
|
||||||
|
print(etree.tostring(req_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1"))
|
||||||
|
|
||||||
data = "<?xml version=\"1.0\"?>\n" + etree.tostring(req_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1")
|
data = "<?xml version=\"1.0\"?>\n" + etree.tostring(req_xml, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("latin-1")
|
||||||
|
|
||||||
@ -487,8 +496,9 @@ def activateDevice(useVersionIndex: int = 0):
|
|||||||
except:
|
except:
|
||||||
return False, "Error parsing Adobe /Activate response"
|
return False, "Error parsing Adobe /Activate response"
|
||||||
|
|
||||||
#print("Response from server: ")
|
if verbose_logging:
|
||||||
#print(ret)
|
print("Response from server: ")
|
||||||
|
print(ret)
|
||||||
|
|
||||||
# Soooo, lets go and append that to the XML:
|
# Soooo, lets go and append that to the XML:
|
||||||
|
|
||||||
|
@ -1,225 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Most of the code in this file has been taken from adobekey.pyw written by i♥cabbages
|
|
||||||
# adobekey.pyw, version 7.0
|
|
||||||
# Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al.
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
|
||||||
# <http://www.gnu.org/licenses/>
|
|
||||||
|
|
||||||
|
|
||||||
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
|
||||||
create_unicode_buffer, create_string_buffer, CFUNCTYPE, \
|
|
||||||
string_at, Structure, c_void_p, cast, c_size_t, memmove
|
|
||||||
|
|
||||||
from ctypes.wintypes import LPVOID, DWORD, BOOL
|
|
||||||
import struct
|
|
||||||
|
|
||||||
try:
|
|
||||||
import winreg
|
|
||||||
except ImportError:
|
|
||||||
import _winreg as winreg
|
|
||||||
|
|
||||||
|
|
||||||
MAX_PATH = 255
|
|
||||||
|
|
||||||
kernel32 = windll.kernel32
|
|
||||||
advapi32 = windll.advapi32
|
|
||||||
crypt32 = windll.crypt32
|
|
||||||
|
|
||||||
def GetSystemDirectory():
|
|
||||||
GetSystemDirectoryW = kernel32.GetSystemDirectoryW
|
|
||||||
GetSystemDirectoryW.argtypes = [c_wchar_p, c_uint]
|
|
||||||
GetSystemDirectoryW.restype = c_uint
|
|
||||||
def GetSystemDirectory():
|
|
||||||
buffer = create_unicode_buffer(MAX_PATH + 1)
|
|
||||||
GetSystemDirectoryW(buffer, len(buffer))
|
|
||||||
return buffer.value
|
|
||||||
return GetSystemDirectory
|
|
||||||
GetSystemDirectory = GetSystemDirectory()
|
|
||||||
|
|
||||||
def GetVolumeSerialNumber():
|
|
||||||
GetVolumeInformationW = kernel32.GetVolumeInformationW
|
|
||||||
GetVolumeInformationW.argtypes = [c_wchar_p, c_wchar_p, c_uint,
|
|
||||||
POINTER(c_uint), POINTER(c_uint),
|
|
||||||
POINTER(c_uint), c_wchar_p, c_uint]
|
|
||||||
GetVolumeInformationW.restype = c_uint
|
|
||||||
def GetVolumeSerialNumber(path):
|
|
||||||
vsn = c_uint(0)
|
|
||||||
GetVolumeInformationW(
|
|
||||||
path, None, 0, byref(vsn), None, None, None, 0)
|
|
||||||
return vsn.value
|
|
||||||
return GetVolumeSerialNumber
|
|
||||||
GetVolumeSerialNumber = GetVolumeSerialNumber()
|
|
||||||
|
|
||||||
def GetUserName():
|
|
||||||
GetUserNameW = advapi32.GetUserNameW
|
|
||||||
GetUserNameW.argtypes = [c_wchar_p, POINTER(c_uint)]
|
|
||||||
GetUserNameW.restype = c_uint
|
|
||||||
def GetUserName():
|
|
||||||
buffer = create_unicode_buffer(32)
|
|
||||||
size = c_uint(len(buffer))
|
|
||||||
while not GetUserNameW(buffer, byref(size)):
|
|
||||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
|
||||||
size.value = len(buffer)
|
|
||||||
return buffer.value.encode('utf-16-le')[::2]
|
|
||||||
return GetUserName
|
|
||||||
GetUserName = GetUserName()
|
|
||||||
|
|
||||||
PAGE_EXECUTE_READWRITE = 0x40
|
|
||||||
MEM_COMMIT = 0x1000
|
|
||||||
MEM_RESERVE = 0x2000
|
|
||||||
|
|
||||||
def VirtualAlloc():
|
|
||||||
_VirtualAlloc = kernel32.VirtualAlloc
|
|
||||||
_VirtualAlloc.argtypes = [LPVOID, c_size_t, DWORD, DWORD]
|
|
||||||
_VirtualAlloc.restype = LPVOID
|
|
||||||
def VirtualAlloc(addr, size, alloctype=(MEM_COMMIT | MEM_RESERVE),
|
|
||||||
protect=PAGE_EXECUTE_READWRITE):
|
|
||||||
return _VirtualAlloc(addr, size, alloctype, protect)
|
|
||||||
return VirtualAlloc
|
|
||||||
VirtualAlloc = VirtualAlloc()
|
|
||||||
|
|
||||||
MEM_RELEASE = 0x8000
|
|
||||||
|
|
||||||
def VirtualFree():
|
|
||||||
_VirtualFree = kernel32.VirtualFree
|
|
||||||
_VirtualFree.argtypes = [LPVOID, c_size_t, DWORD]
|
|
||||||
_VirtualFree.restype = BOOL
|
|
||||||
def VirtualFree(addr, size=0, freetype=MEM_RELEASE):
|
|
||||||
return _VirtualFree(addr, size, freetype)
|
|
||||||
return VirtualFree
|
|
||||||
VirtualFree = VirtualFree()
|
|
||||||
|
|
||||||
class NativeFunction(object):
|
|
||||||
def __init__(self, restype, argtypes, insns):
|
|
||||||
self._buf = buf = VirtualAlloc(None, len(insns))
|
|
||||||
memmove(buf, insns, len(insns))
|
|
||||||
ftype = CFUNCTYPE(restype, *argtypes)
|
|
||||||
self._native = ftype(buf)
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
return self._native(*args)
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self._buf is not None:
|
|
||||||
VirtualFree(self._buf)
|
|
||||||
self._buf = None
|
|
||||||
|
|
||||||
if struct.calcsize("P") == 4:
|
|
||||||
CPUID0_INSNS = (
|
|
||||||
b"\x53" # push %ebx
|
|
||||||
b"\x31\xc0" # xor %eax,%eax
|
|
||||||
b"\x0f\xa2" # cpuid
|
|
||||||
b"\x8b\x44\x24\x08" # mov 0x8(%esp),%eax
|
|
||||||
b"\x89\x18" # mov %ebx,0x0(%eax)
|
|
||||||
b"\x89\x50\x04" # mov %edx,0x4(%eax)
|
|
||||||
b"\x89\x48\x08" # mov %ecx,0x8(%eax)
|
|
||||||
b"\x5b" # pop %ebx
|
|
||||||
b"\xc3" # ret
|
|
||||||
)
|
|
||||||
CPUID1_INSNS = (
|
|
||||||
b"\x53" # push %ebx
|
|
||||||
b"\x31\xc0" # xor %eax,%eax
|
|
||||||
b"\x40" # inc %eax
|
|
||||||
b"\x0f\xa2" # cpuid
|
|
||||||
b"\x5b" # pop %ebx
|
|
||||||
b"\xc3" # ret
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
CPUID0_INSNS = (
|
|
||||||
b"\x49\x89\xd8" # mov %rbx,%r8
|
|
||||||
b"\x49\x89\xc9" # mov %rcx,%r9
|
|
||||||
b"\x48\x31\xc0" # xor %rax,%rax
|
|
||||||
b"\x0f\xa2" # cpuid
|
|
||||||
b"\x4c\x89\xc8" # mov %r9,%rax
|
|
||||||
b"\x89\x18" # mov %ebx,0x0(%rax)
|
|
||||||
b"\x89\x50\x04" # mov %edx,0x4(%rax)
|
|
||||||
b"\x89\x48\x08" # mov %ecx,0x8(%rax)
|
|
||||||
b"\x4c\x89\xc3" # mov %r8,%rbx
|
|
||||||
b"\xc3" # retq
|
|
||||||
)
|
|
||||||
CPUID1_INSNS = (
|
|
||||||
b"\x53" # push %rbx
|
|
||||||
b"\x48\x31\xc0" # xor %rax,%rax
|
|
||||||
b"\x48\xff\xc0" # inc %rax
|
|
||||||
b"\x0f\xa2" # cpuid
|
|
||||||
b"\x5b" # pop %rbx
|
|
||||||
b"\xc3" # retq
|
|
||||||
)
|
|
||||||
|
|
||||||
def cpuid0():
|
|
||||||
_cpuid0 = NativeFunction(None, [c_char_p], CPUID0_INSNS)
|
|
||||||
buf = create_string_buffer(12)
|
|
||||||
def cpuid0():
|
|
||||||
_cpuid0(buf)
|
|
||||||
return buf.raw
|
|
||||||
return cpuid0
|
|
||||||
cpuid0 = cpuid0()
|
|
||||||
|
|
||||||
cpuid1 = NativeFunction(c_uint, [], CPUID1_INSNS)
|
|
||||||
|
|
||||||
class DataBlob(Structure):
|
|
||||||
_fields_ = [('cbData', c_uint),
|
|
||||||
('pbData', c_void_p)]
|
|
||||||
DataBlob_p = POINTER(DataBlob)
|
|
||||||
|
|
||||||
def CryptUnprotectData():
|
|
||||||
_CryptUnprotectData = crypt32.CryptUnprotectData
|
|
||||||
_CryptUnprotectData.argtypes = [DataBlob_p, c_wchar_p, DataBlob_p,
|
|
||||||
c_void_p, c_void_p, c_uint, DataBlob_p]
|
|
||||||
_CryptUnprotectData.restype = c_uint
|
|
||||||
def CryptUnprotectData(indata, entropy):
|
|
||||||
indatab = create_string_buffer(indata)
|
|
||||||
indata = DataBlob(len(indata), cast(indatab, c_void_p))
|
|
||||||
entropyb = create_string_buffer(entropy)
|
|
||||||
entropy = DataBlob(len(entropy), cast(entropyb, c_void_p))
|
|
||||||
outdata = DataBlob()
|
|
||||||
if not _CryptUnprotectData(byref(indata), None, byref(entropy),
|
|
||||||
None, None, 0, byref(outdata)):
|
|
||||||
raise Exception("Failed to decrypt user key key (sic)")
|
|
||||||
return string_at(outdata.pbData, outdata.cbData)
|
|
||||||
return CryptUnprotectData
|
|
||||||
CryptUnprotectData = CryptUnprotectData()
|
|
||||||
|
|
||||||
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
|
||||||
|
|
||||||
def GetMasterKey():
|
|
||||||
root = GetSystemDirectory().split('\\')[0] + '\\'
|
|
||||||
serial = GetVolumeSerialNumber(root)
|
|
||||||
vendor = cpuid0()
|
|
||||||
signature = struct.pack('>I', cpuid1())[1:]
|
|
||||||
try:
|
|
||||||
regkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, DEVICE_KEY_PATH)
|
|
||||||
device = winreg.QueryValueEx(regkey, 'key')[0]
|
|
||||||
|
|
||||||
# ADE puts an "username" attribute into that key which was unused
|
|
||||||
# in previous versions of this script. This means that this key
|
|
||||||
# retrieval script would break / not work if the user had ever
|
|
||||||
# changed their Windows account user name after installing ADE.
|
|
||||||
# By reading the "username" registry entry if available we won't
|
|
||||||
# have that problem anymore.
|
|
||||||
|
|
||||||
try:
|
|
||||||
user = winreg.QueryValueEx(regkey, 'username')[0].encode('utf-16-le')[::2]
|
|
||||||
# Yes, this actually only uses the lowest byte of each character.
|
|
||||||
except:
|
|
||||||
# This value should always be available, but just in case
|
|
||||||
# it's not, use the old implementation.
|
|
||||||
user = GetUserName()
|
|
||||||
|
|
||||||
except WindowsError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
|
|
||||||
try:
|
|
||||||
keykey = CryptUnprotectData(device, entropy)
|
|
||||||
except Exception:
|
|
||||||
# There was an exception, so this thing was unable to decrypt
|
|
||||||
# the key. Maybe this is due to the new user name handling, so
|
|
||||||
# let's retry with the old code.
|
|
||||||
user = GetUserName()
|
|
||||||
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
|
|
||||||
keykey = CryptUnprotectData(device, entropy)
|
|
||||||
|
|
||||||
return keykey
|
|
@ -26,7 +26,7 @@ def buildFulfillRequest(acsm):
|
|||||||
fingerprint = None
|
fingerprint = None
|
||||||
device_type = None
|
device_type = None
|
||||||
fingerprint = activationxml.find("./%s/%s" % (adNS("activationToken"), adNS("fingerprint"))).text
|
fingerprint = activationxml.find("./%s/%s" % (adNS("activationToken"), adNS("fingerprint"))).text
|
||||||
fingerprint = activationxml.find("./%s/%s" % (adNS("activationToken"), adNS("deviceType"))).text
|
device_type = activationxml.find("./%s/%s" % (adNS("activationToken"), adNS("deviceType"))).text
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -286,6 +286,15 @@ def buildRights(license_token_node):
|
|||||||
|
|
||||||
|
|
||||||
def fulfill(acsm_file, do_notify = False):
|
def fulfill(acsm_file, do_notify = False):
|
||||||
|
|
||||||
|
verbose_logging = False
|
||||||
|
try:
|
||||||
|
import calibre_plugins.deacsm.prefs as prefs
|
||||||
|
deacsmprefs = prefs.DeACSM_Prefs()
|
||||||
|
verbose_logging = deacsmprefs["detailed_logging"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# Get pkcs12:
|
# Get pkcs12:
|
||||||
|
|
||||||
pkcs12 = None
|
pkcs12 = None
|
||||||
@ -325,7 +334,9 @@ def fulfill(acsm_file, do_notify = False):
|
|||||||
|
|
||||||
fulfill_request, adept_ns = buildFulfillRequest(acsmxml)
|
fulfill_request, adept_ns = buildFulfillRequest(acsmxml)
|
||||||
|
|
||||||
#print(fulfill_request)
|
if verbose_logging:
|
||||||
|
print("Fulfill request:")
|
||||||
|
print(fulfill_request)
|
||||||
|
|
||||||
fulfill_request_xml = etree.fromstring(fulfill_request)
|
fulfill_request_xml = etree.fromstring(fulfill_request)
|
||||||
# Sign the request:
|
# Sign the request:
|
||||||
@ -388,8 +399,9 @@ def fulfill(acsm_file, do_notify = False):
|
|||||||
else:
|
else:
|
||||||
return False, "Looks like there's been an error during Fulfillment: %s" % replyData
|
return False, "Looks like there's been an error during Fulfillment: %s" % replyData
|
||||||
|
|
||||||
# Print fulfillmentResult
|
if verbose_logging:
|
||||||
#print(replyData)
|
print("fulfillmentResult:")
|
||||||
|
print(replyData)
|
||||||
|
|
||||||
adobe_fulfill_response = etree.fromstring(replyData)
|
adobe_fulfill_response = etree.fromstring(replyData)
|
||||||
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
|
NSMAP = { "adept" : "http://ns.adobe.com/adept" }
|
||||||
|
@ -19,6 +19,88 @@ except:
|
|||||||
from calibre_plugins.deacsm.libadobe import VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS, VAR_VER_DEFAULT_BUILD_ID, VAR_VER_BUILD_IDS
|
from calibre_plugins.deacsm.libadobe import VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS, VAR_VER_DEFAULT_BUILD_ID, VAR_VER_BUILD_IDS
|
||||||
|
|
||||||
|
|
||||||
|
def importADEactivationLinuxWine(wine_prefix_path, buildIDtoEmulate=VAR_VER_DEFAULT_BUILD_ID):
|
||||||
|
# Similar to importADEactivationWindows - extracts the activation data from a Wine prefix
|
||||||
|
try:
|
||||||
|
from calibre.constants import islinux
|
||||||
|
if not islinux:
|
||||||
|
print("This function is for Linux only!")
|
||||||
|
return False, "Linux only!"
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Get encryption key
|
||||||
|
try:
|
||||||
|
from getEncryptionKeyLinux import GetMasterKey
|
||||||
|
except:
|
||||||
|
from calibre_plugins.deacsm.getEncryptionKeyLinux import GetMasterKey
|
||||||
|
|
||||||
|
master_key = GetMasterKey(wine_prefix_path)
|
||||||
|
|
||||||
|
if master_key is None:
|
||||||
|
err = "Could not access ADE encryption key. If you have just installed ADE in Wine, "
|
||||||
|
err += "please reboot your machine then try again. Also, make sure neither ADE nor any other "
|
||||||
|
err += "software is running in WINE while you're trying to import the authorization. "
|
||||||
|
err += "If it still doesn't work but ADE in that particular WINEPREFIX is working fine, "
|
||||||
|
err += "please open a bug report."
|
||||||
|
|
||||||
|
return False, err
|
||||||
|
|
||||||
|
# Loop through the registry:
|
||||||
|
try:
|
||||||
|
registry_file = open(os.path.join(wine_prefix_path, "user.reg"), "r")
|
||||||
|
waiting_for_element = False
|
||||||
|
current_parent = None
|
||||||
|
current_name = None
|
||||||
|
current_value = None
|
||||||
|
current_method = None
|
||||||
|
while True:
|
||||||
|
line = registry_file.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
if waiting_for_element:
|
||||||
|
if (line.lower().startswith("@=")):
|
||||||
|
current_name = line.split('=', 1)[1].strip()[1:-1]
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (line.lower().startswith('"value"=')):
|
||||||
|
current_value = line.split('=', 1)[1].strip()[1:-1]
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (line.lower().startswith('"method"=')):
|
||||||
|
current_method = line.split('=', 1)[1].strip()[1:-1]
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (len(line) == 0):
|
||||||
|
# Empty line - finalize this element
|
||||||
|
if current_value is None:
|
||||||
|
current_parent = current_name
|
||||||
|
current_name = None
|
||||||
|
current_method = None
|
||||||
|
current_value = None
|
||||||
|
waiting_for_element = False
|
||||||
|
continue
|
||||||
|
handle_subkey(current_parent, current_name, current_value, master_key, current_method, None)
|
||||||
|
current_name = None
|
||||||
|
current_value = None
|
||||||
|
current_method = None
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
if (line.startswith("[Software\\\\Adobe\\\\Adept\\\\Activation\\\\")):
|
||||||
|
waiting_for_element = True
|
||||||
|
|
||||||
|
|
||||||
|
registry_file.close()
|
||||||
|
return handle_subkey(None, None, None, master_key, None, buildIDtoEmulate)
|
||||||
|
|
||||||
|
except:
|
||||||
|
# There was an error hunting through the registry.
|
||||||
|
raise
|
||||||
|
pass
|
||||||
|
|
||||||
def importADEactivationWindows(buildIDtoEmulate=VAR_VER_DEFAULT_BUILD_ID):
|
def importADEactivationWindows(buildIDtoEmulate=VAR_VER_DEFAULT_BUILD_ID):
|
||||||
# Tries to import the system activation from Adobe Digital Editions on Windows into the plugin
|
# Tries to import the system activation from Adobe Digital Editions on Windows into the plugin
|
||||||
# This can be used to "clone" the ADE activation so you don't need to waste an additional activation.
|
# This can be used to "clone" the ADE activation so you don't need to waste an additional activation.
|
||||||
@ -35,14 +117,14 @@ def importADEactivationWindows(buildIDtoEmulate=VAR_VER_DEFAULT_BUILD_ID):
|
|||||||
|
|
||||||
# Get encryption key:
|
# Get encryption key:
|
||||||
try:
|
try:
|
||||||
from libadobeEncryptionWindows import GetMasterKey
|
from getEncryptionKeyWindows import GetMasterKey
|
||||||
except:
|
except:
|
||||||
from calibre_plugins.deacsm.libadobeEncryptionWindows import GetMasterKey
|
from calibre_plugins.deacsm.getEncryptionKeyWindows import GetMasterKey
|
||||||
|
|
||||||
master_key = GetMasterKey()
|
master_key = GetMasterKey()
|
||||||
|
|
||||||
if master_key is None:
|
if master_key is None:
|
||||||
return False, "master_key is None ..."
|
return False, "Could not access ADE encryption key"
|
||||||
|
|
||||||
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
||||||
|
|
||||||
@ -185,7 +267,7 @@ def handle_subkey(parent, subkey, value, encryption_key, method, buildID):
|
|||||||
# The first 16 bytes of the fingerprint are used as IV for the privateLicenseKey
|
# The first 16 bytes of the fingerprint are used as IV for the privateLicenseKey
|
||||||
# Older versions of this decryption code, like in the DeDRM plugin, didn't
|
# Older versions of this decryption code, like in the DeDRM plugin, didn't
|
||||||
# do that correctly. For DeDRM that doesn't matter as a wrong IV only causes
|
# do that correctly. For DeDRM that doesn't matter as a wrong IV only causes
|
||||||
# the first 16 bytes to be corrupted, and these aren't used for decryption anyways.
|
# the first 16 bytes to be corrupted, and these aren't used for eBook decryption anyways.
|
||||||
# For this plugin I want the exact correct data, so lets use the fingerprint as IV.
|
# For this plugin I want the exact correct data, so lets use the fingerprint as IV.
|
||||||
# See jhowell's post: https://www.mobileread.com/forums/showpost.php?p=4173908
|
# See jhowell's post: https://www.mobileread.com/forums/showpost.php?p=4173908
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ class DeACSM_Prefs():
|
|||||||
self.deacsmprefs.defaults['configured'] = False
|
self.deacsmprefs.defaults['configured'] = False
|
||||||
|
|
||||||
self.deacsmprefs.defaults['notify_fulfillment'] = True
|
self.deacsmprefs.defaults['notify_fulfillment'] = True
|
||||||
|
self.deacsmprefs.defaults['detailed_logging'] = False
|
||||||
|
|
||||||
self.deacsmprefs.defaults['list_of_rented_books'] = []
|
self.deacsmprefs.defaults['list_of_rented_books'] = []
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user