Source code for eolib.data.eo_writer

from eolib.data.eo_numeric_limits import CHAR_MAX, SHORT_MAX, THREE_MAX, INT_MAX
from eolib.data.number_encoding_utils import encode_number
from eolib.data.string_encoding_utils import encode_string


[docs] class EoWriter: """ A class for writing EO data to a sequence of bytes. """
[docs] def __init__(self): """ Creates a new ``EoWriter`` with no data. """ self.data = bytearray() self._string_sanitization_mode = False
[docs] def add_byte(self, value: int) -> None: """ Adds a raw byte to the writer data. Args: value (int): The byte to add. Raises: ValueError: If the value is above `0xFF`. """ self._check_number_size(value, 0xFF) self.data.append(value)
[docs] def add_bytes(self, bytes: bytes) -> None: """ Adds raw bytes to the writer data. Args: bytes (bytes): The bytes to add. """ self.data.extend(bytes)
[docs] def add_char(self, number: int) -> None: """ Adds an encoded 1-byte integer to the writer data. Args: number (int): The number to encode and add. Raises: ValueError: If the value is not below `CHAR_MAX`. """ self._check_number_size(number, CHAR_MAX - 1) number_bytes = encode_number(number) self._add_bytes_with_length(number_bytes, 1)
[docs] def add_short(self, number: int) -> None: """ Adds an encoded 2-byte integer to the writer data. Args: number (int): The number to encode and add. Raises: ValueError: If the value is not below `SHORT_MAX`. """ self._check_number_size(number, SHORT_MAX - 1) number_bytes = encode_number(number) self._add_bytes_with_length(number_bytes, 2)
[docs] def add_three(self, number: int) -> None: """ Adds an encoded 3-byte integer to the writer data. Args: number (int): The number to encode and add. Raises: ValueError: If the value is not below `THREE_MAX`. """ self._check_number_size(number, THREE_MAX - 1) number_bytes = encode_number(number) self._add_bytes_with_length(number_bytes, 3)
[docs] def add_int(self, number: int) -> None: """ Adds an encoded 4-byte integer to the writer data. Args: number (int): The number to encode and add. Raises: ValueError: If the value is not below `INT_MAX`. """ self._check_number_size(number, INT_MAX - 1) number_bytes = encode_number(number) self._add_bytes_with_length(number_bytes, 4)
[docs] def add_string(self, string: str) -> None: """ Adds a string to the writer data. Args: string (str): The string to be added. """ string_bytes = self._encode_ansi(string) self._sanitize_string(string_bytes) self.add_bytes(string_bytes)
[docs] def add_fixed_string(self, string: str, length: int, padded: bool = False) -> None: """ Adds a fixed-length string to the writer data. Args: string (str): The string to be added. length (int): The expected length of the string. padded (bool, optional): True if the string should be padded to the length with trailing `0xFF` bytes. Defaults to False. """ self._check_string_length(string, length, padded) string_bytes = self._encode_ansi(string) self._sanitize_string(string_bytes) if padded: string_bytes = self._add_padding(string_bytes, length) self.add_bytes(string_bytes)
[docs] def add_encoded_string(self, string: str) -> None: """ Adds an encoded string to the writer data. Args: string (str): The string to be encoded and added. """ string_bytes = self._encode_ansi(string) self._sanitize_string(string_bytes) encode_string(string_bytes) self.add_bytes(string_bytes)
[docs] def add_fixed_encoded_string(self, string: str, length: int, padded: bool = False) -> None: """ Adds a fixed-length encoded string to the writer data. Args: string (str): The string to be encoded and added. length (int): The expected length of the string. padded (bool, optional): True if the string should be padded to the length with trailing `0xFF` bytes. Defaults to False. """ self._check_string_length(string, length, padded) string_bytes = self._encode_ansi(string) self._sanitize_string(string_bytes) if padded: string_bytes = self._add_padding(string_bytes, length) encode_string(string_bytes) self.add_bytes(string_bytes)
@property def string_sanitization_mode(self) -> bool: """ Whether string sanitization is enabled for the writer. """ return self._string_sanitization_mode @string_sanitization_mode.setter def string_sanitization_mode(self, string_sanitization_mode: bool) -> None: self._string_sanitization_mode = string_sanitization_mode
[docs] def to_bytearray(self) -> bytearray: """ Gets the writer data as a byte array. Returns: A copy of the writer data as a byte array. """ return self.data.copy()
def __len__(self) -> int: """ Gets the length of the writer data. Returns: The length of the writer data. """ return len(self.data) def _add_bytes_with_length(self, bytes: bytes, bytes_length: int) -> None: """ Adds raw bytes with a specified length to the writer data. Args: bytes (bytes): The bytes to add. bytes_length (int): The number of bytes to add. """ self.data.extend(bytes[:bytes_length]) def _sanitize_string(self, bytes: bytearray) -> None: if self.string_sanitization_mode: for i in range(len(bytes)): if bytes[i] == 0xFF: # 'ΓΏ' bytes[i] = 0x79 # 'y' @staticmethod def _check_number_size(number: int, max_value: int) -> None: if number > max_value: raise ValueError(f"Value {number} exceeds maximum of {max_value}.") @staticmethod def _add_padding(bytes: bytearray, length: int) -> bytearray: if len(bytes) == length: return bytes result = bytearray(length) result[: len(bytes)] = bytes result[len(bytes) :] = bytearray([0xFF] * (length - len(bytes))) return result @staticmethod def _check_string_length(string: str, length: int, padded: bool) -> None: if padded: if length >= len(string): return raise ValueError(f'Padded string "{string}" is too large for a length of {length}.') if len(string) != length: raise ValueError(f'String "{string}" does not have expected length of {length}.') @staticmethod def _encode_ansi(string: str) -> bytearray: """ Encodes string to windows-1252 bytes. Args: string (str): The string to encode. Returns: The encoded string. """ return bytearray(string, 'windows-1252', 'replace')