|
SL2 Analyzer
Read a FromSoftware .sl2 save and report what is in it
|
Functions | |
| nr_find_profiles (menu) | |
| Locate the profile table in a decrypted menu entry. | |
| nr_roster (menu) | |
| Read the Nightreign roster: one name per slot, empty where unused. | |
| nr_steam_id (menu) | |
| Read the account this save belongs to. | |
| nr_slot_used (slot) | |
| Is this decrypted slot occupied? | |
| nr_parse (name) | |
| Build the unified character dict for one Nightreign slot. | |
Variables | |
| int | NR_SLOT_COUNT = 10 |
| Number of character slots, matching the ten slot entries in the container. | |
| str | NR_FACE_MAGIC = b"FACE" |
| The four-byte marker that opens each profile's appearance block. | |
| NR_PROFILE_STRIDE | |
| Bytes between one profile and the next, and where a name sits relative to the appearance marker that anchors it. | |
| NR_NAME_BACK_FROM_FACE | |
| Bytes between one profile and the next, and where a name sits relative to the appearance marker that anchors it. | |
| NR_NAME_CHARS | |
| Bytes between one profile and the next, and where a name sits relative to the appearance marker that anchors it. | |
| NR_STEAM_ENTRY | |
| Which entry carries the account, and where in it. | |
| NR_STEAM_OFF | |
| Which entry carries the account, and where in it. | |
| int | NR_EMPTY_SLOT_MAX_NONZERO = 4096 |
| A slot this empty holds no character. | |
Elden Ring Nightreign. What is read here is identity, and the module says so rather than implying more. The save is fully decrypted (see @ref sl2.crypto.decrypt_nr) and every entry verifies its own MD5, so the bytes are not in question — the layout past the roster is. Nightreign does not persist a character level, attributes or souls the way the other games do: progression lives in relics, unlocked Nightfarers and Nightlord kills, and none of those has been pinned against a second save yet.
| sl2.nr.nr_find_profiles | ( | menu | ) |
Locate the profile table in a decrypted menu entry.
Finds the run of NR_SLOT_COUNT appearance markers spaced exactly NR_PROFILE_STRIDE apart and returns where the first profile's name starts. A single marker proves nothing — character data elsewhere in the file could spell the same four bytes — so the whole run has to hold before anything is returned.
| menu | The decrypted menu entry. |
Definition at line 56 of file nr.py.
Referenced by sl2.nr.nr_roster().
| sl2.nr.nr_roster | ( | menu | ) |
Read the Nightreign roster: one name per slot, empty where unused.
| menu | The decrypted menu entry. |
Definition at line 74 of file nr.py.
References sl2.nr.nr_find_profiles().
| sl2.nr.nr_steam_id | ( | menu | ) |
Read the account this save belongs to.
| menu | The decrypted menu entry. |
(low dword, high dword) of the SteamID64, or None.Returned as two halves rather than one integer for the same reason every other game here does it: a SteamID64 does not survive a JavaScript double, and the two front ends have to agree byte for byte.
| sl2.nr.nr_slot_used | ( | slot | ) |
| sl2.nr.nr_parse | ( | name | ) |
Build the unified character dict for one Nightreign slot.
Identity only, and every other key is None on purpose so the writers can treat this like any other character without inventing fields. The tier is "roster", which is the whole claim: this save was opened, decrypted and its character named, and nothing further is asserted.
| name | The roster name for this slot. |
| int sl2.nr.NR_SLOT_COUNT = 10 |
| str sl2.nr.NR_FACE_MAGIC = b"FACE" |
The four-byte marker that opens each profile's appearance block.
The roster is found by this rather than by a fixed offset. Nightreign writes a variable-length block ahead of the profile table exactly as Elden Ring does, so an offset measured on one save is an offset measured on one save. Ten of these at a constant stride is a shape nothing else in the file reproduces.
| sl2.nr.NR_PROFILE_STRIDE |
Bytes between one profile and the next, and where a name sits relative to the appearance marker that anchors it.
Measured: the ten markers in the test save are 632 bytes apart, and the name ends 54 bytes before each one. The name is UTF-16 in a 32-byte field, so NR_NAME_CHARS is sixteen — sl2.reader.read_utf16 counts characters, not bytes, and reading it as 32 would run a name into the appearance block behind it.
| sl2.nr.NR_NAME_BACK_FROM_FACE |
Bytes between one profile and the next, and where a name sits relative to the appearance marker that anchors it.
Measured: the ten markers in the test save are 632 bytes apart, and the name ends 54 bytes before each one. The name is UTF-16 in a 32-byte field, so NR_NAME_CHARS is sixteen — sl2.reader.read_utf16 counts characters, not bytes, and reading it as 32 would run a name into the appearance block behind it.
| sl2.nr.NR_NAME_CHARS |
Bytes between one profile and the next, and where a name sits relative to the appearance marker that anchors it.
Measured: the ten markers in the test save are 632 bytes apart, and the name ends 54 bytes before each one. The name is UTF-16 in a 32-byte field, so NR_NAME_CHARS is sixteen — sl2.reader.read_utf16 counts characters, not bytes, and reading it as 32 would run a name into the appearance block behind it.
| sl2.nr.NR_STEAM_ENTRY |
Which entry carries the account, and where in it.
Entry 10 is the menu block; the SteamID64 is a little-endian uint64 eight bytes in. Checked against the folder the test save shipped in — the file reads 76561197960272671 and the folder is named 76561197960272671.
| sl2.nr.NR_STEAM_OFF |
Which entry carries the account, and where in it.
Entry 10 is the menu block; the SteamID64 is a little-endian uint64 eight bytes in. Checked against the folder the test save shipped in — the file reads 76561197960272671 and the folder is named 76561197960272671.
| int sl2.nr.NR_EMPTY_SLOT_MAX_NONZERO = 4096 |