SL2 Analyzer
Read a FromSoftware .sl2 save and report what is in it
Loading...
Searching...
No Matches
bnd4.py
Go to the documentation of this file.
1"""The BND4 archive every .sl2 is, and its entry table."""
2
3import hashlib
4import sys
5
6from .reader import u32, u64
7
8## @brief Size of the fixed BND4 file header, in bytes.
9BND4_HEADER_LEN = 64
10
11
12## @brief Size of one BND4 entry header, in bytes.
13BND4_ENTRY_LEN = 32
14
15
16##
17# @brief One decoded BND4 entry: its index and where its blob lives in the file.
19 ## @brief Construct from already-validated fields.
20 # @param index The entry's position in the archive.
21 # @param offset Byte offset of the entry blob inside the file.
22 # @param size Length of the entry blob in bytes.
23 def __init__(self, index, offset, size):
24 self.index = index
25 self.offset = offset
26 self.size = size
27
28
29##
30# @brief Parse and validate the BND4 entry table.
31# @details Refuses anything that is not a well-formed BND4 archive: bad magic, a
32# silly entry count, or an entry whose blob would fall outside the file. This is
33# the boundary check that lets everything downstream trust its offsets.
34# @param data The full `.sl2` bytes.
35# @return A list of @ref Bnd4Entry.
36# @exception SystemExit on any structural problem.
37def parse_bnd4(data):
38 if len(data) < BND4_HEADER_LEN or data[:4] != b"BND4":
39 sys.exit("Not a BND4 / .sl2 file.")
40 count = u32(data, 12)
41 if count is None or not (0 < count <= 64):
42 sys.exit(f"Implausible BND4 entry count: {count}")
43 entries = []
44 for i in range(count):
45 base = BND4_HEADER_LEN + BND4_ENTRY_LEN * i
46 if base + BND4_ENTRY_LEN > len(data):
47 sys.exit(f"Truncated entry header #{i}.")
48 size = u64(data, base + 8)
49 offset = u32(data, base + 16)
50 if size is None or offset is None or offset + size > len(data) or size <= 0:
51 sys.exit(
52 f"Entry #{i} points outside the file (offset={offset}, size={size})."
53 )
54 entries.append(Bnd4Entry(i, offset, size))
55 return entries
56
57
58##
59# @brief Does this entry blob carry a valid MD5 checksum wrapper?
60# @details Every Souls game prefixes each entry with @c MD5(rest). It is not a
61# game discriminator (they all have it), but it is a cheap integrity check.
62# @param data The full file bytes.
63# @param entry The entry to check.
64# @return True if @c blob[0:16] equals the MD5 of the remaining blob bytes.
65def checksum_ok(data, entry):
66 blob = data[entry.offset : entry.offset + entry.size]
67 return len(blob) >= 16 and hashlib.md5(blob[16:]).digest() == blob[:16]
One decoded BND4 entry: its index and where its blob lives in the file.
Definition bnd4.py:18
__init__(self, index, offset, size)
Construct from already-validated fields.
Definition bnd4.py:23