|
SL2 Analyzer
Read a FromSoftware .sl2 save and report what is in it
|
Functions | |
| ds2_name (buf) | |
| Read a DS2 name, or None for an empty slot. | |
| ds2_inventory (buf, item_db) | |
| Sort both DS2 inventory regions into categories. | |
| ds2_phys_atk (stat) | |
| Physical attack bonus (ATK: Str/Dex) at a stat value: linear-interpolate the decade breakpoints of DS2_PHYS_ATK_BP, floored to the game's integer display. | |
| ds2_elem_def (stat) | |
| Shared DS2 elemental-defence curve: +6/pt to 10, +8/pt to 20, +1/pt to 60, +0.5/pt (one every other) to 99. | |
| ds2_derived_stats (stats) | |
| Compute DS2 base derived stats from the attribute block. | |
| ds2_covenants (buf) | |
| Every DS2 covenant the character has discovered, with its rank. | |
| ds2_parse (buf, item_db, game="ds2sotfs") | |
| Parse one DS2 slot into the unified character dict, or None if empty. | |
| load_ds2_bonfires (base_dir) | |
| load_ds2_bonfire_areas (base_dir) | |
| load_ds2_bosses (base_dir) | |
| load_ds2_boss_souls (base_dir) | |
| ds2_base_name (name) | |
| An inventory name with its " +N" reinforcement suffix removed. | |
| ds2_infer_bosses (world, ch, base_dir) | |
Bosses this character has defeated, as {boss: [evidence]}, or None. | |
| ds2_visited_bonfires (world, bf_db) | |
| Names of the bonfires this character has discovered, or None. | |
| ds2_bonfire_areas (visited, area_db, bf_db) | |
| Group discovered bonfires by area, in the (area, count, names) shape DS1 and DS3 already emit, so all three render through the same section. | |
| ds2_augment (ch, data, entries, i, base_dir, dec=decrypt_ds2) | |
| DS2-only augment: attach world-block progression (bonfires, bosses) to a parsed character. | |
| ds2_active_slots (data, entries, slots, dec=decrypt_ds2) | |
| Which DS2 block entries hold a character still listed in the menu. | |
Variables | |
| tuple | DS2_GAMES = ("ds2sotfs", "ds2vanilla") |
| The two DS2 releases. | |
| dict | DS2_FAMILY = {"dsr": "ds1", "ptde": "ds1", "ds2vanilla": "ds2sotfs"} |
| Game id to the id whose lookup tables it uses (attribute reference, derived stats, themes). | |
| DS2_NAME_OFF | |
| DS2 character-slot offsets (absolute, into decrypted game data). | |
| DS2_SOULS_OFF | |
| DS2 character-slot offsets (absolute, into decrypted game data). | |
| DS2_SOULMEM_OFF | |
| DS2 character-slot offsets (absolute, into decrypted game data). | |
| DS2_HP_OFF | |
| DS2 character-slot offsets (absolute, into decrypted game data). | |
| DS2_NG_OFF | |
| DS2 character-slot offsets (absolute, into decrypted game data). | |
| DS2_TITLE_NAME_OFF | |
| DS2 header (BND4 entry 0) title-list layout: each menu slot's name sits at DS2_TITLE_NAME_OFF + DS2_TITLE_STRIDE * title_index. | |
| DS2_TITLE_STRIDE | |
| DS2 header (BND4 entry 0) title-list layout: each menu slot's name sits at DS2_TITLE_NAME_OFF + DS2_TITLE_STRIDE * title_index. | |
| int | DS2_TITLE_PLAYTIME_OFF = 66 |
| Play time (u32 seconds) inside a header title record: name is at record base +0, the play-time counter at +66. | |
| DS2_CLASS_OFF | |
| Starting class (byte) and current covenant (byte) offsets in the slot block. | |
| DS2_COVENANT_OFF | |
| Starting class (byte) and current covenant (byte) offsets in the slot block. | |
| dict | DS2_CLASS |
| DS2 starting-class and covenant id→name (from the SOTFS Cheat Engine table dropdowns). | |
| dict | DS2_COVENANT |
| DS2_COV_DISC_D | |
| Per-covenant discovered flag and rank, as two dense byte runs in covenant-id order just past the current-covenant byte. | |
| DS2_COV_RANK_D | |
| Per-covenant discovered flag and rank, as two dense byte runs in covenant-id order just past the current-covenant byte. | |
| DS2_COV_MAX_RANK | |
| Per-covenant discovered flag and rank, as two dense byte runs in covenant-id order just past the current-covenant byte. | |
| DS2_GENDER_OFF | |
| Gender (u8) and hollowing level (u8) offsets in the slot block. | |
| DS2_HOLLOW_OFF | |
| Gender (u8) and hollowing level (u8) offsets in the slot block. | |
| int | DS2_DEATHS_OFF = 104 |
| Total deaths (u32) in the slot block. | |
| dict | DS2_GENDER = {0: "Male", 1: "Female"} |
| DS2 gender enum. | |
| DS2_WORLD_ENTRY_DELTA | |
| Bonfire (rest-point) progression lives in a separate WORLD block, not the character-status block. | |
| DS2_BONFIRE_FLAG_DELTA | |
| Bonfire (rest-point) progression lives in a separate WORLD block, not the character-status block. | |
| DS2_BONFIRE_MIN_RUN | |
| Bonfire (rest-point) progression lives in a separate WORLD block, not the character-status block. | |
| DS2_STAT_OFF | |
| DS2 attribute offsets (uint16 each), in display order; Level last. | |
| DS2_STAMINA_BASE | |
| DS2 derived-stat bases (values BEFORE rings/equipment). | |
| DS2_EQUIP_BASE | |
| DS2 derived-stat bases (values BEFORE rings/equipment). | |
| DS2_AGL_BASE | |
| DS2 derived-stat bases (values BEFORE rings/equipment). | |
| DS2_IFRAMES | |
| Roll i-frames by Agility value (fextralife/community breakpoints). | |
| tuple | DS2_SLOT_BREAKS = (10, 13, 16, 20, 25, 30, 40, 50, 60, 75, 94) |
| Attunement values at which a spell slot is unlocked (fextralife Attunement). | |
| DS2_PHYS_ATK_BP | |
| Physical attack bonus (ATK: Str / ATK: Dex) by stat value — decade breakpoints of the weapon-independent curve (the weapon then applies its own scaling on top). | |
| DS2_INV_RANGE | |
| Shared elemental-defence curve breakpoint rates (per stat point): +6 (1-10), +8 (11-20), +1 (21-60), +0.5 (61-99); base 0. | |
| DS2_KEY_RANGE | |
| Shared elemental-defence curve breakpoint rates (per stat point): +6 (1-10), +8 (11-20), +1 (21-60), +0.5 (61-99); base 0. | |
| dict | DS2_STACKABLE |
| DS2 categories whose slot +8 field is a real count (float durability elsewhere). | |
| dict | DS2_UPGRADEABLE = {"weapons", "armors"} |
| Categories whose slot +12 field carries a reinforcement level. | |
| DS2_REINF_OFF | |
| Byte offsets inside the uint32 upgrade field of a 16-byte item record: the LOW byte (+12) is the reinforcement level (0..10); the next byte (+13) is the infusion id. | |
| DS2_INFUSE_OFF | |
| Byte offsets inside the uint32 upgrade field of a 16-byte item record: the LOW byte (+12) is the reinforcement level (0..10); the next byte (+13) is the infusion id. | |
| dict | DS2_INFUSION |
| DS2 infusion ids to names. | |
| dict | DS2_GREAT_SOULS |
| The four DS2 "Old" great souls (from the Lost Sinner, the Rotten, the Old Iron King, and the Duke's Dear Freja). | |
| dict | _DS2_BONFIRE_CACHE = {} |
| Load the DS2 bonfire id→name table (db_ds2/bonfires.json, keyed by the low-16-bit id as 4-hex). | |
| dict | _DS2_AREA_CACHE = {} |
| Load the DS2 bonfire→area table (db_ds2/bonfire_areas.json): bonfire id → the area it belongs to, so discovered bonfires group the way DS1's and DS3's do instead of listing 77 flat names. | |
| dict | _DS2_BOSS_CACHE = {} |
| Load the DS2 boss-defeat flag table (db_ds2/boss_flags.json, world-block byte offset as hex → boss name). | |
| dict | _DS2_BOSS_SOUL_CACHE = {} |
| Load the DS2 boss-soul → boss-name table (db_ds2/boss_souls.json). | |
| dict | DS2_BOSS_GATE |
| Progression gates: a boss proven dead by something the character has. | |
| dict | DS2_ITEM_GATE |
| Inventory item held ⇒ boss dead (item only obtainable past it). | |
| dict | DS2_BOSS_PREREQ |
| Boss defeated ⇒ its mandatory predecessors also defeated (each list is the full transitive set, so a single pass closes it). | |
Dark Souls II, vanilla and Scholar of the First Sin.
| sl2.ds2.ds2_name | ( | buf | ) |
Read a DS2 name, or None for an empty slot.
Definition at line 268 of file ds2.py.
Referenced by sl2.ds2.ds2_parse().
| sl2.ds2.ds2_inventory | ( | buf, | |
| item_db | |||
| ) |
Sort both DS2 inventory regions into categories.
(buckets, unknown_count). Definition at line 276 of file ds2.py.
Referenced by sl2.ds2.ds2_parse().
| sl2.ds2.ds2_phys_atk | ( | stat | ) |
Physical attack bonus (ATK: Str/Dex) at a stat value: linear-interpolate the decade breakpoints of DS2_PHYS_ATK_BP, floored to the game's integer display.
Definition at line 315 of file ds2.py.
Referenced by sl2.ds2.ds2_derived_stats().
| sl2.ds2.ds2_elem_def | ( | stat | ) |
Shared DS2 elemental-defence curve: +6/pt to 10, +8/pt to 20, +1/pt to 60, +0.5/pt (one every other) to 99.
Base 0. See DS2_PHYS_ATK_BP note for the map.
Definition at line 325 of file ds2.py.
Referenced by sl2.ds2.ds2_derived_stats().
| sl2.ds2.ds2_derived_stats | ( | stats | ) |
Compute DS2 base derived stats from the attribute block.
Base = before rings/equipment; the in-game screen adds ring/gear bonuses on top (e.g. a +HP ring, a load ring). Stamina, equip load and agility are pure attribute functions verified against a real save; i-frames come from the agility breakpoint table (DS2_IFRAMES), omitted below AGL 85 (undocumented).
Definition at line 344 of file ds2.py.
References sl2.ds2.ds2_elem_def(), and sl2.ds2.ds2_phys_atk().
| sl2.ds2.ds2_covenants | ( | buf | ) |
Every DS2 covenant the character has discovered, with its rank.
Two dense byte runs in covenant-id order (DS2_COV_DISC_D / DS2_COV_RANK_D past the current-covenant byte). Both runs are validated as a whole before anything is returned — a discovered flag must be 0 or 1, a rank must be 0..3, and a rank cannot be nonzero where the covenant was never discovered. If any of that fails the offsets have moved, so the feature turns itself off rather than printing a wrong rank.
Definition at line 404 of file ds2.py.
Referenced by sl2.ds2.ds2_parse().
| sl2.ds2.ds2_parse | ( | buf, | |
| item_db, | |||
game = "ds2sotfs" |
|||
| ) |
Parse one DS2 slot into the unified character dict, or None if empty.
| game | Which DS2 release this slot came from. The layout is identical for both (see DS2_GAMES), so this only labels the output. |
Definition at line 421 of file ds2.py.
References sl2.ds2.ds2_covenants(), sl2.ds2.ds2_inventory(), and sl2.ds2.ds2_name().
| sl2.ds2.load_ds2_bonfires | ( | base_dir | ) |
Definition at line 459 of file ds2.py.
Referenced by sl2.ds2.ds2_augment().
| sl2.ds2.load_ds2_bonfire_areas | ( | base_dir | ) |
Definition at line 480 of file ds2.py.
Referenced by sl2.ds2.ds2_augment().
| sl2.ds2.ds2_base_name | ( | name | ) |
An inventory name with its " +N" reinforcement suffix removed.
Gate matching compares against the plain db name, and armour/weapons render upgraded. Only strips a trailing " +digits", so a name that genuinely ends that way is untouched (none does).
Definition at line 592 of file ds2.py.
Referenced by sl2.ds2.ds2_infer_bosses().
| sl2.ds2.ds2_infer_bosses | ( | world, | |
| ch, | |||
| base_dir | |||
| ) |
Bosses this character has defeated, as {boss: [evidence]}, or None.
A FLOOR from three independent, positive-only signals — each is certain when it fires, none is exhaustive:
Definition at line 609 of file ds2.py.
References sl2.ds2.ds2_base_name().
Referenced by sl2.ds2.ds2_augment().
| sl2.ds2.ds2_visited_bonfires | ( | world, | |
| bf_db | |||
| ) |
Names of the bonfires this character has discovered, or None.
The world block holds a contiguous u16 array of bonfire ids and, exactly DS2_BONFIRE_FLAG_DELTA bytes later, one unlock byte per id (non-zero = discovered). The array's offset shifts between saves, so it is located by content: the start of the longest run of known bonfire ids (a false run is astronomically unlikely given the ~78-id vocabulary in the u16 space). Returns the discovered names in world order, or None when the array can't be found (no world block / unknown layout).
Definition at line 652 of file ds2.py.
Referenced by sl2.ds2.ds2_augment().
| sl2.ds2.ds2_bonfire_areas | ( | visited, | |
| area_db, | |||
| bf_db | |||
| ) |
Group discovered bonfires by area, in the (area, count, names) shape DS1 and DS3 already emit, so all three render through the same section.
| visited | The (id, name) pairs from ds2_visited_bonfires. |
| area_db | Bonfire id → area, from load_ds2_bonfire_areas. |
Definition at line 690 of file ds2.py.
Referenced by sl2.ds2.ds2_augment().
| sl2.ds2.ds2_augment | ( | ch, | |
| data, | |||
| entries, | |||
| i, | |||
| base_dir, | |||
dec = decrypt_ds2 |
|||
| ) |
DS2-only augment: attach world-block progression (bonfires, bosses) to a parsed character.
The world block for status entry i is entry i+DS2_WORLD_ENTRY_DELTA; a missing/undecryptable block leaves both fields None (sections omitted). Decrypts the world block once for both reads.
Definition at line 715 of file ds2.py.
References sl2.ds2.ds2_bonfire_areas(), sl2.ds2.ds2_infer_bosses(), sl2.ds2.ds2_visited_bonfires(), sl2.ds2.load_ds2_bonfire_areas(), and sl2.ds2.load_ds2_bonfires().
| sl2.ds2.ds2_active_slots | ( | data, | |
| entries, | |||
| slots, | |||
dec = decrypt_ds2 |
|||
| ) |
Which DS2 block entries hold a character still listed in the menu.
Deleting a character in-game only clears its entry in the header title list (BND4 entry 0) — the encrypted slot block is left untouched, so a plain scan resurrects deleted "ghost" characters. The title list is the menu's source of truth: block entry i owns title index i-slots.start, occupied only when that title name field holds a valid name. Reads through the bounds-checked helpers, so a short/garbled header yields None and the caller then skips the filter (degrade to showing everything rather than wrongly hiding a real character). An empty result is treated the same way: more likely a shifted offset on a future patch than a save the user would bother converting with every character deleted.
| tuple sl2.ds2.DS2_GAMES = ("ds2sotfs", "ds2vanilla") |
The two DS2 releases.
Vanilla (the DX9 original) and Scholar of the First Sin share the ENTIRE save layout — only the AES key differs. Verified field by field on a real DARKSII0000.sl2 against a real DS2SOFS0000.sl2: identical BND4 entry count and sizes (bar one non-character block), the name at DS2_NAME_OFF, and DS2's own level identity (sum of the nine attributes minus level == 53) holding on both. So every DS2 table below is shared; anything keyed by game id resolves vanilla through DS2_FAMILY rather than carrying a duplicate entry.
| dict sl2.ds2.DS2_FAMILY = {"dsr": "ds1", "ptde": "ds1", "ds2vanilla": "ds2sotfs"} |
| sl2.ds2.DS2_NAME_OFF |
| sl2.ds2.DS2_SOULS_OFF |
| sl2.ds2.DS2_SOULMEM_OFF |
| sl2.ds2.DS2_HP_OFF |
| sl2.ds2.DS2_NG_OFF |
| sl2.ds2.DS2_TITLE_NAME_OFF |
| sl2.ds2.DS2_TITLE_STRIDE |
| int sl2.ds2.DS2_TITLE_PLAYTIME_OFF = 66 |
Play time (u32 seconds) inside a header title record: name is at record base +0, the play-time counter at +66.
Pinned by a real ~1-minute differential pair (40:10:25 → 40:11:27, the u32 rose by exactly 62). Per-character, since each title record is one slot. This is DS2's play time, which no editor exposed and an earlier differential missed; it lives in the header, not the character block.
| sl2.ds2.DS2_CLASS_OFF |
Starting class (byte) and current covenant (byte) offsets in the slot block.
Pinned by differential saves: class read 2 (Knight) on one character and 8 (Explorer) on another at +1024; covenant read 3 (Brotherhood of Blood) then 0 (None) after leaving the covenant at +189, cross-checked against a third char.
| sl2.ds2.DS2_COVENANT_OFF |
Starting class (byte) and current covenant (byte) offsets in the slot block.
Pinned by differential saves: class read 2 (Knight) on one character and 8 (Explorer) on another at +1024; covenant read 3 (Brotherhood of Blood) then 0 (None) after leaving the covenant at +189, cross-checked against a third char.
| dict sl2.ds2.DS2_CLASS |
DS2 starting-class and covenant id→name (from the SOTFS Cheat Engine table dropdowns).
Id 0 / unknown is absent, so .get yields None and the field is omitted rather than shown wrong. Covenant 0 = not in a covenant (omitted).
| dict sl2.ds2.DS2_COVENANT |
| sl2.ds2.DS2_COV_DISC_D |
Per-covenant discovered flag and rank, as two dense byte runs in covenant-id order just past the current-covenant byte.
DS2S-META puts CurrentCovenant at 0x1AD with Discovered at 0x1AF.. and Rank at 0x1B9.., so relative to our own (differential- verified) DS2_COVENANT_OFF the runs start +2 and +12 — the absolute bases differ between that editor and this tool, but the layout inside the struct does not. Checked across Joy's ladder: the flags read a clean 0/1, rank is never nonzero for an undiscovered covenant, and the only two she ranked to 3 are Way of Blue and Pilgrims of Dark — the two cheapest ladders in the game (1/5/10 and 1/2/3).
| sl2.ds2.DS2_COV_RANK_D |
Per-covenant discovered flag and rank, as two dense byte runs in covenant-id order just past the current-covenant byte.
DS2S-META puts CurrentCovenant at 0x1AD with Discovered at 0x1AF.. and Rank at 0x1B9.., so relative to our own (differential- verified) DS2_COVENANT_OFF the runs start +2 and +12 — the absolute bases differ between that editor and this tool, but the layout inside the struct does not. Checked across Joy's ladder: the flags read a clean 0/1, rank is never nonzero for an undiscovered covenant, and the only two she ranked to 3 are Way of Blue and Pilgrims of Dark — the two cheapest ladders in the game (1/5/10 and 1/2/3).
| sl2.ds2.DS2_COV_MAX_RANK |
Per-covenant discovered flag and rank, as two dense byte runs in covenant-id order just past the current-covenant byte.
DS2S-META puts CurrentCovenant at 0x1AD with Discovered at 0x1AF.. and Rank at 0x1B9.., so relative to our own (differential- verified) DS2_COVENANT_OFF the runs start +2 and +12 — the absolute bases differ between that editor and this tool, but the layout inside the struct does not. Checked across Joy's ladder: the flags read a clean 0/1, rank is never nonzero for an undiscovered covenant, and the only two she ranked to 3 are Way of Blue and Pilgrims of Dark — the two cheapest ladders in the game (1/5/10 and 1/2/3).
| sl2.ds2.DS2_GENDER_OFF |
Gender (u8) and hollowing level (u8) offsets in the slot block.
From the Jappi88 DS2 save editor: its player block reads Gender then HollowLv at block[0] 0x15A/0x15B, and that block starts at slot flat +32 (Level/Souls/Soul-Memory/Health line up), so Gender is 0x15A+32 = 378 and HollowLv 0x15B+32 = 379. HollowLv verified on a 30h character (Hollow Lv 1). Gender polarity verified by a real F→M differential save pair (the byte flipped 1→0), so 1 = Female, 0 = Male.
| sl2.ds2.DS2_HOLLOW_OFF |
Gender (u8) and hollowing level (u8) offsets in the slot block.
From the Jappi88 DS2 save editor: its player block reads Gender then HollowLv at block[0] 0x15A/0x15B, and that block starts at slot flat +32 (Level/Souls/Soul-Memory/Health line up), so Gender is 0x15A+32 = 378 and HollowLv 0x15B+32 = 379. HollowLv verified on a 30h character (Hollow Lv 1). Gender polarity verified by a real F→M differential save pair (the byte flipped 1→0), so 1 = Female, 0 = Male.
| int sl2.ds2.DS2_DEATHS_OFF = 104 |
Total deaths (u32) in the slot block.
Pinned by a real 201→202 death differential: the u32 rose by exactly 1, and it climbs monotonically with play time across the whole backup set (181 at 37h → 202 at 40.4h), reaching the labelled death counts. DS2 mirrors it at three offsets (+104, +184, +7272) that always agree; +104 is used. This is the deaths counter no editor exposed and an earlier differential could not find in the player region.
| dict sl2.ds2.DS2_GENDER = {0: "Male", 1: "Female"} |
| sl2.ds2.DS2_WORLD_ENTRY_DELTA |
Bonfire (rest-point) progression lives in a separate WORLD block, not the character-status block.
In the SOTFS .sl2 the world block for status entry i is entry i + DS2_WORLD_ENTRY_DELTA. Inside it (per the Jappi88 editor's MapData: ids at block 0x1598, unlock flags at 0x1798) a contiguous u16 array of bonfire ids is followed DS2_BONFIRE_FLAG_DELTA bytes later by one unlock byte each. The array's slot offset is not fixed across saves, so it is found by content (a long run of known bonfire ids). Verified: a fresh mule shows 1 bonfire (the start), a 30h save shows 49 across the whole game.
| sl2.ds2.DS2_BONFIRE_FLAG_DELTA |
Bonfire (rest-point) progression lives in a separate WORLD block, not the character-status block.
In the SOTFS .sl2 the world block for status entry i is entry i + DS2_WORLD_ENTRY_DELTA. Inside it (per the Jappi88 editor's MapData: ids at block 0x1598, unlock flags at 0x1798) a contiguous u16 array of bonfire ids is followed DS2_BONFIRE_FLAG_DELTA bytes later by one unlock byte each. The array's slot offset is not fixed across saves, so it is found by content (a long run of known bonfire ids). Verified: a fresh mule shows 1 bonfire (the start), a 30h save shows 49 across the whole game.
| sl2.ds2.DS2_BONFIRE_MIN_RUN |
Bonfire (rest-point) progression lives in a separate WORLD block, not the character-status block.
In the SOTFS .sl2 the world block for status entry i is entry i + DS2_WORLD_ENTRY_DELTA. Inside it (per the Jappi88 editor's MapData: ids at block 0x1598, unlock flags at 0x1798) a contiguous u16 array of bonfire ids is followed DS2_BONFIRE_FLAG_DELTA bytes later by one unlock byte each. The array's slot offset is not fixed across saves, so it is found by content (a long run of known bonfire ids). Verified: a fresh mule shows 1 bonfire (the start), a 30h save shows 49 across the whole game.
| sl2.ds2.DS2_STAT_OFF |
DS2 attribute offsets (uint16 each), in display order; Level last.
Adaptability, Intelligence and Faith are NOT stored in display order: memory keeps Intelligence @44, Faith @46, Adaptability @48 (verified against a known SL88 character whose real ADP/INT/FTH were 15/3/6 but read out as 3/6/15 under the naive contiguous mapping). The dict below lists them in display order with their true offsets, so the table reads ADP, INT, FTH while pointing at 48/44/46.
| sl2.ds2.DS2_STAMINA_BASE |
DS2 derived-stat bases (values BEFORE rings/equipment).
Each derived stat is a pure function of one/two attributes, verified byte-exact against a real save's in-game Level-Up screen (Lv155 char: END 31 -> 131 stamina, VIT 30 -> 83.0 equip load, ADP 20 / ATN 4 -> 96 agility / 11 roll i-frames). Unlike HP — which carries a class/base offset the flat table misses (so HP is read from the save, not computed; see STAT_CAPS note) — these three start from a universal base with no class variance, so the formula reproduces the game exactly. Sources: fextralife Endurance / Equipment Load / Agility pages.
| sl2.ds2.DS2_EQUIP_BASE |
DS2 derived-stat bases (values BEFORE rings/equipment).
Each derived stat is a pure function of one/two attributes, verified byte-exact against a real save's in-game Level-Up screen (Lv155 char: END 31 -> 131 stamina, VIT 30 -> 83.0 equip load, ADP 20 / ATN 4 -> 96 agility / 11 roll i-frames). Unlike HP — which carries a class/base offset the flat table misses (so HP is read from the save, not computed; see STAT_CAPS note) — these three start from a universal base with no class variance, so the formula reproduces the game exactly. Sources: fextralife Endurance / Equipment Load / Agility pages.
| sl2.ds2.DS2_AGL_BASE |
DS2 derived-stat bases (values BEFORE rings/equipment).
Each derived stat is a pure function of one/two attributes, verified byte-exact against a real save's in-game Level-Up screen (Lv155 char: END 31 -> 131 stamina, VIT 30 -> 83.0 equip load, ADP 20 / ATN 4 -> 96 agility / 11 roll i-frames). Unlike HP — which carries a class/base offset the flat table misses (so HP is read from the save, not computed; see STAT_CAPS note) — these three start from a universal base with no class variance, so the formula reproduces the game exactly. Sources: fextralife Endurance / Equipment Load / Agility pages.
| sl2.ds2.DS2_IFRAMES |
Roll i-frames by Agility value (fextralife/community breakpoints).
Look up the highest key <= AGL; below 85 the count is undocumented, so i-frames are omitted there.
| tuple sl2.ds2.DS2_SLOT_BREAKS = (10, 13, 16, 20, 25, 30, 40, 50, 60, 75, 94) |
| sl2.ds2.DS2_PHYS_ATK_BP |
Physical attack bonus (ATK: Str / ATK: Dex) by stat value — decade breakpoints of the weapon-independent curve (the weapon then applies its own scaling on top).
Base 50 at 0, soft caps 40/50/80. From the DS2 wikidot/fextralife scaling table; verified STR 50 -> 155 and DEX 16 -> 70 (interpolated) against a real save. ATK: Str and ATK: Dex share this identical curve.
| sl2.ds2.DS2_INV_RANGE |
Shared elemental-defence curve breakpoint rates (per stat point): +6 (1-10), +8 (11-20), +1 (21-60), +0.5 (61-99); base 0.
Magic DEF uses INT, Lightning DEF FTH, Dark DEF min(INT,FTH), Fire DEF the floor-average of INT & FTH ("scales with both"). Verified: INT 3 -> Magic DEF 18, FTH 10 -> Lightning DEF 60, min 3 -> Dark DEF 18, avg 6 -> Fire DEF 36. (fextralife Magic/defence pages.)
DS2 inventory regions (start, end); 16-byte slots throughout.
| sl2.ds2.DS2_KEY_RANGE |
Shared elemental-defence curve breakpoint rates (per stat point): +6 (1-10), +8 (11-20), +1 (21-60), +0.5 (61-99); base 0.
Magic DEF uses INT, Lightning DEF FTH, Dark DEF min(INT,FTH), Fire DEF the floor-average of INT & FTH ("scales with both"). Verified: INT 3 -> Magic DEF 18, FTH 10 -> Lightning DEF 60, min 3 -> Dark DEF 18, avg 6 -> Fire DEF 36. (fextralife Magic/defence pages.)
DS2 inventory regions (start, end); 16-byte slots throughout.
| dict sl2.ds2.DS2_STACKABLE |
| dict sl2.ds2.DS2_UPGRADEABLE = {"weapons", "armors"} |
| sl2.ds2.DS2_REINF_OFF |
| sl2.ds2.DS2_INFUSE_OFF |
| dict sl2.ds2.DS2_INFUSION |
DS2 infusion ids to names.
From Atvaark's DS2 SOTFS Cheat Engine guide attachments (the "Infusion IDs" list). 0 (None) carries no prefix.
| dict sl2.ds2.DS2_GREAT_SOULS |
The four DS2 "Old" great souls (from the Lost Sinner, the Rotten, the Old Iron King, and the Duke's Dear Freja).
The game treats these apart from the ordinary boss souls, so the output does too.
|
protected |
|
protected |
Load the DS2 bonfire→area table (db_ds2/bonfire_areas.json): bonfire id → the area it belongs to, so discovered bonfires group the way DS1's and DS3's do instead of listing 77 flat names.
Generated from the fextralife Bonfires page, which lists every bonfire under its location; the two bonfires sharing the name "Tower of Prayer" are split by the id's own map cluster. Cached. Returns {} if absent, and the grouping then falls back to the flat list.
|
protected |
|
protected |
| dict sl2.ds2.DS2_BOSS_GATE |
Progression gates: a boss proven dead by something the character has.
Only DS2's STRICTLY-LINEAR endgame qualifies — the mid-game is four parallel, largely skippable paths, so a mid-game gate would risk a false kill (the core rule). The endgame is unskippable: Drangleic Castle → Looking Glass Knight → Shrine of Amana → Demon of Song → Undead Crypt → Velstadt → (King's Ring, behind him) → King's Gate → Throne → Throne Watcher & Defender → Nashandra. Sources: fextralife Game Progress Route + King's Ring page.
Bonfire present ⇒ these bosses dead (the bonfire is only reachable past them).
| dict sl2.ds2.DS2_ITEM_GATE |
Inventory item held ⇒ boss dead (item only obtainable past it).
The King's Ring sits in the room behind Velstadt and cannot be had otherwise.
Item ⇒ boss it sits behind. Each item has exactly one documented source, and that source is past the boss's fog gate, so holding it is a certain kill. The two DLC gank bosses drop no soul at all, which is why they need this route: Pharros Mask lies on a corpse past the (Blue) Smelter Demon's arena in Iron Passage, and the Flower Skirt is in the chest between Cave of the Dead's trio and the exit.
| dict sl2.ds2.DS2_BOSS_PREREQ |
Boss defeated ⇒ its mandatory predecessors also defeated (each list is the full transitive set, so a single pass closes it).
Endgame only, where the order is forced.