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

Functions

 load_ds3_ring_effects (base_dir)
 
 ds3_ring_level (name)
 Split an equipped ring name into its table key and reinforcement level.
 
 ds3_attach_ring_effects (ch, base_dir)
 Attach each equipped ring's documented effect, and the subset of those effects that is a structured modifier to a derived stat.
 
 ds3_ring_bonuses (ring_mods)
 Sum the worn rings' structured modifiers.
 
 ds3_derived_stats (stats, ring_mods=None)
 DS3 base derived stats that are closed-form functions of attributes only, plus the worn rings' documented bonuses to those same three values.
 
 load_ds3_bonfires (base_dir)
 
 load_ds3_boss_flags (base_dir)
 
 load_ds3_boss_victory (base_dir)
 
 load_ds3_lord_cinders (base_dir)
 
 load_ds3_questlines (base_dir)
 
 load_ds3_pickups (base_dir)
 
 load_ds3_enemies (base_dir)
 
 load_ds3_npcs (base_dir)
 
 load_ds3_covenants (base_dir)
 
 load_ds3_endings (base_dir)
 
 ds3_goods_cat (iid)
 Refine a DS3 goods id to its finer category (see DS3_GOODS_RANGES).
 
 ds3_item_cat (iid, cat)
 Refine a scanned DS3 id's category.
 
 scan_inventory (buf, iddb)
 Find inventory by scanning for known item ids in fixed-size records.
 
 ds3_find_stats (buf)
 Locate the DS3 stat block by content, or None if none validates.
 
 ds3_embered (buf, v)
 DS3 embered state for a slot, or None.
 
 ds3_covenant (buf, v)
 DS3 covenant name for a slot, or None.
 
 ds3_resolve_weapon (iddb, iid)
 Resolve an equipped DS3 weapon id to a name, unwrapping the baked "+N".
 
 ds3_resolve_estus (iid)
 Resolve a DS3 goods id to an Estus flask name with its +N, or None.
 
 ds3_gaitem_map (buf)
 Map every GaItem handle to its item id by walking the GaItem array.
 
 ds3_equipped_weapons (buf, iddb, v)
 Equipped weapons (up to three per hand) from EquipGameData.
 
 ds3_equipped_armor (buf, iddb, v)
 Equipped armour (the four protection slots) from EquipGameData.
 
 ds3_equipped_rings (buf, iddb, v)
 Equipped rings (up to four) from EquipGameData.
 
 ds3_equipped_ammo (buf, iddb, v)
 Equipped ammunition (the arrow/bolt quiver slots) from EquipGameData.
 
 ds3_parse (buf, iddb, name)
 Parse one DS3 slot into the unified dict (full tier where stats validate).
 
 ds3_playtime (menu_data, i)
 
 ds3_event_flag_base (buf)
 Locate the DS3 event-flag region base in a decrypted slot, or None.
 
 ds3_attach_flags (ch, buf, base, base_dir)
 Read DS3 bonfires + boss-defeat flags off the event-flag region and attach them to ch: bonfire_areas as [(area, count, [names])] — every lit bonfire named from load_ds3_bonfires — and merge flag boss evidence into ch["bosses"] (deduping with any soul/gate evidence already there, from both the per-map and the group-6 victory tables), cinders as the Lords of Cinder whose ashes are on the throne, and endings as the endings this character has reached (cumulative, so an NG+ save can hold several).
 
 ds3_journey (buf, base)
 

Variables

tuple DS3_SLOT_BREAKS = (10, 14, 18, 24, 30, 40, 50, 60, 80, 99)
 DS3 attunement-slot breakpoints (fextralife Attunement table): the nth entry is the ATN needed for the nth spell slot.
 
dict _DS3_RING_CACHE = {}
 Load db_ds3/ring_effects.json: ring name → {"effect": [lines], "mods": {level: {stat: value}}}.
 
dict _DS3_BONFIRE_CACHE = {}
 Load the DS3 bonfire table (db_ds3/bonfires.json): area → list of [distance, bit, name], one entry per bonfire.
 
dict _DS3_BOSS_CACHE = {}
 Load the DS3 boss-defeat flag table (db_ds3/boss_flags.json): boss name → [distance, bit] of its single "boss dead" event flag (the 13xxxx8xx per-map flag).
 
dict _DS3_VICTORY_CACHE = {}
 Load the DS3 boss-victory flag table (db_ds3/boss_victory.json): boss name → [distance, bit] of its 63xx "boss victory" event flag, a SECOND independent kill signal that survives the soul being consumed.
 
dict _DS3_CINDER_CACHE = {}
 Load the DS3 Lords-of-Cinder table (db_ds3/lord_cinders.json): lord name → [distance, bit] of the flag set when that lord's Cinders are placed on the Firelink throne.
 
dict _DS3_QUEST_CACHE = {}
 Load the DS3 NPC-questline table (db_ds3/questlines.json): NPC/source → list of [distance, bit, reward] — one per reward that NPC hands out, each a "you received this" event flag in the common group 50006.
 
dict _DS3_PICKUP_CACHE = {}
 Load the DS3 world item-pickup table (db_ds3/item_pickups.json): area → list of [distance, bit, item] — one per one-off item lying in that area, each a "you picked this up" event flag in the world groups 533xx–540xx.
 
dict _DS3_ENEMY_CACHE = {}
 Load the one-time-enemy table (db_ds3/enemies.json): area → list of [distance, bit, enemy type] — one per enemy that does not respawn and whose death DS3 therefore has to remember.
 
dict _DS3_NPC_CACHE = {}
 Load the DS3 NPC-state table (db_ds3/npcs.json): family → [[distance, bit, label]] — NPCs killed, NPCs turned hostile, questline states.
 
dict _DS3_COV_CACHE = {}
 Load the DS3 covenant table (db_ds3/covenants.json, {covenant: [[region distance, bit, what it proves]]}).
 
dict _DS3_ENDING_CACHE = {}
 Load the DS3 endings table (db_ds3/endings.json): ending name → [distance, bit].
 
 DS3_RECORD
 DS3 held-item record size and the offset of the quantity within it.
 
 DS3_QTY_OFF
 DS3 held-item record size and the offset of the quantity within it.
 
 DS3_STAT_D
 DS3 stat block as signed distances from the Vigor field (the anchor).
 
 DS3_HP_D
 DS3 max HP, FP, stamina, soul level and souls, same anchor-relative scheme.
 
 DS3_FP_D
 DS3 max HP, FP, stamina, soul level and souls, same anchor-relative scheme.
 
 DS3_STAM_D
 DS3 max HP, FP, stamina, soul level and souls, same anchor-relative scheme.
 
 DS3_LEVEL_D
 DS3 max HP, FP, stamina, soul level and souls, same anchor-relative scheme.
 
 DS3_SOULS_D
 DS3 max HP, FP, stamina, soul level and souls, same anchor-relative scheme.
 
