PyLibMS 4.1.7__tar.gz → 4.2.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-4.2.1/MANIFEST.in +1 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/PKG-INFO +28 -7
- {pylibms-4.1.7 → pylibms-4.2.1}/PyLibMS.egg-info/PKG-INFO +28 -7
- {pylibms-4.1.7 → pylibms-4.2.1}/README.md +27 -6
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/field/io.py +20 -6
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/definitions/flowchart.py +1 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/definitions/node.py +22 -26
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/definitions/node_type.py +22 -2
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/flw3.py +26 -12
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/msbf.py +4 -8
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/definitions/nodes.py +9 -2
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Tomodachi Life NA-EU.yaml +6 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/pyproject.toml +1 -1
- pylibms-4.1.7/MANIFEST.in +0 -2
- {pylibms-4.1.7 → pylibms-4.2.1}/LICENSE +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/PyLibMS.egg-info/SOURCES.txt +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/PyLibMS.egg-info/dependency_links.txt +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/PyLibMS.egg-info/requires.txt +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/PyLibMS.egg-info/top_level.txt +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/field/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/field/lms_datatype.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/field/lms_field.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/lms_constants.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/lms_exceptions.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/lms_fileinfo.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/stream/fileinfo.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/stream/hashtable.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/stream/section.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/fileio/encoding.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/fileio/io.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/definitions/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/definitions/lms_nodeexceptions.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/msbfio.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/definitions/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/definitions/field/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/lms_messagetext.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/msbt.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/msbtentry.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/msbtio.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/section/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/section/atr1.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/section/nli1.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/section/tsy1.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/section/txt2.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/tag/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/tag/io/param_io.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/tag/io/tag_io.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/tag/lms_tag.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/tag/lms_tagexceptions.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/definitions/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/definitions/attribute.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/definitions/color.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/definitions/style.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/definitions/tag.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/msbp.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/msbpread.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/ali2.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/ati2.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/clr1.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/string.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/syl3.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/tag2.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/tgg2.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/tgp2.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/config.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/definitions/__init__.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/definitions/attribute.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/definitions/tags.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/definitions/value.py +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Badge Arcade.yaml +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Brain Age Concentration Training.yaml +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Kirby Planet Robobot.yaml +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Super Mario 3D Land.yaml +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Super Mario 3D World + Bowsers Fury.yaml +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Super Mario Odyssey.yaml +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/The Legend of Zelda a Link Between Worlds.yaml +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Tomodachi Life Living The Dream.yaml +0 -0
- {pylibms-4.1.7 → pylibms-4.2.1}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include README.md LICENSE
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyLibMS
|
|
3
|
-
Version: 4.1
|
|
3
|
+
Version: 4.2.1
|
|
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
|
|
@@ -23,7 +23,7 @@ Dynamic: license-file
|
|
|
23
23
|
[](https://pypi.org/project/PyLibMS/)
|
|
24
24
|
[](https://badge.fury.io/py/PyLibMS)
|
|
25
25
|
|
|
26
|
-
PyLibMS is a library built
|
|
26
|
+
PyLibMS is a library built in Python 3.10 and above for reading and writing of libMessageStudio (LMS) proprietary file formats from Nintendo.
|
|
27
27
|
|
|
28
28
|
The following is supported
|
|
29
29
|
| Format | Read | Write | Configuration Support |
|
|
@@ -41,24 +41,45 @@ and examples.
|
|
|
41
41
|
|
|
42
42
|
## Reading
|
|
43
43
|
|
|
44
|
-
MSBT
|
|
44
|
+
### MSBT
|
|
45
45
|
|
|
46
46
|
```py
|
|
47
47
|
from lms.message.msbtio import read_msbt_path
|
|
48
|
+
|
|
49
|
+
msbt = read_msbt_path("Text.msbt")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### MSBP
|
|
53
|
+
```py
|
|
54
|
+
from lms.project.msbpread import read_msbp_path
|
|
55
|
+
|
|
56
|
+
msbp = read_msbp_path("Project.msbp")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### MSBF
|
|
61
|
+
```py
|
|
48
62
|
from lms.flowchart.msbfio import read_msbf_path
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
msbf = read_msbf_path("Game_Flowchart.msbf")
|
|
64
|
+
msbf = read_msbf_path("Flowchart.msbf")
|
|
52
65
|
```
|
|
53
66
|
|
|
67
|
+
|
|
54
68
|
## Writing
|
|
69
|
+
There is no writing for `MSBP` files as most of the time it acts as leftover metadata and not actually loaded by games.
|
|
55
70
|
|
|
71
|
+
### MSBT
|
|
56
72
|
```py
|
|
57
73
|
from lms.message.msbtio import write_msbt_path
|
|
74
|
+
|
|
75
|
+
write_msbt_path("Out_text.msbt")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### MSBF
|
|
79
|
+
```py
|
|
58
80
|
from lms.flowchart.msbfio import write_msbf_path
|
|
59
81
|
|
|
60
|
-
|
|
61
|
-
write_msbf_path("Out_Game_Flowchart.msbt")
|
|
82
|
+
write_msbf_path("Out_Flowchart.msbf")
|
|
62
83
|
```
|
|
63
84
|
|
|
64
85
|
# Adding/Editing Presets
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyLibMS
|
|
3
|
-
Version: 4.1
|
|
3
|
+
Version: 4.2.1
|
|
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
|
|
@@ -23,7 +23,7 @@ Dynamic: license-file
|
|
|
23
23
|
[](https://pypi.org/project/PyLibMS/)
|
|
24
24
|
[](https://badge.fury.io/py/PyLibMS)
|
|
25
25
|
|
|
26
|
-
PyLibMS is a library built
|
|
26
|
+
PyLibMS is a library built in Python 3.10 and above for reading and writing of libMessageStudio (LMS) proprietary file formats from Nintendo.
|
|
27
27
|
|
|
28
28
|
The following is supported
|
|
29
29
|
| Format | Read | Write | Configuration Support |
|
|
@@ -41,24 +41,45 @@ and examples.
|
|
|
41
41
|
|
|
42
42
|
## Reading
|
|
43
43
|
|
|
44
|
-
MSBT
|
|
44
|
+
### MSBT
|
|
45
45
|
|
|
46
46
|
```py
|
|
47
47
|
from lms.message.msbtio import read_msbt_path
|
|
48
|
+
|
|
49
|
+
msbt = read_msbt_path("Text.msbt")
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### MSBP
|
|
53
|
+
```py
|
|
54
|
+
from lms.project.msbpread import read_msbp_path
|
|
55
|
+
|
|
56
|
+
msbp = read_msbp_path("Project.msbp")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### MSBF
|
|
61
|
+
```py
|
|
48
62
|
from lms.flowchart.msbfio import read_msbf_path
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
msbf = read_msbf_path("Game_Flowchart.msbf")
|
|
64
|
+
msbf = read_msbf_path("Flowchart.msbf")
|
|
52
65
|
```
|
|
53
66
|
|
|
67
|
+
|
|
54
68
|
## Writing
|
|
69
|
+
There is no writing for `MSBP` files as most of the time it acts as leftover metadata and not actually loaded by games.
|
|
55
70
|
|
|
71
|
+
### MSBT
|
|
56
72
|
```py
|
|
57
73
|
from lms.message.msbtio import write_msbt_path
|
|
74
|
+
|
|
75
|
+
write_msbt_path("Out_text.msbt")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### MSBF
|
|
79
|
+
```py
|
|
58
80
|
from lms.flowchart.msbfio import write_msbf_path
|
|
59
81
|
|
|
60
|
-
|
|
61
|
-
write_msbf_path("Out_Game_Flowchart.msbt")
|
|
82
|
+
write_msbf_path("Out_Flowchart.msbf")
|
|
62
83
|
```
|
|
63
84
|
|
|
64
85
|
# Adding/Editing Presets
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
[](https://pypi.org/project/PyLibMS/)
|
|
3
3
|
[](https://badge.fury.io/py/PyLibMS)
|
|
4
4
|
|
|
5
|
-
PyLibMS is a library built
|
|
5
|
+
PyLibMS is a library built in Python 3.10 and above for reading and writing of libMessageStudio (LMS) proprietary file formats from Nintendo.
|
|
6
6
|
|
|
7
7
|
The following is supported
|
|
8
8
|
| Format | Read | Write | Configuration Support |
|
|
@@ -20,24 +20,45 @@ and examples.
|
|
|
20
20
|
|
|
21
21
|
## Reading
|
|
22
22
|
|
|
23
|
-
MSBT
|
|
23
|
+
### MSBT
|
|
24
24
|
|
|
25
25
|
```py
|
|
26
26
|
from lms.message.msbtio import read_msbt_path
|
|
27
|
+
|
|
28
|
+
msbt = read_msbt_path("Text.msbt")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### MSBP
|
|
32
|
+
```py
|
|
33
|
+
from lms.project.msbpread import read_msbp_path
|
|
34
|
+
|
|
35
|
+
msbp = read_msbp_path("Project.msbp")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### MSBF
|
|
40
|
+
```py
|
|
27
41
|
from lms.flowchart.msbfio import read_msbf_path
|
|
28
42
|
|
|
29
|
-
|
|
30
|
-
msbf = read_msbf_path("Game_Flowchart.msbf")
|
|
43
|
+
msbf = read_msbf_path("Flowchart.msbf")
|
|
31
44
|
```
|
|
32
45
|
|
|
46
|
+
|
|
33
47
|
## Writing
|
|
48
|
+
There is no writing for `MSBP` files as most of the time it acts as leftover metadata and not actually loaded by games.
|
|
34
49
|
|
|
50
|
+
### MSBT
|
|
35
51
|
```py
|
|
36
52
|
from lms.message.msbtio import write_msbt_path
|
|
53
|
+
|
|
54
|
+
write_msbt_path("Out_text.msbt")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### MSBF
|
|
58
|
+
```py
|
|
37
59
|
from lms.flowchart.msbfio import write_msbf_path
|
|
38
60
|
|
|
39
|
-
|
|
40
|
-
write_msbf_path("Out_Game_Flowchart.msbt")
|
|
61
|
+
write_msbf_path("Out_Flowchart.msbf")
|
|
41
62
|
```
|
|
42
63
|
|
|
43
64
|
# Adding/Editing Presets
|
|
@@ -8,7 +8,8 @@ from lms.fileio.io import FileReader, FileWriter
|
|
|
8
8
|
from lms.titleconfig.definitions.value import ValueDefinition
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def read_field(reader: FileReader, definition: ValueDefinition
|
|
11
|
+
def read_field(reader: FileReader, definition: ValueDefinition,
|
|
12
|
+
read_variable_width_value: Callable | None = None) -> LMS_Field:
|
|
12
13
|
# String is excluded as their reading varies between MSBT/MSBF
|
|
13
14
|
match definition.datatype:
|
|
14
15
|
case LMS_DataType.UINT8:
|
|
@@ -26,17 +27,23 @@ def read_field(reader: FileReader, definition: ValueDefinition) -> LMS_Field:
|
|
|
26
27
|
case LMS_DataType.FLOAT32:
|
|
27
28
|
value = reader.read_float32()
|
|
28
29
|
case LMS_DataType.LIST:
|
|
29
|
-
|
|
30
|
+
if read_variable_width_value is not None:
|
|
31
|
+
index = read_variable_width_value()
|
|
32
|
+
else:
|
|
33
|
+
index = reader.read_uint8()
|
|
30
34
|
value = definition.list_items[index]
|
|
31
35
|
case LMS_DataType.BOOL:
|
|
32
|
-
|
|
36
|
+
if read_variable_width_value is not None:
|
|
37
|
+
value = bool(read_variable_width_value())
|
|
38
|
+
else:
|
|
39
|
+
value = bool(reader.read_uint8())
|
|
33
40
|
case LMS_DataType.BYTES:
|
|
34
41
|
value = reader.read_bytes(1)
|
|
35
42
|
|
|
36
43
|
return LMS_Field(value, definition) # type: ignore
|
|
37
44
|
|
|
38
45
|
|
|
39
|
-
def write_field(writer: FileWriter, field: LMS_Field) -> None:
|
|
46
|
+
def write_field(writer: FileWriter, field: LMS_Field, write_variable_width_value: Callable | None = None) -> None:
|
|
40
47
|
if is_number_datatype(field.value, field.datatype):
|
|
41
48
|
write_functions: dict[LMS_DataType, Callable] = {
|
|
42
49
|
LMS_DataType.UINT8: writer.write_uint8,
|
|
@@ -51,10 +58,17 @@ def write_field(writer: FileWriter, field: LMS_Field) -> None:
|
|
|
51
58
|
return
|
|
52
59
|
|
|
53
60
|
if is_list_datatype(field.value, field.datatype):
|
|
54
|
-
|
|
61
|
+
value = field.list_items.index(field.value)
|
|
62
|
+
if write_variable_width_value is not None:
|
|
63
|
+
write_variable_width_value(value)
|
|
64
|
+
else:
|
|
65
|
+
writer.write_uint8(value)
|
|
55
66
|
elif is_bytes_datatype(field.value, field.datatype):
|
|
56
67
|
writer.write_bytes(field.value)
|
|
57
68
|
elif is_bool_datatype(field.value, field.datatype):
|
|
58
|
-
|
|
69
|
+
if write_variable_width_value is not None:
|
|
70
|
+
write_variable_width_value(bool(field.value))
|
|
71
|
+
else:
|
|
72
|
+
writer.write_uint8(bool(field.value))
|
|
59
73
|
else:
|
|
60
74
|
raise ValueError(f"Unsupported datatype: {field.datatype}")
|
|
@@ -27,15 +27,14 @@ class LMS_BaseNode:
|
|
|
27
27
|
id: int | None,
|
|
28
28
|
node_type: LMS_NodeType,
|
|
29
29
|
parameter_type: LMS_NodeParameterType,
|
|
30
|
-
parameter_value: LMS_NodeParameter | None
|
|
31
|
-
stream_next_id: int | None = None):
|
|
30
|
+
parameter_value: LMS_NodeParameter | None):
|
|
32
31
|
self.id = id
|
|
33
32
|
self._node_type = node_type
|
|
34
33
|
|
|
35
34
|
self._parameter_type = parameter_type
|
|
36
35
|
self._parameter_value = parameter_value
|
|
37
36
|
|
|
38
|
-
self._stream_next_id: int | None =
|
|
37
|
+
self._stream_next_id: int | None = None
|
|
39
38
|
self._next_node: LMS_BaseNode | None = None
|
|
40
39
|
|
|
41
40
|
@property
|
|
@@ -76,14 +75,6 @@ class LMS_BaseNode:
|
|
|
76
75
|
if not isinstance(value, self._parameter_type.builtin_type):
|
|
77
76
|
raise TypeError(f"Wrong value provided! Expected {self._parameter_type.builtin_type} got {type(value)}")
|
|
78
77
|
|
|
79
|
-
sliced_datatype = {
|
|
80
|
-
LMS_NodeParameterType.PARAM_16_16: (LMS_DataType.UINT16, LMS_DataType.UINT16),
|
|
81
|
-
LMS_NodeParameterType.PARAM_16_8_8: (LMS_DataType.UINT16, LMS_DataType.UINT8, LMS_DataType.UINT8),
|
|
82
|
-
LMS_NodeParameterType.PARAM_8_8_16: (LMS_DataType.UINT8, LMS_DataType.UINT8, LMS_DataType.UINT16),
|
|
83
|
-
LMS_NodeParameterType.PARAM_8_8_8_8: (LMS_DataType.UINT8, LMS_DataType.UINT8, LMS_DataType.UINT8,
|
|
84
|
-
LMS_DataType.UINT8)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
78
|
match self._parameter_type:
|
|
88
79
|
case LMS_NodeParameterType.STRING:
|
|
89
80
|
self._parameter_value = value
|
|
@@ -92,7 +83,8 @@ class LMS_BaseNode:
|
|
|
92
83
|
self._parameter_value = value
|
|
93
84
|
case _:
|
|
94
85
|
for i, parameter in enumerate(value):
|
|
95
|
-
verify_number_from_datatype(parameter,
|
|
86
|
+
verify_number_from_datatype(parameter,
|
|
87
|
+
self._parameter_type.sliced_datatype[self._parameter_type][i])
|
|
96
88
|
|
|
97
89
|
self._parameter_value = value
|
|
98
90
|
|
|
@@ -100,8 +92,6 @@ class LMS_BaseNode:
|
|
|
100
92
|
"""Set the next node for the instance.
|
|
101
93
|
|
|
102
94
|
:param node: the node object, None for no next node."""
|
|
103
|
-
# Once next_node is properly set, stream_id is never accessed again
|
|
104
|
-
# While it does become stale, there is no point to altering it afterward
|
|
105
95
|
self._next_node = node
|
|
106
96
|
return node
|
|
107
97
|
|
|
@@ -143,7 +133,6 @@ class LMS_MessageNode(LMS_BaseNode):
|
|
|
143
133
|
|
|
144
134
|
def __init__(self,
|
|
145
135
|
id: int | None,
|
|
146
|
-
stream_next_node: int,
|
|
147
136
|
msbt_index: int,
|
|
148
137
|
label_index: int,
|
|
149
138
|
msbt: MSBT | None = None
|
|
@@ -153,7 +142,6 @@ class LMS_MessageNode(LMS_BaseNode):
|
|
|
153
142
|
LMS_NodeType.MESSAGE,
|
|
154
143
|
LMS_NodeParameterType.NONE,
|
|
155
144
|
None,
|
|
156
|
-
stream_next_node
|
|
157
145
|
)
|
|
158
146
|
|
|
159
147
|
self.msbt_index = msbt_index
|
|
@@ -211,13 +199,20 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
211
199
|
LMS_NodeType.BRANCH,
|
|
212
200
|
parameter_type,
|
|
213
201
|
parameter_value,
|
|
214
|
-
NO_NEXT_NODE
|
|
215
202
|
)
|
|
216
203
|
|
|
217
204
|
self._condition_id = condition_id
|
|
218
205
|
self._branches: dict[int, LMS_BaseNode | None] = {}
|
|
219
206
|
self._definition = definition
|
|
220
207
|
|
|
208
|
+
def __repr__(self):
|
|
209
|
+
if self._definition is None:
|
|
210
|
+
return super().__repr__()
|
|
211
|
+
return f"{self._definition.name}({ {field.name: field.value for field in self._parameter_value} } {self.id}"
|
|
212
|
+
|
|
213
|
+
def set_next_node(self, node: LMS_BaseNode | None) -> LMS_BaseNode | None:
|
|
214
|
+
raise NotImplementedError("Use function set_branch_case to alter the flow of a branch node!")
|
|
215
|
+
|
|
221
216
|
@classmethod
|
|
222
217
|
def new(cls,
|
|
223
218
|
parameter_type: LMS_NodeParameterType,
|
|
@@ -345,19 +340,22 @@ class LMS_EventNode(LMS_BaseNode):
|
|
|
345
340
|
parameter_type: LMS_NodeParameterType,
|
|
346
341
|
parameter_value: LMS_FieldMap | int | str | tuple[int, ...],
|
|
347
342
|
action_id: int,
|
|
348
|
-
stream_next_id: int | None = None,
|
|
349
343
|
definition: NodeDefinition | None = None,
|
|
350
344
|
):
|
|
351
345
|
super().__init__(
|
|
352
346
|
id,
|
|
353
347
|
LMS_NodeType.EVENT,
|
|
354
348
|
parameter_type,
|
|
355
|
-
parameter_value
|
|
356
|
-
stream_next_id)
|
|
349
|
+
parameter_value)
|
|
357
350
|
|
|
358
351
|
self._action_id = action_id
|
|
359
352
|
self._definition = definition
|
|
360
353
|
|
|
354
|
+
def __repr__(self):
|
|
355
|
+
if self._definition is None:
|
|
356
|
+
return super().__repr__()
|
|
357
|
+
return f"{self._definition.name}({ {field.name: field.value for field in self._parameter_value} } {self.id}"
|
|
358
|
+
|
|
361
359
|
@classmethod
|
|
362
360
|
def new(cls, parameter_type: LMS_NodeParameterType, parameter_value: int | str | tuple[int, ...], action_id: int):
|
|
363
361
|
"""
|
|
@@ -409,13 +407,12 @@ class LMS_EntryNode(LMS_BaseNode):
|
|
|
409
407
|
Class that represents an entry node. Node that is at the start of a flowchart.
|
|
410
408
|
"""
|
|
411
409
|
|
|
412
|
-
def __init__(self, id: int,
|
|
410
|
+
def __init__(self, id: int, flowchart_name: str = ""):
|
|
413
411
|
super().__init__(
|
|
414
412
|
id,
|
|
415
413
|
LMS_NodeType.ENTRY,
|
|
416
414
|
LMS_NodeParameterType.NONE,
|
|
417
|
-
None
|
|
418
|
-
stream_next_id)
|
|
415
|
+
None)
|
|
419
416
|
|
|
420
417
|
self.flowchart_name = flowchart_name
|
|
421
418
|
|
|
@@ -425,13 +422,12 @@ class LMS_JumpNode(LMS_BaseNode):
|
|
|
425
422
|
Class that represents a jump node. Node that jumps to another flowchart.
|
|
426
423
|
"""
|
|
427
424
|
|
|
428
|
-
def __init__(self, id: int,
|
|
425
|
+
def __init__(self, id: int, unknown_short0a: int):
|
|
429
426
|
super().__init__(
|
|
430
427
|
id,
|
|
431
428
|
LMS_NodeType.JUMP,
|
|
432
429
|
LMS_NodeParameterType.NONE,
|
|
433
|
-
None
|
|
434
|
-
next_flowchart_id)
|
|
430
|
+
None)
|
|
435
431
|
|
|
436
432
|
self.next_flowchart: LMS_EntryNode | None = None
|
|
437
433
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from typing import Type
|
|
2
1
|
from enum import IntEnum
|
|
2
|
+
from typing import Type
|
|
3
3
|
|
|
4
|
-
from lms.common.
|
|
4
|
+
from lms.common.field.lms_datatype import LMS_DataType
|
|
5
5
|
|
|
6
6
|
PARAMETER_ALIASES = {
|
|
7
7
|
"p32_0": "PARAM_32_0",
|
|
@@ -53,8 +53,27 @@ class LMS_NodeParameterType(IntEnum):
|
|
|
53
53
|
PARAM_32_1 = 6
|
|
54
54
|
NONE = -1
|
|
55
55
|
|
|
56
|
+
@property
|
|
57
|
+
def sliced_datatype(self) -> tuple[LMS_DataType, ...]:
|
|
58
|
+
"""The parameter type split into the raw datatypes. Not valid for STRING."""
|
|
59
|
+
if self in [
|
|
60
|
+
LMS_NodeParameterType.NONE,
|
|
61
|
+
LMS_NodeParameterType.STRING]:
|
|
62
|
+
raise TypeError(f"There is no sliced datatypes for {self} parameter types!")
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
LMS_NodeParameterType.PARAM_32_0: (LMS_DataType.UINT32,),
|
|
66
|
+
LMS_NodeParameterType.PARAM_16_16: (LMS_DataType.UINT16, LMS_DataType.UINT16),
|
|
67
|
+
LMS_NodeParameterType.PARAM_16_8_8: (LMS_DataType.UINT16, LMS_DataType.UINT8, LMS_DataType.UINT8),
|
|
68
|
+
LMS_NodeParameterType.PARAM_8_8_16: (LMS_DataType.UINT8, LMS_DataType.UINT8, LMS_DataType.UINT16),
|
|
69
|
+
LMS_NodeParameterType.PARAM_8_8_8_8: (LMS_DataType.UINT8, LMS_DataType.UINT8, LMS_DataType.UINT8,
|
|
70
|
+
LMS_DataType.UINT8),
|
|
71
|
+
LMS_NodeParameterType.PARAM_32_1: (LMS_DataType.UINT32,)
|
|
72
|
+
}[self]
|
|
73
|
+
|
|
56
74
|
@property
|
|
57
75
|
def value_count(self) -> int:
|
|
76
|
+
"""Amount of values the parameter holds."""
|
|
58
77
|
if self is LMS_NodeParameterType.NONE:
|
|
59
78
|
raise TypeError("There is no value count for NONE parameter types!")
|
|
60
79
|
|
|
@@ -70,6 +89,7 @@ class LMS_NodeParameterType(IntEnum):
|
|
|
70
89
|
|
|
71
90
|
@property
|
|
72
91
|
def builtin_type(self) -> Type[int] | Type[tuple[int, ...]] | Type[str]:
|
|
92
|
+
"""The builtin type of the parameter type."""
|
|
73
93
|
if self is LMS_NodeParameterType.NONE:
|
|
74
94
|
raise TypeError("There is no builtin type for NONE parameter types!")
|
|
75
95
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from lms.common.field.io import read_field, write_field
|
|
2
|
+
from lms.common.field.lms_datatype import LMS_DataType
|
|
2
3
|
from lms.common.field.lms_field import LMS_FieldMap, LMS_Field
|
|
3
4
|
from lms.common.lms_exceptions import LMS_Error
|
|
4
5
|
from lms.fileio.io import FileReader, FileWriter
|
|
@@ -13,6 +14,8 @@ NO_NEXT_NODE = 0xFFFF
|
|
|
13
14
|
FLW3_HEADER_SIZE = 16
|
|
14
15
|
NODE_SIZE = 16
|
|
15
16
|
|
|
17
|
+
VARIABLE_WIDTH_DATATYPES = (LMS_DataType.BOOL, LMS_DataType.LIST)
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
def read_flw3(reader: FileReader, config: NodeConfig | None, msbt: MSBT | None, ) -> tuple[
|
|
18
21
|
int, dict[int, LMS_EntryNode]]:
|
|
@@ -25,6 +28,8 @@ def read_flw3(reader: FileReader, config: NodeConfig | None, msbt: MSBT | None,
|
|
|
25
28
|
|
|
26
29
|
nodes: list[LMS_BaseNode] = []
|
|
27
30
|
entry_nodes: list[LMS_EntryNode] = []
|
|
31
|
+
|
|
32
|
+
next_id_map: dict[LMS_BaseNode, int | None] = {}
|
|
28
33
|
branch_metadata: dict[LMS_BranchNode, dict] = {}
|
|
29
34
|
|
|
30
35
|
for i in range(node_count):
|
|
@@ -42,7 +47,7 @@ def read_flw3(reader: FileReader, config: NodeConfig | None, msbt: MSBT | None,
|
|
|
42
47
|
stream_next_id = read_next_node_id(reader)
|
|
43
48
|
file_index = reader.read_uint16()
|
|
44
49
|
message_index = reader.read_uint16()
|
|
45
|
-
node = LMS_MessageNode(i,
|
|
50
|
+
node = LMS_MessageNode(i, file_index, message_index, msbt)
|
|
46
51
|
reader.skip(2)
|
|
47
52
|
case LMS_NodeType.BRANCH:
|
|
48
53
|
# There is no next node id as the next node is determined by the branches
|
|
@@ -75,27 +80,29 @@ def read_flw3(reader: FileReader, config: NodeConfig | None, msbt: MSBT | None,
|
|
|
75
80
|
definition = get_config_definition(config, LMS_NodeType.EVENT, parameter_type, event_id)
|
|
76
81
|
parameter_value = evaluate_node_parameter(reader, section_start, parameter_type, definition)
|
|
77
82
|
|
|
78
|
-
node = LMS_EventNode(i, parameter_type, parameter_value, event_id,
|
|
83
|
+
node = LMS_EventNode(i, parameter_type, parameter_value, event_id, definition)
|
|
79
84
|
reader.seek(end)
|
|
80
85
|
case LMS_NodeType.ENTRY:
|
|
81
86
|
reader.seek(node_data)
|
|
82
87
|
stream_next_id = read_next_node_id(reader)
|
|
83
|
-
node = LMS_EntryNode(i
|
|
88
|
+
node = LMS_EntryNode(i)
|
|
84
89
|
entry_nodes.append(node)
|
|
85
90
|
reader.skip(6)
|
|
86
91
|
case LMS_NodeType.JUMP:
|
|
87
92
|
reader.seek(node_data)
|
|
88
|
-
|
|
93
|
+
stream_next_id = read_next_node_id(reader)
|
|
89
94
|
unknown_short0a = reader.read_uint16()
|
|
90
|
-
node = LMS_JumpNode(i,
|
|
95
|
+
node = LMS_JumpNode(i, unknown_short0a)
|
|
91
96
|
reader.skip(4)
|
|
92
97
|
|
|
98
|
+
next_id_map[node] = stream_next_id
|
|
93
99
|
nodes.append(node)
|
|
94
100
|
|
|
95
101
|
node_map: dict[int, LMS_BaseNode] = {node.id: node for node in nodes}
|
|
96
102
|
branch_ids = [read_next_node_id(reader) for _ in range(branch_table_id_count)]
|
|
97
103
|
|
|
98
104
|
for node in nodes:
|
|
105
|
+
next_node_id = next_id_map[node]
|
|
99
106
|
|
|
100
107
|
if isinstance(node, LMS_BranchNode):
|
|
101
108
|
case_count, starting_index = branch_metadata[node]
|
|
@@ -109,13 +116,13 @@ def read_flw3(reader: FileReader, config: NodeConfig | None, msbt: MSBT | None,
|
|
|
109
116
|
|
|
110
117
|
continue
|
|
111
118
|
elif isinstance(node, LMS_JumpNode):
|
|
112
|
-
node.next_flowchart = node_map[
|
|
119
|
+
node.next_flowchart = node_map[next_node_id]
|
|
113
120
|
continue
|
|
114
121
|
|
|
115
|
-
if
|
|
122
|
+
if next_node_id is None:
|
|
116
123
|
continue
|
|
117
124
|
|
|
118
|
-
node.set_next_node(node_map[
|
|
125
|
+
node.set_next_node(node_map[next_node_id])
|
|
119
126
|
|
|
120
127
|
return node_count, entry_nodes
|
|
121
128
|
|
|
@@ -155,8 +162,10 @@ def evaluate_node_parameter(reader: FileReader,
|
|
|
155
162
|
param_definition.name: LMS_Field(value, param_definition)
|
|
156
163
|
})
|
|
157
164
|
|
|
158
|
-
for param_definition in definition.parameter_definitions:
|
|
159
|
-
|
|
165
|
+
for i, param_definition in enumerate(definition.parameter_definitions):
|
|
166
|
+
stream_map = {1: reader.read_uint8, 2: reader.read_uint16, 4: reader.read_uint32}
|
|
167
|
+
result[param_definition.name] = read_field(reader, param_definition,
|
|
168
|
+
stream_map[parameter_type.sliced_datatype[i].stream_size])
|
|
160
169
|
|
|
161
170
|
return LMS_FieldMap(result)
|
|
162
171
|
else:
|
|
@@ -297,8 +306,13 @@ def write_node_parameter(writer: FileWriter,
|
|
|
297
306
|
value: LMS_NodeParameter,
|
|
298
307
|
parameter_type: LMS_NodeParameterType) -> None:
|
|
299
308
|
if isinstance(value, LMS_FieldMap):
|
|
300
|
-
for field in value:
|
|
301
|
-
|
|
309
|
+
for i, field in enumerate(value):
|
|
310
|
+
# In MSBF files there could be a case where naturally one bit datatypes take up 2/4 bits.
|
|
311
|
+
if field.datatype in VARIABLE_WIDTH_DATATYPES:
|
|
312
|
+
stream_map = {1: writer.write_uint8, 2: writer.write_uint16, 3: writer.write_uint32}
|
|
313
|
+
write_field(writer, field, stream_map[parameter_type.sliced_datatype[i].stream_size])
|
|
314
|
+
else:
|
|
315
|
+
write_field(writer, field)
|
|
302
316
|
return
|
|
303
317
|
|
|
304
318
|
match parameter_type:
|
|
@@ -63,17 +63,13 @@ class MSBF:
|
|
|
63
63
|
"""The flowcharts of the MSBF instance."""
|
|
64
64
|
return MappingProxyType({flowchart.name: flowchart for flowchart in self._flowcharts})
|
|
65
65
|
|
|
66
|
-
@property
|
|
67
|
-
def global_node_id(self) -> int:
|
|
68
|
-
"""Current node ID count."""
|
|
69
|
-
return self._global_node_id
|
|
70
|
-
|
|
71
66
|
def set_global_id_count(self, value: int) -> None:
|
|
72
|
-
if value < self._global_node_id:
|
|
73
|
-
raise ValueError("Cannot decrease global node ID below existing count.")
|
|
74
67
|
self._global_node_id = value
|
|
75
68
|
|
|
76
69
|
def generate_next_id(self) -> int:
|
|
70
|
+
"""
|
|
71
|
+
Lazy generation for the next ID of a node. IDs are normalized properly when the msbf file is being written.
|
|
72
|
+
"""
|
|
77
73
|
next_id = self._global_node_id
|
|
78
74
|
self._global_node_id += 1
|
|
79
75
|
return next_id
|
|
@@ -97,7 +93,7 @@ class MSBF:
|
|
|
97
93
|
def delete_flowchart(self, name: str):
|
|
98
94
|
"""Delete a flowchart from the MSBF instance."""
|
|
99
95
|
|
|
100
|
-
if name not in self.
|
|
96
|
+
if name not in self.flowcharts:
|
|
101
97
|
raise KeyError(f"Flowchart with name {name} does not exist!")
|
|
102
98
|
|
|
103
99
|
del self._flowcharts[name]
|
|
@@ -55,11 +55,13 @@ class NodeDefinition:
|
|
|
55
55
|
type: LMS_NodeType.BRANCH | LMS_NodeType.EVENT
|
|
56
56
|
parameter_type: LMS_NodeParameterType
|
|
57
57
|
parameter_definitions: tuple[ValueDefinition, ...]
|
|
58
|
+
case_format: str | None
|
|
58
59
|
next_node_dependency: bool = False
|
|
59
60
|
|
|
60
61
|
@classmethod
|
|
61
62
|
def from_dict(cls, id: int, node_type: LMS_NodeType, data: dict) -> NodeDefinition:
|
|
62
63
|
|
|
64
|
+
description = data.get("description", "")
|
|
63
65
|
converted_parameters: list[ValueDefinition] = []
|
|
64
66
|
parameter_type = LMS_NodeParameterType.from_string(data["parameter_type"])
|
|
65
67
|
|
|
@@ -83,15 +85,19 @@ class NodeDefinition:
|
|
|
83
85
|
(LMS_DataType.UINT8, LMS_DataType.UINT8, LMS_DataType.UINT8, LMS_DataType.UINT8)[i]
|
|
84
86
|
case LMS_NodeParameterType.STRING:
|
|
85
87
|
datatype_from_dict = LMS_DataType.STRING
|
|
86
|
-
definition = ValueDefinition(parameter["name"],
|
|
88
|
+
definition = ValueDefinition(parameter["name"], description, datatype_from_dict,
|
|
87
89
|
list_items)
|
|
88
90
|
else:
|
|
89
|
-
definition = ValueDefinition(parameter["name"],
|
|
91
|
+
definition = ValueDefinition(parameter["name"], description,
|
|
90
92
|
LMS_DataType.from_string(datatype_from_dict),
|
|
91
93
|
list_items)
|
|
92
94
|
|
|
93
95
|
converted_parameters.append(definition)
|
|
94
96
|
|
|
97
|
+
# case_format is only for branch nodesa
|
|
98
|
+
if (case_format := data.get("case_format", None)) is not None and type == LMS_NodeType.EVENT:
|
|
99
|
+
raise ValueError("There may only be case_format for branch nodes!")
|
|
100
|
+
|
|
95
101
|
return NodeDefinition(
|
|
96
102
|
name=data["name"],
|
|
97
103
|
id=id,
|
|
@@ -99,5 +105,6 @@ class NodeDefinition:
|
|
|
99
105
|
type=node_type,
|
|
100
106
|
parameter_type=parameter_type,
|
|
101
107
|
parameter_definitions=converted_parameters,
|
|
108
|
+
case_format=case_format,
|
|
102
109
|
next_node_dependency=data.get("next_node_dependency", False)
|
|
103
110
|
)
|
|
@@ -8,6 +8,7 @@ node_definitions:
|
|
|
8
8
|
- name: exp
|
|
9
9
|
- id: 1
|
|
10
10
|
name: SwitchVariable
|
|
11
|
+
case_format: "var == $case"
|
|
11
12
|
parameter_type: string
|
|
12
13
|
parameters:
|
|
13
14
|
- name: var
|
|
@@ -28,6 +29,11 @@ node_definitions:
|
|
|
28
29
|
parameter_type: string
|
|
29
30
|
parameters:
|
|
30
31
|
- name: var
|
|
32
|
+
- id: 82
|
|
33
|
+
name: SetFlag
|
|
34
|
+
parameter_type: p32_1
|
|
35
|
+
parameters:
|
|
36
|
+
- name: flag
|
|
31
37
|
- id: 90
|
|
32
38
|
name: LoadSEGroup
|
|
33
39
|
parameter_type: string
|
|
@@ -7,7 +7,7 @@ include = ["lms*"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "PyLibMS"
|
|
10
|
-
version = "4.1
|
|
10
|
+
version = "4.2.1"
|
|
11
11
|
requires-python = ">=3.10"
|
|
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"
|
pylibms-4.1.7/MANIFEST.in
DELETED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Brain Age Concentration Training.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Super Mario 3D World + Bowsers Fury.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Tomodachi Life Living The Dream.yaml
RENAMED
|
File without changes
|
|
File without changes
|