5from collections
import OrderedDict, defaultdict
7from .reader
import is_valid_name, read_utf16, u8, u32
29ER_GAITEM_COUNT = 0x1400
36ER_MENU_LEN_OFF, ER_MENU_DATA_OFF = 352, 356
39ER_SLOT_COUNT, ER_PROFILE_STRIDE = 10, 588
42ER_PROFILE_NAME_LEN, ER_PROFILE_LEVEL_OFF = 16, 34
48ER_STAT_D = OrderedDict(
64ER_HP_D, ER_STAM_D, ER_LEVEL_D, ER_RUNES_D = -40, -12, 44, 48
76ER_LEVEL_MAX = 8 * 99 - ER_LEVEL_BASE
87 length = u32(menu, ER_MENU_LEN_OFF)
90 active_base = ER_MENU_DATA_OFF + length
91 pbase = active_base + ER_SLOT_COUNT
93 for i
in range(ER_SLOT_COUNT):
94 active = bool(u8(menu, active_base + i))
95 base = pbase + i * ER_PROFILE_STRIDE
96 name = read_utf16(menu, base, ER_PROFILE_NAME_LEN)
97 level = u32(menu, base + ER_PROFILE_LEVEL_OFF)
98 out.append((active, name, level))
110ER_EMPTY_SLOT_IDS = frozenset(
111 {0x0001ADB0, 0x10002710, 0x10002774, 0x100027D8, 0x1000283C}
125 for _
in range(ER_GAITEM_COUNT):
128 iid = u32(buf, o + 4)
131 cat = iid & 0xF0000000
132 if cat == 0x00000000:
134 elif cat == 0x10000000:
163 if level
is None or not (1 <= level <= ER_LEVEL_MAX):
165 dists = list(ER_STAT_D.values())
166 pat = level.to_bytes(4,
"little")
171 vals = [u32(buf, v + d)
for d
in dists]
173 all(x
is not None and 1 <= x <= 99
for x
in vals)
174 and sum(vals) - ER_LEVEL_BASE == level
177 at = buf.find(pat, at + 1)
195def er_parse(buf, iddb, name, level):
196 buckets, unknown = defaultdict(set), 0
198 if iid
in ER_EMPTY_SLOT_IDS:
205 if not any(buckets.values()):
207 inv = {c: [(n,
None)
for n
in sorted(v)]
for c, v
in buckets.items()}
209 (n,
None)
for c
in buckets
for n
in sorted(buckets[c])
if "Remembrance" in n
213 OrderedDict((k, u32(buf, v + d))
for k, d
in ER_STAT_D.items())
218 "tier":
"full" if stats
else "inventory",
220 "name": name
if (name
and is_valid_name(name))
else "(unnamed slot)",
226 "level": u32(buf, v + ER_LEVEL_D)
if v
is not None else level,
227 "souls": u32(buf, v + ER_RUNES_D)
if v
is not None else None,
228 "stamina": u32(buf, v + ER_STAM_D)
if v
is not None else None,
229 "hp": u32(buf, v + ER_HP_D)
if v
is not None else None,
230 "boss_souls": remembrances,
233 "unknown_count": unknown,
242ER_CAT = {0x0:
"weapons", 0x1:
"armors", 0x2:
"talismans", 0x4:
"goods", 0x8:
"ashes"}
246ER_DB_FILES = tuple(ER_CAT.values())
253ER_WEAPON_BASE_STEP = 10000
254ER_WEAPON_AFFINITY_STEP = 100
266def load_er_db(db_dir):
268 for cat
in ER_DB_FILES:
270 with open(os.path.join(db_dir, cat +
".json"), encoding=
"utf-8")
as f:
271 db[cat] = {int(k, 16): v
for k, v
in json.load(f).items()}
272 except (OSError, ValueError):
288 cat = ER_CAT.get((iid >> 28) & 0xF)
291 table = db.get(cat, {})
292 name = table.get(iid)
293 if name
is not None or cat !=
"weapons":
295 level = iid % ER_WEAPON_AFFINITY_STEP
296 if level > ER_MAX_REINFORCE:
298 for step
in (ER_WEAPON_AFFINITY_STEP, ER_WEAPON_BASE_STEP):
299 name = table.get(iid - iid % step)
301 return (f
"{name} +{level}" if level
else name), cat
er_gaitems(buf)
Walk the ER GaItem array and yield every owned item id.
er_find_stats(buf, level)
Locate the ER stat block by content, or None if none validates.
er_resolve(iid, db)
Resolve an ER item id to (name, category), type-scoped by its nibble.