PyLibMS 4.3.4__py3-none-any.whl → 5.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. lms/common/field/io.py +13 -15
  2. lms/common/field/lms_datatype.py +35 -42
  3. lms/common/field/lms_field.py +34 -30
  4. lms/common/lms_constants.py +1 -0
  5. lms/common/stream/section.py +9 -13
  6. lms/fileio/io.py +62 -63
  7. lms/flowchart/definitions/node.py +53 -21
  8. lms/flowchart/flw3.py +26 -8
  9. lms/flowchart/msbf.py +65 -51
  10. lms/flowchart/msbfio.py +22 -15
  11. lms/message/lms_messagetext.py +15 -13
  12. lms/message/msbt.py +39 -21
  13. lms/message/msbtio.py +51 -18
  14. lms/message/tag/io/param_io.py +11 -8
  15. lms/message/tag/io/tag_io.py +12 -12
  16. lms/message/tag/lms_tag.py +19 -20
  17. lms/project/definitions/attribute.py +14 -6
  18. lms/project/definitions/tag.py +79 -35
  19. lms/project/msbp.py +12 -12
  20. lms/project/msbpread.py +26 -28
  21. lms/project/section/ali2.py +10 -7
  22. lms/project/section/ati2.py +5 -6
  23. lms/project/section/clr1.py +1 -1
  24. lms/project/section/string.py +5 -7
  25. lms/project/section/syl3.py +4 -4
  26. lms/project/section/tag2.py +4 -4
  27. lms/project/section/tgg2.py +3 -3
  28. lms/project/section/tgp2.py +7 -7
  29. lms/py.typed +0 -0
  30. lms/titleconfig/config.py +31 -88
  31. lms/titleconfig/definitions/attribute.py +23 -7
  32. lms/titleconfig/definitions/field.py +32 -0
  33. lms/titleconfig/definitions/nodes.py +68 -64
  34. lms/titleconfig/definitions/tags.py +45 -48
  35. {pylibms-4.3.4.dist-info → pylibms-5.0.0.dist-info}/METADATA +20 -27
  36. {pylibms-4.3.4.dist-info → pylibms-5.0.0.dist-info}/RECORD +39 -39
  37. lms/flowchart/definitions/flowchart.py +0 -100
  38. lms/titleconfig/definitions/value.py +0 -18
  39. {pylibms-4.3.4.dist-info → pylibms-5.0.0.dist-info}/WHEEL +0 -0
  40. {pylibms-4.3.4.dist-info → pylibms-5.0.0.dist-info}/licenses/LICENSE +0 -0
  41. {pylibms-4.3.4.dist-info → pylibms-5.0.0.dist-info}/top_level.txt +0 -0
lms/common/field/io.py CHANGED
@@ -4,18 +4,18 @@ from lms.common.field.lms_datatype import (
4
4
  LMS_DataType,
5
5
  is_bool_datatype,
6
6
  is_bytes_datatype,
7
- is_list_datatype,
7
+ is_enum_datatype,
8
8
  is_number_datatype,
9
9
  )
10
10
  from lms.common.field.lms_field import LMS_Field
11
11
  from lms.fileio.io import FileReader, FileWriter
12
- from lms.titleconfig.definitions.value import ValueDefinition
12
+ from lms.titleconfig.definitions.field import FieldDefinition
13
13
 
14
14
 
15
15
  def read_field(
16
- reader: FileReader,
17
- definition: ValueDefinition,
18
- read_variable_width_value: Callable | None = None,
16
+ reader: FileReader,
17
+ definition: FieldDefinition,
18
+ read_variable_width_value: Callable | None = None,
19
19
  ) -> LMS_Field:
20
20
  # String is excluded as their reading varies between MSBT/MSBF
21
21
  match definition.datatype:
