Skip to content

Commit

Permalink
tools v1.0
Browse files Browse the repository at this point in the history
(With some additions)
Lots of authors, brought together by Apprentice Alf.
  • Loading branch information
apprenticealf committed Feb 28, 2015
1 parent 71d6695 commit 93c2ccd
Show file tree
Hide file tree
Showing 26 changed files with 1,923 additions and 129 deletions.
94 changes: 53 additions & 41 deletions Kindle_Mobi_Tools/lib/mobidedrm.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
# 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 - use autoflushed stdout and proper return values
# 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

class Unbuffered:
def __init__(self, stream):
Expand Down Expand Up @@ -143,6 +146,17 @@ def parseDRM(self, data, count, pid):
if verification == ver and cksum == temp_key_sum and (flags & 0x1F) == 1:
found_key = finalkey
break
if not found_key:
# Then try the default encoding that doesn't require a PID
temp_key = keyvec1
temp_key_sum = sum(map(ord,temp_key)) & 0xff
for i in xrange(count):
verification, size, type, cksum, cookie = struct.unpack('>LLLBxxx32s', data[i*0x30:i*0x30+0x30])
cookie = PC1(temp_key, cookie)
ver,flags,finalkey,expiry,expiry2 = struct.unpack('>LL16sLL', cookie)
if verification == ver and cksum == temp_key_sum:
found_key = finalkey
break
return found_key


Expand Down Expand Up @@ -178,34 +192,35 @@ def __init__(self, data_file, pid):

crypto_type, = struct.unpack('>H', sect[0xC:0xC+2])
if crypto_type == 0:
raise DrmException("it seems that this book isn't encrypted")
if crypto_type == 1:
raise DrmException("cannot decode Mobipocket encryption type 1")
if crypto_type != 2:
raise DrmException("unknown encryption type: %d" % crypto_type)

# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("no PIDs found in this file")
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
if not found_key:
raise DrmException("no key found. maybe the PID is incorrect")

# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC)