int DS3_EMBER_D = 188
 DS3 embered flag: a lone uint8 at +188 in the stat-mirror struct behind the stat anchor — 1 = embered, 0 = hollow.
 
int DS3_COVENANT_D = 3944
 DS3 covenant: a uint32 EQUIP HANDLE at +3944 from the stat anchor, not a small enum — the game models the covenant as a worn accessory, so the field holds 0xA00027xx and the covenant's own item id is the low 28 bits (Rosaria's Fingers 0xA0002760 -> 10080).
 
dict DS3_COVENANT
 
int DS3_LEVEL_BASE = 89
 DS3's soul-level identity: level == (sum of all nine attributes) - 89.
 
int SCAN_MIN_RUN = 3
 Shortest run of consecutive records that counts as real inventory.
 
int DS3_MAX_RUN_GAP = 48
 Largest gap (bytes) a run may bridge.
 
int DS3_GOODS_ID_BASE = 0x40000000
 DS3 goods ids carry a type prefix (0x40000000); the real id is what the game's own EquipParamGoods table uses, and it blocks out cleanly by kind.
 
tuple DS3_GOODS_RANGES
 
dict DS3_GOODS_OVERRIDE
 The two flask upgrades that sit in the key-item block but are materials.
 
 DS3_AMMO_LO
 Where ammunition lives in the weapon id space.
 
 DS3_AMMO_HI
 Where ammunition lives in the weapon id space.
 
int DS3_EQUIP_D = 664
 EquipGameData sits a fixed 664 bytes past the stat (Vigor) anchor — invariant across saves whose anchor itself moves (56124..75156 on the Joy ladder), so it is read at a fixed anchor-relative offset, not searched for.
 
 DS3_ARMOR_SLOTS
 Armour sub-offsets inside EquipGameData (base +0x20..+0x2C), in the game's own head-to-toe order.
 
tuple DS3_RING_SLOTS = (0x34, 0x38, 0x3C, 0x40)
 The four ring sub-offsets inside EquipGameData (base +0x34..+0x40).
 
