SL2 Analyzer
Read a FromSoftware .sl2 save and report what is in it
Loading...
Searching...
No Matches
sl2.ds2 Namespace Reference

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).
 

Detailed Description

Dark Souls II, vanilla and Scholar of the First Sin.

Function Documentation

◆ ds2_name()

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().

Here is the caller graph for this function:

◆ ds2_inventory()

sl2.ds2.ds2_inventory (   buf,
  item_db 
)

Sort both DS2 inventory regions into categories.

Returns
(buckets, unknown_count).

Definition at line 276 of file ds2.py.

Referenced by sl2.ds2.ds2_parse().

Here is the caller graph for this function:

◆ ds2_phys_atk()

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().

Here is the caller graph for this function:

◆ ds2_elem_def()

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().

Here is the caller graph for this function:

◆ 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).

Returns
dict: stamina (int), equip_load (float), agility (int), iframes (int|None).

Definition at line 344 of file ds2.py.

References sl2.ds2.ds2_elem_def(), and sl2.ds2.ds2_phys_atk().

Here is the call graph for this function:

◆ ds2_covenants()

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.

Returns
An OrderedDict {covenant: [description]}, or None.

Definition at line 404 of file ds2.py.

Referenced by sl2.ds2.ds2_parse().

Here is the caller graph for this function:

◆ ds2_parse()

sl2.ds2.ds2_parse (   buf,
  item_db,
  game = "ds2sotfs" 
)

Parse one DS2 slot into the unified character dict, or None if empty.

Parameters
gameWhich 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().

Here is the call graph for this function:

◆ load_ds2_bonfires()

sl2.ds2.load_ds2_bonfires (   base_dir)

Definition at line 459 of file ds2.py.

Referenced by sl2.ds2.ds2_augment().

Here is the caller graph for this function:

◆ load_ds2_bonfire_areas()

sl2.ds2.load_ds2_bonfire_areas (   base_dir)

Definition at line 480 of file ds2.py.

Referenced by sl2.ds2.ds2_augment().

Here is the caller graph for this function:

◆ load_ds2_bosses()

sl2.ds2.load_ds2_bosses (   base_dir)

Definition at line 497 of file ds2.py.

◆ load_ds2_boss_souls()

sl2.ds2.load_ds2_boss_souls (   base_dir)

Definition at line 513 of file ds2.py.

◆ ds2_base_name()

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().

Here is the caller graph for this function:

◆ 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:

  • flag: a mapped defeat event flag is set (world block; see boss_flags.json). Verified by the 41-boss differential matrix. Only a handful are mapped.
  • soul: the boss's soul is still in inventory. Cannot be obtained without the kill, but a consumed/traded soul goes invisible.
  • progression: a bonfire the character has can only be reached past this boss (DS2_BOSS_GATE). A boss absent here may still be defeated (its soul consumed and not gated). Sources are merged per boss so overlap reads as corroboration.

Definition at line 609 of file ds2.py.

References sl2.ds2.ds2_base_name().

Referenced by sl2.ds2.ds2_augment().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ds2_visited_bonfires()

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().

Here is the caller graph for this function:

◆ ds2_bonfire_areas()

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.

Parameters
visitedThe (id, name) pairs from ds2_visited_bonfires.
area_dbBonfire id → area, from load_ds2_bonfire_areas.
Returns
The grouped list, or None when no id has a known area (the caller then keeps the flat list rather than inventing an "Unknown" bucket).

Definition at line 690 of file ds2.py.

Referenced by sl2.ds2.ds2_augment().

Here is the caller graph for this function:

◆ 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().

Here is the call graph for this function:

◆ ds2_active_slots()

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.

Returns
The set of active entry indices, or None if the header can't be read or the list came back empty (caller then applies no filter).

Definition at line 752 of file ds2.py.

Variable Documentation

◆ DS2_GAMES

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.

Definition at line 18 of file ds2.py.

◆ DS2_FAMILY

dict sl2.ds2.DS2_FAMILY = {"dsr": "ds1", "ptde": "ds1", "ds2vanilla": "ds2sotfs"}

Game id to the id whose lookup tables it uses (attribute reference, derived stats, themes).

DS1's two releases already collapse this way; DS2's now do too.

Definition at line 23 of file ds2.py.

◆ DS2_NAME_OFF

sl2.ds2.DS2_NAME_OFF

DS2 character-slot offsets (absolute, into decrypted game data).

Definition at line 27 of file ds2.py.

◆ DS2_SOULS_OFF

sl2.ds2.DS2_SOULS_OFF

DS2 character-slot offsets (absolute, into decrypted game data).

Definition at line 27 of file ds2.py.

◆ DS2_SOULMEM_OFF

sl2.ds2.DS2_SOULMEM_OFF

DS2 character-slot offsets (absolute, into decrypted game data).

Definition at line 27 of file ds2.py.

◆ DS2_HP_OFF

sl2.ds2.DS2_HP_OFF

DS2 character-slot offsets (absolute, into decrypted game data).

Definition at line 27 of file ds2.py.

◆ DS2_NG_OFF

