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)
|
return " ".join(grouped)
|
||||||
|
|
||||||
|
|
||||||
def read_stash(path):
|
def read_stash(path, verbose):
|
||||||
with path.open("rb") as f:
|
with path.open("rb") as f:
|
||||||
stash = parse_stash(f.read())
|
stash = parse_stash(f.read())
|
||||||
|
|
||||||
@@ -19,9 +19,10 @@ def read_stash(path):
|
|||||||
for i, tab in enumerate(stash.tabs):
|
for i, tab in enumerate(stash.tabs):
|
||||||
print(f" - tab {i}")
|
print(f" - tab {i}")
|
||||||
print(f" - {len(tab.items)} items")
|
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):
|
for j, item in enumerate(tab.items):
|
||||||
item.print()
|
item.print(with_raw=verbose)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -30,5 +31,6 @@ def main():
|
|||||||
description="dump a text description of the items in d2r shared stash files",
|
description="dump a text description of the items in d2r shared stash files",
|
||||||
)
|
)
|
||||||
parser.add_argument("stashfile", type=Path)
|
parser.add_argument("stashfile", type=Path)
|
||||||
|
parser.add_argument("-v", "--verbose", action="store_true")
|
||||||
args = parser.parse_args()
|
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()
|
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:
|
def txtbits(bits: bitarray) -> str:
|
||||||
txt = "".join(str(b) for b in bits)
|
txt = "".join(str(b) for b in bits)
|
||||||
grouped = [txt[i : i + 8] for i in range(0, len(txt), 8)]
|
grouped = [txt[i : i + 8] for i in range(0, len(txt), 8)]
|
||||||
@@ -36,13 +59,22 @@ class Item:
|
|||||||
pos_x: int
|
pos_x: int
|
||||||
pos_y: int
|
pos_y: int
|
||||||
kind: str
|
kind: str
|
||||||
uid: int | None
|
uid: int | None = None
|
||||||
lvl: int | None
|
lvl: int | None = None
|
||||||
quality: Quality | None
|
quality: Quality | None = None
|
||||||
graphic: int | None
|
graphic: int | None = None
|
||||||
inherent: int | 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 = []
|
properties = []
|
||||||
print(" " * indent, self.kind)
|
print(" " * indent, self.kind)
|
||||||
if self.lvl:
|
if self.lvl:
|
||||||
@@ -60,8 +92,28 @@ class Item:
|
|||||||
if properties:
|
if properties:
|
||||||
print(" " * indent, ", ".join(properties))
|
print(" " * indent, ", ".join(properties))
|
||||||
print(" " * indent, f"at {self.pos_x}, {self.pos_y}")
|
print(" " * indent, f"at {self.pos_x}, {self.pos_y}")
|
||||||
bits = bitarray(endian="little")
|
if self.quality:
|
||||||
bits.frombytes(self.raw_data)
|
print(" " * indent, self.quality)
|
||||||
print(" " * indent, txtbits(bits))
|
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("")
|
||||||
print("")
|
print("")
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import struct
|
|||||||
from bitarray import bitarray
|
from bitarray import bitarray
|
||||||
from bitarray.util import ba2int
|
from bitarray.util import ba2int
|
||||||
from d2warehouse.stash import Stash, StashTab
|
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
|
import d2warehouse.huffman as huffman
|
||||||
|
|
||||||
STASH_TAB_MAGIC = b"\x55\xAA\x55\xAA"
|
STASH_TAB_MAGIC = b"\x55\xAA\x55\xAA"
|
||||||
@@ -110,20 +110,10 @@ def parse_item(data: bytes) -> tuple[bytes, Item]:
|
|||||||
if is_ear:
|
if is_ear:
|
||||||
raise UnsupportedItemError("Ear items are not supported")
|
raise UnsupportedItemError("Ear items are not supported")
|
||||||
|
|
||||||
uid, lvl, quality, graphic, inherent = None, None, None, None, None
|
simple_byte_sz = int((sockets_end + 7) / 8)
|
||||||
if not is_simple:
|
print("simple size", simple_byte_sz)
|
||||||
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
|
|
||||||
|
|
||||||
item = Item(
|
item = Item(
|
||||||
data[:], # TODO: only take parsed bytes
|
data[:simple_byte_sz],
|
||||||
is_identified,
|
is_identified,
|
||||||
is_socketed,
|
is_socketed,
|
||||||
is_beginner,
|
is_beginner,
|
||||||
@@ -134,24 +124,52 @@ def parse_item(data: bytes) -> tuple[bytes, Item]:
|
|||||||
pos_x,
|
pos_x,
|
||||||
pos_y,
|
pos_y,
|
||||||
kind,
|
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
|
return b"", item # TODO: properly return remaining data
|
||||||
|
|
||||||
|
|
||||||
def parse_item_graphic(bits: bitarray) -> tuple[int | None, int]:
|
def parse_item_graphic(bits: bitarray) -> tuple[int | None, int]:
|
||||||
if bits[0]:
|
if not bits[0]:
|
||||||
return None, 1
|
return None, 1
|
||||||
else:
|
else:
|
||||||
return ba2int(bits[1:4]), 4
|
return ba2int(bits[1:4]), 4
|
||||||
|
|
||||||
|
|
||||||
def parse_inherent_mod(bits: bitarray) -> tuple[int | None, int]:
|
def parse_inherent_mod(bits: bitarray) -> tuple[int | None, int]:
|
||||||
if bits[0]:
|
if not bits[0]:
|
||||||
return None, 1
|
return None, 1
|
||||||
else:
|
else:
|
||||||
return ba2int(bits[1:4]), 4
|
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)
|
return uid, lvl, Quality(quality)
|
||||||
|
|
||||||
|
|
||||||
# TODO: figure out what we want to return here
|
def parse_quality_data(bits: bitarray, item: Item) -> tuple[Item, int]:
|
||||||
# TODO: Introduce enums for quality
|
if item.quality == Quality.LOW:
|
||||||
def parse_quality_data(bits: bitarray, quality: int):
|
return parse_low_quality_data(bits, item)
|
||||||
if quality == 1:
|
elif item.quality == item.quality.NORMAL:
|
||||||
return parse_low_quality_data(bits)
|
return item, 0
|
||||||
elif quality == 2:
|
elif item.quality == item.quality.HIGH:
|
||||||
return None, 0
|
return parse_high_quality_data(bits, item)
|
||||||
elif quality == 3:
|
elif item.quality == item.quality.MAGIC:
|
||||||
return parse_high_quality_data(bits)
|
return parse_magic_data(bits, item)
|
||||||
elif quality == 4:
|
elif item.quality == item.quality.SET:
|
||||||
return parse_magic_data(bits)
|
return parse_set_data(bits, item)
|
||||||
elif quality == 5:
|
elif item.quality == item.quality.RARE:
|
||||||
return parse_set_data(bits)
|
return parse_rare_data(bits, item)
|
||||||
elif quality == 6:
|
elif item.quality == item.quality.UNIQUE:
|
||||||
return parse_rare_data(bits)
|
return parse_unique_data(bits, item)
|
||||||
elif quality == 7:
|
elif item.quality == item.quality.CRAFTED:
|
||||||
return parse_unique_data(bits)
|
return parse_rare_data(bits, item) # crafted & rare are the same
|
||||||
elif quality == 8:
|
|
||||||
return parse_crafted_data(bits)
|
|
||||||
# magic
|
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]:
|
def parse_items(data: bytes) -> list[Item]:
|
||||||
|
|||||||
Reference in New Issue
Block a user