tuple DS3_AMMO_SLOTS = (0x08, 0x0C, 0x10, 0x14)
 The ammo sub-offsets (Arrow 1 / Bolt 1 / Arrow 2 / Bolt 2) at base +0x08..+0x14 — GaItem handles like weapons/armour (arrows and bolts share the db's bolts category).
 
 DS3_WEAPON_SLOTS
 The six weapon sub-offsets, in the game's own two-hand-by-slot order.
 
int DS3_FISTS = 110000
 Item id of the default bare fist (an empty weapon slot reads this, not a null handle), so a slot resolving to it is skipped as "unarmed".
 
int DS3_REINF_MAX = 10
 Max reinforcement level, a sanity bound on the id-baked "+N".
 
int DS3_GOODS_TYPE = 0x40000000
 The two Estus flasks' base goods ids, and the reinforcement ceiling.
 
tuple DS3_ESTUS = ((150, "Estus Flask"), (190, "Ashen Estus Flask"))
 
int DS3_ESTUS_MAX = 10
 
int DS3_RING_ID_MASK = 0x0FFFFFFF
 A ring's equip handle encodes its own id: the low 28 bits are shared and the type nibble is 0xA where the goods/ring id's is 0x2 — so the id is the handle with its top nibble rewritten to 2 (verified 4/4 on the Joy ring set: 0xa0004e20 → 0x20004e20 = Life Ring, etc.).
 
int DS3_RING_ID_TYPE = 0x20000000
 
dict DS3_DB_FILES
 DS3 id-scan tables: filename stem to category.
 
int DS3_ROSTER_PLAYTIME_OFF = 38
 Play time (seconds, uint32) inside slot i's DS3 roster descriptor.
 
int DS3_GAITEM_START = 0x6C
 
int DS3_GAITEM_SLOTS = 6144
 
int DS3_GAITEM_BIG = 60
 
tuple DS3_GAITEM_TYPES_BIG = (0x80000000, 0x90000000)
 
float DS3_FLAG_MAX_DENSITY = 0.01
 
int DS3_FLAG_SAMPLE = 0x8000
 
int DS3_NG_MAX = 99
 DS3 New Game+ cycle (journey count), a uint16 just before the event-flag region, or None.
 

Detailed Description

Dark Souls III.

Function Documentation

◆ load_ds3_ring_effects()

sl2.ds3.load_ds3_ring_effects (   base_dir)

Definition at line 25 of file ds3.py.

Referenced by sl2.ds3.ds3_attach_ring_effects().

Here is the caller graph for this function:

◆ ds3_ring_level()

sl2.ds3.ds3_ring_level (   name)

Split an equipped ring name into its table key and reinforcement level.

The db spells three of the four Ring of Favor ids "Ring of Favor+N" and the fourth "Ring of Favor +3", so the suffix is matched with the space optional.

Returns
(base name, level).

Definition at line 41 of file ds3.py.

Referenced by sl2.ds3.ds3_attach_ring_effects().

Here is the caller graph for this function:

◆ ds3_attach_ring_effects()

sl2.ds3.ds3_attach_ring_effects (   ch,
  base_dir 
)

Attach each equipped ring's documented effect, and the subset of those effects that is a structured modifier to a derived stat.

ch["ring_effects"] is [[name, effect text]] for the display; the text is the base line plus the worn level's own line, so a +2 ring shows what +2 does rather than only what the base ring does. ch["ring_mods"] is [[name, mods]] for the rings the derived stats fold in. Both are omitted entirely when nothing resolves, so a game without the table renders exactly as before.

Parameters
chThe character dict.
base_dirFolder holding db_ds3.

Definition at line 55 of file ds3.py.

References sl2.ds3.ds3_ring_level(), and sl2.ds3.load_ds3_ring_effects().

Here is the call graph for this function:

◆ ds3_ring_bonuses()

sl2.ds3.ds3_ring_bonuses (   ring_mods)

Sum the worn rings' structured modifiers.

Parameters
ring_modsch["ring_mods"], or None.
Returns
(totals, contributors) keyed by stat; contributors is {stat: [(ring name, value)]} so the render can name who supplied what.

Definition at line 85 of file ds3.py.

Referenced by sl2.ds3.ds3_derived_stats().

Here is the caller graph for this function:

◆ ds3_derived_stats()

sl2.ds3.ds3_derived_stats (   stats,
  ring_mods = None 
)

DS3 base derived stats that are closed-form functions of attributes only, plus the worn rings' documented bonuses to those same three values.

The attribute-only halves are the three the character screen shows that don't need gear: attunement slots (breakpoint count), base Equip Load (40 + Vitality) and base Item Discovery (100 + Luck, hard cap 199). HP/FP/stamina are read from the save, not recomputed; poise is gear-only in DS3, and defences, resistances and attack power are gear- and level-scaled, so none of those are derived here. Formulas from the fextralife Equipment Load / Attunement / Item Discovery pages.

Rings are folded in because these three values are NOT stored in the save, so unlike Max HP and stamina nothing else would ever show the bonus. Only the six rings with a structured mod count, and only into the value they name — a ring that boosts sorcery damage has nothing to add to a number printed here. Equip-load percentages are summed rather than compounded (DS3's equip-load rates are additive); the base and each contributor are kept so the render prints the sum's parts and the reader can check it. equip_load_base / item_discovery_base / slots_base keep the ringless figures.

Parameters
statsThe attribute dict.
ring_modsch["ring_mods"], or None.

Definition at line 115 of file ds3.py.

References sl2.ds3.ds3_ring_bonuses().

Here is the call graph for this function:

◆ load_ds3_bonfires()

sl2.ds3.load_ds3_bonfires (   base_dir)

Definition at line 148 of file ds3.py.

Referenced by sl2.ds3.ds3_attach_flags().

Here is the caller graph for this function:

◆ load_ds3_boss_flags()

sl2.ds3.load_ds3_boss_flags (   base_dir)

Definition at line 167 of file ds3.py.

◆ load_ds3_boss_victory()

sl2.ds3.load_ds3_boss_victory (   base_dir)

Definition at line 188 of file ds3.py.

◆ load_ds3_lord_cinders()

sl2.ds3.load_ds3_lord_cinders (   base_dir)

Definition at line 209 of file ds3.py.

Referenced by sl2.ds3.ds3_attach_flags().

Here is the caller graph for this function:

◆ load_ds3_questlines()

sl2.ds3.load_ds3_questlines (   base_dir)

Definition at line 230 of file ds3.py.

Referenced by sl2.ds3.ds3_attach_flags().

Here is the caller graph for this function:

◆ load_ds3_pickups()

sl2.ds3.load_ds3_pickups (   base_dir)

Definition at line 253 of file ds3.py.

Referenced by sl2.ds3.ds3_attach_flags().

Here is the caller graph for this function:

◆ load_ds3_enemies()

sl2.ds3.load_ds3_enemies (   base_dir)

Definition at line 276 of file ds3.py.

Referenced by sl2.ds3.ds3_attach_flags().

Here is the caller graph for this function:

◆ load_ds3_npcs()

sl2.ds3.load_ds3_npcs (   base_dir)

Definition at line 297 of file ds3.py.

Referenced by sl2.ds3.ds3_attach_flags().

Here is the caller graph for this function:

◆ load_ds3_covenants()

sl2.ds3.load_ds3_covenants (   base_dir)

Definition at line 320 of file ds3.py.

◆ load_ds3_endings()

sl2.ds3.load_ds3_endings (   base_dir)

Definition at line 345 of file ds3.py.

Referenced by sl2.ds3.ds3_attach_flags().

Here is the caller graph for this function:

◆ ds3_goods_cat()

sl2.ds3.ds3_goods_cat (   iid)

Refine a DS3 goods id to its finer category (see DS3_GOODS_RANGES).

An id outside every block keeps goods, so an unmapped one is still printed.

Definition at line 483 of file ds3.py.

Referenced by sl2.ds3.ds3_item_cat().

Here is the caller graph for this function:

◆ ds3_item_cat()

sl2.ds3.ds3_item_cat (   iid,
  cat 
)

Refine a scanned DS3 id's category.

The one place that knows the id blocks differ from the file an id happens to be listed in.

Definition at line 504 of file ds3.py.

References sl2.ds3.ds3_goods_cat().

Here is the call graph for this function:

◆ scan_inventory()

sl2.ds3.scan_inventory (   buf,
  iddb 
)

Find inventory by scanning for known item ids in fixed-size records.

Collects every offset whose uint32 is a known id, groups those on the same 16-byte record grid into runs (bridging holes left by untabled items, up to DS3_MAX_RUN_GAP), and keeps runs of at least SCAN_MIN_RUN. Each surviving record contributes its id and quantity. Duplicate ids are summed. A quantity outside a sane range drops the record — a cheap guard against a false run of look-alike bytes.

Parameters
bufThe decrypted slot data.
iddbThe flat id lookup from load_scan_db.
Returns
(buckets, unknown_count), buckets mapping category to (name, qty).

Definition at line 523 of file ds3.py.

References sl2.ds3.ds3_resolve_estus(), and sl2.ds3.ds3_resolve_weapon().

Referenced by sl2.ds3.ds3_parse().

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

◆ ds3_find_stats()

sl2.ds3.ds3_find_stats (   buf)

Locate the DS3 stat block by content, or None if none validates.

DS3's stat offsets move between patches, so the block is not read from a fixed offset — it is found. For each 4-aligned position, treat the next nine uint32 as the attributes and accept only where each is 1..99 and their sum minus DS3_LEVEL_BASE equals the stored soul level. That identity is DS3's own level formula, so a coincidental match on unrelated bytes is not credible.

Parameters
bufThe decrypted slot data.
Returns
The Vigor-field offset (the anchor), or None.

Definition at line 578 of file ds3.py.

Referenced by sl2.ds3.ds3_parse().

Here is the caller graph for this function:

◆ ds3_embered()

sl2.ds3.ds3_embered (   buf,
  v 
)

DS3 embered state for a slot, or None.

Parameters
vThe stat anchor from ds3_find_stats (None → feature off). Reads the DS3_EMBER_D byte and only trusts a clean boolean: 1 → True (embered), 0 → False (hollow), anything else → None (never guess).
Returns
True/False, or None when unlocated or out of range.

Definition at line 601 of file ds3.py.

Referenced by sl2.ds3.ds3_parse().

Here is the caller graph for this function:

◆ ds3_covenant()

sl2.ds3.ds3_covenant (   buf,
  v 
)

DS3 covenant name for a slot, or None.

Parameters
vThe stat anchor from ds3_find_stats (None → feature off). The field is an equip handle, so the covenant id is its low 28 bits and only ids in DS3_COVENANT are named — 0 means no covenant, and a cheated slot's junk handle resolves to nothing and is omitted rather than guessed.
Returns
The covenant name, or None.

Definition at line 613 of file ds3.py.

Referenced by sl2.ds3.ds3_parse().

Here is the caller graph for this function:

◆ ds3_resolve_weapon()

sl2.ds3.ds3_resolve_weapon (   iddb,
  iid 
)

Resolve an equipped DS3 weapon id to a name, unwrapping the baked "+N".

The GaItem array stores a weapon at its EXACT id, and reinforcement is folded into that id as base+infusion*100+level (the same scheme DS1 uses; a Deep Battle Axe +0/+1 read out as 7010900/7010901). Unlike DS1, the DS3 db keys every infusion by name (7010900 → "Deep Battle Axe"), so only the LEVEL (the units) is stripped — the base-infusion id already carries the infusion name. A direct hit wins; otherwise the level is peeled off and a " +N" suffix appended. The infusion itself is thus named for free; the level shows when > 0.

Parameters
iddbThe flat DS3 id lookup.
iidThe exact equipped-weapon id.
Returns
The display name, or None if it is not a (base) weapon.

Definition at line 686 of file ds3.py.

Referenced by sl2.ds3.ds3_equipped_weapons(), and sl2.ds3.scan_inventory().

Here is the caller graph for this function:

◆ ds3_resolve_estus()

sl2.ds3.ds3_resolve_estus (   iid)

Resolve a DS3 goods id to an Estus flask name with its +N, or None.

Only consulted after the id-scan table misses, so it can never shadow a real listed good.

Parameters
iidThe exact goods id.
Returns
The name, or None.

Definition at line 719 of file ds3.py.

Referenced by sl2.ds3.scan_inventory().

Here is the caller graph for this function:

◆ ds3_gaitem_map()

sl2.ds3.ds3_gaitem_map (   buf)

Map every GaItem handle to its item id by walking the GaItem array.

Equip slots reference items by handle; the array (same walk the event-flag base uses) is the handle→id table. Big records (weapon/armour types) are 60 bytes, everything else 8. Stops at the first unreadable slot.

Parameters
bufThe decrypted slot.
Returns
{ handle: item_id } for every populated entry.

Definition at line 747 of file ds3.py.

Referenced by sl2.ds3.ds3_equipped_ammo(), sl2.ds3.ds3_equipped_armor(), and sl2.ds3.ds3_equipped_weapons().

Here is the caller graph for this function:

◆ ds3_equipped_weapons()

sl2.ds3.ds3_equipped_weapons (   buf,
  iddb,
  v 
)

Equipped weapons (up to three per hand) from EquipGameData.

Reads the six handles at DS3_WEAPON_SLOTS, resolves each through the GaItem map, and keeps a slot only when it lands on a real weapons item that is not the bare DS3_FISTS (an empty hand reads Fists, not a null handle). Right/left labelling is verified by a weapon-swap differential; the resolved id carries both the infusion (a Deep weapon names itself) and the reinforcement, which ds3_resolve_weapon peels off as a " +N" suffix (verified by a Deep Battle Axe +0→+1 differential: id 7010900 → 7010901).

Parameters
bufThe decrypted slot.
iddbThe flat DS3 id lookup.
vThe stat anchor (None → feature off).
Returns
{ slot label: weapon name } for each occupied, resolvable slot.

Definition at line 774 of file ds3.py.

References sl2.ds3.ds3_gaitem_map(), and sl2.ds3.ds3_resolve_weapon().

Referenced by sl2.ds3.ds3_parse().

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

◆ ds3_equipped_armor()

sl2.ds3.ds3_equipped_armor (   buf,
  iddb,
  v 
)

Equipped armour (the four protection slots) from EquipGameData.

Reads the four handles at DS3_ARMOR_SLOTS, resolves each through the GaItem map, and keeps it only when it lands on a real armour item — a self-consistency gate: an equipped piece is one you own, and the category proves the slot read a protector and not a stray weapon/ring handle. Verified across the Joy ladder, including a Northern→Fallen Knight set change and a lone gauntlet swap, so the slots track real gear. Weapons, rings and the covenant slot are NOT read: their save layout does not match the runtime editor tables (the +0x50 slot holds a left-hand weapon, not the covenant), and pinning them needs a swap differential — omitted, not guessed.

Parameters
bufThe decrypted slot.
iddbThe flat DS3 id lookup.
vThe stat anchor (None → feature off).
Returns
{ slot: armour name } for each occupied, resolvable slot (may be empty).

Definition at line 806 of file ds3.py.

References sl2.ds3.ds3_gaitem_map().

Referenced by sl2.ds3.ds3_parse().

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

◆ ds3_equipped_rings()

sl2.ds3.ds3_equipped_rings (   buf,
  iddb,
  v 
)

Equipped rings (up to four) from EquipGameData.

Each ring slot holds an accessory handle whose id is the handle with its type nibble rewritten from 0xA to 0x2 (DS3_RING_ID_TYPE) — rings are not in the GaItem array, so this direct transform stands in for a lookup. A slot is kept only when the derived id is a real rings item (self-consistency gate). The transform also carries a ring's reinforcement, so a +N ring names itself (the all-items mule reads "Ring of Steel Protection +3"). Verified across the Joy ladder, tracking her 2→4 ring growth.

Parameters
bufThe decrypted slot.
iddbThe flat DS3 id lookup.
vThe stat anchor (None → feature off).
Returns
An ordered list of ring names (may be empty).

Definition at line 834 of file ds3.py.

Referenced by sl2.ds3.ds3_parse().

Here is the caller graph for this function:

◆ ds3_equipped_ammo()

sl2.ds3.ds3_equipped_ammo (   buf,
  iddb,
  v 
)

Equipped ammunition (the arrow/bolt quiver slots) from EquipGameData.

The four slots at DS3_AMMO_SLOTS hold GaItem handles resolved through the same handle→id map as armour; a slot is kept only when it lands on a bolts item (the db's shared arrow/bolt category), which gates out an empty or non-ammo slot. Verified on the Joy ladder (Standard Arrow/Bolt) and the all-items mule (Millwood Greatarrow, Exploding Bolt, …).

Parameters
bufThe decrypted slot.
iddbThe flat DS3 id lookup.
vThe stat anchor (None → feature off).
Returns
An ordered list of ammo names (may be empty).

Definition at line 861 of file ds3.py.

References sl2.ds3.ds3_gaitem_map().

Referenced by sl2.ds3.ds3_parse().

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

◆ ds3_parse()

sl2.ds3.ds3_parse (   buf,
  iddb,
  name 
)

Parse one DS3 slot into the unified dict (full tier where stats validate).

Inventory comes from the id-scan; the name is supplied by the caller from the load-screen roster. Stats are located by content (ds3_find_stats) and, when the level identity confirms them, promote the slot to full tier. When it does not (an unrecognised patch), stats are dropped and the slot stays inventory tier — a missing number beats a wrong one. Origin class and NG+ are not calibrated and are omitted. Returns None when the slot has no inventory.

Parameters
bufThe decrypted slot data.
iddbThe flat DS3 id lookup.
nameThe character name from the roster, or None.
Returns
A unified character dict, or None if the slot is empty.

Definition at line 888 of file ds3.py.

References sl2.ds3.ds3_covenant(), sl2.ds3.ds3_embered(), sl2.ds3.ds3_equipped_ammo(), sl2.ds3.ds3_equipped_armor(), sl2.ds3.ds3_equipped_rings(), sl2.ds3.ds3_equipped_weapons(), sl2.ds3.ds3_find_stats(), and sl2.ds3.scan_inventory().

Here is the call graph for this function:

◆ ds3_playtime()

sl2.ds3.ds3_playtime (   menu_data,
  i 
)

Definition at line 949 of file ds3.py.

◆ ds3_event_flag_base()

sl2.ds3.ds3_event_flag_base (   buf)

Locate the DS3 event-flag region base in a decrypted slot, or None.

Walks the same block chain the alfizari editor uses. Every read is bounds-checked (u32 returns None past the buffer), so a short or edited save turns the feature off rather than reading garbage. The located base is then sanity-checked on bit density (see DS3_FLAG_MAX_DENSITY) — a mislocated base reads dense and is rejected, turning every flag feature off for that slot rather than inventing progress.

Returns
The base offset, or None.

Definition at line 1002 of file ds3.py.

◆ ds3_attach_flags()

sl2.ds3.ds3_attach_flags (   ch,
  buf,
  base,
  base_dir 
)

Read DS3 bonfires + boss-defeat flags off the event-flag region and attach them to ch: bonfire_areas as [(area, count, [names])] — every lit bonfire named from load_ds3_bonfires — and merge flag boss evidence into ch["bosses"] (deduping with any soul/gate evidence already there, from both the per-map and the group-6 victory tables), cinders as the Lords of Cinder whose ashes are on the throne, and endings as the endings this character has reached (cumulative, so an NG+ save can hold several).

No-op if base is None (region not located).

Parameters
chA parsed character.
bufThe decrypted slot.
baseThe event-flag base from ds3_event_flag_base.
base_dirRepo root holding the db_* folders.

Definition at line 1040 of file ds3.py.

References sl2.ds3.load_ds3_bonfires(), sl2.ds3.load_ds3_endings(), sl2.ds3.load_ds3_enemies(), sl2.ds3.load_ds3_lord_cinders(), sl2.ds3.load_ds3_npcs(), sl2.ds3.load_ds3_pickups(), and sl2.ds3.load_ds3_questlines().

Here is the call graph for this function:

◆ ds3_journey()

sl2.ds3.ds3_journey (   buf,
  base 
)

Definition at line 1151 of file ds3.py.

Variable Documentation

◆ DS3_SLOT_BREAKS

tuple sl2.ds3.DS3_SLOT_BREAKS = (10, 14, 18, 24, 30, 40, 50, 60, 80, 99)

DS3 attunement-slot breakpoints (fextralife Attunement table): the nth entry is the ATN needed for the nth spell slot.

Slots = count of these <= ATN.

Definition at line 14 of file ds3.py.

◆ _DS3_RING_CACHE

dict sl2.ds3._DS3_RING_CACHE = {}
protected

Load db_ds3/ring_effects.json: ring name → {"effect": [lines], "mods": {level: {stat: value}}}.

Effect text is one line for the base ring plus one per reinforcement level, verbatim from the fextralife Rings table; mods exists only for the six rings that move a value this tool derives. Generated by scratch/gen_ds3_ring_effects.py. Cached. Returns {} if absent.

Definition at line 22 of file ds3.py.

◆ _DS3_BONFIRE_CACHE

dict sl2.ds3._DS3_BONFIRE_CACHE = {}
protected

Load the DS3 bonfire table (db_ds3/bonfires.json): area → list of [distance, bit, name], one entry per bonfire.

Distance is from the event-flag base, bit is within that byte. Generated from the SoulSplitter/TGA flag list via the confirmed flag-id→bit formula (each bonfire flag f: group f//1000, n=f%1000, byte = (n>>5)*4 + 3-((n&31)>>3), bit = 7-(n&7); save distance = memory byte + 111, one constant delta verified across all 16 groups against real saves). Cached. Returns {} if absent.

Definition at line 145 of file ds3.py.

◆ _DS3_BOSS_CACHE

dict sl2.ds3._DS3_BOSS_CACHE = {}
protected

Load the DS3 boss-defeat flag table (db_ds3/boss_flags.json): boss name → [distance, bit] of its single "boss dead" event flag (the 13xxxx8xx per-map flag).

Generated from the SoulSplitter boss list via the same flag-id→bit formula as bonfires; every offset independently reproduced the old hand-checked table, so a set bit is a certain kill. Cached. Returns {} if absent.

Definition at line 164 of file ds3.py.

◆ _DS3_VICTORY_CACHE

dict sl2.ds3._DS3_VICTORY_CACHE = {}
protected

Load the DS3 boss-victory flag table (db_ds3/boss_victory.json): boss name → [distance, bit] of its 63xx "boss victory" event flag, a SECOND independent kill signal that survives the soul being consumed.

These live in common group 6, whose save base (879) was derived from the Rosaria join differential and is confirmed here by fifteen more flags: walking the whole ladder, every one first turns on in exactly the snapshot the boss died in. Covers Stray Demon, which the per-map table does not. Cached. Returns {} if absent.

Definition at line 185 of file ds3.py.

◆ _DS3_CINDER_CACHE

dict sl2.ds3._DS3_CINDER_CACHE = {}
protected

Load the DS3 Lords-of-Cinder table (db_ds3/lord_cinders.json): lord name → [distance, bit] of the flag set when that lord's Cinders are placed on the Firelink throne.

All four are listed now, each pinned by its own offering window (14000125 Abyss Watchers first — the only flag gained anywhere in the m40 group across a 46-second window — then Yhorm, Aldrich, and Twin Princes; they turned out to be the four ODD ids in one byte, which is what settled the last seat). Cached. Returns {}.

Definition at line 206 of file ds3.py.

◆ _DS3_QUEST_CACHE

dict sl2.ds3._DS3_QUEST_CACHE = {}
protected

Load the DS3 NPC-questline table (db_ds3/questlines.json): NPC/source → list of [distance, bit, reward] — one per reward that NPC hands out, each a "you received this" event flag in the common group 50006.

The group-50006 save base (86639, region-relative) was derived empirically from a Hawkwood Heavy-Gem differential (the map-flag k*0x500 bases don't cover the common groups) and verified against a real save. A set flag means that reward was obtained — a questline-progress floor. Cached. Returns {} if absent.

Definition at line 227 of file ds3.py.

◆ _DS3_PICKUP_CACHE

dict sl2.ds3._DS3_PICKUP_CACHE = {}
protected

Load the DS3 world item-pickup table (db_ds3/item_pickups.json): area → list of [distance, bit, item] — one per one-off item lying in that area, each a "you picked this up" event flag in the world groups 533xx–540xx.

Only SIX groups are in the file, the ones whose save base could be derived by windowed ladder timing (an item's flag must read 0 in every snapshot before the character first held it and 1 in every snapshot after, plus no flag in the group may ever clear); all six landed on the published k*0x500 + 111 grid, which is independent corroboration. Groups whose base is still unknown are ABSENT rather than guessed — a wrong base invents pickups. Cached. Returns {} if absent.

Definition at line 250 of file ds3.py.

◆ _DS3_ENEMY_CACHE

dict sl2.ds3._DS3_ENEMY_CACHE = {}
protected

Load the one-time-enemy table (db_ds3/enemies.json): area → list of [distance, bit, enemy type] — one per enemy that does not respawn and whose death DS3 therefore has to remember.

Mimics, Crystal Lizards, Black Knights, the Boreal Outrider Knights. Extracted from the committed .emevd, where each is a call to a common_func template taking a death flag and an entity id, so these are DEATH flags rather than the drop flags the same enemies' loot already sets. Trusted because the count climbs 3 → 98 with no regressions across a 79-save ladder and the per-area breakdown tracks where the character actually went; see tools/gen_ds3_enemies.py. Unlike the per-map boss flags these survive a new journey. Cached. Returns {} if absent.

Definition at line 273 of file ds3.py.

◆ _DS3_NPC_CACHE

dict sl2.ds3._DS3_NPC_CACHE = {}
protected

Load the DS3 NPC-state table (db_ds3/npcs.json): family → [[distance, bit, label]] — NPCs killed, NPCs turned hostile, questline states.

Generated by tools/gen_ds3_npcs.py, on the common-group base 111 + 128*g derived there and confirmed by a dated kill on the ladder. Two caveats live in that generator and are repeated in the section note: an English description can sit on the wrong flag (one demonstrably does), and a row with no description prints an entity id because inventing a name is worse. Cached. Returns {} if absent.

Definition at line 294 of file ds3.py.

◆ _DS3_COV_CACHE

dict sl2.ds3._DS3_COV_CACHE = {}
protected

Load the DS3 covenant table (db_ds3/covenants.json, {covenant: [[region distance, bit, what it proves]]}).

Built from the same item-pickup flag list as the questlines, on the group-6 base (879) derived from a real Rosaria's-Fingers join differential and confirmed by the whole ladder reading chronologically (Way of Blue and Warrior of Sunlight appearing together at the High Wall → Undead Settlement step, Blue Sentinels at Road of Sacrifices, Rosaria only in the final save, and no DLC or rank flags anywhere). A set flag means the covenant was found, or that rank reward collected — a floor, like the questlines. Cached. Returns {} if absent.

Definition at line 317 of file ds3.py.

◆ _DS3_ENDING_CACHE

dict sl2.ds3._DS3_ENDING_CACHE = {}
protected

Load the DS3 endings table (db_ds3/endings.json): ending name → [distance, bit].

All four endings live in ONE byte, one bit each — the same shape as the lord-cinder byte. Three bits were pinned by a three-way differential: the same pre-ending save was finished three different ways, and each ending flipped exactly one bit of it (bit 7 Link the First Flame, bit 5 The End of Fire, bit 4 the Fire-Keeper-slain variant), with nine further flips common to all three and so generic. The remaining bit 6 is The Usurpation of Fire BY ELIMINATION — the game has four endings, three seats are pinned by a labelled save, and the family is closed; it is the one field here not observed set, because reaching it needs a save from a playthrough that took it. Flags are cumulative, so a character can hold more than one. Cached. Returns {} if absent.

Definition at line 342 of file ds3.py.

◆ DS3_RECORD

sl2.ds3.DS3_RECORD

DS3 held-item record size and the offset of the quantity within it.

Definition at line 357 of file ds3.py.

◆ DS3_QTY_OFF

sl2.ds3.DS3_QTY_OFF

DS3 held-item record size and the offset of the quantity within it.

Definition at line 357 of file ds3.py.

◆ DS3_STAT_D

sl2.ds3.DS3_STAT_D
Initial value:
1= OrderedDict(
2 [
3 ("Vigor", 0),
4 ("Attunement", 4),
5 ("Endurance", 8),
6 ("Vitality", 40),
7 ("Strength", 12),
8 ("Dexterity", 16),
9 ("Intelligence", 20),
10 ("Faith", 24),
11 ("Luck", 28),
12 ]
13)

DS3 stat block as signed distances from the Vigor field (the anchor).

Storage order is NOT the level-up display order: eight contiguous uint32 (Vigor, Attunement, Endurance, Strength, Dexterity, Intelligence, Faith, Luck), then Vitality alone after a two-field gap. Listed here in display order pointing at the true distances. Calibrated against a real lopsided build (Joy, STR 18 / VIT 14 / LCK 11 read out as VIT 18 / STR 9 / LCK 14 under the old naive mapping, which the order-independent level-sum identity could not catch).

Definition at line 367 of file ds3.py.

◆ DS3_HP_D

sl2.ds3.DS3_HP_D

DS3 max HP, FP, stamina, soul level and souls, same anchor-relative scheme.

HP/FP/stamina each store a current+max triple; these offsets point at the MAX copy (a lopsided real save read HP 728 max / 681 current at -40 / -36 — we take the max). FP at -28 verified 72 at ATN 6 and 450 at a high-attunement char.

Definition at line 386 of file ds3.py.

◆ DS3_FP_D

sl2.ds3.DS3_FP_D

DS3 max HP, FP, stamina, soul level and souls, same anchor-relative scheme.

HP/FP/stamina each store a current+max triple; these offsets point at the MAX copy (a lopsided real save read HP 728 max / 681 current at -40 / -36 — we take the max). FP at -28 verified 72 at ATN 6 and 450 at a high-attunement char.

Definition at line 386 of file ds3.py.

◆ DS3_STAM_D

sl2.ds3.DS3_STAM_D

DS3 max HP, FP, stamina, soul level and souls, same anchor-relative scheme.

HP/FP/stamina each store a current+max triple; these offsets point at the MAX copy (a lopsided real save read HP 728 max / 681 current at -40 / -36 — we take the max). FP at -28 verified 72 at ATN 6 and 450 at a high-attunement char.

Definition at line 386 of file ds3.py.

◆ DS3_LEVEL_D

sl2.ds3.DS3_LEVEL_D

DS3 max HP, FP, stamina, soul level and souls, same anchor-relative scheme.

HP/FP/stamina each store a current+max triple; these offsets point at the MAX copy (a lopsided real save read HP 728 max / 681 current at -40 / -36 — we take the max). FP at -28 verified 72 at ATN 6 and 450 at a high-attunement char.

Definition at line 386 of file ds3.py.

◆ DS3_SOULS_D

sl2.ds3.DS3_SOULS_D

DS3 max HP, FP, stamina, soul level and souls, same anchor-relative scheme.

HP/FP/stamina each store a current+max triple; these offsets point at the MAX copy (a lopsided real save read HP 728 max / 681 current at -40 / -36 — we take the max). FP at -28 verified 72 at ATN 6 and 450 at a high-attunement char.

Definition at line 386 of file ds3.py.

◆ DS3_EMBER_D

int sl2.ds3.DS3_EMBER_D = 188

DS3 embered flag: a lone uint8 at +188 in the stat-mirror struct behind the stat anchor — 1 = embered, 0 = hollow.

Embered restores the ~30% Max HP bonus (which is why the stored Max HP at DS3_HP_D reads base*1.3 while this byte is 1), so reading it labels whether the Max HP figure is the embered or the base value. Pinned by a real Joy differential (using an Ember flipped 0->1 and Max HP 817->1062 = *1.30) and cross-checked on the all-items mule: its two embered slots read 1 with HP = base*1.3 (vig 99, 1400->1819) and its hollow slot reads 0 with base HP (vig 16, 594) — both polarities, HP corroborating each. Guarded to {0,1}; any other value omits the field.

Definition at line 397 of file ds3.py.

◆ DS3_COVENANT_D

int sl2.ds3.DS3_COVENANT_D = 3944

DS3 covenant: a uint32 EQUIP HANDLE at +3944 from the stat anchor, not a small enum — the game models the covenant as a worn accessory, so the field holds 0xA00027xx and the covenant's own item id is the low 28 bits (Rosaria's Fingers 0xA0002760 -> 10080).

Ids and names come from the TGA Cheat Engine table's "Current Covenant" dropdown. Pinned by a real Joy join differential: the two saves are 37 seconds apart with the same level, bonfires and souls, and joining Rosaria's Fingers is the only thing that happened — the handle appears EXACTLY ONCE in the after save and NOT AT ALL in the before one. Cross-checked across the whole ladder (all 27 earlier saves read 0 = no covenant) and against the all-items mule, whose two cheated slots hold values that are not covenant ids at all and are dropped by the table lookup rather than printed.

Definition at line 411 of file ds3.py.

◆ DS3_COVENANT

dict sl2.ds3.DS3_COVENANT
Initial value:
1= {
2 10000: "Blade of the Darkmoon",
3 10020: "Watchdogs of Farron",
4 10030: "Aldrich Faithful",
5 10040: "Warrior of Sunlight",
6 10050: "Mound-makers",
7 10060: "Way of Blue",
8 10070: "Blue Sentinels",
9 10080: "Rosaria's Fingers",
10 10090: "Spears of the Church",
11}

Definition at line 414 of file ds3.py.

◆ DS3_LEVEL_BASE

int sl2.ds3.DS3_LEVEL_BASE = 89

DS3's soul-level identity: level == (sum of all nine attributes) - 89.

Deprived (all 10, sum 90) is level 1, and it holds at every level. This is the content check that pins the stat block without a per-patch offset table.

Definition at line 430 of file ds3.py.

◆ SCAN_MIN_RUN

int sl2.ds3.SCAN_MIN_RUN = 3

Shortest run of consecutive records that counts as real inventory.

Long enough to shrug off a stray id landing in unrelated data.

Definition at line 435 of file ds3.py.

◆ DS3_MAX_RUN_GAP

int sl2.ds3.DS3_MAX_RUN_GAP = 48

Largest gap (bytes) a run may bridge.

The inventory is a 16-byte record grid, but records holding an UNTABLED id (not in our db) leave holes, so two known records can sit 32/48 bytes apart with the hole(s) between them. Bridging a gap that is a multiple of DS3_RECORD up to this bound keeps such a run whole, so an item flanked by unknowns on both sides (an island the strict stride-16 rule dropped) is still read. Observed holes are single-record (gap 32); the extra record of slack tolerates a rare double hole. Kept small so the run can't leap to unrelated on-grid coincidences. (Found via a Soul of a Stray Demon that read out present in the buffer but vanished from the list, wedged between two untabled items.)

Definition at line 448 of file ds3.py.

◆ DS3_GOODS_ID_BASE

int sl2.ds3.DS3_GOODS_ID_BASE = 0x40000000

DS3 goods ids carry a type prefix (0x40000000); the real id is what the game's own EquipParamGoods table uses, and it blocks out cleanly by kind.

Splitting goods on those blocks gives DS3 the same finer categories DS2 already renders (upgrade / consumables / online / keys / boss souls) instead of one 100-row dump. Ranges read off the table itself (db_ds3/goods.json, sorted by id), not guessed: 100..119 soapstones+orbs+Darksign, 240..519 the whole consumable block, 520..524 the multiplayer carvings, 700..799 boss souls, 1000..1030 titanite, 1100..1250 infusion gems, 2001..2014 door keys, 2101..2159 tomes/coals/ashes/banners.

Definition at line 459 of file ds3.py.

◆ DS3_GOODS_RANGES

tuple sl2.ds3.DS3_GOODS_RANGES
Initial value:
1= (
2 (100, 149, "online"),
3 (150, 519, "consumables"),
4 (520, 599, "online"),
5 (600, 699, "consumables"),
6 (700, 799, "bosssouls"),
7 (1000, 1299, "upgrade"),
8 (2000, 2199, "keys"),
9)

Definition at line 462 of file ds3.py.

◆ DS3_GOODS_OVERRIDE

dict sl2.ds3.DS3_GOODS_OVERRIDE
Initial value:
1= {
2 117: "consumables", # Darksign (not a summon item)
3 2141: "upgrade",
4 2143: "upgrade",
5}

The two flask upgrades that sit in the key-item block but are materials.

Definition at line 474 of file ds3.py.

◆ DS3_AMMO_LO

sl2.ds3.DS3_AMMO_LO

Where ammunition lives in the weapon id space.

Arrows and bolts ARE weapons to the param — and to Paramdex, which is where the full table comes from — but the report has always printed them under Ammunition, and ds3_equipped_ammo gates on that category, so they are re-homed on the way in. Bows start at 1300000, so the block is unambiguous.

Definition at line 498 of file ds3.py.

◆ DS3_AMMO_HI

sl2.ds3.DS3_AMMO_HI

Where ammunition lives in the weapon id space.

Arrows and bolts ARE weapons to the param — and to Paramdex, which is where the full table comes from — but the report has always printed them under Ammunition, and ds3_equipped_ammo gates on that category, so they are re-homed on the way in. Bows start at 1300000, so the block is unambiguous.

Definition at line 498 of file ds3.py.

◆ DS3_EQUIP_D

int sl2.ds3.DS3_EQUIP_D = 664

EquipGameData sits a fixed 664 bytes past the stat (Vigor) anchor — invariant across saves whose anchor itself moves (56124..75156 on the Joy ladder), so it is read at a fixed anchor-relative offset, not searched for.

Definition at line 623 of file ds3.py.

◆ DS3_ARMOR_SLOTS

sl2.ds3.DS3_ARMOR_SLOTS
Initial value:
1= OrderedDict(
2 [("Head", 0x20), ("Chest", 0x24), ("Hands", 0x28), ("Legs", 0x2C)]
3)

Armour sub-offsets inside EquipGameData (base +0x20..+0x2C), in the game's own head-to-toe order.

Each holds a GaItem handle, not an id.

Definition at line 628 of file ds3.py.

◆ DS3_RING_SLOTS

tuple sl2.ds3.DS3_RING_SLOTS = (0x34, 0x38, 0x3C, 0x40)

The four ring sub-offsets inside EquipGameData (base +0x34..+0x40).

Definition at line 634 of file ds3.py.

◆ DS3_AMMO_SLOTS

tuple sl2.ds3.DS3_AMMO_SLOTS = (0x08, 0x0C, 0x10, 0x14)

The ammo sub-offsets (Arrow 1 / Bolt 1 / Arrow 2 / Bolt 2) at base +0x08..+0x14 — GaItem handles like weapons/armour (arrows and bolts share the db's bolts category).

Definition at line 640 of file ds3.py.

◆ DS3_WEAPON_SLOTS

sl2.ds3.DS3_WEAPON_SLOTS
Initial value:
1= OrderedDict(
2 [
3 ("Right Hand", -0x0C),
4 ("Right Hand 2", -0x04),
5 ("Right Hand 3", 0x04),
6 ("Left Hand", -0x10),
7 ("Left Hand 2", -0x08),
8 ("Left Hand 3", 0x00),
9 ]
10)

The six weapon sub-offsets, in the game's own two-hand-by-slot order.

The struct interleaves the hands (LH1, RH1, LH2, RH2, LH3, RH3) starting 0x10 BEFORE the armour base — so relative to DS3_EQUIP_D the right hand is -0x0C/-0x04/+0x04 and the left is -0x10/-0x08/+0x00. Each holds a GaItem handle, resolved through the same map as armour/ammo. Pinned by a real weapon-swap differential (a Deep Battle Axe moved right→left read out at RH1 then LH1, everything else unchanged), retiring the old "hand slots unverifiable" blocker. The id carries the infusion (Deep = base+900), so an infused weapon names itself; the +N reinforcement lives in the 52-byte weapon record and is still not read.

Definition at line 653 of file ds3.py.

◆ DS3_FISTS

int sl2.ds3.DS3_FISTS = 110000

Item id of the default bare fist (an empty weapon slot reads this, not a null handle), so a slot resolving to it is skipped as "unarmed".

Definition at line 667 of file ds3.py.

◆ DS3_REINF_MAX

int sl2.ds3.DS3_REINF_MAX = 10

Max reinforcement level, a sanity bound on the id-baked "+N".

Definition at line 671 of file ds3.py.

◆ DS3_GOODS_TYPE

int sl2.ds3.DS3_GOODS_TYPE = 0x40000000

The two Estus flasks' base goods ids, and the reinforcement ceiling.

Like weapons, a flask's level is baked into its id, but it takes TWO consecutive ids per level (150/151 = Estus +0, 152/153 = +1 … 170/171 = +10; 190/191 = Ashen +0 … 210/211 = +10), so the flask is resolved arithmetically rather than listed — a name-keyed db cannot hold one name under two ids. Verified across Joy's 38-save ladder, where the id steps 151 → 153 → 155 → 157 → 159 (and the Ashen id 190 → 192 → 194 → 196 → 198 in lockstep), each step inside a 12-15 second window — one visit to Andre. Both members of a pair really occur: the all-items mule holds 171/211 (both +10) and 151/191, which is what pins the pairing and the endpoints.

Definition at line 706 of file ds3.py.

◆ DS3_ESTUS

tuple sl2.ds3.DS3_ESTUS = ((150, "Estus Flask"), (190, "Ashen Estus Flask"))

Definition at line 709 of file ds3.py.

◆ DS3_ESTUS_MAX

int sl2.ds3.DS3_ESTUS_MAX = 10

Definition at line 712 of file ds3.py.

◆ DS3_RING_ID_MASK

int sl2.ds3.DS3_RING_ID_MASK = 0x0FFFFFFF

A ring's equip handle encodes its own id: the low 28 bits are shared and the type nibble is 0xA where the goods/ring id's is 0x2 — so the id is the handle with its top nibble rewritten to 2 (verified 4/4 on the Joy ring set: 0xa0004e20 → 0x20004e20 = Life Ring, etc.).

Rings are NOT in the GaItem array (that array is weapons/armour only), which is why they need this direct transform instead of a handle lookup.

Definition at line 734 of file ds3.py.

◆ DS3_RING_ID_TYPE

int sl2.ds3.DS3_RING_ID_TYPE = 0x20000000

Definition at line 737 of file ds3.py.

◆ DS3_DB_FILES

dict sl2.ds3.DS3_DB_FILES
Initial value:
1= {
2 "weapons": "weapons",
3 "armors": "armors",
4 "rings": "rings",
5 "goods": "goods",
6 "bolts": "bolts",
7 "spells": "spells",
8}

DS3 id-scan tables: filename stem to category.

Definition at line 931 of file ds3.py.

◆ DS3_ROSTER_PLAYTIME_OFF

int sl2.ds3.DS3_ROSTER_PLAYTIME_OFF = 38

Play time (seconds, uint32) inside slot i's DS3 roster descriptor.

The status block does not carry it — it lives in the header menu block, at the same per-slot descriptor as the name, +38 past the descriptor start. Pinned by a ~17-minute differential (a real Joy save, 7573 s -> 8603 s, matching an on-screen 2:23:24). Per-character (one descriptor per slot).

Definition at line 946 of file ds3.py.

◆ DS3_GAITEM_START

int sl2.ds3.DS3_GAITEM_START = 0x6C

Definition at line 968 of file ds3.py.

◆ DS3_GAITEM_SLOTS

int sl2.ds3.DS3_GAITEM_SLOTS = 6144

Definition at line 971 of file ds3.py.

◆ DS3_GAITEM_BIG

int sl2.ds3.DS3_GAITEM_BIG = 60

Definition at line 974 of file ds3.py.

◆ DS3_GAITEM_TYPES_BIG

tuple sl2.ds3.DS3_GAITEM_TYPES_BIG = (0x80000000, 0x90000000)

Definition at line 977 of file ds3.py.

◆ DS3_FLAG_MAX_DENSITY

float sl2.ds3.DS3_FLAG_MAX_DENSITY = 0.01

Definition at line 988 of file ds3.py.

◆ DS3_FLAG_SAMPLE

int sl2.ds3.DS3_FLAG_SAMPLE = 0x8000

Definition at line 991 of file ds3.py.

◆ DS3_NG_MAX

int sl2.ds3.DS3_NG_MAX = 99

DS3 New Game+ cycle (journey count), a uint16 just before the event-flag region, or None.

new_game_plus sits at base + 0x12 - 0xBCC (base is that region less 0x12 — see ds3_event_flag_base). Guarded to a sane range: a cheated mule read 0xFFFF here, so an out-of-range value is omitted rather than printed wrong. Verified: Joy = 0 (New Game), a real NG+1 char = 1.

Returns
The cycle, or None.

Definition at line 1148 of file ds3.py.