PyLibMS 5.0.4__tar.gz → 5.0.5__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-5.0.4 → pylibms-5.0.5}/PKG-INFO +1 -1
- {pylibms-5.0.4 → pylibms-5.0.5}/PyLibMS.egg-info/PKG-INFO +1 -1
- {pylibms-5.0.4 → pylibms-5.0.5}/PyLibMS.egg-info/SOURCES.txt +3 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/common/lms_fileinfo.py +2 -1
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/common/stream/fileinfo.py +14 -9
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/fileio/encoding.py +5 -7
- pylibms-5.0.5/lms/fileio/endian.py +19 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/fileio/io.py +10 -9
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/flowchart/msbf.py +4 -3
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/flowchart/msbfio.py +1 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/lms_messagetext.py +1 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/msbt.py +22 -7
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/msbtentry.py +11 -11
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/msbtio.py +21 -15
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/section/txt2.py +3 -3
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/tag/io/tag_io.py +5 -4
- pylibms-5.0.5/lms/message/umsbt.py +91 -0
- pylibms-5.0.5/lms/message/umsbtio.py +183 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/msbpread.py +1 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/pyproject.toml +1 -1
- {pylibms-5.0.4 → pylibms-5.0.5}/LICENSE +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/MANIFEST.in +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/PyLibMS.egg-info/dependency_links.txt +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/PyLibMS.egg-info/requires.txt +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/PyLibMS.egg-info/top_level.txt +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/README.md +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/common/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/common/field/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/common/field/io.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/common/field/lms_datatype.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/common/field/lms_field.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/common/lms_constants.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/common/lms_exceptions.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/common/stream/hashtable.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/common/stream/section.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/flowchart/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/flowchart/definitions/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/flowchart/definitions/lms_nodeexceptions.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/flowchart/definitions/node.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/flowchart/definitions/node_type.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/flowchart/flw3.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/definitions/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/section/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/section/atr1.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/section/nli1.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/section/tsy1.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/tag/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/tag/io/param_io.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/tag/lms_tag.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/message/tag/lms_tagexceptions.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/definitions/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/definitions/attribute.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/definitions/color.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/definitions/style.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/definitions/tag.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/msbp.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/section/ali2.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/section/ati2.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/section/clr1.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/section/string.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/section/syl3.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/section/tag2.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/section/tgg2.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/project/section/tgp2.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/py.typed +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/titleconfig/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/titleconfig/config.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/titleconfig/definitions/__init__.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/titleconfig/definitions/attribute.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/titleconfig/definitions/field.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/titleconfig/definitions/nodes.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/lms/titleconfig/definitions/tags.py +0 -0
- {pylibms-5.0.4 → pylibms-5.0.5}/setup.cfg +0 -0
|
@@ -21,6 +21,7 @@ lms/common/stream/fileinfo.py
|
|
|
21
21
|
lms/common/stream/hashtable.py
|
|
22
22
|
lms/common/stream/section.py
|
|
23
23
|
lms/fileio/encoding.py
|
|
24
|
+
lms/fileio/endian.py
|
|
24
25
|
lms/fileio/io.py
|
|
25
26
|
lms/flowchart/__init__.py
|
|
26
27
|
lms/flowchart/flw3.py
|
|
@@ -35,6 +36,8 @@ lms/message/lms_messagetext.py
|
|
|
35
36
|
lms/message/msbt.py
|
|
36
37
|
lms/message/msbtentry.py
|
|
37
38
|
lms/message/msbtio.py
|
|
39
|
+
lms/message/umsbt.py
|
|
40
|
+
lms/message/umsbtio.py
|
|
38
41
|
lms/message/definitions/__init__.py
|
|
39
42
|
lms/message/section/__init__.py
|
|
40
43
|
lms/message/section/atr1.py
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
|
|
3
3
|
from lms.fileio.encoding import FileEncoding
|
|
4
|
+
from lms.fileio.io import FileEndian
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
@dataclass
|
|
7
8
|
class LMS_FileInfo:
|
|
8
|
-
|
|
9
|
+
endian: FileEndian = FileEndian.LITTLE
|
|
9
10
|
encoding: FileEncoding = FileEncoding.UTF16
|
|
10
11
|
version: int = 3
|
|
11
12
|
section_count: int = 2
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from lms.common import lms_exceptions
|
|
2
2
|
from lms.common.lms_constants import (
|
|
3
|
-
BIG_ENDIAN_BOM,
|
|
4
3
|
SECTION_DATA_START,
|
|
5
|
-
LITTLE_ENDIAN_BOM,
|
|
6
4
|
SIZE_OFFSET, LMS_MINIMUM_VERSION,
|
|
5
|
+
LITTLE_ENDIAN_BOM, BIG_ENDIAN_BOM,
|
|
7
6
|
)
|
|
8
7
|
from lms.common.lms_fileinfo import LMS_FileInfo
|
|
9
8
|
from lms.fileio.encoding import FileEncoding
|
|
9
|
+
from lms.fileio.endian import FileEndian
|
|
10
10
|
from lms.fileio.io import FileReader, FileWriter
|
|
11
11
|
|
|
12
12
|
|
|
@@ -19,9 +19,16 @@ def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
|
|
|
19
19
|
This file may not a valid LMS format or the wrong reading function was utilized."""
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
reader.is_big_endian = is_big_endian
|
|
22
|
+
bom = reader.read_bytes(2)
|
|
24
23
|
|
|
24
|
+
if bom == LITTLE_ENDIAN_BOM:
|
|
25
|
+
endian = FileEndian.LITTLE
|
|
26
|
+
elif bom == BIG_ENDIAN_BOM:
|
|
27
|
+
endian = FileEndian.BIG
|
|
28
|
+
else:
|
|
29
|
+
raise ValueError(f"Invalid bom found in file '{bom}'!")
|
|
30
|
+
|
|
31
|
+
reader.endian = endian
|
|
25
32
|
reader.skip(2)
|
|
26
33
|
|
|
27
34
|
encoding = FileEncoding(reader.read_uint8())
|
|
@@ -48,7 +55,7 @@ def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
|
|
|
48
55
|
reader.seek(SECTION_DATA_START)
|
|
49
56
|
|
|
50
57
|
return LMS_FileInfo(
|
|
51
|
-
|
|
58
|
+
endian,
|
|
52
59
|
encoding,
|
|
53
60
|
version,
|
|
54
61
|
section_count,
|
|
@@ -56,13 +63,11 @@ def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
|
|
|
56
63
|
|
|
57
64
|
|
|
58
65
|
def write_file_info(writer: FileWriter, magic: str, file_info: LMS_FileInfo) -> None:
|
|
59
|
-
writer.
|
|
66
|
+
writer.endian = file_info.endian
|
|
60
67
|
writer.encoding = file_info.encoding
|
|
61
68
|
|
|
62
69
|
writer.write_string(magic)
|
|
63
|
-
writer.write_bytes(
|
|
64
|
-
LITTLE_ENDIAN_BOM if not file_info.is_big_endian else BIG_ENDIAN_BOM
|
|
65
|
-
)
|
|
70
|
+
writer.write_bytes(file_info.endian.byte_order_mark)
|
|
66
71
|
writer.write_bytes(b"\x00\x00")
|
|
67
72
|
|
|
68
73
|
writer.write_uint8(file_info.encoding.value)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from enum import IntEnum
|
|
2
2
|
from typing import Literal
|
|
3
3
|
|
|
4
|
+
from lms.fileio.endian import FileEndian
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
class FileEncoding(IntEnum):
|
|
6
8
|
"""An enum that represents a file encoding."""
|
|
@@ -10,20 +12,16 @@ class FileEncoding(IntEnum):
|
|
|
10
12
|
UTF32 = 0x02
|
|
11
13
|
|
|
12
14
|
def to_string_format(
|
|
13
|
-
|
|
15
|
+
self, endian: FileEndian = FileEndian.LITTLE
|
|
14
16
|
) -> Literal["UTF-8", "UTF-16-BE", "UTF-16-LE", "UTF-32-BE", "UTF-32-LE"]:
|
|
15
17
|
"""Converts the FileEncoding to string format."""
|
|
16
18
|
match self:
|
|
17
19
|
case FileEncoding.UTF8:
|
|
18
20
|
return "UTF-8"
|
|
19
21
|
case FileEncoding.UTF16:
|
|
20
|
-
if
|
|
21
|
-
return "UTF-16-BE"
|
|
22
|
-
return "UTF-16-LE"
|
|
22
|
+
return "UTF-16-BE" if endian is FileEndian.BIG else "UTF-16-LE"
|
|
23
23
|
case FileEncoding.UTF32:
|
|
24
|
-
if
|
|
25
|
-
return "UTF-32-BE"
|
|
26
|
-
return "UTF-32-LE"
|
|
24
|
+
return "UTF-32-BE" if endian is FileEndian.BIG else "UTF-32-LE"
|
|
27
25
|
|
|
28
26
|
@property
|
|
29
27
|
def width(self) -> Literal[1, 2, 4]:
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FileEndian(Enum):
|
|
6
|
+
"""An enum that represents a file endianness."""
|
|
7
|
+
|
|
8
|
+
LITTLE = 0
|
|
9
|
+
BIG = 1
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def string_name(self) -> Literal["little", "big"]:
|
|
13
|
+
"""Name of the endian as a string."""
|
|
14
|
+
return self._name_.lower()
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def byte_order_mark(self) -> Literal[b"\xFF\xFE", b"\xFE\xFF"]:
|
|
18
|
+
"""The BOM of the endian."""
|
|
19
|
+
return b"\xFF\xFE" if self is FileEndian.LITTLE else b"\xFE\xFF"
|
|
@@ -6,6 +6,7 @@ from typing import BinaryIO, Generator, Type
|
|
|
6
6
|
|
|
7
7
|
from io import BytesIO, IOBase, TextIOBase
|
|
8
8
|
from lms.fileio.encoding import FileEncoding
|
|
9
|
+
from lms.fileio.endian import FileEndian
|
|
9
10
|
|
|
10
11
|
PADDING_BYTE = b"\x00"
|
|
11
12
|
|
|
@@ -31,7 +32,7 @@ class StructBETypes(StrEnum):
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
class FileReader:
|
|
34
|
-
def __init__(self, data: BinaryIO | bytes | bytearray | memoryview,
|
|
35
|
+
def __init__(self, data: BinaryIO | bytes | bytearray | memoryview, endian: FileEndian = FileEndian.LITTLE) -> None:
|
|
35
36
|
self._stream: BinaryIO = None
|
|
36
37
|
|
|
37
38
|
match data:
|
|
@@ -43,11 +44,11 @@ class FileReader:
|
|
|
43
44
|
raise TypeError("The stream provided is not valid!")
|
|
44
45
|
|
|
45
46
|
self.encoding = FileEncoding.UTF8
|
|
46
|
-
self.
|
|
47
|
+
self.endian = endian
|
|
47
48
|
|
|
48
49
|
@property
|
|
49
50
|
def datatypes(self) -> Type[StructLETypes | StructBETypes]:
|
|
50
|
-
return StructLETypes if
|
|
51
|
+
return StructLETypes if self.endian == FileEndian.LITTLE else StructBETypes
|
|
51
52
|
|
|
52
53
|
def tell(self) -> int:
|
|
53
54
|
return self._stream.tell()
|
|
@@ -110,25 +111,25 @@ class FileReader:
|
|
|
110
111
|
string = b""
|
|
111
112
|
while (raw_char := self.read_bytes(self.encoding.width)) != self.encoding.terminator:
|
|
112
113
|
string += raw_char
|
|
113
|
-
return string.decode(self.encoding.to_string_format(self.
|
|
114
|
+
return string.decode(self.encoding.to_string_format(self.endian))
|
|
114
115
|
|
|
115
116
|
def read_len_string_encoded(self):
|
|
116
117
|
self.align(self.encoding.width)
|
|
117
118
|
length = self.read_uint16()
|
|
118
119
|
return self.read_bytes(length).decode(
|
|
119
|
-
self.encoding.to_string_format(self.
|
|
120
|
+
self.encoding.to_string_format(self.endian)
|
|
120
121
|
)
|
|
121
122
|
|
|
122
123
|
|
|
123
124
|
class FileWriter:
|
|
124
|
-
def __init__(self, encoding: FileEncoding):
|
|
125
|
+
def __init__(self, encoding: FileEncoding = FileEncoding.UTF8, endian: FileEndian = FileEndian.LITTLE) -> None:
|
|
125
126
|
self.data = BytesIO(b"")
|
|
126
127
|
self.encoding = encoding
|
|
127
|
-
self.
|
|
128
|
+
self.endian = endian
|
|
128
129
|
|
|
129
130
|
@property
|
|
130
131
|
def datatypes(self) -> Type[StructLETypes | StructBETypes]:
|
|
131
|
-
return StructLETypes if
|
|
132
|
+
return StructLETypes if self.endian == FileEndian.LITTLE else StructBETypes
|
|
132
133
|
|
|
133
134
|
def skip(self, length: int) -> None:
|
|
134
135
|
self.data.seek(length, 1)
|
|
@@ -186,7 +187,7 @@ class FileWriter:
|
|
|
186
187
|
|
|
187
188
|
def write_encoded_string(self, string: str, terminate: bool = True) -> None:
|
|
188
189
|
self.write_bytes(
|
|
189
|
-
string.encode(self.encoding.to_string_format(self.
|
|
190
|
+
string.encode(self.encoding.to_string_format(self.endian))
|
|
190
191
|
)
|
|
191
192
|
if terminate:
|
|
192
193
|
self.write_bytes(b"\x00" * self.encoding.width)
|
|
@@ -3,6 +3,7 @@ from typing import Mapping, Iterator
|
|
|
3
3
|
|
|
4
4
|
from lms.common.lms_fileinfo import LMS_FileInfo
|
|
5
5
|
from lms.fileio.encoding import FileEncoding
|
|
6
|
+
from lms.fileio.endian import FileEndian
|
|
6
7
|
from lms.flowchart.definitions.node import LMS_EntryNode, LMS_JumpNode, LMS_BranchNode, LMS_BaseNode
|
|
7
8
|
|
|
8
9
|
|
|
@@ -52,7 +53,7 @@ class MSBF:
|
|
|
52
53
|
@classmethod
|
|
53
54
|
def new(
|
|
54
55
|
cls,
|
|
55
|
-
|
|
56
|
+
endian: FileEndian = FileEndian.LITTLE,
|
|
56
57
|
encoding: FileEncoding = FileEncoding.UTF16,
|
|
57
58
|
version: int = 3,
|
|
58
59
|
section_count: int = 2,
|
|
@@ -60,13 +61,13 @@ class MSBF:
|
|
|
60
61
|
"""
|
|
61
62
|
Create a new MSBF instance.
|
|
62
63
|
|
|
63
|
-
:param
|
|
64
|
+
:param endian: the endian of the file.
|
|
64
65
|
:param encoding: the file encoding.
|
|
65
66
|
:param version: the file version.
|
|
66
67
|
:param section_count: the number of sections.
|
|
67
68
|
|
|
68
69
|
"""
|
|
69
|
-
return MSBF(LMS_FileInfo(
|
|
70
|
+
return MSBF(LMS_FileInfo(endian, encoding, version, section_count))
|
|
70
71
|
|
|
71
72
|
@property
|
|
72
73
|
def info(self) -> LMS_FileInfo:
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
from
|
|
1
|
+
from types import MappingProxyType
|
|
2
|
+
from typing import overload, Mapping
|
|
2
3
|
|
|
3
4
|
from lms.common.field.lms_field import LMS_FieldMap
|
|
4
5
|
from lms.common.lms_fileinfo import LMS_FileInfo
|
|
5
6
|
from lms.fileio.encoding import FileEncoding
|
|
7
|
+
from lms.fileio.endian import FileEndian
|
|
6
8
|
from lms.message.lms_messagetext import LMS_MessageText
|
|
7
9
|
from lms.message.msbtentry import MSBTEntry
|
|
8
10
|
from lms.titleconfig.definitions.attribute import AttributeConfig
|
|
@@ -56,9 +58,7 @@ class MSBT:
|
|
|
56
58
|
self._unsupported_section_map = unsupported_section_map or {}
|
|
57
59
|
|
|
58
60
|
# Store the section list so that the order of any and all sections is preserved when writing
|
|
59
|
-
self._section_list: list[str] = section_list
|
|
60
|
-
"LBL1" if not uses_nli1 else "NLI1"
|
|
61
|
-
]
|
|
61
|
+
self._section_list: list[str] = section_list
|
|
62
62
|
|
|
63
63
|
self._attribute_config = attribute_config
|
|
64
64
|
self._tag_config = tag_config
|
|
@@ -91,7 +91,7 @@ class MSBT:
|
|
|
91
91
|
uses_nli1: bool = False,
|
|
92
92
|
attribute_config: AttributeConfig | None = None,
|
|
93
93
|
tag_config: TagConfig | None = None,
|
|
94
|
-
|
|
94
|
+
endian: FileEndian = FileEndian.LITTLE,
|
|
95
95
|
encoding: FileEncoding = FileEncoding.UTF16,
|
|
96
96
|
version: int = 3,
|
|
97
97
|
section_count: int = 2,
|
|
@@ -102,7 +102,7 @@ class MSBT:
|
|
|
102
102
|
:param uses_nli1: flag to determine if to use nli1 section for labels.
|
|
103
103
|
:param attribute_config: the attribute config object
|
|
104
104
|
:param tag_config: the tag config object
|
|
105
|
-
:param
|
|
105
|
+
:param endian: if the file is big endian.
|
|
106
106
|
:param encoding: the file encoding.
|
|
107
107
|
:param version: the file version.
|
|
108
108
|
:param section_count: the number of sections.
|
|
@@ -113,7 +113,8 @@ class MSBT:
|
|
|
113
113
|
See https://github.com/AbdyyEee/PylibMS/wiki/MSBT#creating-a-msbt
|
|
114
114
|
"""
|
|
115
115
|
return MSBT(
|
|
116
|
-
LMS_FileInfo(
|
|
116
|
+
LMS_FileInfo(endian, encoding, version, section_count),
|
|
117
|
+
section_list=["LBL1" if not uses_nli1 else "NLI1", "TXT2"],
|
|
117
118
|
uses_nli1=uses_nli1,
|
|
118
119
|
attribute_config=attribute_config,
|
|
119
120
|
tag_config=tag_config,
|
|
@@ -129,6 +130,11 @@ class MSBT:
|
|
|
129
130
|
"""Tuple of all the MSBT entries."""
|
|
130
131
|
return tuple(self._entries)
|
|
131
132
|
|
|
133
|
+
@property
|
|
134
|
+
def label_map(self) -> Mapping[str, MSBTEntry]:
|
|
135
|
+
"""Mapping of all labels to MSBT entries."""
|
|
136
|
+
return MappingProxyType(self._label_map)
|
|
137
|
+
|
|
132
138
|
@property
|
|
133
139
|
def section_list(self) -> tuple[str, ...]:
|
|
134
140
|
"""The list of sections with order preserved."""
|
|
@@ -160,10 +166,14 @@ class MSBT:
|
|
|
160
166
|
:param label: the label to check."""
|
|
161
167
|
return label in self._label_map
|
|
162
168
|
|
|
169
|
+
# Backwards compatibility just in case
|
|
170
|
+
# Recommended to use accessors MSBT[index] or MSBT[label]
|
|
163
171
|
def get_entry_by_index(self, index: int) -> MSBTEntry:
|
|
164
172
|
"""
|
|
165
173
|
Retrieves an entry given its index. Supports negative indexing.
|
|
166
174
|
|
|
175
|
+
Recommended to use ``MSBT[index]`` over this function.
|
|
176
|
+
|
|
167
177
|
:param index: the index of the entry.
|
|
168
178
|
"""
|
|
169
179
|
if not (-len(self._entries) <= index < len(self._entries)):
|
|
@@ -175,6 +185,8 @@ class MSBT:
|
|
|
175
185
|
"""
|
|
176
186
|
Retrieves an entry given its name.
|
|
177
187
|
|
|
188
|
+
Recommended to use ``MSBT[label]`` over this function.
|
|
189
|
+
|
|
178
190
|
:param label: the label name for the entry.
|
|
179
191
|
"""
|
|
180
192
|
if label not in self._label_map:
|
|
@@ -203,6 +215,9 @@ class MSBT:
|
|
|
203
215
|
f"Entry '{entry.name}' can't be added with no attributes when attributes already exist!"
|
|
204
216
|
)
|
|
205
217
|
elif entry.attribute is not None:
|
|
218
|
+
if self._entries:
|
|
219
|
+
raise ValueError("All entries must have attributes tied to them!")
|
|
220
|
+
|
|
206
221
|
self._section_list.insert(self.ATR1_INDEX, "ATR1")
|
|
207
222
|
self._info.section_count += 1
|
|
208
223
|
|
|
@@ -8,12 +8,12 @@ class MSBTEntry:
|
|
|
8
8
|
"""A class that represents an entry in a MSBT file."""
|
|
9
9
|
|
|
10
10
|
def __init__(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
self,
|
|
12
|
+
name: str,
|
|
13
|
+
*,
|
|
14
|
+
message: LMS_MessageText | str | None = "",
|
|
15
|
+
attribute: LMS_FieldMap | bytes | None = None,
|
|
16
|
+
style_index: int | None = None,
|
|
17
17
|
):
|
|
18
18
|
self.name = name
|
|
19
19
|
|
|
@@ -37,7 +37,7 @@ class MSBTEntry:
|
|
|
37
37
|
self.style_index = style_index
|
|
38
38
|
|
|
39
39
|
@property
|
|
40
|
-
def message(self) -> LMS_MessageText
|
|
40
|
+
def message(self) -> LMS_MessageText:
|
|
41
41
|
"""The message object for the instance."""
|
|
42
42
|
return self._message
|
|
43
43
|
|
|
@@ -64,10 +64,10 @@ class MSBTEntry:
|
|
|
64
64
|
|
|
65
65
|
@classmethod
|
|
66
66
|
def from_dict(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
cls,
|
|
68
|
+
data: dict,
|
|
69
|
+
attribute_config: AttributeConfig | None = None,
|
|
70
|
+
tag_config: TagConfig | None = None,
|
|
71
71
|
):
|
|
72
72
|
"""
|
|
73
73
|
Creates a MSBTEntry from a dictionary object.
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
IO interface for ``MSBT`` files.
|
|
3
3
|
"""
|
|
4
4
|
import os
|
|
5
|
-
from
|
|
5
|
+
from types import MappingProxyType
|
|
6
|
+
from typing import BinaryIO, Mapping
|
|
6
7
|
|
|
7
8
|
from lms.common import lms_exceptions
|
|
8
9
|
from lms.common.stream.fileinfo import read_file_info, write_file_info, write_file_size
|
|
@@ -23,6 +24,8 @@ from lms.titleconfig.config import AttributeConfig, TagConfig
|
|
|
23
24
|
|
|
24
25
|
__all__ = ("read_msbt_path", "read_msbt_directory", "read_msbt", "write_msbt", "write_msbt_path")
|
|
25
26
|
|
|
27
|
+
MSBT_EXTENSION = ".msbt"
|
|
28
|
+
|
|
26
29
|
|
|
27
30
|
def read_msbt_path(
|
|
28
31
|
file_path: str | os.PathLike[str],
|
|
@@ -60,11 +63,12 @@ def read_msbt_directory(directory: str | os.PathLike[str], *,
|
|
|
60
63
|
attribute_config: AttributeConfig | None = None,
|
|
61
64
|
tag_config: TagConfig | None = None,
|
|
62
65
|
suppress_tag_errors: bool = False,
|
|
63
|
-
ignore_extensions: bool = False) ->
|
|
66
|
+
ignore_extensions: bool = False) -> Mapping[str, MSBT]:
|
|
64
67
|
"""
|
|
65
|
-
Reads and retrieves
|
|
68
|
+
Reads and retrieves all MSBT files from a given directory.
|
|
66
69
|
|
|
67
|
-
:param directory: directory of MSBT files. All files must have the `.msbt` extension
|
|
70
|
+
:param directory: directory of MSBT files. All files must have the `.msbt` extension (unless ``ignore_extensions``
|
|
71
|
+
is True)
|
|
68
72
|
:param attribute_config: the attribute config to use for decoding attributes.
|
|
69
73
|
:param tag_config: the tag config to use for decoding tags.
|
|
70
74
|
:param suppress_tag_errors: when a tag config is used, suppress any errors while reading decoded tags.
|
|
@@ -75,19 +79,21 @@ def read_msbt_directory(directory: str | os.PathLike[str], *,
|
|
|
75
79
|
=====
|
|
76
80
|
>>> msbt = read_msbt_directory("path/to/directory")
|
|
77
81
|
"""
|
|
78
|
-
files:
|
|
82
|
+
files: dict[str, MSBT] = {}
|
|
79
83
|
|
|
80
84
|
for file in os.listdir(directory):
|
|
81
|
-
if not file.endswith(
|
|
82
|
-
raise ValueError("This directory contains a non-MSBT file.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
if not file.endswith(MSBT_EXTENSION) and not ignore_extensions:
|
|
86
|
+
raise ValueError("This directory contains a non-MSBT file. "
|
|
87
|
+
"Only directories with MSBT files can be used! Set ignore_extensions to True to ignore"
|
|
88
|
+
"this error.")
|
|
89
|
+
|
|
90
|
+
msbt = read_msbt_path(os.path.join(directory, file),
|
|
91
|
+
attribute_config=attribute_config,
|
|
92
|
+
tag_config=tag_config, suppress_tag_errors=suppress_tag_errors)
|
|
93
|
+
msbt.filename = file
|
|
94
|
+
files[file] = msbt
|
|
95
|
+
|
|
96
|
+
return MappingProxyType(files)
|
|
91
97
|
|
|
92
98
|
|
|
93
99
|
def read_msbt(
|
|
@@ -6,15 +6,15 @@ from lms.titleconfig.definitions.tags import TagConfig
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def read_txt2(
|
|
9
|
-
|
|
9
|
+
reader: FileReader, config: TagConfig | None, suppress_tag_errors: bool
|
|
10
10
|
) -> list[LMS_MessageText]:
|
|
11
11
|
encoding = reader.encoding
|
|
12
12
|
|
|
13
13
|
messages = []
|
|
14
14
|
message_count = reader.read_uint32()
|
|
15
15
|
|
|
16
|
-
encoding_format = encoding.to_string_format(reader.
|
|
17
|
-
tag_start, tag_close = get_tag_indicator(encoding, reader.
|
|
16
|
+
encoding_format = encoding.to_string_format(reader.endian)
|
|
17
|
+
tag_start, tag_close = get_tag_indicator(encoding, reader.endian)
|
|
18
18
|
|
|
19
19
|
for offset in reader.read_offset_array(message_count):
|
|
20
20
|
reader.seek(offset)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from lms.fileio.encoding import FileEncoding
|
|
2
|
+
from lms.fileio.endian import FileEndian
|
|
2
3
|
from lms.fileio.io import FileReader, FileWriter
|
|
3
4
|
from lms.message.tag.io.param_io import (
|
|
4
5
|
read_decoded_parameters,
|
|
@@ -13,12 +14,12 @@ from lms.titleconfig.definitions.tags import TagConfig, TagDefinition
|
|
|
13
14
|
TAG_PADDING_BYTE = b"\xcd"
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
def get_tag_indicator(encoding: FileEncoding,
|
|
17
|
+
def get_tag_indicator(encoding: FileEncoding, endian: FileEndian):
|
|
17
18
|
start_indicator = int.to_bytes(
|
|
18
|
-
0x0E, encoding.width,
|
|
19
|
+
0x0E, encoding.width, endian.string_name
|
|
19
20
|
)
|
|
20
21
|
closing_indicator = int.to_bytes(
|
|
21
|
-
0x0F, encoding.width,
|
|
22
|
+
0x0F, encoding.width, endian.string_name
|
|
22
23
|
)
|
|
23
24
|
return start_indicator, closing_indicator
|
|
24
25
|
|
|
@@ -95,7 +96,7 @@ def _read_decoded_tag(
|
|
|
95
96
|
|
|
96
97
|
def write_tag(writer: FileWriter, tag: LMS_ControlTag) -> None:
|
|
97
98
|
start_indicator, close_indicator = get_tag_indicator(
|
|
98
|
-
writer.encoding, writer.
|
|
99
|
+
writer.encoding, writer.endian
|
|
99
100
|
)
|
|
100
101
|
writer.write_bytes(start_indicator if not tag.is_closing else close_indicator)
|
|
101
102
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from types import MappingProxyType
|
|
2
|
+
from typing import overload, Mapping, Sequence, Iterator
|
|
3
|
+
|
|
4
|
+
from lms.message.msbt import MSBT
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class UMSBT:
|
|
8
|
+
"""Class that represents a UMSBT file, an archive for MSBT files."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, files: Sequence[MSBT] | None = None) -> None:
|
|
11
|
+
self._files = list(files) if files is not None else []
|
|
12
|
+
|
|
13
|
+
for file in self._files:
|
|
14
|
+
if not isinstance(file, MSBT):
|
|
15
|
+
raise TypeError("Only MSBTs are allowed for UMSBT archives!")
|
|
16
|
+
|
|
17
|
+
self._realign_filenames()
|
|
18
|
+
|
|
19
|
+
@overload
|
|
20
|
+
def __getitem__(self, index: int) -> MSBT:
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
@overload
|
|
24
|
+
def __getitem__(self, label: str) -> MSBT:
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def __getitem__(self, value: int | str) -> MSBT:
|
|
28
|
+
if isinstance(value, str):
|
|
29
|
+
return self.file_map[value]
|
|
30
|
+
|
|
31
|
+
return self._files[value]
|
|
32
|
+
|
|
33
|
+
def __len__(self) -> int:
|
|
34
|
+
return len(self._files)
|
|
35
|
+
|
|
36
|
+
def __iter__(self) -> Iterator[MSBT]:
|
|
37
|
+
return iter(self._files)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def files(self) -> tuple[MSBT, ...]:
|
|
41
|
+
"""Maps of files tied to this instance."""
|
|
42
|
+
return tuple(self._files)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def file_map(self) -> Mapping[str, MSBT]:
|
|
46
|
+
"""Maps of files tied to this instance."""
|
|
47
|
+
return MappingProxyType({msbt.filename: msbt for msbt in self._files})
|
|
48
|
+
|
|
49
|
+
def add_msbt(self, msbt: MSBT) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Adds a MSBT to the UMSBT archive.
|
|
52
|
+
|
|
53
|
+
:param msbt: the msbt to add.
|
|
54
|
+
"""
|
|
55
|
+
self._files.append(msbt)
|
|
56
|
+
self._realign_filenames()
|
|
57
|
+
|
|
58
|
+
def insert_msbt(self, position: int, msbt: MSBT) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Inserts a MSBT in the archive at a specific index
|
|
61
|
+
|
|
62
|
+
:param position: position of the MSBT to insert.
|
|
63
|
+
:param msbt: the msbt to insert.
|
|
64
|
+
"""
|
|
65
|
+
self._files.insert(position, msbt)
|
|
66
|
+
self._realign_filenames()
|
|
67
|
+
|
|
68
|
+
@overload
|
|
69
|
+
def remove_msbt(self, msbt: MSBT) -> None:
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
@overload
|
|
73
|
+
def remove_msbt(self, msbt: int) -> None:
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
def remove_msbt(self, msbt: MSBT | int) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Deletes a MSBT from the UMSBT archive.
|
|
79
|
+
|
|
80
|
+
:param msbt: the msbt object or index to remove.
|
|
81
|
+
"""
|
|
82
|
+
if isinstance(msbt, MSBT):
|
|
83
|
+
self._files.remove(msbt)
|
|
84
|
+
else:
|
|
85
|
+
del self._files[msbt]
|
|
86
|
+
|
|
87
|
+
self._realign_filenames()
|
|
88
|
+
|
|
89
|
+
def _realign_filenames(self) -> None:
|
|
90
|
+
for i, msbt in enumerate(self._files):
|
|
91
|
+
msbt.filename = f"{i}.msbt"
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IO interface for ``UMSBT`` files.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
from typing import Mapping, BinaryIO
|
|
6
|
+
|
|
7
|
+
from lms.common import lms_exceptions
|
|
8
|
+
from lms.fileio.io import FileReader, FileWriter
|
|
9
|
+
from lms.message.msbt import MSBT
|
|
10
|
+
from lms.message.msbtio import read_msbt, write_msbt
|
|
11
|
+
from lms.message.umsbt import UMSBT
|
|
12
|
+
from lms.titleconfig.definitions.attribute import AttributeConfig
|
|
13
|
+
from lms.titleconfig.definitions.tags import TagConfig
|
|
14
|
+
|
|
15
|
+
__all__ = ("read_umsbt_path", "read_umsbt_directory", "read_umsbt", "write_umsbt_path", "write_umsbt")
|
|
16
|
+
|
|
17
|
+
UMSBT_EXTENSION = ".umsbt"
|
|
18
|
+
UMSBT_ALIGNMENT = 16
|
|
19
|
+
SIZE_PER_METADATA = 8
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def read_umsbt_path(
|
|
23
|
+
file_path: str | os.PathLike[str], *,
|
|
24
|
+
attribute_config: AttributeConfig | None = None,
|
|
25
|
+
tag_config: TagConfig | None = None,
|
|
26
|
+
suppress_tag_errors: bool = False,
|
|
27
|
+
) -> UMSBT:
|
|
28
|
+
"""
|
|
29
|
+
Reads and retrieves a UMSBT file from a given path.
|
|
30
|
+
|
|
31
|
+
:param file_path: the path to the UMSBT file.
|
|
32
|
+
:param attribute_config: the attribute config to use for decoding attributes.
|
|
33
|
+
:param tag_config: the tag config to use for decoding tags.
|
|
34
|
+
:param suppress_tag_errors: when a tag config is used, suppress any errors while reading decoded tags.
|
|
35
|
+
|
|
36
|
+
=====
|
|
37
|
+
Usage
|
|
38
|
+
=====
|
|
39
|
+
>>> umsbt = read_umsbt_path("path/to/file.umsbt")
|
|
40
|
+
"""
|
|
41
|
+
with open(file_path, "rb") as stream:
|
|
42
|
+
umsbt = read_umsbt(stream,
|
|
43
|
+
attribute_config=attribute_config,
|
|
44
|
+
tag_config=tag_config,
|
|
45
|
+
suppress_tag_errors=suppress_tag_errors)
|
|
46
|
+
|
|
47
|
+
return umsbt
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def read_umsbt_directory(directory: str | os.PathLike[str], *,
|
|
51
|
+
attribute_config: AttributeConfig | None = None,
|
|
52
|
+
tag_config: TagConfig | None = None,
|
|
53
|
+
suppress_tag_errors: bool = False,
|
|
54
|
+
ignore_extensions: bool = False) -> Mapping[str, tuple[MSBT, ...]]:
|
|
55
|
+
"""
|
|
56
|
+
Reads and retrieves all UMSBT files from a given directory.
|
|
57
|
+
|
|
58
|
+
:param directory: directory of UMSBT files. All files must have the `.umsbt` extension (unless ``ignore_extensions``
|
|
59
|
+
is True)
|
|
60
|
+
:param attribute_config: the attribute config to use for decoding attributes.
|
|
61
|
+
:param tag_config: the tag config to use for decoding tags.
|
|
62
|
+
:param suppress_tag_errors: when a tag config is used, suppress any errors while reading decoded tags.
|
|
63
|
+
:param ignore_extensions: ignore the extensions in the directory.
|
|
64
|
+
|
|
65
|
+
=====
|
|
66
|
+
Usage
|
|
67
|
+
=====
|
|
68
|
+
>>> umsbts = read_msbt_directory("path/to/directory")
|
|
69
|
+
"""
|
|
70
|
+
files: dict[str, tuple[MSBT, ...]] = {}
|
|
71
|
+
|
|
72
|
+
for file in os.listdir(directory):
|
|
73
|
+
if not file.endswith(UMSBT_EXTENSION) and not ignore_extensions:
|
|
74
|
+
raise ValueError("This directory contains a non-UMSBT file. "
|
|
75
|
+
"Only directories with UMSBT files can be used! Set ignore_extensions to True to ignore"
|
|
76
|
+
"this error.")
|
|
77
|
+
|
|
78
|
+
umsbt = read_umsbt_path(os.path.join(directory, file),
|
|
79
|
+
attribute_config=attribute_config,
|
|
80
|
+
tag_config=tag_config, suppress_tag_errors=suppress_tag_errors)
|
|
81
|
+
files[os.path.basename(file)] = umsbt
|
|
82
|
+
|
|
83
|
+
return files
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def read_umsbt(
|
|
87
|
+
stream: BinaryIO | bytes, *,
|
|
88
|
+
attribute_config: AttributeConfig | None = None,
|
|
89
|
+
tag_config: TagConfig | None = None,
|
|
90
|
+
suppress_tag_errors: bool = False,
|
|
91
|
+
) -> UMSBT:
|
|
92
|
+
"""
|
|
93
|
+
Reads and retrieves a UMSBT file from a specified stream.
|
|
94
|
+
|
|
95
|
+
:param stream: an ``IOBase``, ``BytesIO``, ``memoryview``, or ``bytes`` object.
|
|
96
|
+
:param attribute_config: the attribute config to use for decoding attributes.
|
|
97
|
+
:param tag_config: the tag config to use for decoding tags.
|
|
98
|
+
:param suppress_tag_errors: when a tag config is used, suppress any errors while reading decoded tags.
|
|
99
|
+
|
|
100
|
+
=====
|
|
101
|
+
Usage
|
|
102
|
+
=====
|
|
103
|
+
>>> umsbt = read_umsbt(stream)
|
|
104
|
+
"""
|
|
105
|
+
reader = FileReader(stream)
|
|
106
|
+
data_start = reader.read_uint32()
|
|
107
|
+
|
|
108
|
+
offsets, sizes = [], []
|
|
109
|
+
|
|
110
|
+
reader.seek(0)
|
|
111
|
+
while reader.tell() < data_start:
|
|
112
|
+
offset, size = reader.read_uint32(), reader.read_uint32()
|
|
113
|
+
|
|
114
|
+
# Reached padding section
|
|
115
|
+
if offset == 0 and size == 0:
|
|
116
|
+
break
|
|
117
|
+
|
|
118
|
+
offsets.append(offset)
|
|
119
|
+
sizes.append(size)
|
|
120
|
+
|
|
121
|
+
files = []
|
|
122
|
+
for i, (offset, size) in enumerate(zip(offsets, sizes)):
|
|
123
|
+
reader.seek(offset)
|
|
124
|
+
files.append(read_msbt(reader.read_bytes(size),
|
|
125
|
+
attribute_config=attribute_config,
|
|
126
|
+
tag_config=tag_config,
|
|
127
|
+
suppress_tag_errors=suppress_tag_errors))
|
|
128
|
+
|
|
129
|
+
return UMSBT(files)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def write_umsbt_path(file_path: str | os.PathLike[str], file: UMSBT) -> None:
|
|
133
|
+
"""
|
|
134
|
+
Writes a UMSBT file to a given file path. If the target path does not exist, it will be created.
|
|
135
|
+
|
|
136
|
+
:param file_path: the path to write the file to.
|
|
137
|
+
:param file: the UMSBT file object.
|
|
138
|
+
|
|
139
|
+
=====
|
|
140
|
+
Usage
|
|
141
|
+
=====
|
|
142
|
+
>>> write_umsbt_path("path/to/file.umsbt", umsbt)
|
|
143
|
+
"""
|
|
144
|
+
with open(file_path, "wb") as stream:
|
|
145
|
+
data = write_umsbt(file)
|
|
146
|
+
stream.write(data)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def write_umsbt(file: UMSBT) -> bytes:
|
|
150
|
+
"""
|
|
151
|
+
Writes a UMSBT file and returns the data.
|
|
152
|
+
|
|
153
|
+
:param file: a UMSBT object.
|
|
154
|
+
|
|
155
|
+
=====
|
|
156
|
+
Usage
|
|
157
|
+
=====
|
|
158
|
+
>>> data = write_umsbt(umsbt)
|
|
159
|
+
"""
|
|
160
|
+
if not isinstance(file, UMSBT):
|
|
161
|
+
raise lms_exceptions.LMS_Error(
|
|
162
|
+
f"File provided is not valid. Expected UMSBT got {type(file)}."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
writer = FileWriter()
|
|
166
|
+
msbt_count = len(file)
|
|
167
|
+
writer.write_bytes(b"\x00" * (msbt_count * SIZE_PER_METADATA))
|
|
168
|
+
|
|
169
|
+
writer.write_alignment(b"\x00", UMSBT_ALIGNMENT)
|
|
170
|
+
writer.write_bytes(b"\x00" * UMSBT_ALIGNMENT)
|
|
171
|
+
|
|
172
|
+
for i, msbt in enumerate(file):
|
|
173
|
+
offset = writer.tell()
|
|
174
|
+
data = write_msbt(msbt)
|
|
175
|
+
writer.write_bytes(data)
|
|
176
|
+
last_position = writer.tell()
|
|
177
|
+
|
|
178
|
+
writer.seek(i * SIZE_PER_METADATA)
|
|
179
|
+
writer.write_uint32(offset)
|
|
180
|
+
writer.write_uint32(len(data))
|
|
181
|
+
writer.seek(last_position)
|
|
182
|
+
|
|
183
|
+
return writer.get_data()
|
|
@@ -7,7 +7,7 @@ include = ["lms*"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "PyLibMS"
|
|
10
|
-
version = "5.0.
|
|
10
|
+
version = "5.0.5"
|
|
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"
|
|
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
|
|
File without changes
|