PyLibMS 2.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 (57) hide show
  1. LMS/Common/LMS_Exceptions.py +16 -0
  2. LMS/Common/LMS_FileInfo.py +11 -0
  3. LMS/Common/Stream/FileInfo.py +64 -0
  4. LMS/Common/Stream/Hashtable.py +87 -0
  5. LMS/Common/Stream/Section.py +60 -0
  6. LMS/Common/__init__.py +0 -0
  7. LMS/Config/Definitions/Attributes.py +14 -0
  8. LMS/Config/Definitions/Tags.py +52 -0
  9. LMS/Config/Definitions/Value.py +11 -0
  10. LMS/Config/Definitions/__init__.py +0 -0
  11. LMS/Config/TitleConfig.py +208 -0
  12. LMS/Config/__init__.py +0 -0
  13. LMS/Field/LMS_DataType.py +83 -0
  14. LMS/Field/LMS_Field.py +123 -0
  15. LMS/Field/Stream.py +56 -0
  16. LMS/Field/__init__.py +0 -0
  17. LMS/FileIO/Encoding.py +42 -0
  18. LMS/FileIO/Stream.py +181 -0
  19. LMS/Message/Definitions/LMS_FieldMap.py +5 -0
  20. LMS/Message/Definitions/LMS_MessageText.py +124 -0
  21. LMS/Message/Definitions/__init__.py +0 -0
  22. LMS/Message/MSBT.py +63 -0
  23. LMS/Message/MSBTEntry.py +33 -0
  24. LMS/Message/MSBTStream.py +138 -0
  25. LMS/Message/Section/ATR1.py +101 -0
  26. LMS/Message/Section/TSY1.py +13 -0
  27. LMS/Message/Section/TXT2.py +79 -0
  28. LMS/Message/Section/__init__.py +0 -0
  29. LMS/Message/Tag/LMS_Tag.py +169 -0
  30. LMS/Message/Tag/LMS_TagExceptions.py +7 -0
  31. LMS/Message/Tag/Stream.py +149 -0
  32. LMS/Message/Tag/System.yaml +47 -0
  33. LMS/Message/Tag/Tag_Formats.py +10 -0
  34. LMS/Message/Tag/__init__.py +0 -0
  35. LMS/Message/__init__.py +0 -0
  36. LMS/Project/Definitions/Attribute.py +27 -0
  37. LMS/Project/Definitions/Color.py +10 -0
  38. LMS/Project/Definitions/Style.py +14 -0
  39. LMS/Project/Definitions/Tag.py +58 -0
  40. LMS/Project/Definitions/__init__.py +0 -0
  41. LMS/Project/MSBP.py +64 -0
  42. LMS/Project/MSBPRead.py +101 -0
  43. LMS/Project/Section/ALI2.py +19 -0
  44. LMS/Project/Section/ATI2.py +17 -0
  45. LMS/Project/Section/CLR1.py +15 -0
  46. LMS/Project/Section/SYL3.py +17 -0
  47. LMS/Project/Section/String.py +18 -0
  48. LMS/Project/Section/TAG2.py +19 -0
  49. LMS/Project/Section/TGG2.py +20 -0
  50. LMS/Project/Section/TGP2.py +26 -0
  51. LMS/Project/__init__.py +0 -0
  52. LMS/__init__.py +0 -0
  53. pylibms-2.0.0.dist-info/METADATA +40 -0
  54. pylibms-2.0.0.dist-info/RECORD +57 -0
  55. pylibms-2.0.0.dist-info/WHEEL +5 -0
  56. pylibms-2.0.0.dist-info/licenses/LICENSE +7 -0
  57. pylibms-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,16 @@
