SL2 Analyzer
Read a FromSoftware .sl2 save and report what is in it
Loading...
Searching...
No Matches
detect.py
Go to the documentation of this file.
1"""Which game a save belongs to, from its header signature and entry count."""
2
3import sys
4
5from .crypto import _aes_cbc
6from .keys import DS2_KEY, DS2_VANILLA_KEY
7from .reader import u32
8
9## @brief The BND4 signature DS2 stamps into its header.
10DS2_SIGNATURE = b"14e503cb"
11
12
13## @brief Size of one Sekiro character slot's BND4 entry: 0x100000 of payload behind
14# the 16-byte MD5. This is what identifies the game, because its entry COUNT does
15# not: 11 on the published layout (DS1's count) and 12 on the current patch, which
16# adds a reserved, all-zero twelfth entry (DS3's and Elden Ring's count).
17SDT_SLOT_ENTRY_SIZE = 0x100010
18
19
20## @brief Size of one Elden Ring Nightreign character slot's BND4 entry, and the
21# number of entries the file carries.
22# @details Nightreign is the only save here with fourteen entries, so the count alone
23# would do; the slot size is checked as well because it sits **0x20 away from
24# Sekiro's** (`0x100030` against `0x100010`), and a size test that close is worth
25# stating rather than leaving to a reader to notice.
26NR_SLOT_ENTRY_SIZE, NR_ENTRY_COUNT = 0x100030, 14
27
28
29##
30# @brief Identify which game wrote this save, from the bytes alone.
31# @details The header signature and entry count narrow it down; the remaining
32# ambiguities are settled by content — SOTFS is the DS2 variant whose key produces a
33# sane length prefix, Sekiro is the one whose slots are 0x100010, and ER's entries are
34# far larger than DS3's. Nightreign is the only one with fourteen entries.
35# @param data The full file bytes.
36# @param entries The parsed entry table.
37# @return One of @c "ds2vanilla", @c "ds2sotfs", @c "dsr", @c "ptde",
38# @c "ds3", @c "er", @c "sdt", @c "nr".
39def detect_game(data, entries):
40 sig = data[24:32]
41 n = len(entries)
42 if sig == DS2_SIGNATURE:
43 # Both DS2 variants share the signature, so they are told apart by which key
44 # decrypts: the length prefix at plaintext +0 must fit the block. A wrong key
45 # yields noise, which fails that test essentially always.
46 blob = data[entries[1].offset : entries[1].offset + entries[1].size]
47 for key, game in ((DS2_KEY, "ds2sotfs"), (DS2_VANILLA_KEY, "ds2vanilla")):
48 pt = _aes_cbc(key, blob[16:32], blob[32:])
49 dlen = u32(pt, 0)
50 if dlen is not None and 0 < dlen <= len(pt) - 4:
51 return game
52 sys.exit(
53 "Dark Souls II save found, but neither the Scholar nor the vanilla "
54 "key decrypts it."
55 )
56 # Sekiro's entry count is shared with DS1 and with DS3/ER, so the slot SIZE is
57 # what settles it, and it is unambiguous: DSR 0x60030, PtDE 0x60014, DS3 0xC0030,
58 # ER 0x280010, Sekiro 0x100010.
59 if n >= 11 and entries[0].size == SDT_SLOT_ENTRY_SIZE:
60 return "sdt"
61 if n == 11:
62 return "dsr" if sig == b"\x00" * 8 else "ptde"
63 if n == 12:
64 return "er" if entries[0].size > 2_000_000 else "ds3"
65 if n == NR_ENTRY_COUNT and entries[0].size == NR_SLOT_ENTRY_SIZE:
66 return "nr"
67 sys.exit("Unrecognised .sl2 — not a supported Souls save.")