sl2.ds2.DS2_NG_OFF

DS2 character-slot offsets (absolute, into decrypted game data).

Definition at line 27 of file ds2.py.

◆ DS2_TITLE_NAME_OFF

sl2.ds2.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.

Block entry i maps to title index (i - slots.start). Used to tell active characters from deleted ghosts.

Definition at line 39 of file ds2.py.

◆ DS2_TITLE_STRIDE

sl2.ds2.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.

Block entry i maps to title index (i - slots.start). Used to tell active characters from deleted ghosts.

Definition at line 39 of file ds2.py.

◆ DS2_TITLE_PLAYTIME_OFF

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.

Definition at line 47 of file ds2.py.

◆ DS2_CLASS_OFF

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.

Definition at line 54 of file ds2.py.

◆ DS2_COVENANT_OFF

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.

Definition at line 54 of file ds2.py.

◆ DS2_CLASS

dict sl2.ds2.DS2_CLASS
Initial value:
1= {
2 1: "Warrior",
3 2: "Knight",
4 4: "Bandit",
5 6: "Cleric",
6 7: "Sorcerer",
7 8: "Explorer",
8 9: "Swordsman",
9 10: "Deprived",
10}

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).

Definition at line 60 of file ds2.py.

◆ DS2_COVENANT

dict sl2.ds2.DS2_COVENANT
Initial value:
1= {
2 1: "Heirs of the Sun",
3 2: "Blue Sentinels",
4 3: "Brotherhood of Blood",
5 4: "Way of Blue",
6 5: "Rat King",
7 6: "Bell Keepers",
8 7: "Dragon Remnants",
9 8: "Company of Champions",
10 9: "Pilgrims of Dark",
11}

Definition at line 72 of file ds2.py.

◆ DS2_COV_DISC_D

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).

Definition at line 93 of file ds2.py.

◆ DS2_COV_RANK_D

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).

Definition at line 93 of file ds2.py.

◆ DS2_COV_MAX_RANK

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).

Definition at line 93 of file ds2.py.

◆ DS2_GENDER_OFF

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.

Definition at line 102 of file ds2.py.

◆ DS2_HOLLOW_OFF

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.

Definition at line 102 of file ds2.py.

◆ DS2_DEATHS_OFF

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.

Definition at line 111 of file ds2.py.

◆ DS2_GENDER

dict sl2.ds2.DS2_GENDER = {0: "Male", 1: "Female"}

DS2 gender enum.

Female = 1, Male = 0 (see DS2_GENDER_OFF). Any other value yields None via .get and the field is omitted rather than shown wrong.

Definition at line 116 of file ds2.py.

◆ DS2_WORLD_ENTRY_DELTA

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.

Definition at line 127 of file ds2.py.

◆ DS2_BONFIRE_FLAG_DELTA

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.

Definition at line 127 of file ds2.py.

◆ DS2_BONFIRE_MIN_RUN

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.

Definition at line 127 of file ds2.py.

◆ DS2_STAT_OFF

sl2.ds2.DS2_STAT_OFF
Initial value:
1= OrderedDict(
2 [
3 ("Vigor", 32),
4 ("Endurance", 34),
5 ("Vitality", 36),
6 ("Attunement", 38),
7 ("Strength", 40),
8 ("Dexterity", 42),
9 ("Adaptability", 48),
10 ("Intelligence", 44),
11 ("Faith", 46),
12 ("Level", 0x38),
13 ]
14)

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.

Definition at line 136 of file ds2.py.

◆ DS2_STAMINA_BASE

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.

Definition at line 160 of file ds2.py.

◆ DS2_EQUIP_BASE

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.

Definition at line 160 of file ds2.py.

◆ DS2_AGL_BASE

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.

Definition at line 160 of file ds2.py.

◆ DS2_IFRAMES

sl2.ds2.DS2_IFRAMES
Initial value:
1= OrderedDict(
2 [
3 (85, 5),
4 (86, 8),
5 (88, 9),
6 (92, 10),
7 (96, 11),
8 (99, 12),
9 (105, 13),
10 (111, 14),
11 (114, 15),
12 (116, 16),
13 ]
14)

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.

Definition at line 165 of file ds2.py.

◆ DS2_SLOT_BREAKS

tuple sl2.ds2.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).

Slot count = how many of these are <= ATN. ATN 4 -> 0 slots (first slot at 10).

Definition at line 183 of file ds2.py.

◆ DS2_PHYS_ATK_BP

sl2.ds2.DS2_PHYS_ATK_BP
Initial value:
1= OrderedDict(
2 [
3 (0, 50),
4 (10, 57),
5 (20, 80),
6 (30, 102),
7 (40, 140),
8 (50, 155),
9 (60, 162),
10 (70, 170),
11 (80, 185),
12 (90, 192),
13 (99, 200),
14 ]
15)

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.

Definition at line 191 of file ds2.py.

◆ DS2_INV_RANGE

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.

Definition at line 214 of file ds2.py.

◆ DS2_KEY_RANGE

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.

Definition at line 214 of file ds2.py.

◆ DS2_STACKABLE