# decrypt sections
print "Decrypting. Please wait...",
for i in xrange(1, records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
# print "record %d, extra_size %d" %(i,extra_size)
self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
print "This book is not encrypted."
else:
if crypto_type == 1:
raise DrmException("cannot decode Mobipocket encryption type 1")
if crypto_type != 2:
raise DrmException("unknown encryption type: %d" % crypto_type)

# calculate the keys
drm_ptr, drm_count, drm_size, drm_flags = struct.unpack('>LLLL', sect[0xA8:0xA8+16])
if drm_count == 0:
raise DrmException("no PIDs found in this file")
found_key = self.parseDRM(sect[drm_ptr:drm_ptr+drm_size], drm_count, pid)
if not found_key:
raise DrmException("no key found. maybe the PID is incorrect")

# kill the drm keys
self.patchSection(0, "\0" * drm_size, drm_ptr)
# kill the drm pointers
self.patchSection(0, "\xff" * 4 + "\0" * 12, 0xA8)
# clear the crypto type
self.patchSection(0, "\0" * 2, 0xC)

# decrypt sections
print "Decrypting. Please wait...",
for i in xrange(1, records+1):
data = self.loadSection(i)
extra_size = getSizeOfTrailingDataEntries(data, len(data), extra_data_flags)
# print "record %d, extra_size %d" %(i,extra_size)
self.patchSection(i, PC1(found_key, data[0:len(data) - extra_size]))
print "done"
def getResult(self):
return self.data_file
Expand All @@ -219,7 +234,7 @@ class MobiDeDRM(FileTypePlugin):
description = 'Removes DRM from secure Mobi files'
supported_platforms = ['linux', 'osx', 'windows'] # Platforms this plugin will run on
author = 'The Dark Reverser' # The author of this plugin
version = (0, 0, 10) # The version number of this plugin
version = (0, 1, 0) # The version number of this plugin
file_types = set(['prc','mobi','azw']) # The file types that this plugin will be applied to
on_import = True # Run this plugin during the import

Expand All @@ -245,25 +260,22 @@ def run(self, path_to_ebook):
def customization_help(self, gui=False):
return 'Enter PID (separate multiple PIDs with comma)'

def main(argv=sys.argv):
print "MobiDeDrm v0.10. Copyright (c) 2008 The Dark Reverser"
if __name__ == "__main__":
print "MobiDeDrm v0.11. Copyright (c) 2008 The Dark Reverser"
if len(sys.argv)<4:
print "Removes protection from Mobipocket books"
print "Usage:"
print " mobidedrm infile.mobi outfile.mobi PID"
return 1
print " mobidedrm infile.mobi outfile.mobi (PID)"
sys.exit(1)
else:
infile = sys.argv[1]
outfile = sys.argv[2]
pid = sys.argv[3]
data_file = file(infile, 'rb').read()
try:
file(outfile, 'wb').write(DrmStripper(data_file, pid).getResult())
strippedFile = DrmStripper(data_file, pid)
file(outfile, 'wb').write(strippedFile.getResult())
except DrmException, e:
print "Error: %s" % e
return 1
return 0

if __name__ == "__main__":
sys.exit(main())

sys.exit(1)
sys.exit(0)
193 changes: 193 additions & 0 deletions Topaz_Tools/TopazExtract_Kindle4PC.pyw
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab

import sys
sys.path.append('lib')

import os, os.path, urllib
import subprocess
from subprocess import Popen, PIPE, STDOUT
import Tkinter
import Tkconstants
import tkFileDialog
import tkMessageBox
import subasyncio
from subasyncio import Process
from scrolltextwidget import ScrolledText

class MainDialog(Tkinter.Frame):
def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5)
self.root = root
self.interval = 2000
self.p2 = None
self.status = Tkinter.Label(self, text='Extract Contents of Topaz eBook to a Directory')
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='Topaz eBook input file').grid(row=0, sticky=Tkconstants.E)
self.tpzpath = Tkinter.Entry(body, width=50)
self.tpzpath.grid(row=0, column=1, sticky=sticky)
self.tpzpath.insert(0, os.getcwd())
button = Tkinter.Button(body, text="...", command=self.get_tpzpath)
button.grid(row=0, column=2)

Tkinter.Label(body, text='Output Directory').grid(row=1, sticky=Tkconstants.E)
self.outpath = Tkinter.Entry(body, width=50)
self.outpath.grid(row=1, column=1, sticky=sticky)
self.outpath.insert(0, os.getcwd())
button = Tkinter.Button(body, text="...", command=self.get_outpath)
button.grid(row=1, column=2)

Tkinter.Label(body, text='First 8 char of PID (optional)').grid(row=3, sticky=Tkconstants.E)
self.pidnum = Tkinter.StringVar()
self.ccinfo = Tkinter.Entry(body, width=10, textvariable=self.pidnum)
self.ccinfo.grid(row=3, column=1, sticky=sticky)

msg1 = 'Conversion Log \n\n'
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=15, width=60, wrap=Tkconstants.WORD)
self.stext.grid(row=4, column=0, columnspan=2,sticky=sticky)
self.stext.insert(Tkconstants.END,msg1)

buttons = Tkinter.Frame(self)
buttons.pack()
self.sbotton = Tkinter.Button(
buttons, text="Start", width=10, command=self.convertit)
self.sbotton.pack(side=Tkconstants.LEFT)

Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
self.qbutton = Tkinter.Button(
buttons, text="Quit", width=10, command=self.quitting)
self.qbutton.pack(side=Tkconstants.RIGHT)

# read from subprocess pipe without blocking
# invoked every interval via the widget "after"
# option being used, so need to reset it for the next time
def processPipe(self):
poll = self.p2.wait('nowait')
if poll != None:
text = self.p2.readerr()
text += self.p2.read()
msg = text + '\n\n' + 'Files successfully extracted\n'
if poll != 0:
msg = text + '\n\n' + 'Error: File Extraction Failed\n'
self.showCmdOutput(msg)
self.p2 = None
self.sbotton.configure(state='normal')
return
text = self.p2.readerr()
text += self.p2.read()
self.showCmdOutput(text)
# make sure we get invoked again by event loop after interval
self.stext.after(self.interval,self.processPipe)
return