1
+ class LMS_Error(Exception):
2
+ """Exception for non-specifc errors"""
3
+
4
+ pass
5
+
6
+
7
+ class LMS_UnexpectedMagicError(Exception):
8
+ """Exception for wrong magic of a LMS file"""
9
+
10
+ pass
11
+
12
+
13
+ class LMS_MisalignedSizeError(Exception):
14
+ """Exception for when size is not aligned."""
15
+
16
+ pass
@@ -0,0 +1,11 @@
1
+ from dataclasses import dataclass
2
+
3
+ from LMS.FileIO.Encoding import FileEncoding
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class LMS_FileInfo:
8
+ big_endian: bool
9
+ encoding: FileEncoding
10
+ version: int
11
+ section_count: int
@@ -0,0 +1,64 @@
1
+ from LMS.Common import LMS_Exceptions
2
+ from LMS.Common.LMS_FileInfo import LMS_FileInfo
3
+ from LMS.FileIO.Encoding import FileEncoding
4
+ from LMS.FileIO.Stream import FileReader, FileWriter
5
+
6
+
7
+ def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
8
+ magic = reader.read_string_len(8)
9
+
10
+ if magic != expected_magic:
11
+ raise LMS_Exceptions.LMS_UnexpectedMagicError(
12
+ f"Invalid magic!' Expected {expected_magic}', got '{magic}'."
13
+ )
14
+
15
+ big_endian = reader.read_bytes(2) == b"\xfe\xff"
16
+ reader.big_endian = big_endian
17
+
18
+ reader.skip(2)
19
+
20
+ encoding = FileEncoding(reader.read_uint8())
21
+ reader.encoding = encoding
22
+
23
+ version = reader.read_uint8()
24
+ section_count = reader.read_uint16()
25
+
26
+ reader.skip(2)
27
+ file_size = reader.read_uint32()
28
+
29
+ reader.seek(0, 2)
30
+ if file_size != reader.tell():
31
+ raise LMS_Exceptions.LMS_MisalignedSizeError(f"Filesize is misaligned!")
32
+
33
+ # Seek to the start of data
34
+ reader.seek(0x20)
35
+
36
+ return LMS_FileInfo(
37
+ big_endian,
38
+ encoding,
39
+ version,
40
+ section_count,
41
+ )
42
+
43
+
44
+ def write_file_info(writer: FileWriter, magic: str, file_info: LMS_FileInfo) -> None:
45
+ """Writes the file info to a stream.
46
+
47
+ :param writer: a Writer object.
48
+ :param file_info: the file_info object."""
49
+ writer.big_endian = file_info.big_endian
50
+ writer.encoding = file_info.encoding
51
+
52
+ writer.write_string(magic)
53
+ writer.write_bytes(b"\xff\xfe" if not file_info.big_endian else b"\xfe\xff")
54
+ writer.write_bytes(b"\x00\x00")
55
+
56
+ writer.write_uint8(file_info.encoding.value)
57
+ writer.write_uint8(file_info.version)
58
+ writer.write_uint16(file_info.section_count)
59
+
60
+ # Padding
61
+ writer.write_bytes(b"\x00\x00")
62
+ writer.write_bytes(b"\x00" * 4)
63
+ writer.write_bytes(b"\x00" * 10)
64
+ writer.seek(0x20)
@@ -0,0 +1,87 @@
1
+ from LMS.FileIO.Stream import FileReader, FileWriter
2
+
3
+
4
+ def read_labels(reader: FileReader) -> tuple[dict[int, str], int]:
5
+ labels = {}
6
+
7
+ data_start = reader.tell()
8
+ slot_count = reader.read_uint32()
9
+ for _ in range(slot_count):
10
+ # Read initial label data
11
+ label_count = reader.read_uint32()
12
+ offset = reader.read_uint32()
13
+ next_offset = reader.tell()
14
+
15
+ # Read the actual label data
16
+ reader.seek(data_start + offset)
17
+ for _ in range(label_count):
18
+ length = reader.read_uint8()
19
+ label = reader.read_string_len(length)
20
+ item_index = reader.read_uint32()
21
+ labels[item_index] = label
22
+
23
+ reader.seek(next_offset)
24
+
25
+ sorted_labels = {i: labels[i] for i in sorted(labels)}
26
+
27
+ # While the slot count is consistent for most files, some vary them.
28
+ # Return the slot count to ensure that this change can be recorded.
29
+ return (sorted_labels, slot_count)
30
+
31
+
32
+ def write_labels(writer: FileWriter, labels: list[str], slot_count: int) -> None:
33
+ """Writes the hashtable block to a stream.
34
+
35
+ :param writer: a Writer object.
36
+ :param labels: the dictionary of labels.
37
+ :param slot_count: the amount of hash slots.
38
+ """
39
+ writer.write_uint32(slot_count)
40
+
41
+ hash_slots = {}
42
+ index_map = {}
43
+ # Add each label to each hash slot
44
+ for i, label in enumerate(labels):
45
+ hash = _calculate_hash(label, slot_count)
46
+
47
+ # Add to the list if hash exists, and create a new list for each new hash
48
+ if hash not in hash_slots:
49
+ hash_slots[hash] = [label]
50
+ else:
51
+ hash_slots[hash].append(label)
52
+
53
+ index_map[label] = i
54
+ label_offsets = slot_count * 8 + 4
55
+
56
+ # Sort by the hash slots
57
+ hash_slots = dict(sorted(hash_slots.items(), key=lambda x: x[0]))
58
+
59
+ # Write the slots
60
+ for i in range(slot_count):
61
+ if i in hash_slots:
62
+ writer.write_uint32(len(hash_slots[i]))
63
+ writer.write_uint32(label_offsets)
64
+ for label in hash_slots[i]:
65
+ label_offsets += len(label) + 5
66
+ else:
67
+ writer.write_uint32(0)
68
+ writer.write_uint32(label_offsets)
69
+
70
+ # Write the labels
71
+ for key in hash_slots:
72
+ stored_labels = hash_slots[key]
73
+ for label in stored_labels:
74
+ writer.write_uint8(len(label))
75
+ writer.write_string(label)
76
+ writer.write_uint32(index_map[label])
77
+
78
+
79
+ def _calculate_hash(label: str, slot_count: int) -> int:
80
+ """Calculates the hash of a label.
81
+
82
+ See https://nintendo-formats.com/libs/lms/overview.html#hash-tables
83
+ """
84
+ hash = 0
85
+ for character in label:
86
+ hash = hash * 0x492 + ord(character)
87
+ return (hash & 0xFFFFFFFF) % slot_count
@@ -0,0 +1,60 @@
1
+ from typing import Any, Callable, Generator
2
+
3
+ from LMS.FileIO.Stream import FileReader, FileWriter
4
+
5
+
6
+ def read_section_data(
7
+ reader: FileReader, section_count: int
8
+ ) -> Generator[tuple[str, int], Any, None]:
9
+ reader.seek(0x20)
10
+ for _ in range(section_count):
11
+ magic = reader.read_string_len(4)
12
+ size = reader.read_uint32()
13
+
14
+ # Skip to start of data
15
+ reader.skip(8)
16
+ end = reader.tell() + size
17
+
18
+ yield (magic, size)
19
+
20
+ # Seek past the AB padding on next iteration
21
+ reader.seek(end)
22
+ reader.align(16)
23
+
24
+
25
+ def write_section(
26
+ writer: FileWriter,
27
+ magic: str,
28
+ section_call: Callable,
29
+ data: list[Any],
30
+ *write_parameters,
31
+ ) -> None:
32
+ writer.write_string(magic)
33
+ size_offset = writer.tell()
34
+
35
+ writer.write_uint32(0)
36
+ writer.write_bytes(b"\x00" * 8)
37
+ data_start = writer.tell()
38
+
39
+ section_call(writer, data, *write_parameters)
40
+
41
+ _write_end_data(writer, data_start, size_offset)
42
+
43
+
44
+ def write_unsupported_section(writer: FileWriter, magic: str, data: bytes) -> None:
45
+ writer.write_string(magic)
46
+ size_offset = writer.tell()
47
+ writer.write_uint32(0)
48
+ writer.write_bytes(b"\x00" * 8)
49
+ data_start = writer.tell()
50
+ writer.write_bytes(data)
51
+ _write_end_data(writer, data_start, size_offset)
52
+
53
+
54
+ def _write_end_data(writer: FileWriter, data_start: int, size_offset: int) -> None:
55
+ end = writer.tell()
56
+ size = end - data_start
57
+ writer.seek(size_offset)
58
+ writer.write_uint32(size)
59
+ writer.seek(end)
60
+ writer.write_alignment(b"\xAB", 16)
LMS/Common/__init__.py ADDED
File without changes
@@ -0,0 +1,14 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from LMS.Config.Definitions.Value import ValueDefinition
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class AttributeConfig:
8
+ """Class that represents an attribute structure definition.
9
+
10
+ Configs may have multiple, linked to the if a game has multiple MSBP files."""
11
+
12
+ name: str
13
+ description: str = field(repr=False)
14
+ definitions: list[ValueDefinition]
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from LMS.Config.Definitions.Value import ValueDefinition
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class TagDefinition:
10
+ """Class that represents a signle tag definition in the structure."""
11
+
12
+ group_name: str
13
+ group_index: int
14
+ tag_name: str
15
+ tag_index: int
16
+ description: str
17
+ parameters: list[ValueDefinition] | None
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class TagConfig:
22
+ """Class that represents an tag structure definition."""
23
+
24
+ group_map: dict[int, str]
25
+ definitions: list[TagDefinition]
26
+
27
+ def get_definition_by_names(self, group: str, tag: str) -> TagDefinition:
28
+ group_index = None
29
+ for i, name in self.group_map.items():
30
+ if name == group:
31
+ group_index = i
32
+ break
33
+
34
+ if group_index is None:
35
+ raise KeyError(f"Group name '{group}' was not found! Is the group defined?")
36
+
37
+ for tag_def in self.definitions:
38
+ if tag_def.group_index == group_index and tag_def.tag_name == tag:
39
+ return tag_def
40
+
41
+ raise KeyError(
42
+ f"Tag name '{tag}' not found in group '{group}'. Os the tag defined?"
43
+ )
44
+
45
+ def get_definition_by_indexes(self, group: int, tag: int) -> TagDefinition:
46
+ for tag_def in self.definitions:
47
+ if tag_def.group_index == group and tag_def.tag_index == tag:
48
+ return tag_def
49
+
50
+ raise KeyError(
51
+ f"Tag index of {tag} was not found in group index {group}. Is the tag defined?"
52
+ )
@@ -0,0 +1,11 @@
1
+ from dataclasses import dataclass
2
+
3
+ from LMS.Field.LMS_DataType import LMS_DataType
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class ValueDefinition:
8
+ name: str
9
+ description: str
10
+ datatype: LMS_DataType
11
+ list_items: dict[int, str]
File without changes
@@ -0,0 +1,208 @@
1
+ import importlib.resources as pkg_resources
2
+ import os
3
+ from typing import Self
4
+
5
+ import yaml
6
+
7
+ from LMS.Config.Definitions.Attributes import AttributeConfig
8
+ from LMS.Config.Definitions.Tags import TagConfig, TagDefinition
9
+ from LMS.Config.Definitions.Value import ValueDefinition
10
+ from LMS.Field.LMS_DataType import LMS_DataType
11
+ from LMS.Project.MSBP import MSBP
12
+
13
+
14
+ class TitleConfig:
15
+ """Represents a configuration for a specific game/title."""
16
+
17
+ # Populate the preset list from the directory
18
+ PRESET_LIST = [
19
+ name.removesuffix(".yaml") for name in pkg_resources.contents("LMS.Config.Presets") if name.endswith(".yaml")
20
+ ]
21
+
22
+ def __init__(self, attribute_configs: dict[str, AttributeConfig], tag_config: TagConfig):
23
+ self._attribute_configs = attribute_configs
24
+ self._tag_config = tag_config
25
+
26
+ @property
27
+ def attribute_configs(self) -> dict[str, AttributeConfig]:
28
+ """The map of attribute config instances."""
29
+ return self._attribute_configs
30
+
31
+ @property
32
+ def tag_config(self) -> TagConfig:
33
+ """The loaded tag config instance."""
34
+ return self._tag_config
35
+
36
+ @classmethod
37
+ def load_preset(cls, game: str) -> Self:
38
+ """Loads an existing preset from the package."""
39
+ if game not in cls.PRESET_LIST:
40
+ raise FileNotFoundError(f"Preset '{game}' not found.")
41
+
42
+ with pkg_resources.open_text("LMS.Config.Presets", f"{game}.yaml") as f:
43
+ return cls.load_config(f.read())
44
+
45
+ @classmethod
46
+ def load_file(self, file_path: str) -> Self:
47
+ """Loads a config from a file.
48
+
49
+ :param file_path: the path to the config."""
50
+ with open(file_path, "r") as f:
51
+ return TitleConfig.load_config(f.read())
52
+
53
+ @classmethod
54
+ def load_config(cls, content: str | dict) -> Self:
55
+ """Loads the config of a specified game.
56
+
57
+ :param content: the config content, as a string or loaded as a dictionary."""
58
+ if isinstance(content, str):
59
+ parsed_content = yaml.safe_load(content)
60
+ else:
61
+ parsed_content = content
62
+
63
+ group_map = {0: "System"}
64
+ tag_config = []
65
+
66
+ # Add the System tag definitions to the config
67
+ with open(r"LMS\Message\Tag\System.yaml", "r") as f:
68
+ system_tags = yaml.safe_load(f)
69
+ tag_config = system_tags
70
+
71
+ # Combine with the rest of the cofnig
72
+ group_map |= parsed_content["tag_definitions"]["groups"]
73
+ for tag_def in parsed_content["tag_definitions"]["tags"]:
74
+ tag_config.append(tag_def)
75
+
76
+ # Load tag definitions
77
+ tag_definitions = []
78
+ for tag_def in tag_config:
79
+ tag_name = tag_def["name"]
80
+ group_index, tag_index = tag_def["group_index"], tag_def["tag_index"]
81
+ group_name = group_map[group_index]
82
+ tag_description = tag_def["description"]
83
+
84
+ group_name = group_map[group_index]
85
+
86
+ parameters = None
87
+ if "parameters" in tag_def:
88
+ parameters = []
89
+ for param_def in tag_def["parameters"]:
90
+ param_name = param_def["name"]
91
+ param_description = param_def["description"]
92
+ datatype = LMS_DataType.from_string(param_def["datatype"])
93
+ list_items = param_def.get("list_items")
94
+ parameters.append(ValueDefinition(param_name, param_description, datatype, list_items))
95
+
96
+ tag_definitions.append(
97
+ TagDefinition(
98
+ group_name,
99
+ group_index,
100
+ tag_name,
101
+ tag_index,
102
+ tag_description,
103
+ parameters,
104
+ )
105
+ )
106
+
107
+ tag_config = TagConfig(group_map, tag_definitions)
108
+
109
+ # Load the attribute definitions
110
+ attribute_config = {}
111
+ for structure in parsed_content["attributes"]:
112
+ structure_name = structure["name"]
113
+
114
+ definitions = []
115
+ for info in structure["definitions"]:
116
+ name, description = info["name"], info["description"]
117
+ datatype = LMS_DataType.from_string(info["datatype"])
118
+ list_items = info.get("list_items")
119
+
120
+ definition = ValueDefinition(name, description, datatype, list_items)
121
+ definitions.append(definition)
122
+
123
+ structure = AttributeConfig(structure_name, structure["description"], definitions)
124
+ attribute_config[structure_name] = structure
125
+
126
+ return cls(attribute_config, tag_config)
127
+
128
+ @staticmethod
129
+ def generate_file(file_path: str, project: MSBP) -> None:
130
+ with open(file_path, "w+") as f:
131
+ yaml.safe_dump(
132
+ TitleConfig.generate_profile(project),
133
+ f,
134
+ default_flow_style=False,
135
+ sort_keys=False,
136
+ )
137
+
138
+ @staticmethod
139
+ def generate_config(project: MSBP) -> dict | None:
140
+ """Creates a message config file for the game.
141
+
142
+ :param project: a MSBP object."""
143
+ # TODO: Add custom node definitions
144
+
145
+ tag_key = "tag_definitions"
146
+ attr_key = "attributes"
147
+
148
+ config = {}
149
+ # Source files may be a path to a non-existent directory.
150
+ # These files were generated from the source machine from the actual libMS tool
151
+ # Shorten the filename with basname and replace the extension with .msbt for lookup later when reading a MSBT
152
+ config[tag_key] = {
153
+ "groups": {i + 1: group.name for i, group in enumerate(project.tag_groups[1:])},
154
+ "tags": [],
155
+ }
156
+
157
+ # Slice to exclude System group
158
+ for group_i, group in enumerate(project.tag_groups[1:], start=1):
159
+ for tag_i, info in enumerate(group.tag_definitions):
160
+
161
+ definition = {
162
+ "name": info.name,
163
+ "group_index": group_i,
164
+ "tag_index": tag_i,
165
+ "description": "",
166
+ "parameters": [],
167
+ }
168
+
169
+ for param_info in info.param_info:
170
+ param_definition = {
171
+ "name": param_info.name,
172
+ "description": "",
173
+ "datatype": param_info.datatype.to_string(),
174
+ }
175
+
176
+ if param_info.datatype is LMS_DataType.LIST:
177
+ param_definition["list_items"] = param_info.list_items
178
+
179
+ definition["parameters"].append(param_definition)
180
+
181
+ config[tag_key]["tags"].append(definition)
182
+
183
+ # Set main attribute entries as the definitions from the MSBP
184
+ # Since most games use one MSBP these act the primary definitions
185
+ config[attr_key] = []
186
+
187
+ attr_definitions = []
188
+ for attr_info in project.attribute_info:
189
+ definition = {
190
+ "name": attr_info.name,
191
+ "description": "",
192
+ "datatype": attr_info.datatype.to_string(),
193
+ }
194
+ if attr_info.datatype is LMS_DataType.LIST:
195
+ definition["list_items"] = attr_info.list_items
196
+
197
+ attr_definitions.append(definition)
198
+
199
+ # Main attribute entries
200
+ config[attr_key].append(
201
+ {
202
+ "name": project.name,
203
+ "description": "",
204
+ "definitions": attr_definitions,
205
+ }
206
+ )
207
+
208
+ return config
LMS/Config/__init__.py ADDED
File without changes
@@ -0,0 +1,83 @@
1
+ from enum import Enum
2
+ from typing import Literal, Type
3
+
4
+
5
+ class LMS_DataType(Enum):
6
+
7
+ UINT8 = 0
8
+ UINT16 = 1
9
+ UINT32 = 2
10
+
11
+ INT8 = 3
12
+ INT16 = 4
13
+ INT32 = 5
14
+
15
+ FLOAT32 = 6
16
+
17
+ # Unknown 16 bit type (value of 6) has yet to be documented
18
+ ...
19
+
20
+ STRING = 8
21
+ LIST = 9
22
+
23
+ # Interface types
24
+ BOOL = "bool"
25
+ BYTE = "byte"
26
+
27
+ def to_string(self) -> str:
28
+ return self._name_.lower()
29
+
30
+ @property
31
+ def signed(self) -> bool:
32
+ """Property for if the type is signed or not."""
33
+ if self not in [
34
+ LMS_DataType.STRING,
35
+ LMS_DataType.LIST,
36
+ LMS_DataType.BOOL,
37
+ LMS_DataType.BYTE,
38
+ ]:
39
+ return self in [LMS_DataType.INT8, LMS_DataType.INT16, LMS_DataType.INT32]
40
+
41
+ raise TypeError(f"Signed is not a valid property for '{self.to_string()}'!")
42
+
43
+ @property
44
+ def builtin_type(self) -> Type[int | str | float | bool | bytes]:
45
+ """The enum as the builtin python type."""
46
+ return {
47
+ LMS_DataType.UINT8: int,
48
+ LMS_DataType.UINT16: int,
49
+ LMS_DataType.UINT32: int,
50
+ LMS_DataType.INT8: int,
51
+ LMS_DataType.INT16: int,
52
+ LMS_DataType.INT32: int,
53
+ LMS_DataType.FLOAT32: float,
54
+ LMS_DataType.STRING: str,
55
+ LMS_DataType.LIST: str,
56
+ LMS_DataType.BOOL: bool,
57
+ LMS_DataType.BYTE: bytes,
58
+ }[self]
59
+
60
+ @property
61
+ def stream_size(self) -> Literal[1, 2, 4]:
62
+ """The size the datatype takes up in a strema."""
63
+ return {
64
+ LMS_DataType.UINT8: 1,
65
+ LMS_DataType.UINT16: 2,
66
+ LMS_DataType.UINT32: 4,
67
+ LMS_DataType.INT8: 1,
68
+ LMS_DataType.INT16: 2,
69
+ LMS_DataType.INT32: 4,
70
+ LMS_DataType.FLOAT32: 4,
71
+ LMS_DataType.LIST: 1,
72
+ LMS_DataType.BOOL: 1,
73
+ LMS_DataType.BYTE: 1,
74
+ }[self]
75
+
76
+ @classmethod
77
+ def from_string(cls, string: str):
78
+ """Creates a LMS_Datatype enum value from it's string representation"""
79
+ string = string.upper()
80
+ if string in cls.__members__:
81
+ return cls[string]
82
+ else:
83
+ raise ValueError(f"Unknown value of '{string}' was provided!")