dict sl2.ds2.DS2_STACKABLE
Initial value:
1= {
2 "consumables",
3 "online",
4 "bolts",
5 "spells",
6 "upgrade",
7 "keys",
8 "bosssouls",
9}

DS2 categories whose slot +8 field is a real count (float durability elsewhere).

Weapons/armour/rings/emotes are one instance per slot.

Definition at line 219 of file ds2.py.

◆ DS2_UPGRADEABLE

dict sl2.ds2.DS2_UPGRADEABLE = {"weapons", "armors"}

Categories whose slot +12 field carries a reinforcement level.

Only weapons and armour reinforce in DS2; other categories keep other state there.

Definition at line 232 of file ds2.py.

◆ DS2_REINF_OFF

sl2.ds2.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.

Both verified on a mule save whose high bytes were 1/2/3/4/8.

Definition at line 238 of file ds2.py.

◆ DS2_INFUSE_OFF

sl2.ds2.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.

Both verified on a mule save whose high bytes were 1/2/3/4/8.

Definition at line 238 of file ds2.py.

◆ DS2_INFUSION

dict sl2.ds2.DS2_INFUSION
Initial value:
1= {
2 1: "Fire",
3 2: "Magic",
4 3: "Lightning",
5 4: "Dark",
6 5: "Poison",
7 6: "Bleed",
8 7: "Raw",
9 8: "Enchanted",
10 9: "Mundane",
11}

DS2 infusion ids to names.

From Atvaark's DS2 SOTFS Cheat Engine guide attachments (the "Infusion IDs" list). 0 (None) carries no prefix.

Definition at line 243 of file ds2.py.

◆ DS2_GREAT_SOULS

dict sl2.ds2.DS2_GREAT_SOULS
Initial value:
1= {
2 "Old Witch Soul",
3 "Old Dead One Soul",
4 "Old King Soul",
5 "Old Paledrake Soul",
6}

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.

Definition at line 259 of file ds2.py.

◆ _DS2_BONFIRE_CACHE

dict sl2.ds2._DS2_BONFIRE_CACHE = {}
protected

Load the DS2 bonfire id→name table (db_ds2/bonfires.json, keyed by the low-16-bit id as 4-hex).

Cached after first read. Returns {} if the file is absent.

Definition at line 456 of file ds2.py.

◆ _DS2_AREA_CACHE

dict sl2.ds2._DS2_AREA_CACHE = {}
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.

Definition at line 477 of file ds2.py.

◆ _DS2_BOSS_CACHE

dict sl2.ds2._DS2_BOSS_CACHE = {}
protected

Load the DS2 boss-defeat flag table (db_ds2/boss_flags.json, world-block byte offset as hex → boss name).

Cached. Returns {} if the file is absent.

Definition at line 494 of file ds2.py.

◆ _DS2_BOSS_SOUL_CACHE

dict sl2.ds2._DS2_BOSS_SOUL_CACHE = {}
protected

Load the DS2 boss-soul → boss-name table (db_ds2/boss_souls.json).

Cached.

Definition at line 510 of file ds2.py.

◆ DS2_BOSS_GATE

dict sl2.ds2.DS2_BOSS_GATE
Initial value:
1= {
2 # No-Man's Wharf is only reachable through Heide's Tower of Flame, and the way out
3 # of Heide's opens when the Dragonrider dies — a fog gate, not a suggested order. # This is the one MID-game gate here; the rest of DS2's middle is four parallel,
4 # largely skippable paths, which is why the others below are all endgame.
5 "Unseen Path to Heide": ("Dragonrider",),
6 "Undead Crypt Entrance": ("Looking Glass Knight", "Demon of Song"),
7 "Throne Floor": (
8 "Looking Glass Knight",
9 "Demon of Song",
10 "Velstadt, the Royal Aegis",
11 ),
12}

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).

Definition at line 532 of file ds2.py.

◆ DS2_ITEM_GATE

dict sl2.ds2.DS2_ITEM_GATE
Initial value:
1= {
2 "King's Ring": ("Velstadt, the Royal Aegis",),
3 "Pharros Mask": ("Blue Smelter Demon",),
4 "Flower Skirt": ("Graverobber, Varg, and Cerah",),
5}

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.

Definition at line 554 of file ds2.py.

◆ DS2_BOSS_PREREQ

dict sl2.ds2.DS2_BOSS_PREREQ
Initial value:
1= {
2 "Nashandra": (
3 "Throne Watcher",
4 "Throne Defender",
5 "Velstadt, the Royal Aegis",
6 "Demon of Song",
7 "Looking Glass Knight",
8 ),
9 "Throne Watcher": (
10 "Velstadt, the Royal Aegis",
11 "Demon of Song",
12 "Looking Glass Knight",
13 ),
14 "Throne Defender": (
15 "Velstadt, the Royal Aegis",
16 "Demon of Song",
17 "Looking Glass Knight",
18 ),
19 "Velstadt, the Royal Aegis": ("Demon of Song", "Looking Glass Knight"),
20 "Demon of Song": ("Looking Glass Knight",),
21}

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.

Definition at line 564 of file ds2.py.