PyLibMS 3.3__tar.gz → 3.3.1__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.
- {pylibms-3.3 → pylibms-3.3.1}/PKG-INFO +1 -1
- {pylibms-3.3 → pylibms-3.3.1}/PyLibMS.egg-info/PKG-INFO +1 -1
- {pylibms-3.3 → pylibms-3.3.1}/lms/common/lms_datatype.py +14 -15
- {pylibms-3.3 → pylibms-3.3.1}/lms/common/stream/fileinfo.py +1 -1
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/definitions/lms_messagetext.py +16 -16
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/msbt.py +32 -13
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/msbtio.py +4 -1
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/section/txt2.py +6 -7
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/msbp.py +11 -4
- {pylibms-3.3 → pylibms-3.3.1}/pyproject.toml +1 -1
- {pylibms-3.3 → pylibms-3.3.1}/LICENSE +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/MANIFEST.in +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/PyLibMS.egg-info/SOURCES.txt +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/PyLibMS.egg-info/dependency_links.txt +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/PyLibMS.egg-info/requires.txt +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/PyLibMS.egg-info/top_level.txt +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/README.md +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/__init__.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/common/__init__.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/common/lms_exceptions.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/common/lms_fileinfo.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/common/stream/hashtable.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/common/stream/section.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/fileio/encoding.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/fileio/io.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/__init__.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/definitions/__init__.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/definitions/field/__init__.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/definitions/field/io.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/definitions/field/lms_field.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/msbtentry.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/section/__init__.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/section/atr1.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/section/nli1.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/section/tsy1.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/tag/__init__.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/tag/io/param_io.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/tag/io/tag_io.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/tag/lms_tag.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/message/tag/lms_tagexceptions.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/__init__.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/definitions/__init__.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/definitions/attribute.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/definitions/color.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/definitions/style.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/definitions/tag.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/msbpread.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/section/ali2.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/section/ati2.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/section/clr1.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/section/string.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/section/syl3.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/section/tag2.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/section/tgg2.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/project/section/tgp2.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/__init__.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/config.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/definitions/__init__.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/definitions/attribute.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/definitions/tags.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/definitions/value.py +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/Badge Arcade.yaml +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/Brain Age Concentration Training.yaml +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/Kirby Planet Robobot.yaml +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/Super Mario 3D Land.yaml +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/Super Mario 3D World + Bowsers Fury.yaml +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/Super Mario Odyssey.yaml +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/The Legend of Zelda a Link Between Worlds.yaml +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/Tomodachi Life.yaml +0 -0
- {pylibms-3.3 → pylibms-3.3.1}/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.1
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyLibMS
|
|
3
|
-
Version: 3.3
|
|
3
|
+
Version: 3.3.1
|
|
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
|
|
@@ -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 (
|
|
@@ -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 |
|
|
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
|
-
|
|
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"
|
|
36
|
+
raise lms_exceptions.LMS_MisalignedSizeError(f"File size is misaligned!")
|
|
37
37
|
|
|
38
38
|
reader.seek(DATA_START)
|
|
39
39
|
|
|
@@ -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.
|
|
23
|
+
self._set_segments(message)
|
|
24
24
|
else:
|
|
25
|
-
self.
|
|
25
|
+
self._segments = message
|
|
26
26
|
|
|
27
27
|
def __iter__(self):
|
|
28
|
-
return iter(self.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
161
|
+
self._segments.append(tag_obj)
|
|
162
162
|
return tag_obj
|
|
163
163
|
|
|
164
|
-
def
|
|
165
|
-
self.
|
|
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.
|
|
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
|
-
|
|
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,
|
|
@@ -35,21 +43,26 @@ class MSBT:
|
|
|
35
43
|
|
|
36
44
|
self.size_per_attribute = 0
|
|
37
45
|
|
|
38
|
-
self.
|
|
46
|
+
self._slot_count = MSBT.DEFAULT_SLOT_COUNT
|
|
39
47
|
|
|
40
48
|
self.uses_encoded_attributes = True
|
|
41
49
|
self.attr_string_table: bytes | None = None
|
|
42
50
|
|
|
43
51
|
self._unsupported_section_map = unsupported_section_map or {}
|
|
44
52
|
|
|
53
|
+
# Some MSBTs store labels with NLI1 over LBL1
|
|
54
|
+
# This flag creates a MSBT instance with NLI1 preferred over LBL1
|
|
55
|
+
self.uses_nli1 = uses_nli1
|
|
56
|
+
|
|
45
57
|
# 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"]
|
|
58
|
+
self._section_list: list[str] = section_list or ["LBL1" if not self.uses_nli1 else "NLI1"]
|
|
47
59
|
|
|
48
60
|
self._attribute_config = attribute_config
|
|
49
61
|
self._tag_config = tag_config
|
|
50
62
|
|
|
51
63
|
@classmethod
|
|
52
64
|
def new(cls,
|
|
65
|
+
uses_nli1: bool = False,
|
|
53
66
|
attribute_config: AttributeConfig | None = None,
|
|
54
67
|
tag_config: TagConfig | None = None,
|
|
55
68
|
is_big_endian: bool = False,
|
|
@@ -58,6 +71,7 @@ class MSBT:
|
|
|
58
71
|
section_count: int = 2):
|
|
59
72
|
"""Create a new MSBT instance.
|
|
60
73
|
|
|
74
|
+
:param uses_nli1: flag to determine if to use nli1 section for labels.
|
|
61
75
|
:param attribute_config: the attribute config object
|
|
62
76
|
:param tag_config: the tag config object
|
|
63
77
|
:param is_big_endian: if the file is big endian.
|
|
@@ -65,13 +79,13 @@ class MSBT:
|
|
|
65
79
|
:param version: the file version.
|
|
66
80
|
:param section_count: the number of sections.
|
|
67
81
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
82
|
+
======
|
|
83
|
+
Usages
|
|
84
|
+
======
|
|
71
85
|
See https://github.com/AbdyyEee/PylibMS/wiki/MSBT#creating-a-msbt
|
|
72
86
|
"""
|
|
73
|
-
return MSBT(
|
|
74
|
-
attribute_config=attribute_config, tag_config=tag_config)
|
|
87
|
+
return MSBT(LMS_FileInfo(is_big_endian, encoding, version, section_count),
|
|
88
|
+
uses_nli1=uses_nli1, attribute_config=attribute_config, tag_config=tag_config)
|
|
75
89
|
|
|
76
90
|
def __len__(self) -> int:
|
|
77
91
|
return len(self._entries)
|
|
@@ -89,6 +103,11 @@ class MSBT:
|
|
|
89
103
|
"""Tuple of all the MSBT entries."""
|
|
90
104
|
return tuple(self._entries)
|
|
91
105
|
|
|
106
|
+
@property
|
|
107
|
+
def slot_count(self) -> int:
|
|
108
|
+
"""The slot count for the MSBT instance."""
|
|
109
|
+
return self._slot_count
|
|
110
|
+
|
|
92
111
|
@property
|
|
93
112
|
def section_list(self) -> tuple[str, ...]:
|
|
94
113
|
"""The list of sections with order preserved."""
|
|
@@ -100,12 +119,12 @@ class MSBT:
|
|
|
100
119
|
return tuple(self._unsupported_section_map.keys())
|
|
101
120
|
|
|
102
121
|
@property
|
|
103
|
-
def
|
|
122
|
+
def contains_attributes(self) -> bool:
|
|
104
123
|
"""If the MSBT contains attributes."""
|
|
105
124
|
return self.section_exists("ATR1")
|
|
106
125
|
|
|
107
126
|
@property
|
|
108
|
-
def
|
|
127
|
+
def contains_styles(self) -> bool:
|
|
109
128
|
"""If the MSBT contains style indexes."""
|
|
110
129
|
return self.section_exists("TSY1")
|
|
111
130
|
|
|
@@ -141,7 +160,7 @@ class MSBT:
|
|
|
141
160
|
raise KeyError(f"The label '{entry.name}' already exists!")
|
|
142
161
|
|
|
143
162
|
# 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.
|
|
163
|
+
# In most MSBT files, it goes as LBL1/NLI1 -> TXT2 -> ATR1 -> TSY1.
|
|
145
164
|
# If add_entry is utilized on a new MSBT instance, then these indexes ensure the order is maintained.
|
|
146
165
|
# While technically, undocumented sections (i.e. ATO1) can prefix TXT2 and other sections,
|
|
147
166
|
# we do not need to account for that scenario as they aren't supported by the library
|
|
@@ -164,7 +183,7 @@ class MSBT:
|
|
|
164
183
|
f"Entry '{entry.name}' can't be added with no style index when styles already exist!"
|
|
165
184
|
)
|
|
166
185
|
elif entry.style_index is not None:
|
|
167
|
-
self._section_list.insert(self.
|
|
186
|
+
self._section_list.insert(self.TSY1_INDEX, "TSY1")
|
|
168
187
|
self._info.section_count += 1
|
|
169
188
|
|
|
170
189
|
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,
|
|
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
|
|
|
@@ -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,
|
|
22
|
+
text, message_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
|
|
28
|
-
|
|
26
|
+
if data == tag_start or is_closing_tag:
|
|
27
|
+
message_segments.append(text.decode(encoding_format))
|
|
29
28
|
tag = read_tag(reader, config, is_closing_tag, suppress_tag_errors)
|
|
30
|
-
|
|
29
|
+
message_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
|
-
|
|
36
|
+
message_segments.append(text.decode(encoding_format))
|
|
38
37
|
|
|
39
|
-
message = LMS_MessageText(
|
|
38
|
+
message = LMS_MessageText(message_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
|
-
|
|
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
|
|
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
|
|
45
|
+
"""The file info for the MSBP instance."""
|
|
39
46
|
return self._info
|
|
40
47
|
|
|
41
48
|
@property
|
|
@@ -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.1"
|
|
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
|
|
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
|
|
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
|
|
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
|
{pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/Super Mario 3D World + Bowsers Fury.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml
RENAMED
|
File without changes
|
{pylibms-3.3 → pylibms-3.3.1}/lms/titleconfig/presets/The Legend of Zelda a Link Between Worlds.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|