PyLibMS 5.0.1__tar.gz → 5.0.3__tar.gz

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 (83) hide show
  1. {pylibms-5.0.1 → pylibms-5.0.3}/PKG-INFO +5 -11
  2. {pylibms-5.0.1 → pylibms-5.0.3}/PyLibMS.egg-info/PKG-INFO +5 -11
  3. {pylibms-5.0.1 → pylibms-5.0.3}/PyLibMS.egg-info/SOURCES.txt +0 -1
  4. {pylibms-5.0.1 → pylibms-5.0.3}/README.md +3 -10
  5. {pylibms-5.0.1 → pylibms-5.0.3}/lms/common/field/io.py +0 -3
  6. {pylibms-5.0.1 → pylibms-5.0.3}/lms/common/field/lms_datatype.py +5 -3
  7. {pylibms-5.0.1 → pylibms-5.0.3}/lms/common/field/lms_field.py +2 -2
  8. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/lms_messagetext.py +7 -3
  9. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/msbt.py +20 -0
  10. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/tag/io/param_io.py +2 -0
  11. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/tag/io/tag_io.py +4 -4
  12. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/tag/lms_tag.py +27 -23
  13. {pylibms-5.0.1 → pylibms-5.0.3}/pyproject.toml +2 -1
  14. pylibms-5.0.1/lms/titleconfig/definitions/__init__.py +0 -0
  15. {pylibms-5.0.1 → pylibms-5.0.3}/LICENSE +0 -0
  16. {pylibms-5.0.1 → pylibms-5.0.3}/MANIFEST.in +0 -0
  17. {pylibms-5.0.1 → pylibms-5.0.3}/PyLibMS.egg-info/dependency_links.txt +0 -0
  18. {pylibms-5.0.1 → pylibms-5.0.3}/PyLibMS.egg-info/requires.txt +0 -0
  19. {pylibms-5.0.1 → pylibms-5.0.3}/PyLibMS.egg-info/top_level.txt +0 -0
  20. {pylibms-5.0.1 → pylibms-5.0.3}/lms/__init__.py +0 -0
  21. {pylibms-5.0.1 → pylibms-5.0.3}/lms/common/__init__.py +0 -0
  22. {pylibms-5.0.1 → pylibms-5.0.3}/lms/common/field/__init__.py +0 -0
  23. {pylibms-5.0.1 → pylibms-5.0.3}/lms/common/lms_constants.py +0 -0
  24. {pylibms-5.0.1 → pylibms-5.0.3}/lms/common/lms_exceptions.py +0 -0
  25. {pylibms-5.0.1 → pylibms-5.0.3}/lms/common/lms_fileinfo.py +0 -0
  26. {pylibms-5.0.1 → pylibms-5.0.3}/lms/common/stream/fileinfo.py +0 -0
  27. {pylibms-5.0.1 → pylibms-5.0.3}/lms/common/stream/hashtable.py +0 -0
  28. {pylibms-5.0.1 → pylibms-5.0.3}/lms/common/stream/section.py +0 -0
  29. {pylibms-5.0.1 → pylibms-5.0.3}/lms/fileio/encoding.py +0 -0
  30. {pylibms-5.0.1 → pylibms-5.0.3}/lms/fileio/io.py +0 -0
  31. {pylibms-5.0.1 → pylibms-5.0.3}/lms/flowchart/__init__.py +0 -0
  32. {pylibms-5.0.1 → pylibms-5.0.3}/lms/flowchart/definitions/__init__.py +0 -0
  33. {pylibms-5.0.1 → pylibms-5.0.3}/lms/flowchart/definitions/lms_nodeexceptions.py +0 -0
  34. {pylibms-5.0.1 → pylibms-5.0.3}/lms/flowchart/definitions/node.py +0 -0
  35. {pylibms-5.0.1 → pylibms-5.0.3}/lms/flowchart/definitions/node_type.py +0 -0
  36. {pylibms-5.0.1 → pylibms-5.0.3}/lms/flowchart/flw3.py +0 -0
  37. {pylibms-5.0.1 → pylibms-5.0.3}/lms/flowchart/msbf.py +0 -0
  38. {pylibms-5.0.1 → pylibms-5.0.3}/lms/flowchart/msbfio.py +0 -0
  39. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/__init__.py +0 -0
  40. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/definitions/__init__.py +0 -0
  41. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/msbtentry.py +0 -0
  42. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/msbtio.py +0 -0
  43. {pylibms-5.0.1/lms/message/definitions/field → pylibms-5.0.3/lms/message/section}/__init__.py +0 -0
  44. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/section/atr1.py +0 -0
  45. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/section/nli1.py +0 -0
  46. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/section/tsy1.py +0 -0
  47. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/section/txt2.py +0 -0
  48. {pylibms-5.0.1/lms/message/section → pylibms-5.0.3/lms/message/tag}/__init__.py +0 -0
  49. {pylibms-5.0.1 → pylibms-5.0.3}/lms/message/tag/lms_tagexceptions.py +0 -0
  50. {pylibms-5.0.1/lms/message/tag → pylibms-5.0.3/lms/project}/__init__.py +0 -0
  51. {pylibms-5.0.1/lms/project → pylibms-5.0.3/lms/project/definitions}/__init__.py +0 -0
  52. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/definitions/attribute.py +0 -0
  53. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/definitions/color.py +0 -0
  54. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/definitions/style.py +0 -0
  55. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/definitions/tag.py +0 -0
  56. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/msbp.py +0 -0
  57. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/msbpread.py +0 -0
  58. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/section/ali2.py +0 -0
  59. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/section/ati2.py +0 -0
  60. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/section/clr1.py +0 -0
  61. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/section/string.py +0 -0
  62. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/section/syl3.py +0 -0
  63. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/section/tag2.py +0 -0
  64. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/section/tgg2.py +0 -0
  65. {pylibms-5.0.1 → pylibms-5.0.3}/lms/project/section/tgp2.py +0 -0
  66. {pylibms-5.0.1 → pylibms-5.0.3}/lms/py.typed +0 -0
  67. {pylibms-5.0.1/lms/project/definitions → pylibms-5.0.3/lms/titleconfig}/__init__.py +0 -0
  68. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/config.py +0 -0
  69. {pylibms-5.0.1/lms/titleconfig → pylibms-5.0.3/lms/titleconfig/definitions}/__init__.py +0 -0
  70. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/definitions/attribute.py +0 -0
  71. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/definitions/field.py +0 -0
  72. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/definitions/nodes.py +0 -0
  73. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/definitions/tags.py +0 -0
  74. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/presets/Badge Arcade.yaml +0 -0
  75. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/presets/Brain Age Concentration Training.yaml +0 -0
  76. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/presets/Kirby Planet Robobot.yaml +0 -0
  77. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/presets/Super Mario 3D Land.yaml +0 -0
  78. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/presets/Super Mario 3D World + Bowsers Fury.yaml +0 -0
  79. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/presets/Super Mario Odyssey.yaml +0 -0
  80. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml +0 -0
  81. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/presets/The Legend of Zelda a Link Between Worlds.yaml +0 -0
  82. {pylibms-5.0.1 → pylibms-5.0.3}/lms/titleconfig/presets/Tomodachi Life Living The Dream.yaml +0 -0
  83. {pylibms-5.0.1 → pylibms-5.0.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyLibMS
3
- Version: 5.0.1
3
+ Version: 5.0.3
4
4
  Summary: Python library built for the libMessageStudio (LMS) proprietary file formats from Nintendo. Supports MSBT, MSBP, and MSBF.
5
5
  Author: AbdyyEee
6
6
  License-Expression: MIT
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
12
  Classifier: Programming Language :: Python :: 3.13
13
13
  Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Programming Language :: Python :: 3.15
14
15
  Requires-Python: >=3.11
15
16
  Description-Content-Type: text/markdown
16
17
  License-File: LICENSE
@@ -23,6 +24,7 @@ Dynamic: license-file
23
24
  [![Python Versions](https://img.shields.io/pypi/pyversions/PyLibMS)](https://pypi.org/project/PyLibMS/)
24
25
  ![LICENSE](https://img.shields.io/badge/license-MIT-blue)
25
26
  [![PyPI version](https://badge.fury.io/py/PyLibMS.svg)](https://badge.fury.io/py/PyLibMS)
27
+ ![Tests](https://github.com/abdyyeee/PyLibMS/actions/workflows/tests.yml/badge.svg?branch=main)
26
28
 
27
29
  PyLibMS is a library built in Python 3.11 and above for reading and writing of libMessageStudio (LMS) proprietary file
28
30
  formats from Nintendo.
@@ -90,7 +92,7 @@ pip install PyLibMS
90
92
 
91
93
  # Build Instructions
92
94
 
93
- Python version must be `3.10` or higher.
95
+ Python version must be `3.11` or higher.
94
96
 
95
97
  Clone the repository, then run `pip install` (venv recommended)
96
98
 
@@ -102,15 +104,7 @@ pip install -e .
102
104
 
103
105
  # AI Disclaimer
104
106
 
105
- Code in this library was written by me. AI was utilized occasionally when fit and always reviewed by me.
106
-
107
- `MSBT`: Only usage of AI was for control tag regex formats and for understanding implementing string importing with
108
- `re`.
109
-
110
- `MSBP`: AI was not utilized anywhere.
111
-
112
- `MSBF`: AI was only utilized for the `LMS_BaseNode.get_descendents` function and for consultation for ways to improve
113
- the API.
107
+ Code in this library was written by me. AI was utilized occasionally when developing MSBT (specifically tag string importing) , MSBF and assisting in creating unit tests.
114
108
 
115
109
  # Credits & Sources
116
110
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyLibMS
3
- Version: 5.0.1
3
+ Version: 5.0.3
4
4
  Summary: Python library built for the libMessageStudio (LMS) proprietary file formats from Nintendo. Supports MSBT, MSBP, and MSBF.
5
5
  Author: AbdyyEee
6
6
  License-Expression: MIT
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
12
  Classifier: Programming Language :: Python :: 3.13
13
13
  Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Programming Language :: Python :: 3.15
14
15
  Requires-Python: >=3.11
15
16
  Description-Content-Type: text/markdown
16
17
  License-File: LICENSE
@@ -23,6 +24,7 @@ Dynamic: license-file
23
24
  [![Python Versions](https://img.shields.io/pypi/pyversions/PyLibMS)](https://pypi.org/project/PyLibMS/)
24
25
  ![LICENSE](https://img.shields.io/badge/license-MIT-blue)
25
26
  [![PyPI version](https://badge.fury.io/py/PyLibMS.svg)](https://badge.fury.io/py/PyLibMS)
27
+ ![Tests](https://github.com/abdyyeee/PyLibMS/actions/workflows/tests.yml/badge.svg?branch=main)
26
28
 
27
29
  PyLibMS is a library built in Python 3.11 and above for reading and writing of libMessageStudio (LMS) proprietary file
28
30
  formats from Nintendo.
@@ -90,7 +92,7 @@ pip install PyLibMS
90
92
 
91
93
  # Build Instructions
92
94
 
93
- Python version must be `3.10` or higher.
95
+ Python version must be `3.11` or higher.
94
96
 
95
97
  Clone the repository, then run `pip install` (venv recommended)
96
98
 
@@ -102,15 +104,7 @@ pip install -e .
102
104
 
103
105
  # AI Disclaimer
104
106
 
105
- Code in this library was written by me. AI was utilized occasionally when fit and always reviewed by me.
106
-
107
- `MSBT`: Only usage of AI was for control tag regex formats and for understanding implementing string importing with
108
- `re`.
109
-
110
- `MSBP`: AI was not utilized anywhere.
111
-
112
- `MSBF`: AI was only utilized for the `LMS_BaseNode.get_descendents` function and for consultation for ways to improve
113
- the API.
107
+ Code in this library was written by me. AI was utilized occasionally when developing MSBT (specifically tag string importing) , MSBF and assisting in creating unit tests.
114
108
 
115
109
  # Credits & Sources
116
110
 
@@ -36,7 +36,6 @@ lms/message/msbt.py
36
36
  lms/message/msbtentry.py
37
37
  lms/message/msbtio.py
38
38
  lms/message/definitions/__init__.py
39
- lms/message/definitions/field/__init__.py
40
39
  lms/message/section/__init__.py
41
40
  lms/message/section/atr1.py
42
41
  lms/message/section/nli1.py
@@ -3,6 +3,7 @@
3
3
  [![Python Versions](https://img.shields.io/pypi/pyversions/PyLibMS)](https://pypi.org/project/PyLibMS/)
4
4
  ![LICENSE](https://img.shields.io/badge/license-MIT-blue)
5
5
  [![PyPI version](https://badge.fury.io/py/PyLibMS.svg)](https://badge.fury.io/py/PyLibMS)
6
+ ![Tests](https://github.com/abdyyeee/PyLibMS/actions/workflows/tests.yml/badge.svg?branch=main)
6
7
 
7
8
  PyLibMS is a library built in Python 3.11 and above for reading and writing of libMessageStudio (LMS) proprietary file
8
9
  formats from Nintendo.
@@ -70,7 +71,7 @@ pip install PyLibMS
70
71
 
71
72
  # Build Instructions
72
73
 
73
- Python version must be `3.10` or higher.
74
+ Python version must be `3.11` or higher.
74
75
 
75
76
  Clone the repository, then run `pip install` (venv recommended)
76
77
 
@@ -82,15 +83,7 @@ pip install -e .
82
83
 
83
84
  # AI Disclaimer
84
85
 
85
- Code in this library was written by me. AI was utilized occasionally when fit and always reviewed by me.
86
-
87
- `MSBT`: Only usage of AI was for control tag regex formats and for understanding implementing string importing with
88
- `re`.
89
-
90
- `MSBP`: AI was not utilized anywhere.
91
-
92
- `MSBF`: AI was only utilized for the `LMS_BaseNode.get_descendents` function and for consultation for ways to improve
93
- the API.
86
+ Code in this library was written by me. AI was utilized occasionally when developing MSBT (specifically tag string importing) , MSBF and assisting in creating unit tests.
94
87
 
95
88
  # Credits & Sources
96
89
 
@@ -3,7 +3,6 @@ from typing import Callable
3
3
  from lms.common.field.lms_datatype import (
4
4
  LMS_DataType,
5
5
  is_bool_datatype,
6
- is_bytes_datatype,
7
6
  is_enum_datatype,
8
7
  is_number_datatype,
9
8
  )
@@ -72,8 +71,6 @@ def write_field(
72
71
  write_variable_width_value(value)
73
72
  else:
74
73
  writer.write_uint8(value)
75
- elif is_bytes_datatype(field.value, field.datatype):
76
- writer.write_bytes(field.value)
77
74
  elif is_bool_datatype(field.value, field.datatype):
78
75
  if write_variable_width_value is not None:
79
76
  write_variable_width_value(bool(field.value))
@@ -22,6 +22,9 @@ ALIAS_MAP = {
22
22
 
23
23
 
24
24
  def is_number_datatype(value: object, datatype: LMS_DataType) -> TypeGuard[int | float]:
25
+ if isinstance(value, float) and datatype is LMS_DataType.FLOAT32:
26
+ return True
27
+
25
28
  return datatype in (
26
29
  LMS_DataType.UINT8,
27
30
  LMS_DataType.UINT16,
@@ -29,7 +32,6 @@ def is_number_datatype(value: object, datatype: LMS_DataType) -> TypeGuard[int |
29
32
  LMS_DataType.INT8,
30
33
  LMS_DataType.INT16,
31
34
  LMS_DataType.INT32,
32
- LMS_DataType.FLOAT32,
33
35
  ) and isinstance(value, int)
34
36
 
35
37
 
@@ -138,8 +140,8 @@ def verify_number_from_datatype(
138
140
  else:
139
141
  bits = datatype.stream_size * 8
140
142
  if datatype.signed:
141
- max_value = 2 ** (bits - 1)
142
- min_value = -max_value
143
+ min_value = -(2 ** (bits - 1))
144
+ max_value = (2 ** (bits - 1)) - 1
143
145
  else:
144
146
  min_value, max_value = 0, (2 ** bits) - 1
145
147
 
@@ -93,7 +93,7 @@ class LMS_FieldMap:
93
93
  value = data[definition.name]
94
94
  match definition.datatype:
95
95
  case LMS_DataType.BOOL:
96
- if value not in ("false", "true"):
96
+ if value.lower() not in ("false", "true"):
97
97
  raise ValueError("Invalid boolean value!")
98
98
  casted_value = value.strip().lower() == "true"
99
99
  case LMS_DataType.FLOAT32:
@@ -174,7 +174,7 @@ def _verify_value_from_definition(
174
174
  )
175
175
  else:
176
176
  return
177
- case _ if isinstance(value, int):
177
+ case _ if isinstance(value, (int, float)):
178
178
  verify_number_from_datatype(value, definition.datatype)
179
179
  return
180
180
 
@@ -7,7 +7,7 @@ from lms.message.tag.lms_tag import (
7
7
  LMS_ControlTag,
8
8
  LMS_DecodedTag,
9
9
  LMS_EncodedTag,
10
- is_tag,
10
+ is_tag, TAG_PADDING_VALUE,
11
11
  )
12
12
  from lms.message.tag.lms_tagexceptions import LMS_TagForbiddenParametersError
13
13
  from lms.titleconfig.config import TagConfig
@@ -90,8 +90,12 @@ class LMS_MessageText:
90
90
 
91
91
  tag = LMS_EncodedTag(group_id, tag_index, is_closing=True)
92
92
  else:
93
+ list_parameters = list(parameters)
94
+ if len(list_parameters) % 2 == 1:
95
+ list_parameters.append(TAG_PADDING_VALUE)
96
+
93
97
  tag = LMS_EncodedTag(
94
- group_id, tag_index, None if not parameters else list(parameters)
98
+ group_id, tag_index, None if not parameters else list_parameters
95
99
  )
96
100
 
97
101
  self._segments.append(tag)
@@ -121,7 +125,7 @@ class LMS_MessageText:
121
125
  if self._tag_config is None:
122
126
  raise ValueError("A TitleConfig is required to append decoded tags.")
123
127
 
124
- definition = self._tag_config.get_definition_by_names(group_name, tag_name)
128
+ definition = self._tag_config[group_name][tag_name]
125
129
 
126
130
  if is_closing:
127
131
  if parameters:
@@ -1,7 +1,9 @@
1
1
  from typing import overload
2
2
 
3
+ from lms.common.field.lms_field import LMS_FieldMap
3
4
  from lms.common.lms_fileinfo import LMS_FileInfo
4
5
  from lms.fileio.encoding import FileEncoding
6
+ from lms.message.lms_messagetext import LMS_MessageText
5
7
  from lms.message.msbtentry import MSBTEntry
6
8
  from lms.titleconfig.definitions.attribute import AttributeConfig
7
9
  from lms.titleconfig.definitions.tags import TagConfig
@@ -219,6 +221,24 @@ class MSBT:
219
221
  self._entries.append(entry)
220
222
  self._label_map[entry.name] = entry
221
223
 
224
+ def create_entry(self, name: str,
225
+ message: str = None,
226
+ attribute: LMS_FieldMap | bytes | None = None,
227
+ style_index: int = None) -> MSBTEntry:
228
+ """
229
+ Creates an MSBT Entry and adds it to the file.
230
+
231
+ param name: the name of the entry.
232
+ param message: the message of the entry.
233
+ param attribute: the attribute of the entry.
234
+ param style_index: the style of the entry.
235
+ """
236
+ self.add_entry(entry := MSBTEntry(name,
237
+ message=LMS_MessageText(message, self._tag_config),
238
+ attribute=attribute,
239
+ style_index=style_index))
240
+ return entry
241
+
222
242
  def delete_entry(self, entry: MSBTEntry) -> None:
223
243
  """
224
244
  Deletes an entry from the MSBT instance.
@@ -59,6 +59,8 @@ def write_decoded_parameters(
59
59
  param_size += 2 + len(field.value) * writer.encoding.width
60
60
  case LMS_DataType.ENUM:
61
61
  param_size += 1
62
+ case LMS_DataType.BOOL:
63
+ param_size += 1
62
64
  case _:
63
65
  param_size += field.datatype.stream_size
64
66
 
@@ -37,7 +37,7 @@ def read_tag(
37
37
  return _read_encoded_tag(reader, group_id, tag_index, is_closing)
38
38
 
39
39
  definition = tag_config[group_id][tag_index]
40
-
40
+
41
41
  # Tags not defined in the config are not considered fallback tags
42
42
  # Not all configs will define every tag, so this is simply a measure to still read tags that aren't defined
43
43
  if definition is None:
@@ -79,15 +79,15 @@ def _read_encoded_tag(
79
79
  def _read_decoded_tag(
80
80
  reader: FileReader, definition: TagDefinition, is_closing: bool = False
81
81
  ) -> LMS_DecodedTag:
82
+ if is_closing:
83
+ return LMS_DecodedTag(definition, is_closing=True)
84
+
82
85
  parameter_size = reader.read_uint16()
83
86
  end = reader.tell() + parameter_size
84
87
 
85
88
  if not parameter_size:
86
89
  return LMS_DecodedTag(definition)
87
90
 
88
- if is_closing:
89
- return LMS_DecodedTag(definition, is_closing=True)
90
-
91
91
  parameters = read_decoded_parameters(reader, definition)
92
92
  reader.seek(end)
93
93
  return LMS_DecodedTag(definition, parameters)
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
- from typing import TypeGuard, TypeAlias
4
+ from typing import TypeGuard, TypeAlias, Sequence
5
5
 
6
6
  from lms.common.field.lms_field import LMS_FieldMap
7
7
  from lms.message.tag.lms_tagexceptions import (
@@ -29,44 +29,48 @@ class LMS_EncodedTag:
29
29
  is_fallback: bool = False,
30
30
  is_closing: bool = False,
31
31
  ):
32
- self._group_id = group_id
33
- self._tag_index = tag_index
32
+ self.group_id = group_id
33
+ self.tag_index = tag_index
34
+ self.is_closing = is_closing
35
+
34
36
  self._parameters = parameters
35
37
 
36
38
  self._is_fallback = is_fallback
37
- self._is_closing = is_closing
38
39
 
39
40
  def __len__(self) -> int:
40
41
  return len(self.to_text())
41
42
 
42
43
  @property
43
- def group_id(self) -> int:
44
- """The group id for the tag."""
45
- return self._group_id
44
+ def parameters(self) -> tuple[int, ...]:
45
+ """The list of parameters."""
46
+ return () if self._parameters is None else tuple(self._parameters)
46
47
 
47
- @property
48
- def tag_index(self) -> int:
49
- """The tag index in the tags group."""
50
- return self._tag_index
48
+ @parameters.setter
49
+ def parameters(self, parameters: Sequence[int] | None) -> None:
50
+ if parameters is None:
51
+ self._parameters = None
52
+ return
51
53
 
52
- @property
53
- def parameters(self) -> tuple[int, ...] | None:
54
- """The list of parameters."""
55
- return tuple(self._parameters)
54
+ for value in parameters:
55
+ if type(value) is not int:
56
+ raise TypeError("Each parameter must be an integer.")
57
+ if not 0 <= value <= 0xFF:
58
+ raise ValueError("Each parameter must be between 0 and 255.")
59
+
60
+ new = list(parameters)
61
+ if len(new) % 2 == 1:
62
+ new.append(TAG_PADDING_VALUE)
63
+
64
+ self._parameters = tuple(new)
56
65
 
57
66
  @property
58
67
  def is_fallback(self) -> bool:
59
68
  """Determines if the tag is a fallback tag. Fallback tags have a '!' prefix before the group index."""
60
69
  return self._is_fallback
61
70
 
62
- @property
63
- def is_closing(self) -> bool:
64
- """Determines if the tag is a closing tag."""
65
- return self._is_closing
66
-
67
71
  def to_text(self) -> str:
68
- if self._is_closing:
69
- return f"[/{self._group_id}:{self._tag_index}]"
72
+ if self.is_closing:
73
+ return f"[/{self.group_id}:{self.tag_index}]"
70
74
 
71
75
  fallback_prefix = "!" if self._is_fallback else ""
72
76
 
@@ -134,7 +138,7 @@ class LMS_DecodedTag:
134
138
  """
135
139
 
136
140
  TAG_FORMAT = re.compile(
137
- r"\[\s*(/)?\s*([A-Za-z]\w*)\s*:\s*([A-Za-z]+)(?:\s+[^]]*)?\s*]"
141
+ r"\[\s*(/)?\s*([A-Za-z]\w*)\s*:\s*([A-Za-z]\w*)(?:\s+[^]]*)?\s*]"
138
142
  )
139
143
  PARAMETER_FORMAT = re.compile(r'(\w+)="([^"]*)"')
140
144
 
@@ -7,7 +7,7 @@ include = ["lms*"]
7
7
 
8
8
  [project]
9
9
  name = "PyLibMS"
10
- version = "5.0.1"
10
+ version = "5.0.3"
11
11
  requires-python = ">=3.11"
12
12
  description = "Python library built for the libMessageStudio (LMS) proprietary file formats from Nintendo. Supports MSBT, MSBP, and MSBF."
13
13
  readme = "README.md"
@@ -30,6 +30,7 @@ classifiers = [
30
30
  "Programming Language :: Python :: 3.12",
31
31
  "Programming Language :: Python :: 3.13",
32
32
  "Programming Language :: Python :: 3.14",
33
+ "Programming Language :: Python :: 3.15",
33
34
  ]
34
35
  dependencies = [
35
36
  "PyYAML>=6.0.1",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes