PyLibMS 3.3__tar.gz → 3.3.2__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 (71) hide show
  1. {pylibms-3.3 → pylibms-3.3.2}/PKG-INFO +3 -3
  2. {pylibms-3.3 → pylibms-3.3.2}/PyLibMS.egg-info/PKG-INFO +3 -3
  3. {pylibms-3.3 → pylibms-3.3.2}/README.md +2 -2
  4. {pylibms-3.3 → pylibms-3.3.2}/lms/common/lms_datatype.py +15 -16
  5. {pylibms-3.3 → pylibms-3.3.2}/lms/common/stream/fileinfo.py +1 -1
  6. {pylibms-3.3 → pylibms-3.3.2}/lms/message/definitions/field/lms_field.py +9 -6
  7. {pylibms-3.3 → pylibms-3.3.2}/lms/message/definitions/lms_messagetext.py +16 -16
  8. {pylibms-3.3 → pylibms-3.3.2}/lms/message/msbt.py +27 -12
  9. {pylibms-3.3 → pylibms-3.3.2}/lms/message/msbtio.py +4 -1
  10. {pylibms-3.3 → pylibms-3.3.2}/lms/message/section/atr1.py +1 -1
  11. {pylibms-3.3 → pylibms-3.3.2}/lms/message/section/txt2.py +6 -7
  12. {pylibms-3.3 → pylibms-3.3.2}/lms/project/msbp.py +11 -4
  13. {pylibms-3.3 → pylibms-3.3.2}/lms/project/msbpread.py +1 -1
  14. {pylibms-3.3 → pylibms-3.3.2}/pyproject.toml +1 -1
  15. {pylibms-3.3 → pylibms-3.3.2}/LICENSE +0 -0
  16. {pylibms-3.3 → pylibms-3.3.2}/MANIFEST.in +0 -0
  17. {pylibms-3.3 → pylibms-3.3.2}/PyLibMS.egg-info/SOURCES.txt +0 -0
  18. {pylibms-3.3 → pylibms-3.3.2}/PyLibMS.egg-info/dependency_links.txt +0 -0
  19. {pylibms-3.3 → pylibms-3.3.2}/PyLibMS.egg-info/requires.txt +0 -0
  20. {pylibms-3.3 → pylibms-3.3.2}/PyLibMS.egg-info/top_level.txt +0 -0
  21. {pylibms-3.3 → pylibms-3.3.2}/lms/__init__.py +0 -0
  22. {pylibms-3.3 → pylibms-3.3.2}/lms/common/__init__.py +0 -0
  23. {pylibms-3.3 → pylibms-3.3.2}/lms/common/lms_exceptions.py +0 -0
  24. {pylibms-3.3 → pylibms-3.3.2}/lms/common/lms_fileinfo.py +0 -0
  25. {pylibms-3.3 → pylibms-3.3.2}/lms/common/stream/hashtable.py +0 -0
  26. {pylibms-3.3 → pylibms-3.3.2}/lms/common/stream/section.py +0 -0
  27. {pylibms-3.3 → pylibms-3.3.2}/lms/fileio/encoding.py +0 -0
  28. {pylibms-3.3 → pylibms-3.3.2}/lms/fileio/io.py +0 -0
  29. {pylibms-3.3 → pylibms-3.3.2}/lms/message/__init__.py +0 -0
  30. {pylibms-3.3 → pylibms-3.3.2}/lms/message/definitions/__init__.py +0 -0
  31. {pylibms-3.3 → pylibms-3.3.2}/lms/message/definitions/field/__init__.py +0 -0
  32. {pylibms-3.3 → pylibms-3.3.2}/lms/message/definitions/field/io.py +0 -0
  33. {pylibms-3.3 → pylibms-3.3.2}/lms/message/msbtentry.py +0 -0
  34. {pylibms-3.3 → pylibms-3.3.2}/lms/message/section/__init__.py +0 -0
  35. {pylibms-3.3 → pylibms-3.3.2}/lms/message/section/nli1.py +0 -0
  36. {pylibms-3.3 → pylibms-3.3.2}/lms/message/section/tsy1.py +0 -0
  37. {pylibms-3.3 → pylibms-3.3.2}/lms/message/tag/__init__.py +0 -0
  38. {pylibms-3.3 → pylibms-3.3.2}/lms/message/tag/io/param_io.py +0 -0
  39. {pylibms-3.3 → pylibms-3.3.2}/lms/message/tag/io/tag_io.py +0 -0
  40. {pylibms-3.3 → pylibms-3.3.2}/lms/message/tag/lms_tag.py +0 -0
  41. {pylibms-3.3 → pylibms-3.3.2}/lms/message/tag/lms_tagexceptions.py +0 -0
  42. {pylibms-3.3 → pylibms-3.3.2}/lms/project/__init__.py +0 -0
  43. {pylibms-3.3 → pylibms-3.3.2}/lms/project/definitions/__init__.py +0 -0
  44. {pylibms-3.3 → pylibms-3.3.2}/lms/project/definitions/attribute.py +0 -0
  45. {pylibms-3.3 → pylibms-3.3.2}/lms/project/definitions/color.py +0 -0
  46. {pylibms-3.3 → pylibms-3.3.2}/lms/project/definitions/style.py +0 -0
  47. {pylibms-3.3 → pylibms-3.3.2}/lms/project/definitions/tag.py +0 -0
  48. {pylibms-3.3 → pylibms-3.3.2}/lms/project/section/ali2.py +0 -0
  49. {pylibms-3.3 → pylibms-3.3.2}/lms/project/section/ati2.py +0 -0
  50. {pylibms-3.3 → pylibms-3.3.2}/lms/project/section/clr1.py +0 -0
  51. {pylibms-3.3 → pylibms-3.3.2}/lms/project/section/string.py +0 -0
  52. {pylibms-3.3 → pylibms-3.3.2}/lms/project/section/syl3.py +0 -0
  53. {pylibms-3.3 → pylibms-3.3.2}/lms/project/section/tag2.py +0 -0
  54. {pylibms-3.3 → pylibms-3.3.2}/lms/project/section/tgg2.py +0 -0
  55. {pylibms-3.3 → pylibms-3.3.2}/lms/project/section/tgp2.py +0 -0
  56. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/__init__.py +0 -0
  57. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/config.py +0 -0
  58. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/definitions/__init__.py +0 -0
  59. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/definitions/attribute.py +0 -0
  60. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/definitions/tags.py +0 -0
  61. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/definitions/value.py +0 -0
  62. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/presets/Badge Arcade.yaml +0 -0
  63. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/presets/Brain Age Concentration Training.yaml +0 -0
  64. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/presets/Kirby Planet Robobot.yaml +0 -0
  65. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/presets/Super Mario 3D Land.yaml +0 -0
  66. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/presets/Super Mario 3D World + Bowsers Fury.yaml +0 -0
  67. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/presets/Super Mario Odyssey.yaml +0 -0
  68. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml +0 -0
  69. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/presets/The Legend of Zelda a Link Between Worlds.yaml +0 -0
  70. {pylibms-3.3 → pylibms-3.3.2}/lms/titleconfig/presets/Tomodachi Life.yaml +0 -0
  71. {pylibms-3.3 → pylibms-3.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyLibMS
3
- Version: 3.3
3
+ Version: 3.3.2
4
4
  Summary: Python library built for the libMessageStudio (LMS) proprietary file formats from Nintendo. Supports MSBT, MSBP, and soon to be MSBF.
5
5
  Author: AbdyyEee
6
6
  License: Copyright 2025 AbdyyEee
@@ -20,7 +20,7 @@ Requires-Dist: PyYAML>=6.0.1
20
20
  Dynamic: license-file
21
21
 
22
22
  # PylibMS
23
- PylibMS is a library built in Python 3.10+ for the libMessageStudio (LMS) proprietary file formats from Nintendo. It supports the following:
23
+ PylibMS is a library built in Python 3.12+ for the libMessageStudio (LMS) proprietary file formats (`msbt`, `msbp`, `msbf`) from Nintendo. It supports the following:
24
24
 
25
25
  * Full reading and writing of MSBT files.
26
26
  * Full reading of MSBP files.
@@ -35,7 +35,7 @@ This library is designed to support LMS revision 3.0 and above, with the associa
35
35
  * Mobile (Specific titles only)
36
36
  * Nintendo Switch
37
37
 
38
- MSBF is in current in the works.
38
+ MSBF is currently in the works.
39
39
 
40
40
  # Features and Usage
41
41
  Simple preview of the library is below. See [the wiki](https://github.com/AbdyyEee/PylibMS/wiki) for more explanations and examples.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyLibMS
3
- Version: 3.3
3
+ Version: 3.3.2
4
4
  Summary: Python library built for the libMessageStudio (LMS) proprietary file formats from Nintendo. Supports MSBT, MSBP, and soon to be MSBF.
5
5
  Author: AbdyyEee
6
6
  License: Copyright 2025 AbdyyEee
@@ -20,7 +20,7 @@ Requires-Dist: PyYAML>=6.0.1
20
20
  Dynamic: license-file
21
21
 
22
22
  # PylibMS
23
- PylibMS is a library built in Python 3.10+ for the libMessageStudio (LMS) proprietary file formats from Nintendo. It supports the following:
23
+ PylibMS is a library built in Python 3.12+ for the libMessageStudio (LMS) proprietary file formats (`msbt`, `msbp`, `msbf`) from Nintendo. It supports the following:
24
24
 
25
25
  * Full reading and writing of MSBT files.
26
26
  * Full reading of MSBP files.
@@ -35,7 +35,7 @@ This library is designed to support LMS revision 3.0 and above, with the associa
35
35
  * Mobile (Specific titles only)
36
36
  * Nintendo Switch
37
37
 
38
- MSBF is in current in the works.
38
+ MSBF is currently in the works.
39
39
 
40
40
  # Features and Usage
41
41
  Simple preview of the library is below. See [the wiki](https://github.com/AbdyyEee/PylibMS/wiki) for more explanations and examples.
@@ -1,5 +1,5 @@
1
1
  # PylibMS
2
- PylibMS is a library built in Python 3.10+ for the libMessageStudio (LMS) proprietary file formats from Nintendo. It supports the following:
2
+ PylibMS is a library built in Python 3.12+ for the libMessageStudio (LMS) proprietary file formats (`msbt`, `msbp`, `msbf`) from Nintendo. It supports the following:
3
3
 
4
4
  * Full reading and writing of MSBT files.
5
5
  * Full reading of MSBP files.
@@ -14,7 +14,7 @@ This library is designed to support LMS revision 3.0 and above, with the associa
14
14
  * Mobile (Specific titles only)
15
15
  * Nintendo Switch
16
16
 
17
- MSBF is in current in the works.
17
+ MSBF is currently in the works.
18
18
 
19
19
  # Features and Usage
20
20
  Simple preview of the library is below. See [the wiki](https://github.com/AbdyyEee/PylibMS/wiki) for more explanations and examples.
@@ -3,6 +3,18 @@ from __future__ import annotations
3
3
  from enum import Enum
4
4
  from typing import Type, TypeGuard
5
5
 
6
+ ALIAS_MAP = {
7
+ "u8": "UINT8",
8
+ "u16": "UINT16",
9
+ "u32": "UINT32",
10
+ "i8": "INT8",
11
+ "i16": "INT16",
12
+ "i32": "INT32",
13
+ "f32": "FLOAT32",
14
+ "str": "STRING",
15
+ "bool": "BOOL",
16
+ "byte": "BYTES",
17
+ }
6
18
 
7
19
  def is_number_datatype(value: object, datatype: LMS_DataType) -> TypeGuard[int | float]:
8
20
  return datatype in (
@@ -13,7 +25,7 @@ def is_number_datatype(value: object, datatype: LMS_DataType) -> TypeGuard[int |
13
25
  LMS_DataType.INT16,
14
26
  LMS_DataType.INT32,
15
27
  LMS_DataType.FLOAT32,
16
- ) and isinstance(value, (int, float))
28
+ ) and isinstance(value, int)
17
29
 
18
30
 
19
31
  def is_list_datatype(value: object, datatype: LMS_DataType) -> TypeGuard[str]:
@@ -77,7 +89,7 @@ class LMS_DataType(Enum):
77
89
  raise TypeError(f"Signed is not a valid property for '{self.to_string()}'!")
78
90
 
79
91
  @property
80
- def builtin_type(self) -> Type[int | str | float | bool | bytes]:
92
+ def builtin_type(self) -> Type[int | float | str | bool | bytes]:
81
93
  """The enum as the builtin python type."""
82
94
  return {
83
95
  LMS_DataType.UINT8: int,
@@ -116,20 +128,7 @@ class LMS_DataType(Enum):
116
128
  if member in cls.__members__:
117
129
  return cls[member]
118
130
 
119
- alias_map = {
120
- "u8": "UINT8",
121
- "u16": "UINT16",
122
- "u32": "UINT32",
123
- "i8": "INT8",
124
- "i16": "INT16",
125
- "i32": "INT32",
126
- "f32": "FLOAT32",
127
- "str": "STRING",
128
- "bool": "BOOL",
129
- "byte": "BYTES",
130
- }
131
-
132
- alias_member = alias_map.get(string.lower())
131
+ alias_member = ALIAS_MAP.get(string.lower())
133
132
  if alias_member is not None:
134
133
  return cls[alias_member]
135
134
  else:
@@ -33,7 +33,7 @@ def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
33
33
 
34
34
  reader.seek(0, 2)
35
35
  if file_size != reader.tell():
36
- raise lms_exceptions.LMS_MisalignedSizeError(f"Filesize is misaligned!")
36
+ raise lms_exceptions.LMS_MisalignedSizeError(f"File size is misaligned!")
37
37
 
38
38
  reader.seek(DATA_START)
39
39
 
@@ -15,8 +15,7 @@ type FieldValue = int | str | float | bool | bytes
15
15
  @dataclass(frozen=True)
16
16
  class LMS_FieldMap:
17
17
  """
18
- A wrapper for a dictionary of LMS_Field objects for controlled access, validation
19
- and abstraction from the dict object.
18
+ A wrapper for a basic ``dict[str, LMS_Field]``. Difference is validation upon modification of a field.
20
19
  """
21
20
 
22
21
  fields: dict[str, LMS_Field]
@@ -52,8 +51,10 @@ class LMS_FieldMap:
52
51
  @classmethod
53
52
  def from_string_dict(cls, data: dict[str, str], definitions: list[ValueDefinition]):
54
53
  fields = {}
54
+ casted_value = None
55
55
 
56
56
  for definition in definitions:
57
+ # These datatypes do not require any casting
57
58
  if definition.datatype in (LMS_DataType.STRING, LMS_DataType.LIST):
58
59
  fields[definition.name] = LMS_Field(data[definition.name], definition)
59
60
  continue
@@ -64,7 +65,7 @@ class LMS_FieldMap:
64
65
  casted_value = bytes.fromhex(value)
65
66
  case LMS_DataType.BOOL:
66
67
  if value not in ("false", "true"):
67
- raise ValueError("Value must be true or false for bool type.")
68
+ raise ValueError("Invalid boolean value!")
68
69
  casted_value = value.strip().lower() == "true"
69
70
  case LMS_DataType.FLOAT32:
70
71
  casted_value = float(value)
@@ -115,7 +116,7 @@ class LMS_Field:
115
116
 
116
117
  @property
117
118
  def list_items(self) -> list[str]:
118
- """The list items bound to the field instance. Only is valid for `LMS_Datatype.LIST` values."""
119
+ """The list items bound to the field instance. Only is valid for ``LMS_Datatype.LIST`` values."""
119
120
  return self._definition.list_items
120
121
 
121
122
  @value.setter
@@ -141,7 +142,8 @@ def _verify_value(
141
142
  case LMS_DataType.LIST if isinstance(value, str):
142
143
  if value not in definition.list_items:
143
144
  raise ValueError(
144
- f"The value of '{value}' provided for field '{definition.name}' is not in the list {definition.list_items}."
145
+ f"""The value of '{value}' provided for field '{definition.name}' is not a
146
+ valid item in the list {definition.list_items}."""
145
147
  )
146
148
  else:
147
149
  return
@@ -172,5 +174,6 @@ def _verify_number_is_in_range(
172
174
  ):
173
175
  if not min_value <= value <= max_value:
174
176
  raise ValueError(
175
- f"The value '{value}' of type '{definition.datatype}' provided for field '{definition.name}' is out of range of ({min_value}, {max_value})"
177
+ f"""The value '{value}' of type '{definition.datatype}' provided for field '{definition.name}'
178
+ is out of range of ({min_value}, {max_value})"""
176
179
  )
@@ -1,6 +1,6 @@
1
1
  import re
2
2
 
3
- from lms.message.definitions.field.lms_field import LMS_FieldMap
3
+ from lms.message.definitions.field.lms_field import LMS_FieldMap, FieldValue
4
4
  from lms.message.tag.lms_tag import (LMS_ControlTag, LMS_DecodedTag,
5
5
  LMS_EncodedTag, is_tag)
6
6
  from lms.message.tag.lms_tagexceptions import LMS_TagForbiddenParametersError
@@ -20,18 +20,18 @@ class LMS_MessageText:
20
20
  self._tag_config = tag_config
21
21
 
22
22
  if isinstance(message, str):
23
- self._set_parts(message)
23
+ self._set_segments(message)
24
24
  else:
25
- self._parts = message
25
+ self._segments = message
26
26
 
27
27
  def __iter__(self):
28
- return iter(self._parts)
28
+ return iter(self._segments)
29
29
 
30
30
  @property
31
31
  def text(self) -> str:
32
32
  """The raw text of the message."""
33
33
  result = []
34
- for part in self._parts:
34
+ for part in self._segments:
35
35
  if is_tag(part):
36
36
  result.append(part.to_text())
37
37
  else:
@@ -40,19 +40,19 @@ class LMS_MessageText:
40
40
 
41
41
  @text.setter
42
42
  def text(self, string: str) -> None:
43
- self._set_parts(string)
43
+ self._set_segments(string)
44
44
 
45
45
  @property
46
46
  def tags(self) -> list[LMS_ControlTag]:
47
47
  """The list of control tags in the message."""
48
- return [part for part in self._parts if is_tag(part)]
48
+ return [part for part in self._segments if is_tag(part)]
49
49
 
50
50
  @property
51
51
  def tag_positions(self) -> dict[LMS_ControlTag, tuple[int, int]]:
52
52
  """Dict of tag objects to their start and end positions in text."""
53
53
  positions = {}
54
54
  pos = 0
55
- for part in self._parts:
55
+ for part in self._segments:
56
56
  text_len = len(part)
57
57
  if is_tag(part):
58
58
  positions[part] = (pos, pos + text_len)
@@ -86,7 +86,7 @@ class LMS_MessageText:
86
86
  group_id, tag_index, None if not parameters else list(parameters)
87
87
  )
88
88
 
89
- self._parts.append(tag)
89
+ self._segments.append(tag)
90
90
  return tag
91
91
 
92
92
  def append_decoded_tag(
@@ -94,7 +94,7 @@ class LMS_MessageText:
94
94
  group_name: str,
95
95
  tag_name: str,
96
96
  is_closing: bool = False,
97
- **parameters: int | str | float | bool | bytes,
97
+ **parameters: FieldValue
98
98
  ) -> LMS_DecodedTag:
99
99
  """
100
100
  Appends a decoded tag to the current message and returns that tag.
@@ -120,7 +120,7 @@ class LMS_MessageText:
120
120
  raise LMS_TagForbiddenParametersError("There may not be parameters for closing tags!")
121
121
 
122
122
  tag = LMS_DecodedTag(definition, is_closing=True)
123
- self._parts.append(tag)
123
+ self._segments.append(tag)
124
124
  return tag
125
125
 
126
126
  if parameters:
@@ -129,7 +129,7 @@ class LMS_MessageText:
129
129
  else:
130
130
  tag = LMS_DecodedTag(definition)
131
131
 
132
- self._parts.append(tag)
132
+ self._segments.append(tag)
133
133
  return tag
134
134
 
135
135
  def append_tag_string(self, tag: str) -> LMS_ControlTag:
@@ -158,13 +158,13 @@ class LMS_MessageText:
158
158
  else:
159
159
  raise ValueError(f"Invalid format in tag '{tag}'.")
160
160
 
161
- self._parts.append(tag_obj)
161
+ self._segments.append(tag_obj)
162
162
  return tag_obj
163
163
 
164
- def _set_parts(self, text: str) -> None:
165
- self._parts = []
164
+ def _set_segments(self, text: str) -> None:
165
+ self._segments = []
166
166
  for part in self.TAG_FORMAT.split(text):
167
167
  if bool(re.match(self.TAG_FORMAT, part)):
168
168
  self.append_tag_string(part)
169
169
  else:
170
- self._parts.append(part)
170
+ self._segments.append(part)
@@ -9,20 +9,28 @@ class MSBT:
9
9
  """
10
10
  A class that represents a MSBT file.
11
11
 
12
+ ======
13
+ Usages
14
+ ======
15
+ https://github.com/AbdyyEee/PylibMS/wiki/MSBT
16
+
17
+ =========
18
+ File Info
19
+ =========
12
20
  https://nintendo-formats.com/libs/lms/msbt.html
13
21
  """
14
-
15
22
  MAGIC = "MsgStdBn"
16
23
 
17
24
  DEFAULT_SLOT_COUNT = 101
18
25
 
19
26
  ATR1_INDEX = 1
20
27
  TXT2_INDEX = 2
21
- TSY_INDEX = 3
28
+ TSY1_INDEX = 3
22
29
 
23
30
  def __init__(
24
31
  self,
25
32
  info: LMS_FileInfo | None = None,
33
+ uses_nli1: bool = False,
26
34
  section_list: list[str] | None = None,
27
35
  unsupported_section_map: dict[str, bytes] | None = None,
28
36
  attribute_config: AttributeConfig | None = None,
@@ -43,13 +51,14 @@ class MSBT:
43
51
  self._unsupported_section_map = unsupported_section_map or {}
44
52
 
45
53
  # Store the section list so that the order of any and all sections is preserved when writing
46
- self._section_list: list[str] = section_list or ["LBL1"]
54
+ self._section_list: list[str] = section_list or ["LBL1" if not uses_nli1 else "NLI1"]
47
55
 
48
56
  self._attribute_config = attribute_config
49
57
  self._tag_config = tag_config
50
58
 
51
59
  @classmethod
52
60
  def new(cls,
61
+ uses_nli1: bool = False,
53
62
  attribute_config: AttributeConfig | None = None,
54
63
  tag_config: TagConfig | None = None,
55
64
  is_big_endian: bool = False,
@@ -58,6 +67,7 @@ class MSBT:
58
67
  section_count: int = 2):
59
68
  """Create a new MSBT instance.
60
69
 
70
+ :param uses_nli1: flag to determine if to use nli1 section for labels.
61
71
  :param attribute_config: the attribute config object
62
72
  :param tag_config: the tag config object
63
73
  :param is_big_endian: if the file is big endian.
@@ -65,13 +75,13 @@ class MSBT:
65
75
  :param version: the file version.
66
76
  :param section_count: the number of sections.
67
77
 
68
- ========
69
- Examples
70
- ========
78
+ ======
79
+ Usages
80
+ ======
71
81
  See https://github.com/AbdyyEee/PylibMS/wiki/MSBT#creating-a-msbt
72
82
  """
73
- return MSBT(info=LMS_FileInfo(is_big_endian, encoding, version, section_count),
74
- attribute_config=attribute_config, tag_config=tag_config)
83
+ return MSBT(LMS_FileInfo(is_big_endian, encoding, version, section_count),
84
+ uses_nli1=uses_nli1, attribute_config=attribute_config, tag_config=tag_config)
75
85
 
76
86
  def __len__(self) -> int:
77
87
  return len(self._entries)
@@ -100,12 +110,17 @@ class MSBT:
100
110
  return tuple(self._unsupported_section_map.keys())
101
111
 
102
112
  @property
103
- def has_attributes(self) -> bool:
113
+ def uses_nli1(self) -> bool:
114
+ """If the MSBT contains the NLI1 section."""
115
+ return self.section_exists("NLI1")
116
+
117
+ @property
118
+ def contains_attributes(self) -> bool:
104
119
  """If the MSBT contains attributes."""
105
120
  return self.section_exists("ATR1")
106
121
 
107
122
  @property
108
- def has_styles(self) -> bool:
123
+ def contains_styles(self) -> bool:
109
124
  """If the MSBT contains style indexes."""
110
125
  return self.section_exists("TSY1")
111
126
 
@@ -141,7 +156,7 @@ class MSBT:
141
156
  raise KeyError(f"The label '{entry.name}' already exists!")
142
157
 
143
158
  # The implementation of ensuring section orders are maintained are done by constant indexes.
144
- # In most MSBT files, it goes as LBL1 -> TXT2 -> ATR1 -> TSY1.
159
+ # In most MSBT files, it goes as LBL1/NLI1 -> TXT2 -> ATR1 -> TSY1.
145
160
  # If add_entry is utilized on a new MSBT instance, then these indexes ensure the order is maintained.
146
161
  # While technically, undocumented sections (i.e. ATO1) can prefix TXT2 and other sections,
147
162
  # we do not need to account for that scenario as they aren't supported by the library
@@ -164,7 +179,7 @@ class MSBT:
164
179
  f"Entry '{entry.name}' can't be added with no style index when styles already exist!"
165
180
  )
166
181
  elif entry.style_index is not None:
167
- self._section_list.insert(self.TSY_INDEX, "TSY1")
182
+ self._section_list.insert(self.TSY1_INDEX, "TSY1")
168
183
  self._info.section_count += 1
169
184
 
170
185
  self._entries.append(entry)
@@ -79,12 +79,14 @@ def read_msbt(
79
79
  messages = atr1_data = style_indexes = None
80
80
 
81
81
  labels: dict[int, str] = {}
82
+ uses_nli1 = False
82
83
  for magic, size in read_section_data(reader, file_info.section_count):
83
84
  match magic:
84
85
  case "LBL1":
85
86
  labels, slot_count = read_labels(reader)
86
87
  case "NLI1":
87
88
  labels = read_nli1(reader)
89
+ uses_nli1 = True
88
90
  case "ATR1":
89
91
  atr1_data = read_atr1(reader, attribute_config, size)
90
92
  case "TXT2":
@@ -98,7 +100,8 @@ def read_msbt(
98
100
  section_list.append(magic)
99
101
 
100
102
  file = MSBT(
101
- file_info, section_list, unsupported_sections, attribute_config, tag_config
103
+ file_info, uses_nli1, section_list,
104
+ unsupported_sections, attribute_config, tag_config
102
105
  )
103
106
  file.slot_count = slot_count
104
107
 
@@ -39,7 +39,7 @@ def read_encoded_atr1(reader: FileReader, section_size: int) -> ATR1Data:
39
39
 
40
40
  def read_decoded_atr1(reader: FileReader, config: AttributeConfig) -> ATR1Data:
41
41
  # String table as a list is not necessary for decoded attributes
42
- # It is easier to save all changes from the user by when writing
42
+ # It is easier to save all changes from the user when writing
43
43
  # to recreate the table from all the string parameters in every attribute
44
44
  string_table = None
45
45
 
@@ -19,24 +19,23 @@ def read_txt2(
19
19
  for offset in reader.read_offset_array(message_count):
20
20
  reader.seek(offset)
21
21
 
22
- text, parts = b"", []
22
+ text, text_segments = b"", []
23
23
  while (data := reader.read_bytes(encoding.width)) != encoding.terminator:
24
- is_opening_tag = data == tag_start
25
24
  is_closing_tag = data == tag_close
26
25
 
27
- if is_opening_tag or is_closing_tag:
28
- parts.append(text.decode(encoding_format))
26
+ if data == tag_start or is_closing_tag:
27
+ text_segments.append(text.decode(encoding_format))
29
28
  tag = read_tag(reader, config, is_closing_tag, suppress_tag_errors)
30
- parts.append(tag)
29
+ text_segments.append(tag)
31
30
  text = b""
32
31
  else:
33
32
  text += data
34
33
 
35
34
  # Add the remaining text in case there were no control tags
36
35
  if text:
37
- parts.append(text.decode(encoding_format))
36
+ text_segments.append(text.decode(encoding_format))
38
37
 
39
- message = LMS_MessageText(parts, config)
38
+ message = LMS_MessageText(text_segments, config)
40
39
  messages.append(message)
41
40
 
42
41
  return messages
@@ -9,7 +9,15 @@ class MSBP:
9
9
  """
10
10
  A class that represents a MSBP file.
11
11
 
12
- https://nintendo-formats.com/libs/lms/msbp.html.
12
+ ======
13
+ Usages
14
+ ======
15
+ https://github.com/AbdyyEee/PylibMS/wiki/MSBP
16
+
17
+ =========
18
+ File Info
19
+ =========
20
+ https://nintendo-formats.com/libs/lms/msbp.html
13
21
  """
14
22
 
15
23
  MAGIC = "MsgPrjBn"
@@ -23,10 +31,9 @@ class MSBP:
23
31
  styles: list[LMS_Style] | None,
24
32
  source_files: list[str] | None,
25
33
  ):
26
- self.name: str = ""
34
+ self.name = ""
27
35
 
28
36
  self._info = info
29
-
30
37
  self._colors = colors
31
38
  self._attribute_definitions = config
32
39
  self._tag_groups = tag_groups
@@ -35,7 +42,7 @@ class MSBP:
35
42
 
36
43
  @property
37
44
  def info(self) -> LMS_FileInfo:
38
- """The stream info of the MSBP."""
45
+ """The file info for the MSBP instance."""
39
46
  return self._info
40
47
 
41
48
  @property
@@ -62,7 +62,7 @@ def read_msbp(stream: BinaryIO | bytes) -> MSBP:
62
62
  for magic, _ in read_section_data(reader, file_info.section_count):
63
63
  match magic:
64
64
  case "CLB1" | "ALB1" | "SLB1":
65
- # Set the name attributes of last read item
65
+ # Set the name attribute of last read item
66
66
  labels, _ = read_labels(reader)
67
67
  for i in labels:
68
68
  items[i].name = labels[i]
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "PyLibMS"
7
- version = "3.3"
7
+ version = "3.3.2"
8
8
  requires-python = ">=3.12"
9
9
  description = "Python library built for the libMessageStudio (LMS) proprietary file formats from Nintendo. Supports MSBT, MSBP, and soon to be MSBF."
10
10
  readme = "README.md"
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