@@ -33,27 +33,25 @@ def read_field(
33
33
  value = reader.read_int32()
34
34
  case LMS_DataType.FLOAT32:
35
35
  value = reader.read_float32()
36
- case LMS_DataType.LIST:
36
+ case LMS_DataType.ENUM:
37
37
  if read_variable_width_value is not None:
38
38
  index = read_variable_width_value()
39
39
  else:
40
40
  index = reader.read_uint8()
41
- value = definition.list_items[index]
41
+ value = definition.enum_members[index]
42
42
  case LMS_DataType.BOOL:
43
43
  if read_variable_width_value is not None:
44
44
  value = bool(read_variable_width_value())
45
45
  else:
46
46
  value = bool(reader.read_uint8())
47
- case LMS_DataType.BYTES:
48
- value = reader.read_bytes(1)
49
47
 
50
- return LMS_Field(value, definition) # type: ignore
48
+ return LMS_Field(value, definition)
51
49
 
52
50
 
53
51
  def write_field(
54
- writer: FileWriter,
55
- field: LMS_Field,
56
- write_variable_width_value: Callable | None = None,
52
+ writer: FileWriter,
53
+ field: LMS_Field,
54
+ write_variable_width_value: Callable | None = None,
57
55
  ) -> None:
58
56
  if is_number_datatype(field.value, field.datatype):
59
57
  write_functions: dict[LMS_DataType, Callable] = {
@@ -68,8 +66,8 @@ def write_field(
68
66
  write_functions[field.datatype](field.value)
69
67
  return
70
68
 
71
- if is_list_datatype(field.value, field.datatype):
72
- value = field.list_items.index(field.value)
69
+ if is_enum_datatype(field.value, field.datatype):
70
+ value = list(field.enum_members.values()).index(field.value)
73
71
  if write_variable_width_value is not None:
74
72
  write_variable_width_value(value)
75
73
  else:
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from enum import Enum
3
+ from enum import IntEnum, auto
4
4
  from typing import Type, TypeGuard
5
5
 
6
6
  FLOAT32_MIN = -3.4028235e38
@@ -15,8 +15,9 @@ ALIAS_MAP = {
15
15
  "i32": "INT32",
16
16
  "f32": "FLOAT32",
17
17
  "str": "STRING",
18
+ # LIST fallback for ENUM datatype
19
+ "list": "ENUM",
18
20
  "bool": "BOOL",
19
- "byte": "BYTES",
20
21
  }
21
22
 
22
23
 
@@ -32,8 +33,8 @@ def is_number_datatype(value: object, datatype: LMS_DataType) -> TypeGuard[int |
32
33
  ) and isinstance(value, int)
33
34
 
34
35
 
35
- def is_list_datatype(value: object, datatype: LMS_DataType) -> TypeGuard[str]:
36
- return datatype is LMS_DataType.LIST and isinstance(value, str)
36
+ def is_enum_datatype(value: object, datatype: LMS_DataType) -> TypeGuard[str]:
37
+ return datatype is LMS_DataType.ENUM and isinstance(value, str)
37
38
 
38
39
 
39
40
  def is_bool_datatype(value: object, datatype: LMS_DataType) -> TypeGuard[bool]:
@@ -48,7 +49,7 @@ def is_string_datatype(value: object, datatype: LMS_DataType) -> TypeGuard[str]:
48
49
  return datatype is LMS_DataType.STRING and isinstance(value, str)
49
50
 
50
51
 
51
- class LMS_DataType(Enum):
52
+ class LMS_DataType(IntEnum):
52
53
  """Enum that represents a datatype for a value entry in a MSBT/MSBP file."""
53
54
 
54
55
  UINT8 = 0
@@ -62,64 +63,56 @@ class LMS_DataType(Enum):
62
63
  FLOAT32 = 6
63
64
 
64
65
  STRING = 8
65
- LIST = 9
66
+ ENUM = 9
66
67
 
67
- # - Interface types -
68
- # These types act as an abstraction for a real LMS_Datatype
69
- # The actual value isn't important since they are not real types, but are instantiated from a config
70
- BOOL = "bool"
71
- BYTES = "byte"
72
-
73
- def to_string(self) -> str:
74
- return self._name_.lower()
68
+ # Interface type for UINT8
69
+ BOOL = auto()
75
70
 
76
71
  @property
77
72
  def signed(self) -> bool:
78
73
  """Property for if the type is signed or not."""
79
74
  if self not in [
80
- LMS_DataType.STRING,
81
- LMS_DataType.LIST,
82
- LMS_DataType.BOOL,
83
- LMS_DataType.BYTES,
75
+ self.STRING,
76
+ self.ENUM,
77
+ self.BOOL,
84
78
  ]:
85
- return self in [LMS_DataType.INT8, LMS_DataType.INT16, LMS_DataType.INT32]
79
+ return self in [self.INT8, self.INT16, self.INT32]
86
80
 
87
- raise TypeError(f"Signed is not a valid property for '{self.to_string()}'!")
81
+ raise TypeError(f"Signed is not a valid property for '{self.name.lower()}'!")
88
82
 
89
83
  @property
90
84
  def builtin_type(self) -> Type[int | float | str | bool | bytes]:
91
85
  """The enum as the builtin python type."""
92
86
  return {
93
- LMS_DataType.UINT8: int,
94
- LMS_DataType.UINT16: int,
95
- LMS_DataType.UINT32: int,
96
- LMS_DataType.INT8: int,
97
- LMS_DataType.INT16: int,
98
- LMS_DataType.INT32: int,
99
- LMS_DataType.FLOAT32: float,
100
- LMS_DataType.STRING: str,
101
- LMS_DataType.LIST: str,
102
- LMS_DataType.BOOL: bool,
103
- LMS_DataType.BYTES: bytes,
87
+ self.UINT8: int,
88
+ self.UINT16: int,
89
+ self.UINT32: int,
90
+ self.INT8: int,
91
+ self.INT16: int,
92
+ self.INT32: int,
93
+ self.FLOAT32: float,
94
+ self.STRING: str,
95
+ self.ENUM: str,
96
+ self.BOOL: bool,
104
97
  }[self]
105
98
 
106
99
  @property
107
100
  def stream_size(self) -> int:
108
101
  """Size of the datatype, only for fixed integers."""
109
102
  sizes = {
110
- LMS_DataType.UINT8: 1,
111
- LMS_DataType.UINT16: 2,
112
- LMS_DataType.UINT32: 4,
113
- LMS_DataType.INT8: 1,
114
- LMS_DataType.INT16: 2,
115
- LMS_DataType.INT32: 4,
116
- LMS_DataType.FLOAT32: 4,
103
+ self.UINT8: 1,
104
+ self.UINT16: 2,
105
+ self.UINT32: 4,
106
+ self.INT8: 1,
107
+ self.INT16: 2,
108
+ self.INT32: 4,
109
+ self.FLOAT32: 4,
117
110
  }
118
111
 
119
112
  try:
120
113
  return sizes[self]
121
114
  except KeyError:
122
- raise TypeError(f"Stream size is not defined for '{self.to_string()}'.")
115
+ raise TypeError(f"Stream size is not defined for '{self.name.lower()}'.")
123
116
 
124
117
  @classmethod
125
118
  def from_string(cls, string: str):
@@ -136,8 +129,8 @@ class LMS_DataType(Enum):
136
129
 
137
130
 
138
131
  def verify_number_from_datatype(
139
- value: int | float,
140
- datatype: LMS_DataType,
132
+ value: int | float,
133
+ datatype: LMS_DataType,
141
134
  ):
142
135
  if datatype is LMS_DataType.FLOAT32:
143
136
  max_value = FLOAT32_MAX
@@ -148,7 +141,7 @@ def verify_number_from_datatype(
148
141
  max_value = 2 ** (bits - 1)
149
142
  min_value = -max_value
150
143
  else:
151
- min_value, max_value = 0, (2**bits) - 1
144
+ min_value, max_value = 0, (2 ** bits) - 1
152
145
 
153
146
  if not min_value <= value <= max_value:
154
147
  raise ValueError(
@@ -1,21 +1,32 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Iterator
4
- from dataclasses import dataclass
4
+ from types import MappingProxyType
5
+ from typing import Mapping, TypeAlias
5
6
 
6
7
  from lms.common.field.lms_datatype import LMS_DataType, verify_number_from_datatype
7
- from lms.titleconfig.definitions.value import ValueDefinition
8
+ from lms.titleconfig.definitions.field import FieldDefinition
8
9
 
9
- type FieldValue = int | str | float | bool | bytes
10
+ FieldValue: TypeAlias = int | str | float | bool | bytes
10
11
 
11
12
 
12
- @dataclass(frozen=True)
13
13
  class LMS_FieldMap:
14
14
  """
15
- A wrapper for a basic ``dict[str, LMS_Field]``. Difference is validation upon modification of a field.
15
+ An immutable wrapper for a basic ``Mapping[str, LMS_Field]``.
16
+
17
+ Fields are validated by their datatype upon modification.
16
18
  """
17
19
 
18
- fields: dict[str, LMS_Field]
20
+ def __init__(self, fields: Mapping[str, LMS_Field]) -> None:
21
+ self._fields = MappingProxyType(fields)
22
+
23
+ @property
24
+ def fields(self) -> Mapping[str, LMS_Field]:
25
+ """The fields associated to this LMS_FieldMap."""
26
+ return self._fields
27
+
28
+ def __len__(self) -> int:
29
+ return len(self.fields)
19
30
 
20
31
  def __iter__(self) -> Iterator[LMS_Field]:
21
32
  return iter(self.fields.values())
@@ -38,15 +49,15 @@ class LMS_FieldMap:
38
49
 
39
50
  @staticmethod
40
51
  def create_default_dict_map(
41
- definitions: list[ValueDefinition],
52
+ definitions: list[FieldDefinition],
42
53
  ) -> dict[str, FieldValue]:
43
54
  map = {}
44
55
  for definition in definitions:
45
56
  match definition.datatype:
46
57
  case LMS_DataType.STRING:
47
58
  map[definition.name] = ""
48
- case LMS_DataType.LIST:
49
- map[definition.name] = definition.list_items[0]
59
+ case LMS_DataType.ENUM:
60
+ map[definition.name] = definition.enum_members[0]
50
61
  case LMS_DataType.BOOL:
51
62
  map[definition.name] = False
52
63
  case LMS_DataType.FLOAT32:
@@ -56,11 +67,11 @@ class LMS_FieldMap:
56
67
  return map
57
68
 
58
69
  @classmethod
59
- def create_default_map(cls, definitions: list[ValueDefinition]):
70
+ def create_default_map(cls, definitions: list[FieldDefinition]):
60
71
  return cls.from_dict(LMS_FieldMap.create_default_map(definitions), definitions)
61
72
 
62
73
  @classmethod
63
- def from_dict(cls, data: dict[str, FieldValue], definitions: list[ValueDefinition]):
74
+ def from_dict(cls, data: dict[str, FieldValue], definitions: list[FieldDefinition]):
64
75
  return cls(
65
76
  {
66
77
  definition.name: LMS_Field(data[definition.name], definition)
@@ -69,20 +80,18 @@ class LMS_FieldMap:
69
80
  )
70
81
 
71
82
  @classmethod
72
- def from_string_dict(cls, data: dict[str, str], definitions: list[ValueDefinition]):
83
+ def from_string_dict(cls, data: dict[str, str], definitions: list[FieldDefinition]):
73
84
  fields = {}
74
85
  casted_value = None
75
86
 
76
87
  for definition in definitions:
77
88
  # These datatypes do not require any casting
78
- if definition.datatype in (LMS_DataType.STRING, LMS_DataType.LIST):
89
+ if definition.datatype in (LMS_DataType.STRING, LMS_DataType.ENUM):
79
90
  fields[definition.name] = LMS_Field(data[definition.name], definition)
80
91
  continue
81
92
 
82
93
  value = data[definition.name]
83
94
  match definition.datatype:
84
- case LMS_DataType.BYTES:
85
- casted_value = bytes.fromhex(value)
86
95
  case LMS_DataType.BOOL:
87
96
  if value not in ("false", "true"):
88
97
  raise ValueError("Invalid boolean value!")
@@ -103,9 +112,9 @@ class LMS_Field:
103
112
  """
104
113
 
105
114
  def __init__(
106
- self,
107
- value: int | str | float | bytes | bool,
108
- definition: ValueDefinition,
115
+ self,
116
+ value: int | str | float | bytes | bool,
117
+ definition: FieldDefinition,
109
118
  ):
110
119
  _verify_value_from_definition(value, definition)
111
120
  self._definition = definition
@@ -113,7 +122,7 @@ class LMS_Field:
113
122
 
114
123
  def __repr__(self):
115
124
  if self.datatype is LMS_DataType.LIST:
116
- return f"LMS_Field(value={self._value!r}, list_items={self.list_items!r})"
125
+ return f"LMS_Field(value={self._value!r}, list_items={self.enum_members!r})"
117
126
 
118
127
  return f"LMS_Field(value={self._value!r}, type={self.datatype!r})"
119
128
 
@@ -138,9 +147,9 @@ class LMS_Field:
138
147
  return self._definition.datatype
139
148
 
140
149
  @property
141
- def list_items(self) -> list[str]:
150
+ def enum_members(self) -> list[str]:
142
151
  """The list items bound to the field instance. Only is valid for ``LMS_Datatype.LIST`` values."""
143
- return self._definition.list_items
152
+ return self._definition.enum_members
144
153
 
145
154
  @value.setter
146
155
  def value(self, new_value: int | str | float | bytes | bool):
@@ -149,7 +158,7 @@ class LMS_Field:
149
158
 
150
159
 
151
160
  def _verify_value_from_definition(
152
- value: int | str | float | bytes | bool, definition: ValueDefinition
161
+ value: int | str | float | bytes | bool, definition: FieldDefinition
153
162
  ) -> None:
154
163
  datatype = definition.datatype
155
164
 
@@ -157,16 +166,11 @@ def _verify_value_from_definition(
157
166
  return
158
167
 
159
168
  match datatype:
160
- case LMS_DataType.BYTES if isinstance(value, bytes):
161
- if len(value) != 1:
162
- raise ValueError("Byte types only work for values of length 1!")
163
- else:
164
- return
165
- case LMS_DataType.LIST if isinstance(value, str):
166
- if value not in definition.list_items:
169
+ case LMS_DataType.ENUM if isinstance(value, str):
170
+ if value not in definition.enum_members.values():
167
171
  raise ValueError(
168
172
  f"""The value of '{value}' provided for field '{definition.name}' is not a
169
- valid item in the list {definition.list_items}."""
173
+ valid item in the list {definition.enum_members}."""
170
174
  )
171
175
  else:
172
176
  return
@@ -6,6 +6,7 @@ PADDING_BYTE = b"\x00"
6
6
  SIZE_OFFSET = 0x12
7
7
  SECTION_DATA_START = 0x20
8
8
  SECTION_PADDING_BYTE = b"\xab"
9
+ SECTION_ALIGNMENT = 16
9
10
 
10
11
  LITTLE_ENDIAN_BOM = b"\xff\xfe"
11
12
  BIG_ENDIAN_BOM = b"\xfe\xff"
@@ -1,14 +1,10 @@
1
- from typing import Any, Callable, Generator
1
+ from typing import Any, Callable, Generator, Sequence
2
2
 
3
+ from lms.common.lms_constants import SECTION_ALIGNMENT, SECTION_PADDING_BYTE
3
4
  from lms.fileio.io import FileReader, FileWriter
4
5
 
5
- SECTION_PADDING_BYTE = b"\xab"
6
- SECTION_ALIGNMENT = 16
7
6
 
8
-
9
- def read_section_data(
10
- reader: FileReader, section_count: int
11
- ) -> Generator[tuple[str, int], Any, None]:
7
+ def read_section_data(reader: FileReader, section_count: int) -> Generator[tuple[str, int], Any, None]:
12
8
  reader.seek(0x20)
13
9
  for _ in range(section_count):
14
10
  magic = reader.read_string_len(4)
@@ -20,15 +16,15 @@ def read_section_data(
20
16
  yield magic, size
21
17
 
22
18
  reader.seek(end)
23
- reader.align(16)
19
+ reader.align(SECTION_ALIGNMENT)
24
20
 
25
21
 
26
22
  def write_section(
27
- writer: FileWriter,
28
- magic: str,
29
- section_call: Callable,
30
- data: list[Any],
31
- *write_arguments: Any,
23
+ writer: FileWriter,
24
+ magic: str,
25
+ section_call: Callable,
26
+ data: Sequence[Any],
27
+ *write_arguments: Any,
32
28
  ) -> None:
33
29
  writer.write_string(magic)
34
30
  size_offset = writer.tell()
lms/fileio/io.py CHANGED
@@ -1,47 +1,54 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import struct
4
- from typing import BinaryIO, Generator
4
+ from enum import StrEnum
5
+ from typing import BinaryIO, Generator, Type
5
6
 
6
- from io import BytesIO, IOBase
7
+ from io import BytesIO, IOBase, TextIOBase
7
8
  from lms.fileio.encoding import FileEncoding
8
9
 
9
10
  PADDING_BYTE = b"\x00"
10
11
 
11
- STRUCT_TYPES = {
12
- "little": {
13
- "uint8": "<B",
14
- "uint16": "<H",
15
- "uint32": "<I",
16
- "int8": "<b",
17
- "int16": "<h",
18
- "int32": "<i",
19
- "float": "<f",
20
- },
21
- "big": {
22
- "uint8": ">B",
23
- "uint16": ">H",
24
- "uint32": ">I",
25
- "int8": ">b",
26
- "int16": ">h",
27
- "int32": ">i",
28
- "float": ">f",
29
- },
30
- }
12
+
13
+ class StructLETypes(StrEnum):
14
+ UINT8 = "<B"
15
+ UINT16 = "<H"
16
+ UINT32 = "<I"
17
+ INT8 = "<b"
18
+ INT16 = "<h"
19
+ INT32 = "<i"
20
+ FLOAT32 = "<f"
21
+
22
+
23
+ class StructBETypes(StrEnum):
24
+ UINT8 = ">B"
25
+ UINT16 = ">H"
26
+ UINT32 = ">I"
27
+ INT8 = ">b"
28
+ INT16 = ">h"
29
+ INT32 = ">i"
30
+ FLOAT32 = ">f"
31
31
 
32
32
 
33
33
  class FileReader:
34
- def __init__(self, data: BinaryIO | bytes, big_endian: bool = False):
35
- if isinstance(data, IOBase):
36
- self._stream = data
37
- elif isinstance(data, (bytes, bytearray, memoryview)):
38
- self._stream = BytesIO(data)
39
- else:
40
- raise TypeError("The stream provided is not valid!")
34
+ def __init__(self, data: BinaryIO | bytes | bytearray | memoryview, big_endian: bool = False):
35
+ self._stream: BinaryIO = None
36
+
37
+ match data:
38
+ case IOBase() if not isinstance(data, TextIOBase):
39
+ self._stream = data
40
+ case bytes() | bytearray() | memoryview():
41
+ self._stream = BytesIO(data)
42
+ case _:
43
+ raise TypeError("The stream provided is not valid!")
41
44
 
42
45
  self.encoding = FileEncoding.UTF8
43
46
  self.is_big_endian = big_endian
44
47
 
48
+ @property
49
+ def datatypes(self) -> Type[StructLETypes | StructBETypes]:
50
+ return StructLETypes if not self.is_big_endian else StructBETypes
51
+
45
52
  def tell(self) -> int:
46
53
  return self._stream.tell()
47
54
 
@@ -49,17 +56,13 @@ class FileReader:
49
56
  self._stream.read(length)
50
57
 
51
58
  def align(self, alignment: int) -> None:
52
- alignment = (-self.tell() % alignment + alignment) % alignment
53
- self.skip(alignment)
59
+ self.skip((-self.tell() % alignment + alignment) % alignment)
54
60
 
55
61
  def read_bytes(self, length: int) -> bytes:
56
62
  return self._stream.read(length)
57
63
 
58
64
  def seek(self, offset: int, whence: int = 0) -> None:
59
- if offset < 0:
60
- self._stream.seek(self.tell() + offset)
61
- else:
62
- self._stream.seek(offset, whence)
65
+ self._stream.seek(self.tell() + offset) if offset < 0 else self._stream.seek(offset, whence)
63
66
 
64
67
  def read_offset_array(self, count: int) -> Generator[int, None, None]:
65
68
  start = self.tell() - 4
@@ -72,25 +75,25 @@ class FileReader:
72
75
  return [self.read_uint16() for _ in range(count)]
73
76
 
74
77
  def read_int8(self) -> int:
75
- return struct.unpack(self._get_datatype("int8"), self._stream.read(1))[0]
78
+ return struct.unpack(self.datatypes.INT8, self._stream.read(1))[0]
76
79
 
77
80
  def read_int16(self) -> int:
78
- return struct.unpack(self._get_datatype("int16"), self._stream.read(2))[0]
81
+ return struct.unpack(self.datatypes.INT16, self._stream.read(2))[0]
79
82
 
80
83
  def read_int32(self) -> int:
81
- return struct.unpack(self._get_datatype("int32"), self._stream.read(4))[0]
84
+ return struct.unpack(self.datatypes.INT32, self._stream.read(4))[0]
82
85
 
83
86
  def read_uint8(self) -> int:
84
- return struct.unpack(self._get_datatype("uint8"), self._stream.read(1))[0]
87
+ return struct.unpack(self.datatypes.UINT8, self._stream.read(1))[0]
85
88
 
86
89
  def read_uint16(self) -> int:
87
- return struct.unpack(self._get_datatype("uint16"), self._stream.read(2))[0]
90
+ return struct.unpack(self.datatypes.UINT16, self._stream.read(2))[0]
88
91
 
89
92
  def read_uint32(self) -> int:
90
- return struct.unpack(self._get_datatype("uint32"), self._stream.read(4))[0]
93
+ return struct.unpack(self.datatypes.UINT32, self._stream.read(4))[0]
91
94
 
92
95
  def read_float32(self) -> float:
93
- return struct.unpack(self._get_datatype("float"), self._stream.read(4))[0]
96
+ return struct.unpack(self.datatypes.FLOAT32, self._stream.read(4))[0]
94
97
 
95
98
  def read_string_offset(self, section_start: int) -> str:
96
99
  offset = self.read_uint32()
@@ -104,12 +107,10 @@ class FileReader:
104
107
  return self._stream.read(length).decode("UTF-8")
105
108
 
106
109
  def read_encoded_string(self):
107
- message = b""
108
- while (
109
- raw_char := self.read_bytes(self.encoding.width)
110
- ) != self.encoding.terminator:
111
- message += raw_char
112
- return message.decode(self.encoding.to_string_format(self.is_big_endian))
110
+ string = b""
111
+ while (raw_char := self.read_bytes(self.encoding.width)) != self.encoding.terminator:
112
+ string += raw_char
113
+ return string.decode(self.encoding.to_string_format(self.is_big_endian))
113
114
 
114
115
  def read_len_string_encoded(self):
115
116
  self.align(self.encoding.width)
@@ -118,9 +119,6 @@ class FileReader:
118
119
  self.encoding.to_string_format(self.is_big_endian)
119
120
  )
120
121
 
121
- def _get_datatype(self, name: str) -> str:
122
- return STRUCT_TYPES["little" if not self.is_big_endian else "big"][name]
123
-
124
122
 
125
123
  class FileWriter:
126
124
  def __init__(self, encoding: FileEncoding):
@@ -128,6 +126,10 @@ class FileWriter:
128
126
  self.encoding = encoding
129
127
  self.is_big_endian = False
130
128
 
129
+ @property
130
+ def datatypes(self) -> Type[StructLETypes | StructBETypes]:
131
+ return StructLETypes if not self.is_big_endian else StructBETypes
132
+
131
133
  def skip(self, length: int) -> None:
132
134
  self.data.seek(length, 1)
133
135
 
@@ -155,34 +157,34 @@ class FileWriter:
155
157
  self.write_uint16(number)
156
158
 
157
159
  def write_int8(self, value: int) -> None:
158
- self.data.write(struct.pack(self._get_datatype("int8"), value))
160
+ self.data.write(struct.pack(self.datatypes.INT8, value))
159
161
 
160
162
  def write_int16(self, value: int) -> None:
161
- self.data.write(struct.pack(self._get_datatype("int16"), value))
163
+ self.data.write(struct.pack(self.datatypes.INT16, value))
162
164
 
163
165
  def write_int32(self, value: int) -> None:
164
- self.data.write(struct.pack(self._get_datatype("int32"), value))
166
+ self.data.write(struct.pack(self.datatypes.INT32, value))
165
167
 
166
168
  def write_uint8(self, value: int) -> None:
167
- self.data.write(struct.pack(self._get_datatype("uint8"), value))
169
+ self.data.write(struct.pack(self.datatypes.UINT8, value))
168
170
 
169
171
  def write_uint16(self, value: int) -> None:
170
- self.data.write(struct.pack(self._get_datatype("uint16"), value))
172
+ self.data.write(struct.pack(self.datatypes.UINT16, value))
171
173
 
172
174
  def write_uint32(self, value: int) -> None:
173
- self.data.write(struct.pack(self._get_datatype("uint32"), value))
175
+ self.data.write(struct.pack(self.datatypes.UINT32, value))
174
176
 
175
177
  def write_float32(self, value: float) -> None:
176
- self.data.write(struct.pack(self._get_datatype("float"), value))
178
+ self.data.write(struct.pack(self.datatypes.FLOAT32, value))
177
179
 
178
- def write_string(self, string: str):
180
+ def write_string(self, string: str) -> None:
179
181
  self.write_bytes(string.encode("UTF-8"))
180
182
 
181
183
  def write_len_encoded_string(self, string: str) -> None:
182
184
  self.write_uint16(len(string) * self.encoding.width)
183
185
  self.write_encoded_string(string, False)
184
186
 
185
- def write_encoded_string(self, string: str, terminate: bool = True):
187
+ def write_encoded_string(self, string: str, terminate: bool = True) -> None:
186
188
  self.write_bytes(
187
189
  string.encode(self.encoding.to_string_format(self.is_big_endian))
188
190
  )
@@ -192,8 +194,5 @@ class FileWriter:
192
194
  def _align(self, number: int, alignment: int) -> int:
193
195
  return (-number % alignment + alignment) % alignment
194
196
 
195
- def _get_datatype(self, name: str) -> str:
196
- return STRUCT_TYPES["little" if not self.is_big_endian else "big"][name]
197
-
198
197
  def get_data(self) -> bytes:
199
198
  return self.data.getvalue()