# post output from subprocess in scrolled text widget
def showCmdOutput(self, msg):
if msg and msg !='':
self.stext.insert(Tkconstants.END,msg)
self.stext.yview_pickplace(Tkconstants.END)
return

# run as a subprocess via pipes and collect stdout
def topazrdr(self, infile, outdir, pidnum):
# os.putenv('PYTHONUNBUFFERED', '1')
pidoption = ''
if pidnum and pidnum != '':
pidoption = ' -p "' + pidnum + '" '
outoption = ' -o "' + outdir + '" '
cmdline = 'python ./lib/cmbtc_dump.py -v -d ' + pidoption + outoption + '"' + infile + '"'
if sys.platform[0:3] == 'win':
search_path = os.environ['PATH']
search_path = search_path.lower()
if search_path.find('python') >= 0:
cmdline = 'python lib\cmbtc_dump.py -v -d ' + pidoption + outoption + '"' + infile + '"'
else :
cmdline = 'lib\cmbtc_dump.py -v -d ' + pidoption + outoption + '"' + infile + '"'

p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
return p2


def get_tpzpath(self):
tpzpath = tkFileDialog.askopenfilename(
parent=None, title='Select Topaz File',
defaultextension='.prc', filetypes=[('Topaz azw1', '.azw1'), ('Topaz prc', '.prc'),
('All Files', '.*')])
if tpzpath:
tpzpath = os.path.normpath(tpzpath)
self.tpzpath.delete(0, Tkconstants.END)
self.tpzpath.insert(0, tpzpath)
return

def get_outpath(self):
outpath = tkFileDialog.askdirectory(
parent=None, title='Directory to Extract Files into',
initialdir=os.getcwd(), initialfile=None)
if outpath:
outpath = os.path.normpath(outpath)
self.outpath.delete(0, Tkconstants.END)
self.outpath.insert(0, outpath)
return

def quitting(self):
# kill any still running subprocess
if self.p2 != None:
if (self.p2.wait('nowait') == None):
self.p2.terminate()
self.root.destroy()

# actually ready to run the subprocess and get its output
def convertit(self):
# now disable the button to prevent multiple launches
self.sbotton.configure(state='disabled')
tpzpath = self.tpzpath.get()
outpath = self.outpath.get()
if not tpzpath or not os.path.exists(tpzpath):
self.status['text'] = 'Specified Topaz eBook file does not exist'
self.sbotton.configure(state='normal')
return
if not outpath:
self.status['text'] = 'No output directory specified'
self.sbotton.configure(state='normal')
return
if not os.path.exists(outpath):
os.makedirs(outpath)
pidnum = self.pidnum.get()
# if not pidnum or pidnum == '':
# self.status['text'] = 'You have not entered a PID '
# self.sbotton.configure(state='normal')
# return

log = 'Command = "python cmbtc_dump.py"\n'
log += 'Topaz Path Path = "'+ tpzpath + '"\n'
log += 'Output Directory = "' + outpath + '"\n'
log += 'First 8 chars of PID = "' + pidnum + '"\n'
log += '\n\n'
log += 'Please Wait ...\n'
self.stext.insert(Tkconstants.END,log)
self.p2 = self.topazrdr(tpzpath, outpath, pidnum)

# python does not seem to allow you to create
# your own eventloop which every other gui does - strange
# so need to use the widget "after" command to force
# event loop to run non-gui events every interval
self.stext.after(self.interval,self.processPipe)
return


def main(argv=None):
root = Tkinter.Tk()
root.title('Topaz eBook File Extraction')
root.resizable(True, False)
root.minsize(300, 0)
MainDialog(root).pack(fill=Tkconstants.X, expand=1)
root.mainloop()
return 0


if __name__ == "__main__":
sys.exit(main())
Loading

0 comments on commit 93c2ccd

Please sign in to comment.