42 lines
1.5 KiB
Python
42 lines
1.5 KiB
Python
"""Minimal reader of Enshrouded's KFC4 container: raw content blobs by their content key.
|
|
|
|
enshrouded.kfc header: 17 KFCLocation entries (u32 offset relative to the entry, u32 count) at 16 + i*8.
|
|
Location 1 = .dat containers, 7 = content keys (size, hash0, hash1, hash2), 8 = values
|
|
(offset u32, flags u16, container index u16, 8 bytes pad). Blobs live in enshrouded_NNN.dat.
|
|
"""
|
|
import pathlib
|
|
import struct
|
|
|
|
|
|
class KFC:
|
|
def __init__(self, game):
|
|
self.game = pathlib.Path(game)
|
|
b = (self.game / "enshrouded.kfc").read_bytes()
|
|
assert b[:4] == b"KFC4", "not a KFC4 file"
|
|
locs = []
|
|
for i in range(17):
|
|
pos = 16 + i * 8
|
|
off, cnt = struct.unpack_from("<II", b, pos)
|
|
locs.append((pos + off if off else 0, cnt))
|
|
ko, kc = locs[7]
|
|
vo, vc = locs[8]
|
|
assert kc == vc
|
|
self.content = {}
|
|
for i in range(kc):
|
|
key = struct.unpack_from("<IIII", b, ko + i * 16)
|
|
off, flags, ci = struct.unpack_from("<IHH", b, vo + i * 16)
|
|
self.content[key] = (off, flags, ci)
|
|
|
|
def read(self, size, hash0, hash1, hash2):
|
|
e = self.content.get((size, hash0, hash1, hash2))
|
|
if not e:
|
|
return None
|
|
off, _, ci = e
|
|
with open(self.game / f"enshrouded_{ci:03d}.dat", "rb") as f:
|
|
f.seek(off)
|
|
return f.read(size)
|
|
|
|
def read_hash(self, h: dict):
|
|
"""h: a descriptor's content hash {"size", "hash0", "hash1", "hash2"}."""
|
|
return self.read(h["size"], h["hash0"], h["hash1"], h["hash2"])
|