mirror of
				https://github.com/noDRM/DeDRM_tools.git
				synced 2025-10-23 23:07:47 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			287 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			287 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/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 subasyncio
 | |
| from subasyncio import Process
 | |
| import Tkinter
 | |
| import Tkconstants
 | |
| import tkFileDialog
 | |
| import tkMessageBox
 | |
| from scrolltextwidget import ScrolledText
 | |
| import binascii
 | |
| import hashlib
 | |
| 
 | |
| 
 | |
| #
 | |
| # Returns the SHA1 digest of "message"
 | |
| #
 | |
| def SHA1(message):
 | |
|     ctx = hashlib.sha1()
 | |
|     ctx.update(message)
 | |
|     return ctx.hexdigest()
 | |
| 
 | |
| 
 | |
| 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='Remove Encryption from Kindle for Mac Mobi eBook')
 | |
|         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='Locate your Kindle Applications').grid(row=0, sticky=Tkconstants.E)
 | |
|         self.k4mpath = Tkinter.Entry(body, width=50)
 | |
|         self.k4mpath.grid(row=0, column=1, sticky=sticky)
 | |
|         self.appname = '/Applications/Kindle for Mac.app'
 | |
|         if not os.path.exists(self.appname):
 | |
|             self.appname = '/Applications/Kindle.app'
 | |
|         cwd = self.appname
 | |
|         cwd = cwd.encode('utf-8')
 | |
|         self.k4mpath.insert(0, cwd)
 | |
|         button = Tkinter.Button(body, text="...", command=self.get_k4mpath)
 | |
|         button.grid(row=0, column=2)
 | |
| 
 | |
|         Tkinter.Label(body, text='Directory for Unencrypted Output File').grid(row=1, sticky=Tkconstants.E)
 | |
|         self.outpath = Tkinter.Entry(body, width=50)
 | |
|         self.outpath.grid(row=1, column=1, sticky=sticky)
 | |
|         desktoppath = os.getenv('HOME') + '/Desktop/'
 | |
|         desktoppath = desktoppath.encode('utf-8')
 | |
|         self.outpath.insert(0, desktoppath)
 | |
|         button = Tkinter.Button(body, text="...", command=self.get_outpath)
 | |
|         button.grid(row=1, column=2)
 | |
| 
 | |
|         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=3, 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' + 'Encryption successfully removed\n'
 | |
|             if poll != 0:
 | |
|                 msg = text + '\n\n' + 'Error: Encryption Removal 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 !='':
 | |
|             msg = msg.encode('utf-8')
 | |
|             self.stext.insert(Tkconstants.END,msg)
 | |
|             self.stext.yview_pickplace(Tkconstants.END)
 | |
|         return
 | |
| 
 | |
|     # run as a subprocess via pipes and collect stdout
 | |
|     def mobirdr(self, infile, outfile, pidnum):
 | |
|         cmdline = 'python ./lib/mobidedrm.py "' + infile + '" "' + outfile + '" "' + pidnum + '"'
 | |
|         cmdline = cmdline.encode(sys.getfilesystemencoding())
 | |
|         p2 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
 | |
|         return p2
 | |
| 
 | |
|     def get_k4mpath(self):
 | |
|         k4mpath = tkFileDialog.askopenfilename(
 | |
|             parent=None, title='Select Your Kindle Application',
 | |
|             defaultextension='.app', filetypes=[('Kindle for Mac Application', '.app')])
 | |
| 
 | |
|         if k4mpath:
 | |
|             k4mpath = os.path.normpath(k4mpath)
 | |
|             self.k4mpath.delete(0, Tkconstants.END)
 | |
|             self.k4mpath.insert(0, k4mpath)
 | |
|         return
 | |
| 
 | |
|     def get_outpath(self):
 | |
|         cwd = os.getcwdu()
 | |
|         cwd = cwd.encode('utf-8')
 | |
