i asset viewer
This commit is contained in:
BIN
loaders/__pycache__/ikv_loader.cpython-311.pyc
Normal file
BIN
loaders/__pycache__/ikv_loader.cpython-311.pyc
Normal file
Binary file not shown.
BIN
loaders/__pycache__/imesh_loader.cpython-311.pyc
Normal file
BIN
loaders/__pycache__/imesh_loader.cpython-311.pyc
Normal file
Binary file not shown.
BIN
loaders/__pycache__/itex_loader.cpython-311.pyc
Normal file
BIN
loaders/__pycache__/itex_loader.cpython-311.pyc
Normal file
Binary file not shown.
BIN
loaders/__pycache__/material_loader.cpython-311.pyc
Normal file
BIN
loaders/__pycache__/material_loader.cpython-311.pyc
Normal file
Binary file not shown.
228
loaders/ikv_loader.py
Normal file
228
loaders/ikv_loader.py
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
class IKVToken:
|
||||||
|
__slots__ = ("type", "val")
|
||||||
|
def __init__(self, t, v=None):
|
||||||
|
self.type = t
|
||||||
|
self.val = v
|
||||||
|
|
||||||
|
def ikv_lex(src):
|
||||||
|
n = len(src)
|
||||||
|
i = 0
|
||||||
|
def is_ws(c):
|
||||||
|
return c in " \t\r\n"
|
||||||
|
|
||||||
|
while i < n:
|
||||||
|
c = src[i]
|
||||||
|
if is_ws(c):
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if c == "/" and i + 1 < n and src[i + 1] == "/":
|
||||||
|
i += 2
|
||||||
|
while i < n and src[i] != "\n":
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if c == "#":
|
||||||
|
i += 1
|
||||||
|
while i < n and src[i] != "\n":
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if c in "{}[],":
|
||||||
|
i += 1
|
||||||
|
yield IKVToken(c)
|
||||||
|
continue
|
||||||
|
if c == '"':
|
||||||
|
i += 1
|
||||||
|
out = []
|
||||||
|
while i < n:
|
||||||
|
ch = src[i]
|
||||||
|
if ch == "\\" and i + 1 < n:
|
||||||
|
nx = src[i + 1]
|
||||||
|
if nx == "n":
|
||||||
|
out.append("\n")
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if nx == "r":
|
||||||
|
out.append("\r")
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if nx == "t":
|
||||||
|
out.append("\t")
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if nx == "\\":
|
||||||
|
out.append("\\")
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if nx == '"':
|
||||||
|
out.append('"')
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
out.append(nx)
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if ch == '"':
|
||||||
|
i += 1
|
||||||
|
break
|
||||||
|
out.append(ch)
|
||||||
|
i += 1
|
||||||
|
yield IKVToken("STRING", "".join(out))
|
||||||
|
continue
|
||||||
|
|
||||||
|
j = i
|
||||||
|
while j < n and (not is_ws(src[j])) and src[j] not in "{}[],":
|
||||||
|
j += 1
|
||||||
|
w = src[i:j]
|
||||||
|
i = j
|
||||||
|
yield IKVToken("WORD", w)
|
||||||
|
|
||||||
|
yield IKVToken("EOF")
|
||||||
|
|
||||||
|
class IKVParser:
|
||||||
|
def __init__(self, src):
|
||||||
|
self.tokens = ikv_lex(src)
|
||||||
|
self.cur = next(self.tokens)
|
||||||
|
|
||||||
|
def _eat(self, t):
|
||||||
|
if self.cur.type != t:
|
||||||
|
raise ValueError(f"IKV parse: expected {t}, got {self.cur.type}")
|
||||||
|
self.cur = next(self.tokens)
|
||||||
|
|
||||||
|
def _maybe(self, t):
|
||||||
|
if self.cur.type == t:
|
||||||
|
self.cur = next(self.tokens)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
if self.cur.type == "WORD" and self.cur.val == "ikv1":
|
||||||
|
self._eat("WORD")
|
||||||
|
if self.cur.type in ("STRING", "WORD"):
|
||||||
|
self.cur = next(self.tokens)
|
||||||
|
if self.cur.type != "{":
|
||||||
|
raise ValueError("IKV parse: expected { after header")
|
||||||
|
return self._parse_object()
|
||||||
|
if self.cur.type == "{":
|
||||||
|
return self._parse_object()
|
||||||
|
return self._parse_object_flat()
|
||||||
|
|
||||||
|
def _parse_object(self):
|
||||||
|
self._eat("{")
|
||||||
|
obj = {}
|
||||||
|
while True:
|
||||||
|
if self._maybe("}"):
|
||||||
|
break
|
||||||
|
if self._maybe(","):
|
||||||
|
continue
|
||||||
|
if self.cur.type != "STRING":
|
||||||
|
raise ValueError("IKV parse: object key must be string")
|
||||||
|
key = self.cur.val
|
||||||
|
self._eat("STRING")
|
||||||
|
val = self._parse_value()
|
||||||
|
obj[key] = val
|
||||||
|
self._maybe(",")
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def _parse_array(self):
|
||||||
|
self._eat("[")
|
||||||
|
arr = []
|
||||||
|
while True:
|
||||||
|
if self._maybe("]"):
|
||||||
|
break
|
||||||
|
if self._maybe(","):
|
||||||
|
continue
|
||||||
|
arr.append(self._parse_value())
|
||||||
|
self._maybe(",")
|
||||||
|
return arr
|
||||||
|
|
||||||
|
def _parse_value(self):
|
||||||
|
if self.cur.type == "{":
|
||||||
|
return self._parse_object()
|
||||||
|
if self.cur.type == "[":
|
||||||
|
return self._parse_array()
|
||||||
|
if self.cur.type == "STRING":
|
||||||
|
s = self.cur.val
|
||||||
|
self._eat("STRING")
|
||||||
|
return s
|
||||||
|
if self.cur.type == "WORD":
|
||||||
|
w = self.cur.val
|
||||||
|
self._eat("WORD")
|
||||||
|
if w == "true":
|
||||||
|
return True
|
||||||
|
if w == "false":
|
||||||
|
return False
|
||||||
|
if w == "null":
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
if any(ch in w for ch in ".eE"):
|
||||||
|
return float(w)
|
||||||
|
return int(w, 10)
|
||||||
|
except Exception:
|
||||||
|
return w
|
||||||
|
raise ValueError("IKV parse: unexpected token")
|
||||||
|
|
||||||
|
def _parse_object_flat(self):
|
||||||
|
obj = {}
|
||||||
|
while self.cur.type != "EOF":
|
||||||
|
if self._maybe(","):
|
||||||
|
continue
|
||||||
|
if self.cur.type != "STRING":
|
||||||
|
raise ValueError("IKV parse: expected string key")
|
||||||
|
key = self.cur.val
|
||||||
|
self._eat("STRING")
|
||||||
|
obj[key] = self._parse_value()
|
||||||
|
self._maybe(",")
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def parse_ikv_text(txt):
|
||||||
|
return IKVParser(txt).parse()
|
||||||
|
|
||||||
|
def pack_value_from_index_gen(index, generation):
|
||||||
|
return ((int(generation) & 0xFFFF) << 16) | (int(index) & 0xFFFF)
|
||||||
|
|
||||||
|
def extract_handles_recursive(node):
|
||||||
|
out = []
|
||||||
|
|
||||||
|
def maybe_add_value_type_meta(d):
|
||||||
|
if not isinstance(d, dict):
|
||||||
|
return
|
||||||
|
if "value" in d and "type" in d and "meta" in d:
|
||||||
|
v = d["value"]
|
||||||
|
t = d["type"]
|
||||||
|
m = d["meta"]
|
||||||
|
if isinstance(v, int) and isinstance(t, int) and isinstance(m, int):
|
||||||
|
out.append((int(v) & 0xFFFFFFFF, int(t) & 0xFFFF, int(m) & 0xFFFF))
|
||||||
|
|
||||||
|
def maybe_add_index_gen(d):
|
||||||
|
if not isinstance(d, dict):
|
||||||
|
return
|
||||||
|
if "index" in d and "generation" in d and "type" in d and "meta" in d:
|
||||||
|
idx = d["index"]
|
||||||
|
gen = d["generation"]
|
||||||
|
t = d["type"]
|
||||||
|
m = d["meta"]
|
||||||
|
if isinstance(idx, int) and isinstance(gen, int) and isinstance(t, int) and isinstance(m, int):
|
||||||
|
v = pack_value_from_index_gen(idx, gen)
|
||||||
|
out.append((int(v) & 0xFFFFFFFF, int(t) & 0xFFFF, int(m) & 0xFFFF))
|
||||||
|
|
||||||
|
def visit(x):
|
||||||
|
if isinstance(x, dict):
|
||||||
|
if "ihandle" in x and isinstance(x["ihandle"], dict):
|
||||||
|
maybe_add_value_type_meta(x["ihandle"])
|
||||||
|
maybe_add_index_gen(x["ihandle"])
|
||||||
|
maybe_add_value_type_meta(x)
|
||||||
|
maybe_add_index_gen(x)
|
||||||
|
for vv in x.values():
|
||||||
|
visit(vv)
|
||||||
|
elif isinstance(x, list):
|
||||||
|
for vv in x:
|
||||||
|
visit(vv)
|
||||||
|
|
||||||
|
visit(node)
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
uniq = []
|
||||||
|
for h in out:
|
||||||
|
k = (h[0] | (h[1] << 32) | (h[2] << 48))
|
||||||
|
if k not in seen:
|
||||||
|
seen.add(k)
|
||||||
|
uniq.append(h)
|
||||||
|
return uniq
|
||||||
156
loaders/imesh_loader.py
Normal file
156
loaders/imesh_loader.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import struct
|
||||||
|
|
||||||
|
IMSH_MAGIC = b"IMSH"
|
||||||
|
IMSH_VERSION = 2
|
||||||
|
|
||||||
|
IMSH_HEADER_STRUCT = struct.Struct("<4sIIII" + "IHH" + "I" + "Q")
|
||||||
|
IMSH_SUBMESH_STRUCT = struct.Struct("<IIQ" + "IHH" + "ffffff" + "IIQ")
|
||||||
|
IMSH_LOD_STRUCT = struct.Struct("<IIQQ")
|
||||||
|
|
||||||
|
IMSH_HEADER_SIZE = IMSH_HEADER_STRUCT.size
|
||||||
|
IMSH_SUBMESH_SIZE = IMSH_SUBMESH_STRUCT.size
|
||||||
|
IMSH_LOD_SIZE = IMSH_LOD_STRUCT.size
|
||||||
|
|
||||||
|
MODEL_VERTEX_STRIDE = 48
|
||||||
|
|
||||||
|
def _h_from(value, typ, meta):
|
||||||
|
return (int(value) & 0xFFFFFFFF, int(typ) & 0xFFFF, int(meta) & 0xFFFF)
|
||||||
|
|
||||||
|
def _h_is_zero(h):
|
||||||
|
return (h[0] | h[1] | h[2]) == 0
|
||||||
|
|
||||||
|
def load_imesh_from_bytes(data, base_off=0):
|
||||||
|
n = len(data)
|
||||||
|
if base_off < 0 or base_off > n:
|
||||||
|
raise ValueError("IMSH: bad base_off")
|
||||||
|
if base_off + IMSH_HEADER_SIZE > n:
|
||||||
|
raise ValueError("IMSH: too small")
|
||||||
|
|
||||||
|
(magic, version, flags, submesh_count, reserved0,
|
||||||
|
model_value, model_type, model_meta,
|
||||||
|
_pad0,
|
||||||
|
submesh_table_offset) = IMSH_HEADER_STRUCT.unpack_from(data, base_off)
|
||||||
|
|
||||||
|
if magic != IMSH_MAGIC:
|
||||||
|
raise ValueError("IMSH: bad magic")
|
||||||
|
if version != IMSH_VERSION:
|
||||||
|
raise ValueError("IMSH: bad version")
|
||||||
|
|
||||||
|
submesh_table_offset = int(submesh_table_offset)
|
||||||
|
if submesh_table_offset < 0 or submesh_table_offset >= (n - base_off):
|
||||||
|
raise ValueError("IMSH: submesh table offset out of range")
|
||||||
|
|
||||||
|
smt_off = base_off + submesh_table_offset
|
||||||
|
need = smt_off + int(submesh_count) * IMSH_SUBMESH_SIZE
|
||||||
|
if need > n:
|
||||||
|
raise ValueError("IMSH: submesh table out of range")
|
||||||
|
|
||||||
|
handle = _h_from(model_value, model_type, model_meta)
|
||||||
|
|
||||||
|
refs = []
|
||||||
|
submeshes = []
|
||||||
|
|
||||||
|
for i in range(int(submesh_count)):
|
||||||
|
o = smt_off + i * IMSH_SUBMESH_SIZE
|
||||||
|
|
||||||
|
(sm_flags,
|
||||||
|
material_name_len,
|
||||||
|
material_name_offset,
|
||||||
|
mat_value, mat_type, mat_meta,
|
||||||
|
aabb_min_x, aabb_min_y, aabb_min_z,
|
||||||
|
aabb_max_x, aabb_max_y, aabb_max_z,
|
||||||
|
lod_count,
|
||||||
|
sm_reserved0,
|
||||||
|
lods_offset) = IMSH_SUBMESH_STRUCT.unpack_from(data, o)
|
||||||
|
|
||||||
|
material_name_len = int(material_name_len)
|
||||||
|
material_name_offset = int(material_name_offset)
|
||||||
|
lod_count = int(lod_count)
|
||||||
|
lods_offset = int(lods_offset)
|
||||||
|
|
||||||
|
mat_handle = _h_from(mat_value, mat_type, mat_meta)
|
||||||
|
if not _h_is_zero(mat_handle):
|
||||||
|
refs.append(mat_handle)
|
||||||
|
else:
|
||||||
|
mat_handle = None
|
||||||
|
|
||||||
|
mat_name = None
|
||||||
|
if material_name_len > 0:
|
||||||
|
mo = base_off + material_name_offset
|
||||||
|
me = mo + material_name_len
|
||||||
|
if mo < base_off or me > n:
|
||||||
|
raise ValueError("IMSH: material name out of range")
|
||||||
|
mat_name = data[mo:me].decode("utf-8", errors="ignore")
|
||||||
|
|
||||||
|
if lod_count <= 0:
|
||||||
|
raise ValueError("IMSH: lod_count=0")
|
||||||
|
|
||||||
|
lods_abs = base_off + lods_offset
|
||||||
|
lods_need = lods_abs + lod_count * IMSH_LOD_SIZE
|
||||||
|
if lods_abs < base_off or lods_need > n:
|
||||||
|
raise ValueError("IMSH: lod table out of range")
|
||||||
|
|
||||||
|
lods = []
|
||||||
|
for li in range(lod_count):
|
||||||
|
lo = lods_abs + li * IMSH_LOD_SIZE
|
||||||
|
vcount, icount, voff, ioff = IMSH_LOD_STRUCT.unpack_from(data, lo)
|
||||||
|
|
||||||
|
vcount = int(vcount)
|
||||||
|
icount = int(icount)
|
||||||
|
voff = int(voff)
|
||||||
|
ioff = int(ioff)
|
||||||
|
|
||||||
|
if vcount <= 0 or icount <= 0:
|
||||||
|
raise ValueError("IMSH: empty lod")
|
||||||
|
|
||||||
|
vb = base_off + voff
|
||||||
|
ib = base_off + ioff
|
||||||
|
vbytes = vcount * MODEL_VERTEX_STRIDE
|
||||||
|
ibytes = icount * 4
|
||||||
|
|
||||||
|
if vb < base_off or vb + vbytes > n:
|
||||||
|
raise ValueError("IMSH: vertices out of range")
|
||||||
|
if ib < base_off or ib + ibytes > n:
|
||||||
|
raise ValueError("IMSH: indices out of range")
|
||||||
|
|
||||||
|
lods.append({
|
||||||
|
"vertex_count": vcount,
|
||||||
|
"index_count": icount,
|
||||||
|
"vertices_offset": voff,
|
||||||
|
"indices_offset": ioff,
|
||||||
|
"vertices_size": vbytes,
|
||||||
|
"indices_size": ibytes,
|
||||||
|
"vertex_stride": MODEL_VERTEX_STRIDE,
|
||||||
|
})
|
||||||
|
|
||||||
|
submeshes.append({
|
||||||
|
"flags": int(sm_flags),
|
||||||
|
"material_handle": mat_handle,
|
||||||
|
"material_name": mat_name,
|
||||||
|
"aabb_min": (float(aabb_min_x), float(aabb_min_y), float(aabb_min_z)),
|
||||||
|
"aabb_max": (float(aabb_max_x), float(aabb_max_y), float(aabb_max_z)),
|
||||||
|
"lod_count": lod_count,
|
||||||
|
"lods_offset": lods_offset,
|
||||||
|
"lods": lods,
|
||||||
|
})
|
||||||
|
|
||||||
|
info = {
|
||||||
|
"version": int(version),
|
||||||
|
"flags": int(flags),
|
||||||
|
"submesh_count": int(submesh_count),
|
||||||
|
"submesh_table_offset": int(submesh_table_offset),
|
||||||
|
"decode_offset": int(base_off),
|
||||||
|
"header_size": int(IMSH_HEADER_SIZE),
|
||||||
|
"submesh_record_size": int(IMSH_SUBMESH_SIZE),
|
||||||
|
"lod_record_size": int(IMSH_LOD_SIZE),
|
||||||
|
"vertex_stride": int(MODEL_VERTEX_STRIDE),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"handle": handle,
|
||||||
|
"refs": refs,
|
||||||
|
"submeshes": submeshes,
|
||||||
|
"info": info,
|
||||||
|
"blob": data,
|
||||||
|
"base_off": int(base_off),
|
||||||
|
}
|
||||||
68
loaders/itex_loader.py
Normal file
68
loaders/itex_loader.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import struct
|
||||||
|
import time
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
ITEX_MAGIC = 0x58455449
|
||||||
|
ITEX_VERSION = 1
|
||||||
|
ITEX_HEADER_SIZE = 56
|
||||||
|
ITEX_STRUCT = struct.Struct("<IHHIIIIIIIIIHHII")
|
||||||
|
|
||||||
|
def load_itex_from_bytes(data, base_off=0):
|
||||||
|
if len(data) < base_off + ITEX_HEADER_SIZE:
|
||||||
|
raise ValueError("ITEX: too small")
|
||||||
|
|
||||||
|
(
|
||||||
|
magic,
|
||||||
|
version,
|
||||||
|
header_size,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
channels,
|
||||||
|
is_float,
|
||||||
|
has_alpha,
|
||||||
|
has_smooth_alpha,
|
||||||
|
uncompressed_size,
|
||||||
|
compressed_size,
|
||||||
|
handle_value,
|
||||||
|
handle_type,
|
||||||
|
handle_meta,
|
||||||
|
reserved0,
|
||||||
|
reserved1
|
||||||
|
) = ITEX_STRUCT.unpack_from(data, base_off)
|
||||||
|
|
||||||
|
if magic != ITEX_MAGIC or version != ITEX_VERSION or header_size != ITEX_HEADER_SIZE:
|
||||||
|
raise ValueError("ITEX: bad header")
|
||||||
|
|
||||||
|
if width == 0 or height == 0 or channels == 0:
|
||||||
|
raise ValueError("ITEX: bad dims")
|
||||||
|
|
||||||
|
if compressed_size == 0 or uncompressed_size == 0:
|
||||||
|
raise ValueError("ITEX: bad sizes")
|
||||||
|
|
||||||
|
payload_off = base_off + header_size
|
||||||
|
end = payload_off + compressed_size
|
||||||
|
if end > len(data):
|
||||||
|
raise ValueError("ITEX: truncated payload")
|
||||||
|
|
||||||
|
comp = data[payload_off:end]
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
pixels = zlib.decompress(comp)
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
|
||||||
|
if len(pixels) != uncompressed_size:
|
||||||
|
raise ValueError("ITEX: decompressed size mismatch")
|
||||||
|
|
||||||
|
h = (int(handle_value) & 0xFFFFFFFF, int(handle_type) & 0xFFFF, int(handle_meta) & 0xFFFF)
|
||||||
|
info = {
|
||||||
|
"width": int(width),
|
||||||
|
"height": int(height),
|
||||||
|
"channels": int(channels),
|
||||||
|
"is_float": int(is_float),
|
||||||
|
"has_alpha": int(has_alpha),
|
||||||
|
"has_smooth_alpha": int(has_smooth_alpha),
|
||||||
|
"compressed_size": int(compressed_size),
|
||||||
|
"decompressed_size": int(uncompressed_size),
|
||||||
|
"decompress_ms": (t1 - t0) * 1000.0,
|
||||||
|
"decode_offset": int(base_off),
|
||||||
|
}
|
||||||
|
return h, info, pixels
|
||||||
121
loaders/material_loader.py
Normal file
121
loaders/material_loader.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
from .ikv_loader import parse_ikv_text, extract_handles_recursive
|
||||||
|
|
||||||
|
|
||||||
|
def try_load_material_from_bytes(data):
|
||||||
|
try:
|
||||||
|
txt = data.decode("utf-8", errors="strict")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
txt = data.decode("utf-8", errors="ignore")
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
s = txt.lstrip()
|
||||||
|
if not (s.startswith("ikv1") or s.startswith("{") or s.startswith('"')):
|
||||||
|
return None
|
||||||
|
|
||||||
|
obj = parse_ikv_text(txt)
|
||||||
|
|
||||||
|
root_handle = None
|
||||||
|
if isinstance(obj, dict) and "ihandle" in obj and isinstance(obj["ihandle"], dict):
|
||||||
|
d = obj["ihandle"]
|
||||||
|
if all(k in d for k in ("value", "type", "meta")) and all(
|
||||||
|
isinstance(d[k], int) for k in ("value", "type", "meta")
|
||||||
|
):
|
||||||
|
v = int(d["value"]) & 0xFFFFFFFF
|
||||||
|
t = int(d["type"]) & 0xFFFF
|
||||||
|
m = int(d["meta"]) & 0xFFFF
|
||||||
|
if t != 0:
|
||||||
|
root_handle = (v, t, m)
|
||||||
|
|
||||||
|
refs = extract_handles_recursive(obj)
|
||||||
|
|
||||||
|
if root_handle is not None:
|
||||||
|
rk = root_handle[0] | (root_handle[1] << 32) | (root_handle[2] << 48)
|
||||||
|
refs = [h for h in refs if ((h[0] | (h[1] << 32) | (h[2] << 48)) != rk)]
|
||||||
|
|
||||||
|
info = {}
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
if "shader_id" in obj:
|
||||||
|
info["shader_id"] = obj.get("shader_id")
|
||||||
|
if "flags" in obj:
|
||||||
|
info["flags"] = obj.get("flags")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"handle": root_handle,
|
||||||
|
"refs": refs,
|
||||||
|
"obj": obj,
|
||||||
|
"info": info,
|
||||||
|
"text_len": len(txt),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def pack_value_from_index_gen(index, generation):
|
||||||
|
return ((int(generation) & 0xFFFF) << 16) | (int(index) & 0xFFFF)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_handles_recursive(node):
|
||||||
|
out = []
|
||||||
|
|
||||||
|
def is_valid_triplet(v, t, m):
|
||||||
|
v = int(v) & 0xFFFFFFFF
|
||||||
|
t = int(t) & 0xFFFF
|
||||||
|
m = int(m) & 0xFFFF
|
||||||
|
if t == 0:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def maybe_add_value_type_meta(d):
|
||||||
|
if not isinstance(d, dict):
|
||||||
|
return
|
||||||
|
if "value" in d and "type" in d and "meta" in d:
|
||||||
|
v = d["value"]
|
||||||
|
t = d["type"]
|
||||||
|
m = d["meta"]
|
||||||
|
if isinstance(v, int) and isinstance(t, int) and isinstance(m, int):
|
||||||
|
if is_valid_triplet(v, t, m):
|
||||||
|
out.append((int(v) & 0xFFFFFFFF, int(t) & 0xFFFF, int(m) & 0xFFFF))
|
||||||
|
|
||||||
|
def maybe_add_index_gen(d):
|
||||||
|
if not isinstance(d, dict):
|
||||||
|
return
|
||||||
|
if "index" in d and "generation" in d and "type" in d and "meta" in d:
|
||||||
|
idx = d["index"]
|
||||||
|
gen = d["generation"]
|
||||||
|
t = d["type"]
|
||||||
|
m = d["meta"]
|
||||||
|
if (
|
||||||
|
isinstance(idx, int)
|
||||||
|
and isinstance(gen, int)
|
||||||
|
and isinstance(t, int)
|
||||||
|
and isinstance(m, int)
|
||||||
|
):
|
||||||
|
if (idx == 0 and gen == 0) or int(t) == 0:
|
||||||
|
return
|
||||||
|
v = pack_value_from_index_gen(idx, gen)
|
||||||
|
if is_valid_triplet(v, t, m):
|
||||||
|
out.append((int(v) & 0xFFFFFFFF, int(t) & 0xFFFF, int(m) & 0xFFFF))
|
||||||
|
|
||||||
|
def visit(x):
|
||||||
|
if isinstance(x, dict):
|
||||||
|
if "ihandle" in x and isinstance(x["ihandle"], dict):
|
||||||
|
maybe_add_value_type_meta(x["ihandle"])
|
||||||
|
maybe_add_index_gen(x["ihandle"])
|
||||||
|
maybe_add_value_type_meta(x)
|
||||||
|
maybe_add_index_gen(x)
|
||||||
|
for vv in x.values():
|
||||||
|
visit(vv)
|
||||||
|
elif isinstance(x, list):
|
||||||
|
for vv in x:
|
||||||
|
visit(vv)
|
||||||
|
|
||||||
|
visit(node)
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
uniq = []
|
||||||
|
for h in out:
|
||||||
|
k = h[0] | (h[1] << 32) | (h[2] << 48)
|
||||||
|
if k not in seen:
|
||||||
|
seen.add(k)
|
||||||
|
uniq.append(h)
|
||||||
|
return uniq
|
||||||
Reference in New Issue
Block a user