forked from omicron/d2warehouse
Add parsing of quality specific data
This commit is contained in:
@@ -11,7 +11,7 @@ def txtbits(bits: bitarray) -> str:
|
||||
return " ".join(grouped)
|
||||
|
||||
|
||||
def read_stash(path):
|
||||
def read_stash(path, verbose):
|
||||
with path.open("rb") as f:
|
||||
stash = parse_stash(f.read())
|
||||
|
||||
@@ -19,9 +19,10 @@ def read_stash(path):
|
||||
for i, tab in enumerate(stash.tabs):
|
||||
print(f" - tab {i}")
|
||||
print(f" - {len(tab.items)} items")
|
||||
print(f" - {tab.item_data.hex()}")
|
||||
if verbose:
|
||||
print(f" - {tab.item_data.hex()}")
|
||||
for j, item in enumerate(tab.items):
|
||||
item.print()
|
||||
item.print(with_raw=verbose)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -30,5 +31,6 @@ def main():
|
||||
description="dump a text description of the items in d2r shared stash files",
|
||||
)
|
||||
parser.add_argument("stashfile", type=Path)
|
||||
parser.add_argument("-v", "--verbose", action="store_true")
|
||||
args = parser.parse_args()
|
||||
read_stash(args.stashfile)
|
||||
read_stash(args.stashfile, args.verbose)
|
||||
|
||||
@@ -17,6 +17,29 @@ class Quality(Enum):
|
||||
return self.name.capitalize()
|
||||
|
||||
|
||||
class LowQualityType(Enum):
|
||||
CRUDE = 0
|
||||
CRACKED = 1
|
||||
DAMAGED = 2
|
||||
LOW_QUALITY = 3
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name.capitalize()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Affix:
|
||||
name_id: int
|
||||
id: int | None = None # TODO: read this
|
||||
# TODO: data
|
||||
|
||||
def print(self, indent=5):
|
||||
# TODO: name lookup
|
||||
# TODO: modified lookup
|
||||
# TODO: adding in data
|
||||
print(" " * indent, f"{self.name_id} {hex(self.name_id)}")
|
||||
|
||||
|
||||
def txtbits(bits: bitarray) -> str:
|
||||
txt = "".join(str(b) for b in bits)
|
||||
grouped = [txt[i : i + 8] for i in range(0, len(txt), 8)]
|
||||
@@ -36,13 +59,22 @@ class Item:
|
||||
pos_x: int
|
||||
pos_y: int
|
||||
kind: str
|
||||
uid: int | None
|
||||
lvl: int | None
|
||||
quality: Quality | None
|
||||
graphic: int | None
|
||||
inherent: int | None
|
||||
uid: int | None = None
|
||||
lvl: int | None = None
|
||||
quality: Quality | None = None
|
||||
graphic: int | None = None
|
||||
inherent: int | None = None
|
||||
low_quality: LowQualityType | None = None
|
||||
prefixes: list[Affix] | None = None
|
||||
suffixes: list[Affix] | None = None
|
||||
set_id: int | None = None
|
||||
unique_id: int | None = None
|
||||
nameword1: int | None = None
|
||||
nameword2: int | None = None
|
||||
runeword_id: int | None = None
|
||||
personal_name: str | None = None
|
||||
|
||||
def print(self, indent=5):
|
||||
def print(self, indent=5, with_raw=False):
|
||||
properties = []
|
||||
print(" " * indent, self.kind)
|
||||
if self.lvl:
|
||||
@@ -60,8 +92,28 @@ class Item:
|
||||
if properties:
|
||||
print(" " * indent, ", ".join(properties))
|
||||
print(" " * indent, f"at {self.pos_x}, {self.pos_y}")
|
||||
bits = bitarray(endian="little")
|
||||
bits.frombytes(self.raw_data)
|
||||
print(" " * indent, txtbits(bits))
|
||||
if self.quality:
|
||||
print(" " * indent, self.quality)
|
||||
if self.prefixes:
|
||||
print(" " * indent, "Prefixes:")
|
||||
for prefix in self.prefixes:
|
||||
prefix.print(indent + 4)
|
||||
if self.suffixes:
|
||||
print(" " * indent, "Suffixes:")
|
||||
for suffix in self.suffixes:
|
||||
suffix.print(indent + 4)
|
||||
if self.set_id:
|
||||
print(" " * indent, f"Set Id: {self.set_id}") # TODO: name lookup
|
||||
if self.unique_id:
|
||||
print(" " * indent, f"Set Id: {self.unique_id}") # TODO: name lookup
|
||||
if self.runeword_id:
|
||||
print(" " * indent, f"Runeword Id: {self.runeword_id}") # TODO: name lookup
|
||||
if self.personal_name:
|
||||
print(" " * indent, f"Personal name: {self.personal_name}")
|
||||
if with_raw:
|
||||
print(" " * indent, "Raw Item Data:")
|
||||
bits = bitarray(endian="little")
|
||||
bits.frombytes(self.raw_data)
|
||||
print(" " * indent, txtbits(bits))
|
||||
print("")
|
||||
print("")
|
||||
|
||||
@@ -2,7 +2,7 @@ import struct
|
||||
from bitarray import bitarray
|
||||
from bitarray.util import ba2int
|
||||
from d2warehouse.stash import Stash, StashTab
|
||||
from d2warehouse.item import Item, Quality
|
||||
from d2warehouse.item import Affix, Item, LowQualityType, Quality
|
||||
import d2warehouse.huffman as huffman
|
||||
|
||||
STASH_TAB_MAGIC = b"\x55\xAA\x55\xAA"
|
||||
@@ -110,20 +110,10 @@ def parse_item(data: bytes) -> tuple[bytes, Item]:
|
||||
if is_ear:
|
||||
raise UnsupportedItemError("Ear items are not supported")
|
||||
|
||||
uid, lvl, quality, graphic, inherent = None, None, None, None, None
|
||||
if not is_simple:
|
||||
uid, lvl, quality = parse_extended_item(bits[sockets_end:])
|
||||
|
||||
extended_end = sockets_end + 32 + 7 + 4
|
||||
|
||||
graphic, graphic_end = parse_item_graphic(bits[extended_end:])
|
||||
graphic_end += extended_end
|
||||
|
||||
inherent, inherent_end = parse_inherent_mod(bits[graphic_end:])
|
||||
inherent_end += graphic_end
|
||||
|
||||
simple_byte_sz = int((sockets_end + 7) / 8)
|
||||
print("simple size", simple_byte_sz)
|
||||
item = Item(
|
||||
data[:], # TODO: only take parsed bytes
|
||||
data[:simple_byte_sz],
|
||||
is_identified,
|
||||
is_socketed,
|
||||
is_beginner,
|
||||
@@ -134,24 +124,52 @@ def parse_item(data: bytes) -> tuple[bytes, Item]:
|
||||
pos_x,
|
||||
pos_y,
|
||||
kind,
|
||||
uid,
|
||||
lvl,
|
||||
quality,
|
||||
graphic,
|
||||
inherent,
|
||||
)
|
||||
if is_simple:
|
||||
return data[simple_byte_sz:], item
|
||||
|
||||
item.uid, item.lvl, item.quality = parse_extended_item(bits[sockets_end:])
|
||||
|
||||
extended_end = sockets_end + 32 + 7 + 4
|
||||
|
||||
item.graphic, graphic_end = parse_item_graphic(bits[extended_end:])
|
||||
graphic_end += extended_end
|
||||
|
||||
item.inherent, inherent_end = parse_inherent_mod(bits[graphic_end:])
|
||||
inherent_end += graphic_end
|
||||
|
||||
print("in", inherent_end)
|
||||
item, quality_end = parse_quality_data(bits[inherent_end:], item)
|
||||
quality_end += inherent_end
|
||||
|
||||
if item.is_runeword:
|
||||
item.runeword_id, runeword_end = parse_runeword(bits[quality_end:], item)
|
||||
else:
|
||||
runeword_end = quality_end
|
||||
|
||||
if item.is_personalized:
|
||||
item.personal_name, personalized_end = parse_personalization(
|
||||
bits[runeword_end:], item
|
||||
)
|
||||
else:
|
||||
personalized_end = runeword_end
|
||||
|
||||
extended_byte_size = int((personalized_end + 7) / 8)
|
||||
print("extended size", extended_byte_size)
|
||||
|
||||
item.raw_data = data[:]
|
||||
return b"", item # TODO: properly return remaining data
|
||||
|
||||
|
||||
def parse_item_graphic(bits: bitarray) -> tuple[int | None, int]:
|
||||
if bits[0]:
|
||||
if not bits[0]:
|
||||
return None, 1
|
||||
else:
|
||||
return ba2int(bits[1:4]), 4
|
||||
|
||||
|
||||
def parse_inherent_mod(bits: bitarray) -> tuple[int | None, int]:
|
||||
if bits[0]:
|
||||
if not bits[0]:
|
||||
return None, 1
|
||||
else:
|
||||
return ba2int(bits[1:4]), 4
|
||||
@@ -164,26 +182,87 @@ def parse_extended_item(bits: bitarray) -> tuple[int, int, Quality]:
|
||||
return uid, lvl, Quality(quality)
|
||||
|
||||
|
||||
# TODO: figure out what we want to return here
|
||||
# TODO: Introduce enums for quality
|
||||
def parse_quality_data(bits: bitarray, quality: int):
|
||||
if quality == 1:
|
||||
return parse_low_quality_data(bits)
|
||||
elif quality == 2:
|
||||
return None, 0
|
||||
elif quality == 3:
|
||||
return parse_high_quality_data(bits)
|
||||
elif quality == 4:
|
||||
return parse_magic_data(bits)
|
||||
elif quality == 5:
|
||||
return parse_set_data(bits)
|
||||
elif quality == 6:
|
||||
return parse_rare_data(bits)
|
||||
elif quality == 7:
|
||||
return parse_unique_data(bits)
|
||||
elif quality == 8:
|
||||
return parse_crafted_data(bits)
|
||||
# magic
|
||||
def parse_quality_data(bits: bitarray, item: Item) -> tuple[Item, int]:
|
||||
if item.quality == Quality.LOW:
|
||||
return parse_low_quality_data(bits, item)
|
||||
elif item.quality == item.quality.NORMAL:
|
||||
return item, 0
|
||||
elif item.quality == item.quality.HIGH:
|
||||
return parse_high_quality_data(bits, item)
|
||||
elif item.quality == item.quality.MAGIC:
|
||||
return parse_magic_data(bits, item)
|
||||
elif item.quality == item.quality.SET:
|
||||
return parse_set_data(bits, item)
|
||||
elif item.quality == item.quality.RARE:
|
||||
return parse_rare_data(bits, item)
|
||||
elif item.quality == item.quality.UNIQUE:
|
||||
return parse_unique_data(bits, item)
|
||||
elif item.quality == item.quality.CRAFTED:
|
||||
return parse_rare_data(bits, item) # crafted & rare are the same
|
||||
|
||||
|
||||
def parse_low_quality_data(bits: bitarray, item: Item) -> tuple[Item, int]:
|
||||
item.low_quality = LowQualityType(ba2int(bits[0:3]))
|
||||
return item, 3
|
||||
|
||||
|
||||
def parse_high_quality_data(bits: bitarray, item: Item) -> tuple[Item, int]:
|
||||
# The data for superior item is unknown
|
||||
return item, 3
|
||||
|
||||
|
||||
def parse_magic_data(bits: bitarray, item: Item) -> tuple[Item, int]:
|
||||
item.prefixes = [Affix(name_id=ba2int(bits[0:11]))]
|
||||
item.suffixes = [Affix(name_id=ba2int(bits[11:22]))]
|
||||
return item, 22
|
||||
|
||||
|
||||
def parse_set_data(bits: bitarray, item: Item) -> tuple[Item, int]:
|
||||
item.set_id = ba2int(bits[0:12])
|
||||
return item, 12
|
||||
|
||||
|
||||
def parse_rare_data(bits: bitarray, item: Item) -> tuple[Item, int]:
|
||||
item.nameword1 = ba2int(bits[0:8])
|
||||
item.nameword2 = ba2int(bits[8:16])
|
||||
affixes = []
|
||||
ptr = 16
|
||||
for _ in range(0, 6):
|
||||
(affix, sz) = parse_affix(bits[ptr:])
|
||||
ptr += sz
|
||||
affixes.append(affix)
|
||||
item.prefixes = [affix for affix in affixes[0:3] if affix is not None]
|
||||
item.suffixes = [affix for affix in affixes[3:6] if affix is not None]
|
||||
|
||||
return item, ptr
|
||||
|
||||
|
||||
def parse_unique_data(bits: bitarray, item: Item) -> tuple[Item, int]:
|
||||
item.unique_id = ba2int(bits[0:12])
|
||||
return item, 12
|
||||
|
||||
|
||||
def parse_affix(bits: bitarray) -> tuple[Affix | None, int]:
|
||||
if not bits[0]:
|
||||
return None, 1
|
||||
else:
|
||||
return Affix(name_id=ba2int(bits[1:12])), 12
|
||||
|
||||
|
||||
def parse_runeword(bits: bitarray) -> tuple[int, int]:
|
||||
id = ba2int(bits[0:12])
|
||||
return id, 16
|
||||
|
||||
|
||||
def parse_personalization(bits: bitarray) -> tuple[str, int]:
|
||||
output = ""
|
||||
ptr = 0
|
||||
ascii = ba2int(bits[0:7])
|
||||
while ascii:
|
||||
output += chr(ascii)
|
||||
ascii = ba2int(bits[ptr : ptr + 7])
|
||||
ptr += 7
|
||||
return output, ptr + 1
|
||||
|
||||
|
||||
def parse_items(data: bytes) -> list[Item]:
|
||||
|
||||
Reference in New Issue
Block a user