dmg-builder 26.5.0 → 26.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,486 +0,0 @@
1
- import binascii
2
- import bisect
3
- import os
4
- import struct
5
-
6
-
7
- class BuddyError(Exception):
8
- pass
9
-
10
-
11
- class Block:
12
- def __init__(self, allocator, offset, size):
13
- self._allocator = allocator
14
- self._offset = offset
15
- self._size = size
16
- self._value = bytearray(allocator.read(offset, size))
17
- self._pos = 0
18
- self._dirty = False
19
-
20
- def __len__(self):
21
- return self._size
22
-
23
- def __enter__(self):
24
- return self
25
-
26
- def __exit__(self, exc_type, exc_value, traceback):
27
- self.close()
28
-
29
- def close(self):
30
- if self._dirty:
31
- self.flush()
32
-
33
- def flush(self):
34
- if self._dirty:
35
- self._dirty = False
36
- self._allocator.write(self._offset, self._value)
37
-
38
- def invalidate(self):
39
- self._dirty = False
40
-
41
- def zero_fill(self):
42
- len = self._size - self._pos
43
- zeroes = b"\0" * len
44
- self._value[self._pos : self._size] = zeroes
45
- self._dirty = True
46
-
47
- def tell(self):
48
- return self._pos
49
-
50
- def seek(self, pos, whence=os.SEEK_SET):
51
- if whence == os.SEEK_CUR:
52
- pos += self._pos
53
- elif whence == os.SEEK_END:
54
- pos = self._size - pos
55
-
56
- if pos < 0 or pos > self._size:
57
- raise ValueError("Seek out of range in Block instance")
58
-
59
- self._pos = pos
60
-
61
- def read(self, size_or_format):
62
- if isinstance(size_or_format, (str, bytes)):
63
- size = struct.calcsize(size_or_format)
64
- fmt = size_or_format
65
- else:
66
- size = size_or_format
67
- fmt = None
68
-
69
- if self._size - self._pos < size:
70
- raise BuddyError("Unable to read %lu bytes in block" % size)
71
-
72
- data = self._value[self._pos : self._pos + size]
73
- self._pos += size
74
-
75
- if fmt is not None:
76
- if isinstance(data, bytearray):
77
- return struct.unpack_from(fmt, bytes(data))
78
- else:
79
- return struct.unpack(fmt, data)
80
- else:
81
- return data
82
-
83
- def write(self, data_or_format, *args):
84
- if len(args):
85
- data = struct.pack(data_or_format, *args)
86
- else:
87
- data = data_or_format
88
-
89
- if self._pos + len(data) > self._size:
90
- raise ValueError("Attempt to write past end of Block")
91
-
92
- self._value[self._pos : self._pos + len(data)] = data
93
- self._pos += len(data)
94
-
95
- self._dirty = True
96
-
97
- def insert(self, data_or_format, *args):
98
- if len(args):
99
- data = struct.pack(data_or_format, *args)
100
- else:
101
- data = data_or_format
102
-
103
- del self._value[-len(data) :]
104
- self._value[self._pos : self._pos] = data
105
- self._pos += len(data)
106
-
107
- self._dirty = True
108
-
109
- def delete(self, size):
110
- if self._pos + size > self._size:
111
- raise ValueError("Attempt to delete past end of Block")
112
- del self._value[self._pos : self._pos + size]
113
- self._value += b"\0" * size
114
- self._dirty = True
115
-
116
- def __str__(self):
117
- return binascii.b2a_hex(self._value)
118
-
119
-
120
- class Allocator:
121
- def __init__(self, the_file):
122
- self._file = the_file
123
- self._dirty = False
124
-
125
- self._file.seek(0)
126
-
127
- # Read the header
128
- (magic1, magic2, offset, size, offset2, self._unknown1) = self.read(
129
- -4, ">I4sIII16s"
130
- )
131
-
132
- if magic2 != b"Bud1" or magic1 != 1:
133
- raise BuddyError("Not a buddy file")
134
-
135
- if offset != offset2:
136
- raise BuddyError("Root addresses differ")
137
-
138
- self._root = Block(self, offset, size)
139
-
140
- # Read the block offsets
141
- count, self._unknown2 = self._root.read(">II")
142
- self._offsets = []
143
- c = (count + 255) & ~255
144
- while c:
145
- self._offsets += self._root.read(">256I")
146
- c -= 256
147
- self._offsets = self._offsets[:count]
148
-
149
- # Read the TOC
150
- self._toc = {}
151
- count = self._root.read(">I")[0]
152
- for n in range(count):
153
- nlen = self._root.read("B")[0]
154
- name = bytes(self._root.read(nlen))
155
- value = self._root.read(">I")[0]
156
- self._toc[name] = value
157
-
158
- # Read the free lists
159
- self._free = []
160
- for n in range(32):
161
- count = self._root.read(">I")
162
- self._free.append(list(self._root.read(">%uI" % count)))
163
-
164
- @classmethod
165
- def open(cls, file_or_name, mode="r+"):
166
- if isinstance(file_or_name, str):
167
- if "b" not in mode:
168
- mode = mode[:1] + "b" + mode[1:]
169
- f = open(file_or_name, mode)
170
- else:
171
- f = file_or_name
172
-
173
- if "w" in mode:
174
- # Create an empty file in this case
175
- f.truncate()
176
-
177
- # An empty root block needs 1264 bytes:
178
- #
179
- # 0 4 offset count
180
- # 4 4 unknown
181
- # 8 4 root block offset (2048)
182
- # 12 255 * 4 padding (offsets are in multiples of 256)
183
- # 1032 4 toc count (0)
184
- # 1036 228 free list
185
- # total 1264
186
-
187
- # The free list will contain the following:
188
- #
189
- # 0 5 * 4 no blocks of width less than 5
190
- # 20 6 * 8 1 block each of widths 5 to 10
191
- # 68 4 no blocks of width 11 (allocated for the root)
192
- # 72 19 * 8 1 block each of widths 12 to 30
193
- # 224 4 no blocks of width 31
194
- # total 228
195
- #
196
- # (The reason for this layout is that we allocate 2**5 bytes for
197
- # the header, which splits the initial 2GB region into every size
198
- # below 2**31, including *two* blocks of size 2**5, one of which
199
- # we take. The root block itself then needs a block of size
200
- # 2**11. Conveniently, each of these initial blocks will be
201
- # located at offset 2**n where n is its width.)
202
-
203
- # Write the header
204
- header = struct.pack(
205
- b">I4sIII16s",
206
- 1,
207
- b"Bud1",
208
- 2048,
209
- 1264,
210
- 2048,
211
- b"\x00\x00\x10\x0c"
212
- b"\x00\x00\x00\x87"
213
- b"\x00\x00\x20\x0b"
214
- b"\x00\x00\x00\x00",
215
- )
216
- f.write(header)
217
- f.write(b"\0" * 2016)
218
-
219
- # Write the root block
220
- free_list = [struct.pack(b">5I", 0, 0, 0, 0, 0)]
221
- for n in range(5, 11):
222
- free_list.append(struct.pack(b">II", 1, 2**n))
223
- free_list.append(struct.pack(b">I", 0))
224
- for n in range(12, 31):
225
- free_list.append(struct.pack(b">II", 1, 2**n))
226
- free_list.append(struct.pack(b">I", 0))
227
-
228
- root = b"".join(
229
- [
230
- struct.pack(b">III", 1, 0, 2048 | 5),
231
- struct.pack(b">I", 0) * 255,
232
- struct.pack(b">I", 0),
233
- ]
234
- + free_list
235
- )
236
- f.write(root)
237
-
238
- return Allocator(f)
239
-
240
- def __enter__(self):
241
- return self
242
-
243
- def __exit__(self, exc_type, exc_value, traceback):
244
- self.close()
245
-
246
- def close(self):
247
- self.flush()
248
- self._file.close()
249
-
250
- def flush(self):
251
- if self._dirty:
252
- size = self._root_block_size()
253
- self.allocate(size, 0)
254
- with self.get_block(0) as rblk:
255
- self._write_root_block_into(rblk)
256
-
257
- addr = self._offsets[0]
258
- offset = addr & ~0x1F
259
- size = 1 << (addr & 0x1F)
260
-
261
- self._file.seek(0, os.SEEK_SET)
262
- self._file.write(
263
- struct.pack(
264
- b">I4sIII16s", 1, b"Bud1", offset, size, offset, self._unknown1
265
- )
266
- )
267
-
268
- self._dirty = False
269
-
270
- self._file.flush()
271
-
272
- def read(self, offset, size_or_format):
273
- """Read data at `offset', or raise an exception.
274
-
275
- `size_or_format' may either be a byte count, in which case we
276
- return raw data, or a format string for `struct.unpack', in
277
- which case we work out the size and unpack the data before
278
- returning it.
279
- """
280
- # N.B. There is a fixed offset of four bytes(!)
281
- self._file.seek(offset + 4, os.SEEK_SET)
282
-
283
- if isinstance(size_or_format, str):
284
- size = struct.calcsize(size_or_format)
285
- fmt = size_or_format
286
- else:
287
- size = size_or_format
288
- fmt = None
289
-
290
- ret = self._file.read(size)
291
- if len(ret) < size:
292
- ret += b"\0" * (size - len(ret))
293
-
294
- if fmt is not None:
295
- if isinstance(ret, bytearray):
296
- ret = struct.unpack_from(fmt, bytes(ret))
297
- else:
298
- ret = struct.unpack(fmt, ret)
299
-
300
- return ret
301
-
302
- def write(self, offset, data_or_format, *args):
303
- """Write data at `offset', or raise an exception.
304
-
305
- `data_or_format' may either be the data to write, or a format
306
- string for `struct.pack', in which case we pack the additional
307
- arguments and write the resulting data.
308
- """
309
- # N.B. There is a fixed offset of four bytes(!)
310
- self._file.seek(offset + 4, os.SEEK_SET)
311
-
312
- if len(args):
313
- data = struct.pack(data_or_format, *args)
314
- else:
315
- data = data_or_format
316
-
317
- self._file.write(data)
318
-
319
- def get_block(self, block):
320
- try:
321
- addr = self._offsets[block]
322
- except IndexError:
323
- return None
324
-
325
- offset = addr & ~0x1F
326
- size = 1 << (addr & 0x1F)
327
-
328
- return Block(self, offset, size)
329
-
330
- def _root_block_size(self):
331
- """Return the number of bytes required by the root block."""
332
- # Offsets
333
- size = 8
334
- size += 4 * ((len(self._offsets) + 255) & ~255)
335
-
336
- # TOC
337
- size += 4
338
- size += sum([5 + len(s) for s in self._toc])
339
-
340
- # Free list
341
- size += sum([4 + 4 * len(fl) for fl in self._free])
342
-
343
- return size
344
-
345
- def _write_root_block_into(self, block):
346
- # Offsets
347
- block.write(">II", len(self._offsets), self._unknown2)
348
- block.write(">%uI" % len(self._offsets), *self._offsets)
349
- extra = len(self._offsets) & 255
350
- if extra:
351
- block.write(b"\0\0\0\0" * (256 - extra))
352
-
353
- # TOC
354
- keys = list(self._toc.keys())
355
- keys.sort()
356
-
357
- block.write(">I", len(keys))
358
- for k in keys:
359
- block.write("B", len(k))
360
- block.write(k)
361
- block.write(">I", self._toc[k])
362
-
363
- # Free list
364
- for w, f in enumerate(self._free):
365
- block.write(">I", len(f))
366
- if len(f):
367
- block.write(">%uI" % len(f), *f)
368
-
369
- def _buddy(self, offset, width):
370
- f = self._free[width]
371
- b = offset ^ (1 << width)
372
-
373
- try:
374
- ndx = f.index(b)
375
- except ValueError:
376
- ndx = None
377
-
378
- return (f, b, ndx)
379
-
380
- def _release(self, offset, width):
381
- # Coalesce
382
- while True:
383
- f, b, ndx = self._buddy(offset, width)
384
-
385
- if ndx is None:
386
- break
387
-
388
- offset &= b
389
- width += 1
390
- del f[ndx]
391
-
392
- # Add to the list
393
- bisect.insort(f, offset)
394
-
395
- # Mark as dirty
396
- self._dirty = True
397
-
398
- def _alloc(self, width):
399
- w = width
400
- while not self._free[w]:
401
- w += 1
402
- while w > width:
403
- offset = self._free[w].pop(0)
404
- w -= 1
405
- self._free[w] = [offset, offset ^ (1 << w)]
406
- self._dirty = True
407
- return self._free[width].pop(0)
408
-
409
- def allocate(self, bytes, block=None):
410
- """Allocate or reallocate a block such that it has space for at least
411
- `bytes' bytes."""
412
- if block is None:
413
- # Find the first unused block
414
- try:
415
- block = self._offsets.index(0)
416
- except ValueError:
417
- block = len(self._offsets)
418
- self._offsets.append(0)
419
-
420
- # Compute block width
421
- width = max(bytes.bit_length(), 5)
422
-
423
- addr = self._offsets[block]
424
- offset = addr & ~0x1F
425
-
426
- if addr:
427
- blkwidth = addr & 0x1F
428
- if blkwidth == width:
429
- return block
430
- self._release(offset, width)
431
- self._offsets[block] = 0
432
-
433
- offset = self._alloc(width)
434
- self._offsets[block] = offset | width
435
- return block
436
-
437
- def release(self, block):
438
- addr = self._offsets[block]
439
-
440
- if addr:
441
- width = addr & 0x1F
442
- offset = addr & ~0x1F
443
- self._release(offset, width)
444
-
445
- if block == len(self._offsets):
446
- del self._offsets[block]
447
- else:
448
- self._offsets[block] = 0
449
-
450
- def __len__(self):
451
- return len(self._toc)
452
-
453
- def __getitem__(self, key):
454
- if not isinstance(key, str):
455
- raise TypeError("Keys must be of string type")
456
- if not isinstance(key, bytes):
457
- key = key.encode("latin_1")
458
- return self._toc[key]
459
-
460
- def __setitem__(self, key, value):
461
- if not isinstance(key, str):
462
- raise TypeError("Keys must be of string type")
463
- if not isinstance(key, bytes):
464
- key = key.encode("latin_1")
465
- self._toc[key] = value
466
- self._dirty = True
467
-
468
- def __delitem__(self, key):
469
- if not isinstance(key, str):
470
- raise TypeError("Keys must be of string type")
471
- if not isinstance(key, bytes):
472
- key = key.encode("latin_1")
473
- del self._toc[key]
474
- self._dirty = True
475
-
476
- def iterkeys(self):
477
- return self._toc.keys()
478
-
479
- def keys(self):
480
- return self._toc.keys()
481
-
482
- def __iter__(self):
483
- return self._toc.keys()
484
-
485
- def __contains__(self, key):
486
- return key in self._toc