|         outpath = tkFileDialog.askdirectory(
 | |
|             parent=None, title='Directory to Put Non-DRM eBook into',
 | |
|             initialdir=cwd, 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()
 | |
| 
 | |
|     # run as a gdb subprocess via pipes and collect stdout
 | |
|     def gdbrdr(self, k4mappfile, gdbcmds):
 | |
|         cmdline = 'gdb -q -silent -readnow -batch -x ' +  gdbcmds + ' "' + k4mappfile + '"'
 | |
|         cmdline = cmdline.encode(sys.getfilesystemencoding())
 | |
|         p3 = Process(cmdline, shell=True, bufsize=1, stdin=None, stdout=PIPE, stderr=PIPE, close_fds=False)
 | |
|         poll = p3.wait('wait')
 | |
|         results = p3.read()
 | |
|         pidnum = 'NOTAPID+'
 | |
|         topazbook = 0
 | |
|         bookpath = 'book not found'
 | |
|         # parse the gdb results to get the last pid and the last azw/prc file name in the gdb listing
 | |
|         reslst = results.split('\n')
 | |
|         cnt = len(reslst)
 | |
|         for j in xrange(cnt):
 | |
|             resline = reslst[j]
 | |
|             pp = resline.find('PID is ')
 | |
|             if pp == 0:
 | |
|                 pidnum = resline[7:]
 | |
|                 topazbook = 0
 | |
|             if pp > 0:
 | |
|                 pidnum = resline[13:]
 | |
|                 topazbook = 1
 | |
|             fp = resline.find('File is ')
 | |
|             if fp >= 0:
 | |
|                 tp1 = resline.find('.azw')
 | |
|                 tp2 = resline.find('.prc')
 | |
|                 if tp1 >= 0 or tp2 >= 0:
 | |
|                     bookpath = resline[8:]
 | |
|         # put code here to get pid and file name
 | |
|         return pidnum, bookpath, topazbook
 | |
| 
 | |
|     # convert from 8 digit PID to proper 10 digit PID
 | |
|     def checksumPid(self, 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
 | |
| 
 | |
|     # start the process
 | |
|     def convertit(self):
 | |
|         # dictionary of all known Kindle for Mac Binaries
 | |
|         sha1_app_digests = {
 | |
|             'e197ed2171ceb44a35c24bd30263b7253331694f' : 'gdb_kindle_cmds_r1.txt',
 | |
|             '4f702436171f84acc13bdf9f94fae91525aecef5' : 'gdb_kindle_cmds_r2.txt',
 | |
|             'no_sha1_digest_key_here_________________' : 'no_gdb_kindle_cmds.txt',
 | |
|         }
 | |
|         # now disable the button to prevent multiple launches
 | |
|         self.sbotton.configure(state='disabled')
 | |
| 
 | |
|         k4mpath = self.k4mpath.get()
 | |
|         outpath = self.outpath.get()
 | |
| 
 | |
|         # basic error checking
 | |
|         if not k4mpath or not os.path.exists(k4mpath):
 | |
|             self.status['text'] = 'Error: Specified Kindle for Mac Application does not exist'
 | |
|             self.sbotton.configure(state='normal')
 | |
|             return
 | |
|         if not outpath:
 | |
|             self.status['text'] = 'Error: No output directory specified'
 | |
|             self.sbotton.configure(state='normal')
 | |
|             return
 | |
|         if not os.path.isdir(outpath):
 | |
|             self.status['text'] = 'Error specified outputdirectory does not exist'
 | |
|             self.sbotton.configure(state='normal')
 | |
|             return
 | |
|         if not os.path.isfile('/usr/bin/gdb'):
 | |
|             self.status['text'] = 'Error: gdb does not exist, install the XCode Develoepr Tools'
 | |
|             self.sbotton.configure(state='normal')
 | |
|             return
 | |
| 
 | |
|         # now check if the K4M app bianry is known and if so which gdbcmds to use
 | |
|         binary_app_file = k4mpath + '/Contents/MacOS/Kindle for Mac'
 | |
|         if not os.path.exists(binary_app_file):
 | |
|             binary_app_file = k4mpath + '/Contents/MacOS/Kindle'
 | |
| 
 | |
|         digest = SHA1(file(binary_app_file, 'rb').read())
 | |
|         # print digest
 | |
|         gdbcmds = None
 | |
|         if digest in sha1_app_digests:
 | |
|             gdbcmds = sha1_app_digests[digest]
 | |
|         else :
 | |
|             self.status['text'] = 'Error: Kindle Application does not match any known version, sha1sum is ' + digest
 | |
|             self.sbotton.configure(state='normal')
 | |
|             return
 | |
| 
 | |
|         # run Kindle for Mac in gdb to get what we need
 | |
|         (pidnum, bookpath, topazbook) = self.gdbrdr(k4mpath, gdbcmds)
 | |
| 
 | |
|         if topazbook == 1:
 | |
|             log = 'Warning: ' + bookpath + ' is a Topaz book\n'
 | |
|             log += '\n\n'
 | |
|             log += 'To convert this book please use the Topaz Tools\n'
 | |
|             log += 'With the 8 digit PID: "' + pidnum + '"\n'
 | |
|             log += '\n\n'
 | |
|             log = log.encode('utf-8')
 | |
|             self.stext.insert(Tkconstants.END,log)
 | |
|             return
 | |
| 
 | |
|         pidnum = self.checksumPid(pidnum)
 | |
| 
 | |
|         # default output file name to be input file name + '_nodrm.mobi'
 | |
|         initname = os.path.splitext(os.path.basename(bookpath))[0]
 | |
|         initname += '_nodrm.mobi' 
 | |
|         outpath += '/' + initname
 | |
| 
 | |
|         log = 'Command = "python mobidedrm.py"\n'
 | |
|         log += 'Mobi Path = "'+ bookpath + '"\n'
 | |
|         log += 'Output file = "' + outpath + '"\n'
 | |
|         log += 'PID = "' + pidnum + '"\n'
 | |
|         log += '\n\n'
 | |
|         log += 'Please Wait ...\n\n'
 | |
|         log = log.encode('utf-8')
 | |
|         self.stext.insert(Tkconstants.END,log)
 | |
|         self.p2 = self.mobirdr(bookpath, 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('Kindle for Mac eBook Encryption Removal')
 | |
|     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())
 | 
