Module psfdata.memview

Classes

class MemoryViewAbs (data: bytes | memoryview)
Expand source code
class MemoryViewAbs:
    """memoryview class that keeps track of its offset relative to the original bytes object on which it is based.
    Needed because PSF uses absolute file positions for block start/end."""

    def __init__(self, data: bytes | memoryview, _abspos: int = 0) -> None:
        self._mv = memoryview(data)
        self._abspos = _abspos  # offset of self._mv in the original bytes object

    def __getitem__(self, key) -> MemoryViewAbs:
        if isinstance(key, slice):
            newpos = self._abspos + key.indices(len(self._mv))[0]
            return self.__class__(self._mv[key], newpos)
        else:
            raise KeyError("Only slices allowed")

    def __len__(self) -> int:
        return len(self._mv)

    def split_at_absolute(self, abs_pos: int) -> tuple[MemoryViewAbs, MemoryViewAbs]:
        rel_pos = abs_pos - self._abspos
        assert 0 <= rel_pos <= len(self)
        return (self.__class__(self._mv[:rel_pos], self._abspos),
                self.__class__(self._mv[rel_pos:], self._abspos + rel_pos))

    @property
    def data(self) -> memoryview:
        return self._mv

    @property
    def abspos(self) -> int:
        return self._abspos

    def get_int32(self, pos: int) -> int:
        """Read int at position pos (without consuming it)"""
        if pos < 0:
            pos += len(self.data)
        return int.from_bytes(self._mv[pos: pos + 4], byteorder='big')

    def _consume(self, n: int) -> None:
        self._mv = self._mv[n:]
        self._abspos += n

    # TODO: in all of these, check that the correct number of bytes were read (and no EOF occurred):

    def read_bytes(self, n, peek=False) -> bytes:
        """Consume and return N bytes"""
        data = bytes(self._mv[:n])
        if not peek:
            self._consume(n)
        return data

    def read_int8(self, peek=False) -> int:
        """Consume 1 byte and return int."""
        value = int.from_bytes(self._mv[:], byteorder='big')
        if not peek:
            self._consume(1)
        return value

    def read_int32(self, peek=False) -> int:
        """Consume 4 bytes and return int."""
        value = int.from_bytes(self._mv[:4], byteorder='big')
        if not peek:
            self._consume(4)
        return value

    def read_int64(self, peek=False) -> int:
        """Consume 8 bytes and return int."""
        value = int.from_bytes(self._mv[:8], byteorder='big')
        if not peek:
            self._consume(8)
        return value

    def read_double(self, peek=False) -> float:
        """Consume 8 bytes and return float."""
        value = struct.unpack(">d", self._mv[:8])[0]
        if not peek:
            self._consume(8)
        return value

    def read_cdouble(self) -> float:
        """Consume 16 bytes and return cdouble."""
        raise NotImplementedError()

    def read_string(self) -> str:
        """Consume 4 + length bytes and return string."""
        length = self.read_int32()
        string = bytes(self._mv[:length]).decode()

        if len(string) != length:
            raise ValueError("String length does not match.")

        pad = ((4 - length) & 3)  # align to 4-byte boundary
        self._consume(length + pad)

        return string

    def hexprint(self, maxlines=5) -> None:
        hexprint(self._mv, maxlines)

memoryview class that keeps track of its offset relative to the original bytes object on which it is based. Needed because PSF uses absolute file positions for block start/end.

Instance variables

prop abspos : int
Expand source code
@property
def abspos(self) -> int:
    return self._abspos
prop data : memoryview
Expand source code
@property
def data(self) -> memoryview:
    return self._mv

Methods

def get_int32(self, pos: int) ‑> int
Expand source code
def get_int32(self, pos: int) -> int:
    """Read int at position pos (without consuming it)"""
    if pos < 0:
        pos += len(self.data)
    return int.from_bytes(self._mv[pos: pos + 4], byteorder='big')

Read int at position pos (without consuming it)

def hexprint(self, maxlines=5) ‑> None
Expand source code
def hexprint(self, maxlines=5) -> None:
    hexprint(self._mv, maxlines)
def read_bytes(self, n, peek=False) ‑> bytes
Expand source code
def read_bytes(self, n, peek=False) -> bytes:
    """Consume and return N bytes"""
    data = bytes(self._mv[:n])
    if not peek:
        self._consume(n)
    return data

Consume and return N bytes

def read_cdouble(self) ‑> float
Expand source code
def read_cdouble(self) -> float:
    """Consume 16 bytes and return cdouble."""
    raise NotImplementedError()

Consume 16 bytes and return cdouble.

def read_double(self, peek=False) ‑> float
Expand source code
def read_double(self, peek=False) -> float:
    """Consume 8 bytes and return float."""
    value = struct.unpack(">d", self._mv[:8])[0]
    if not peek:
        self._consume(8)
    return value

Consume 8 bytes and return float.

def read_int32(self, peek=False) ‑> int
Expand source code
def read_int32(self, peek=False) -> int:
    """Consume 4 bytes and return int."""
    value = int.from_bytes(self._mv[:4], byteorder='big')
    if not peek:
        self._consume(4)
    return value

Consume 4 bytes and return int.

def read_int64(self, peek=False) ‑> int
Expand source code
def read_int64(self, peek=False) -> int:
    """Consume 8 bytes and return int."""
    value = int.from_bytes(self._mv[:8], byteorder='big')
    if not peek:
        self._consume(8)
    return value

Consume 8 bytes and return int.

def read_int8(self, peek=False) ‑> int
Expand source code
def read_int8(self, peek=False) -> int:
    """Consume 1 byte and return int."""
    value = int.from_bytes(self._mv[:], byteorder='big')
    if not peek:
        self._consume(1)
    return value

Consume 1 byte and return int.

def read_string(self) ‑> str
Expand source code
def read_string(self) -> str:
    """Consume 4 + length bytes and return string."""
    length = self.read_int32()
    string = bytes(self._mv[:length]).decode()

    if len(string) != length:
        raise ValueError("String length does not match.")

    pad = ((4 - length) & 3)  # align to 4-byte boundary
    self._consume(length + pad)

    return string

Consume 4 + length bytes and return string.

def split_at_absolute(self, abs_pos: int) ‑> tuple[MemoryViewAbsMemoryViewAbs]
Expand source code
def split_at_absolute(self, abs_pos: int) -> tuple[MemoryViewAbs, MemoryViewAbs]:
    rel_pos = abs_pos - self._abspos
    assert 0 <= rel_pos <= len(self)
    return (self.__class__(self._mv[:rel_pos], self._abspos),
            self.__class__(self._mv[rel_pos:], self._abspos + rel_pos))