This repository has been archived by the owner on Sep 4, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
dbpf.py
129 lines (111 loc) · 3.2 KB
/
dbpf.py
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
121
122
123
124
125
126
127
128
129
import struct
import array
from collections import namedtuple
Header = struct.Struct("<4s17L24s")
class Index(namedtuple("DBPF_Index", 'version count offset size')): pass
class Record(namedtuple("DBPF_Record", 'type group instance offset length size raw')): pass
class DBPF:
"""a database backed DBPF file"""
@property
def version(self):
"""a real number representing the header version"""
return version(self.header[1], self.header[2])
@property
def user_version(self):
"""a real number representing the user version"""
return version(self.header[3], self.header[4])
@property
def flags(self):
"""flags"""
return self.header[5]
@property
def ctime(self):
"""creation time"""
return self.header[6]
@property
def mtime(self):
"""modification time"""
return self.header[7]
@property
def index(self):
"""the table of files in this DBPF"""
iv = self.header[8] if self.header[1] == 1 else self.header[15]
return Index(iv, self.header[9], self.header[10], self.header[11])
@property
def holes(self):
"""the table of holes in this DBPF"""
return Index(0, self.header[12], self.header[13], self.header[14])
def __init__(self, fd):
if isinstance(fd, str):
fd = open(fd, 'rb')
if not isinstance(fd, file):
raise ArgumentException('File')
self._fd = fd;
fd.seek(0)
self.header = Header.unpack(fd.read(Header.size))
if self.header[0] != b'DBPF':
raise DBPFException('Not a DBPF file')
@property
def _index_width(self):
"""the width of records in the index table"""
return {7.0:5, 7.1:6}.get(self.index.version, '')
def _table(self, offset, length, width):
"""parse the passed """
self._fd.seek(offset)
raw = array.array('L',self._fd.read(length));
for i in range(0, len(raw), width):
yield raw[i : i + width]
def save(self, fd):
"""save files to the passed fd"""
# prepare
head = list(self.header)
ind = []
o = Header.size
for tgi in self.records:
f = self.record(*tgi)
d = dict( key = tgi, offset = o, length = len(f), raw = f )
ind.append(d)
o += len(f)
# <index<count:4><offset:4><size:4>:12>
head[9] = len(ind)
head[10] = o
head[11] = len(ind) * self._index_width * 4
# zero hole table
head[12] = head[13] = head[14] = 0
# save header
fd.seek(0)
fd.write(Header.pack(*head))
# save files
for r in ind:
fd.write(r['raw'])
# save index
for r in ind:
rec = list(r['key']) + [r['offset'], r['length']]
fd.write(struct.pack("5L", *rec))
@property
def records(self):
"""retrieve all TGIs"""
ind = self.index
for rec in self._table(ind.offset, ind.size, self._index_width):
yield rec[:3]
def record(self, T, G, I):
"""retrieve the (first) file called TGI"""
ind = self.index
for rec in self._table(ind.offset, ind.size, self._index_width):
if rec[0] != T or rec[1] != G or rec[2] != I:
continue
self._fd.seek(rec[-2])
return self._fd.read(rec[-1])
#util
def version(major, minor): return float('.'.join([str(major),str(minor)]))
#exceptions
class ArgumentException(Exception): pass
class DBPFException(Exception): pass
if __name__ == '__main__':
import sys
import tgi
db = DBPF(sys.argv[1])
for r in db.records:
print tgi.TGI(*r)
if len(sys.argv) > 2:
db.save(open(sys.argv[2],"wb"))