aboutsummaryrefslogtreecommitdiffstats
path: root/epy_extras/KindleUnpack/mobi_sectioner.py
blob: 81f62bbc2570ca20c26ed28c95032d9f16162dcf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab

from __future__ import unicode_literals, division, absolute_import, print_function

from .compatibility_utils import PY2, hexlify, bstr, bord, bchar

import datetime

if PY2:
    range = xrange

# note:  struct pack, unpack, unpack_from all require bytestring format
# data all the way up to at least python 2.7.5, python 3 okay with bytestring
import struct

from .unipath import pathof

DUMP = False
""" Set to True to dump all possible information. """

class unpackException(Exception):
    pass


def describe(data):
    txtans = ''
    hexans = hexlify(data)
    for i in data:
        if bord(i) < 32 or bord(i) > 127:
            txtans += '?'
        else:
            txtans += bchar(i).decode('latin-1')
    return '"' + txtans + '"' + ' 0x'+ hexans

def datetimefrompalmtime(palmtime):
    if palmtime > 0x7FFFFFFF:
        pythondatetime = datetime.datetime(year=1904,month=1,day=1)+datetime.timedelta(seconds=palmtime)
    else:
        pythondatetime = datetime.datetime(year=1970,month=1,day=1)+datetime.timedelta(seconds=palmtime)
    return pythondatetime


class Sectionizer:

    def __init__(self, filename):
        self.data = b''
        with open(pathof(filename), 'rb') as f:
            self.data = f.read()
        self.palmheader = self.data[:78]
        self.palmname = self.data[:32]
        self.ident = self.palmheader[0x3C:0x3C+8]
        self.num_sections, = struct.unpack_from(b'>H', self.palmheader, 76)
        self.filelength = len(self.data)
        sectionsdata = struct.unpack_from(bstr('>%dL' % (self.num_sections*2)), self.data, 78) + (self.filelength, 0)
        self.sectionoffsets = sectionsdata[::2]
        self.sectionattributes = sectionsdata[1::2]
        self.sectiondescriptions = ["" for x in range(self.num_sections+1)]
        self.sectiondescriptions[-1] = "File Length Only"
        return

    def dumpsectionsinfo(self):
        print("Section     Offset  Length      UID Attribs Description")
        for i in range(self.num_sections):
            print("%3d %3X  0x%07X 0x%05X % 8d % 7d %s" % (i,i, self.sectionoffsets[i], self.sectionoffsets[
                  i+1] - self.sectionoffsets[i], self.sectionattributes[i]&0xFFFFFF, (self.sectionattributes[i]>>24)&0xFF, self.sectiondescriptions[i]))
        print("%3d %3X  0x%07X                          %s" %
              (self.num_sections,self.num_sections, self.sectionoffsets[self.num_sections], self.sectiondescriptions[self.num_sections]))

    def setsectiondescription(self, section, description):
        if section < len(self.sectiondescriptions):
            self.sectiondescriptions[section] = description
        else:
            print("Section out of range: %d, description %s" % (section,description))

    def dumppalmheader(self):
        print("Palm Database Header")
        print("Database name: " + repr(self.palmheader[:32]))
        dbattributes, = struct.unpack_from(b'>H', self.palmheader, 32)
        print("Bitfield attributes: 0x%0X" % dbattributes,)
        if dbattributes != 0:
            print(" (",)
            if (dbattributes & 2):
                print("Read-only; ",)
            if (dbattributes & 4):
                print("Dirty AppInfoArea; ",)
            if (dbattributes & 8):
                print("Needs to be backed up; ",)
            if (dbattributes & 16):
                print("OK to install over newer; ",)
            if (dbattributes & 32):
                print("Reset after installation; ",)
            if (dbattributes & 64):
                print("No copying by PalmPilot beaming; ",)
            print(")")
        else:
            print("")
        print("File version: %d" % struct.unpack_from(b'>H', self.palmheader, 34)[0])
        dbcreation, = struct.unpack_from(b'>L', self.palmheader, 36)
        print("Creation Date: " + str(datetimefrompalmtime(dbcreation))+ (" (0x%0X)" % dbcreation))
        dbmodification, = struct.unpack_from(b'>L', self.palmheader, 40)
        print("Modification Date: " + str(datetimefrompalmtime(dbmodification))+ (" (0x%0X)" % dbmodification))
        dbbackup, = struct.unpack_from(b'>L', self.palmheader, 44)
        if dbbackup != 0:
            print("Backup Date: " + str(datetimefrompalmtime(dbbackup))+ (" (0x%0X)" % dbbackup))
        print("Modification No.: %d" % struct.unpack_from(b'>L', self.palmheader, 48)[0])
        print("App Info offset: 0x%0X" % struct.unpack_from(b'>L', self.palmheader, 52)[0])
        print("Sort Info offset: 0x%0X" % struct.unpack_from(b'>L', self.palmheader, 56)[0])
        print("Type/Creator: %s/%s" % (repr(self.palmheader[60:64]), repr(self.palmheader[64:68])))
        print("Unique seed: 0x%0X" % struct.unpack_from(b'>L', self.palmheader, 68)[0])
        expectedzero, = struct.unpack_from(b'>L', self.palmheader, 72)
        if expectedzero != 0:
            print("Should be zero but isn't: %d" % struct.unpack_from(b'>L', self.palmheader, 72)[0])
        print("Number of sections: %d" % struct.unpack_from(b'>H', self.palmheader, 76)[0])
        return

    def loadSection(self, section):
        before, after = self.sectionoffsets[section:section+2]
        return self.data[before:after]