PyLibMS 3.3.9__tar.gz → 4.0.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.0.1/PKG-INFO +94 -0
- pylibms-4.0.1/PyLibMS.egg-info/PKG-INFO +94 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/PyLibMS.egg-info/SOURCES.txt +18 -7
- pylibms-4.0.1/README.md +79 -0
- {pylibms-3.3.9/lms/message/definitions → pylibms-4.0.1/lms/common}/field/io.py +5 -5
- {pylibms-3.3.9/lms/common → pylibms-4.0.1/lms/common/field}/lms_datatype.py +12 -13
- {pylibms-3.3.9/lms/message/definitions → pylibms-4.0.1/lms/common}/field/lms_field.py +25 -24
- pylibms-4.0.1/lms/common/lms_constants.py +9 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/common/lms_exceptions.py +3 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/common/stream/fileinfo.py +10 -8
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/common/stream/hashtable.py +19 -10
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/common/stream/section.py +5 -2
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/fileio/io.py +10 -0
- pylibms-4.0.1/lms/flowchart/definitions/flowchart.py +103 -0
- pylibms-4.0.1/lms/flowchart/definitions/lms_nodeexceptions.py +7 -0
- pylibms-4.0.1/lms/flowchart/definitions/node.py +455 -0
- pylibms-4.0.1/lms/flowchart/definitions/node_type.py +97 -0
- pylibms-4.0.1/lms/flowchart/flw3.py +326 -0
- pylibms-4.0.1/lms/flowchart/msbf.py +86 -0
- pylibms-4.0.1/lms/flowchart/msbfio.py +138 -0
- {pylibms-3.3.9/lms/message/definitions → pylibms-4.0.1/lms/message}/lms_messagetext.py +1 -1
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/message/msbtentry.py +2 -2
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/message/msbtio.py +8 -8
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/message/section/atr1.py +3 -3
- pylibms-4.0.1/lms/message/section/tsy1.py +13 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/message/section/txt2.py +1 -1
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/message/tag/io/param_io.py +3 -3
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/message/tag/io/tag_io.py +1 -1
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/message/tag/lms_tag.py +1 -1
- pylibms-4.0.1/lms/project/definitions/__init__.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/definitions/attribute.py +1 -1
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/definitions/tag.py +11 -11
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/msbpread.py +1 -1
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/section/ati2.py +1 -1
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/section/tag2.py +2 -2
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/section/tgp2.py +1 -1
- pylibms-4.0.1/lms/titleconfig/__init__.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/config.py +62 -4
- pylibms-4.0.1/lms/titleconfig/definitions/__init__.py +0 -0
- pylibms-4.0.1/lms/titleconfig/definitions/nodes.py +101 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/definitions/tags.py +3 -3
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/definitions/value.py +2 -2
- pylibms-4.0.1/lms/titleconfig/presets/Tomodachi Life Living The Dream.yaml +3369 -0
- pylibms-3.3.9/lms/titleconfig/presets/Tomodachi Life.yaml → pylibms-4.0.1/lms/titleconfig/presets/Tomodachi Life NA-EU.yaml +53 -1
- {pylibms-3.3.9 → pylibms-4.0.1}/pyproject.toml +8 -4
- pylibms-3.3.9/PKG-INFO +0 -77
- pylibms-3.3.9/PyLibMS.egg-info/PKG-INFO +0 -77
- pylibms-3.3.9/README.md +0 -56
- pylibms-3.3.9/lms/message/section/tsy1.py +0 -13
- pylibms-3.3.9/lms/titleconfig/presets/Tomodachi Life Living the Dream Demo.yaml +0 -3369
- {pylibms-3.3.9 → pylibms-4.0.1}/LICENSE +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/MANIFEST.in +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/PyLibMS.egg-info/dependency_links.txt +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/PyLibMS.egg-info/requires.txt +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/PyLibMS.egg-info/top_level.txt +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/__init__.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/common/__init__.py +0 -0
- {pylibms-3.3.9/lms/message → pylibms-4.0.1/lms/common/field}/__init__.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/common/lms_fileinfo.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/fileio/encoding.py +0 -0
- {pylibms-3.3.9/lms/message/definitions → pylibms-4.0.1/lms/flowchart}/__init__.py +0 -0
- {pylibms-3.3.9/lms/message/definitions/field → pylibms-4.0.1/lms/flowchart/definitions}/__init__.py +0 -0
- {pylibms-3.3.9/lms/message/section → pylibms-4.0.1/lms/message}/__init__.py +0 -0
- {pylibms-3.3.9/lms/message/tag → pylibms-4.0.1/lms/message/definitions}/__init__.py +0 -0
- {pylibms-3.3.9/lms/project → pylibms-4.0.1/lms/message/definitions/field}/__init__.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/message/msbt.py +0 -0
- {pylibms-3.3.9/lms/project/definitions → pylibms-4.0.1/lms/message/section}/__init__.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/message/section/nli1.py +0 -0
- {pylibms-3.3.9/lms/titleconfig → pylibms-4.0.1/lms/message/tag}/__init__.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/message/tag/lms_tagexceptions.py +0 -0
- {pylibms-3.3.9/lms/titleconfig/definitions → pylibms-4.0.1/lms/project}/__init__.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/definitions/color.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/definitions/style.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/msbp.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/section/ali2.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/section/clr1.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/section/string.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/section/syl3.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/project/section/tgg2.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/definitions/attribute.py +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/presets/Badge Arcade.yaml +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/presets/Brain Age Concentration Training.yaml +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/presets/Kirby Planet Robobot.yaml +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/presets/Super Mario 3D Land.yaml +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/presets/Super Mario 3D World + Bowsers Fury.yaml +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/presets/Super Mario Odyssey.yaml +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/lms/titleconfig/presets/The Legend of Zelda a Link Between Worlds.yaml +0 -0
- {pylibms-3.3.9 → pylibms-4.0.1}/setup.cfg +0 -0
pylibms-4.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyLibMS
|
|
3
|
+
Version: 4.0.1
|
|
4
|
+
Summary: Python library built for the libMessageStudio (LMS) proprietary file formats from Nintendo. Supports MSBT, MSBP, and MSBF.
|
|
5
|
+
Author: AbdyyEee
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Documentation, https://github.com/AbdyyEee/PylibMS/wiki
|
|
8
|
+
Project-URL: Repository, https://github.com/AbdyyEee/PylibMS
|
|
9
|
+
Keywords: nintendo,msbf,msbt,msbp,modding,lms,libmessagestudio,flw3
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: PyYAML>=6.0.1
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# PylibMS
|
|
17
|
+
|
|
18
|
+
PylibMS is a library built in Python 3.12+ for the libMessageStudio (LMS) proprietary file formats (`msbt`, `msbp`,
|
|
19
|
+
`msbf`) from Nintendo. It supports the following:
|
|
20
|
+
|
|
21
|
+
| File Format | Read | Write | Configuration Support |
|
|
22
|
+
|-------------|------|-------|-------------------------------------------------------------------------------------------|
|
|
23
|
+
| MSBT | ✅ | ✅ | Complete attribute decoding + tag decoding down to official `mstxt` Nintendo tool syntax. | |
|
|
24
|
+
| MSBF | ✅ | ✅ | Flexible node + parameter decoding through configuration definitions.
|
|
25
|
+
| MSBP | ✅ | ❌ | N/A |
|
|
26
|
+
|
|
27
|
+
This library is designed to support LMS revision 3.0 and above, with the associated file formats used across the
|
|
28
|
+
following Nintendo platforms:
|
|
29
|
+
|
|
30
|
+
* Wii (Specific titles only)
|
|
31
|
+
* Nintendo 3DS
|
|
32
|
+
* Wii U
|
|
33
|
+
* Mobile (Specific titles only)
|
|
34
|
+
* Nintendo Switch
|
|
35
|
+
|
|
36
|
+
# Features and Usage
|
|
37
|
+
|
|
38
|
+
Simple preview of the library is below. See [the wiki](https://github.com/AbdyyEee/PylibMS/wiki) for more explanations
|
|
39
|
+
and examples.
|
|
40
|
+
|
|
41
|
+
## Reading
|
|
42
|
+
|
|
43
|
+
MSBT/MSBF
|
|
44
|
+
|
|
45
|
+
```py
|
|
46
|
+
from lms.message.msbtio import read_msbt_path
|
|
47
|
+
from lms.message.msbfio import read_msbf_path
|
|
48
|
+
|
|
49
|
+
msbt = read_msbt_path("Game.msbt")
|
|
50
|
+
msbf = read_msbf_path("Game_Flowchart.msbf")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Writing
|
|
54
|
+
|
|
55
|
+
```py
|
|
56
|
+
from lms.message.msbtio import write_msbt_path
|
|
57
|
+
from lms.message.msbfio import write_msbf_path
|
|
58
|
+
|
|
59
|
+
write_msbt_path("Out_Game.msbt")
|
|
60
|
+
write_msbf_path("Out_Game_Flowchart.msbt")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
# Adding/Editing Presets
|
|
64
|
+
|
|
65
|
+
To add or edit Preset, you may create an issue with the relevant `yaml` file and the game it is for.
|
|
66
|
+
|
|
67
|
+
# Installation
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
pip install PylibMS
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
[Pip Page](https://pypi.org/project/PyLibMS/)
|
|
74
|
+
|
|
75
|
+
# Build Instructions
|
|
76
|
+
|
|
77
|
+
Python version must be `>=3.12.`
|
|
78
|
+
|
|
79
|
+
Clone the repository, then run `pip install` (venv recommended)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/AbdyyEee/PylibMS.git
|
|
83
|
+
cd PylibMS
|
|
84
|
+
pip install -e
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
# Credits & Sources
|
|
88
|
+
|
|
89
|
+
* [Nintendo-File-Formats](https://nintendo-formats.com) by Kinnay: For existing information on the MSBT and MSBP file
|
|
90
|
+
formats.
|
|
91
|
+
* [Trippixyz](https://github.com/Trippixyz): For helping me get started general decompilation of the formats and general
|
|
92
|
+
help.
|
|
93
|
+
* [AeonSake](https://github.com/AeonSake): Inspiration for some the implementation of the library and a bit of general
|
|
94
|
+
help.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyLibMS
|
|
3
|
+
Version: 4.0.1
|
|
4
|
+
Summary: Python library built for the libMessageStudio (LMS) proprietary file formats from Nintendo. Supports MSBT, MSBP, and MSBF.
|
|
5
|
+
Author: AbdyyEee
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Documentation, https://github.com/AbdyyEee/PylibMS/wiki
|
|
8
|
+
Project-URL: Repository, https://github.com/AbdyyEee/PylibMS
|
|
9
|
+
Keywords: nintendo,msbf,msbt,msbp,modding,lms,libmessagestudio,flw3
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: PyYAML>=6.0.1
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# PylibMS
|
|
17
|
+
|
|
18
|
+
PylibMS is a library built in Python 3.12+ for the libMessageStudio (LMS) proprietary file formats (`msbt`, `msbp`,
|
|
19
|
+
`msbf`) from Nintendo. It supports the following:
|
|
20
|
+
|
|
21
|
+
| File Format | Read | Write | Configuration Support |
|
|
22
|
+
|-------------|------|-------|-------------------------------------------------------------------------------------------|
|
|
23
|
+
| MSBT | ✅ | ✅ | Complete attribute decoding + tag decoding down to official `mstxt` Nintendo tool syntax. | |
|
|
24
|
+
| MSBF | ✅ | ✅ | Flexible node + parameter decoding through configuration definitions.
|
|
25
|
+
| MSBP | ✅ | ❌ | N/A |
|
|
26
|
+
|
|
27
|
+
This library is designed to support LMS revision 3.0 and above, with the associated file formats used across the
|
|
28
|
+
following Nintendo platforms:
|
|
29
|
+
|
|
30
|
+
* Wii (Specific titles only)
|
|
31
|
+
* Nintendo 3DS
|
|
32
|
+
* Wii U
|
|
33
|
+
* Mobile (Specific titles only)
|
|
34
|
+
* Nintendo Switch
|
|
35
|
+
|
|
36
|
+
# Features and Usage
|
|
37
|
+
|
|
38
|
+
Simple preview of the library is below. See [the wiki](https://github.com/AbdyyEee/PylibMS/wiki) for more explanations
|
|
39
|
+
and examples.
|
|
40
|
+
|
|
41
|
+
## Reading
|
|
42
|
+
|
|
43
|
+
MSBT/MSBF
|
|
44
|
+
|
|
45
|
+
```py
|
|
46
|
+
from lms.message.msbtio import read_msbt_path
|
|
47
|
+
from lms.message.msbfio import read_msbf_path
|
|
48
|
+
|
|
49
|
+
msbt = read_msbt_path("Game.msbt")
|
|
50
|
+
msbf = read_msbf_path("Game_Flowchart.msbf")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Writing
|
|
54
|
+
|
|
55
|
+
```py
|
|
56
|
+
from lms.message.msbtio import write_msbt_path
|
|
57
|
+
from lms.message.msbfio import write_msbf_path
|
|
58
|
+
|
|
59
|
+
write_msbt_path("Out_Game.msbt")
|
|
60
|
+
write_msbf_path("Out_Game_Flowchart.msbt")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
# Adding/Editing Presets
|
|
64
|
+
|
|
65
|
+
To add or edit Preset, you may create an issue with the relevant `yaml` file and the game it is for.
|
|
66
|
+
|
|
67
|
+
# Installation
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
pip install PylibMS
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
[Pip Page](https://pypi.org/project/PyLibMS/)
|
|
74
|
+
|
|
75
|
+
# Build Instructions
|
|
76
|
+
|
|
77
|
+
Python version must be `>=3.12.`
|
|
78
|
+
|
|
79
|
+
Clone the repository, then run `pip install` (venv recommended)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/AbdyyEee/PylibMS.git
|
|
83
|
+
cd PylibMS
|
|
84
|
+
pip install -e
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
# Credits & Sources
|
|
88
|
+
|
|
89
|
+
* [Nintendo-File-Formats](https://nintendo-formats.com) by Kinnay: For existing information on the MSBT and MSBP file
|
|
90
|
+
formats.
|
|
91
|
+
* [Trippixyz](https://github.com/Trippixyz): For helping me get started general decompilation of the formats and general
|
|
92
|
+
help.
|
|
93
|
+
* [AeonSake](https://github.com/AeonSake): Inspiration for some the implementation of the library and a bit of general
|
|
94
|
+
help.
|
|
@@ -9,23 +9,34 @@ PyLibMS.egg-info/requires.txt
|
|
|
9
9
|
PyLibMS.egg-info/top_level.txt
|
|
10
10
|
lms/__init__.py
|
|
11
11
|
lms/common/__init__.py
|
|
12
|
-
lms/common/
|
|
12
|
+
lms/common/lms_constants.py
|
|
13
13
|
lms/common/lms_exceptions.py
|
|
14
14
|
lms/common/lms_fileinfo.py
|
|
15
|
+
lms/common/field/__init__.py
|
|
16
|
+
lms/common/field/io.py
|
|
17
|
+
lms/common/field/lms_datatype.py
|
|
18
|
+
lms/common/field/lms_field.py
|
|
15
19
|
lms/common/stream/fileinfo.py
|
|
16
20
|
lms/common/stream/hashtable.py
|
|
17
21
|
lms/common/stream/section.py
|
|
18
22
|
lms/fileio/encoding.py
|
|
19
23
|
lms/fileio/io.py
|
|
24
|
+
lms/flowchart/__init__.py
|
|
25
|
+
lms/flowchart/flw3.py
|
|
26
|
+
lms/flowchart/msbf.py
|
|
27
|
+
lms/flowchart/msbfio.py
|
|
28
|
+
lms/flowchart/definitions/__init__.py
|
|
29
|
+
lms/flowchart/definitions/flowchart.py
|
|
30
|
+
lms/flowchart/definitions/lms_nodeexceptions.py
|
|
31
|
+
lms/flowchart/definitions/node.py
|
|
32
|
+
lms/flowchart/definitions/node_type.py
|
|
20
33
|
lms/message/__init__.py
|
|
34
|
+
lms/message/lms_messagetext.py
|
|
21
35
|
lms/message/msbt.py
|
|
22
36
|
lms/message/msbtentry.py
|
|
23
37
|
lms/message/msbtio.py
|
|
24
38
|
lms/message/definitions/__init__.py
|
|
25
|
-
lms/message/definitions/lms_messagetext.py
|
|
26
39
|
lms/message/definitions/field/__init__.py
|
|
27
|
-
lms/message/definitions/field/io.py
|
|
28
|
-
lms/message/definitions/field/lms_field.py
|
|
29
40
|
lms/message/section/__init__.py
|
|
30
41
|
lms/message/section/atr1.py
|
|
31
42
|
lms/message/section/nli1.py
|
|
@@ -56,6 +67,7 @@ lms/titleconfig/__init__.py
|
|
|
56
67
|
lms/titleconfig/config.py
|
|
57
68
|
lms/titleconfig/definitions/__init__.py
|
|
58
69
|
lms/titleconfig/definitions/attribute.py
|
|
70
|
+
lms/titleconfig/definitions/nodes.py
|
|
59
71
|
lms/titleconfig/definitions/tags.py
|
|
60
72
|
lms/titleconfig/definitions/value.py
|
|
61
73
|
lms/titleconfig/presets/Badge Arcade.yaml
|
|
@@ -66,6 +78,5 @@ lms/titleconfig/presets/Super Mario 3D World + Bowsers Fury.yaml
|
|
|
66
78
|
lms/titleconfig/presets/Super Mario Odyssey.yaml
|
|
67
79
|
lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml
|
|
68
80
|
lms/titleconfig/presets/The Legend of Zelda a Link Between Worlds.yaml
|
|
69
|
-
lms/titleconfig/presets/Tomodachi Life Living The Dream
|
|
70
|
-
lms/titleconfig/presets/Tomodachi Life
|
|
71
|
-
lms/titleconfig/presets/Tomodachi Life.yaml
|
|
81
|
+
lms/titleconfig/presets/Tomodachi Life Living The Dream.yaml
|
|
82
|
+
lms/titleconfig/presets/Tomodachi Life NA-EU.yaml
|
pylibms-4.0.1/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# PylibMS
|
|
2
|
+
|
|
3
|
+
PylibMS is a library built in Python 3.12+ for the libMessageStudio (LMS) proprietary file formats (`msbt`, `msbp`,
|
|
4
|
+
`msbf`) from Nintendo. It supports the following:
|
|
5
|
+
|
|
6
|
+
| File Format | Read | Write | Configuration Support |
|
|
7
|
+
|-------------|------|-------|-------------------------------------------------------------------------------------------|
|
|
8
|
+
| MSBT | ✅ | ✅ | Complete attribute decoding + tag decoding down to official `mstxt` Nintendo tool syntax. | |
|
|
9
|
+
| MSBF | ✅ | ✅ | Flexible node + parameter decoding through configuration definitions.
|
|
10
|
+
| MSBP | ✅ | ❌ | N/A |
|
|
11
|
+
|
|
12
|
+
This library is designed to support LMS revision 3.0 and above, with the associated file formats used across the
|
|
13
|
+
following Nintendo platforms:
|
|
14
|
+
|
|
15
|
+
* Wii (Specific titles only)
|
|
16
|
+
* Nintendo 3DS
|
|
17
|
+
* Wii U
|
|
18
|
+
* Mobile (Specific titles only)
|
|
19
|
+
* Nintendo Switch
|
|
20
|
+
|
|
21
|
+
# Features and Usage
|
|
22
|
+
|
|
23
|
+
Simple preview of the library is below. See [the wiki](https://github.com/AbdyyEee/PylibMS/wiki) for more explanations
|
|
24
|
+
and examples.
|
|
25
|
+
|
|
26
|
+
## Reading
|
|
27
|
+
|
|
28
|
+
MSBT/MSBF
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
from lms.message.msbtio import read_msbt_path
|
|
32
|
+
from lms.message.msbfio import read_msbf_path
|
|
33
|
+
|
|
34
|
+
msbt = read_msbt_path("Game.msbt")
|
|
35
|
+
msbf = read_msbf_path("Game_Flowchart.msbf")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Writing
|
|
39
|
+
|
|
40
|
+
```py
|
|
41
|
+
from lms.message.msbtio import write_msbt_path
|
|
42
|
+
from lms.message.msbfio import write_msbf_path
|
|
43
|
+
|
|
44
|
+
write_msbt_path("Out_Game.msbt")
|
|
45
|
+
write_msbf_path("Out_Game_Flowchart.msbt")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
# Adding/Editing Presets
|
|
49
|
+
|
|
50
|
+
To add or edit Preset, you may create an issue with the relevant `yaml` file and the game it is for.
|
|
51
|
+
|
|
52
|
+
# Installation
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
pip install PylibMS
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
[Pip Page](https://pypi.org/project/PyLibMS/)
|
|
59
|
+
|
|
60
|
+
# Build Instructions
|
|
61
|
+
|
|
62
|
+
Python version must be `>=3.12.`
|
|
63
|
+
|
|
64
|
+
Clone the repository, then run `pip install` (venv recommended)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
git clone https://github.com/AbdyyEee/PylibMS.git
|
|
68
|
+
cd PylibMS
|
|
69
|
+
pip install -e
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
# Credits & Sources
|
|
73
|
+
|
|
74
|
+
* [Nintendo-File-Formats](https://nintendo-formats.com) by Kinnay: For existing information on the MSBT and MSBP file
|
|
75
|
+
formats.
|
|
76
|
+
* [Trippixyz](https://github.com/Trippixyz): For helping me get started general decompilation of the formats and general
|
|
77
|
+
help.
|
|
78
|
+
* [AeonSake](https://github.com/AeonSake): Inspiration for some the implementation of the library and a bit of general
|
|
79
|
+
help.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
from typing import Callable
|
|
2
2
|
|
|
3
|
-
from lms.common.lms_datatype import (LMS_DataType, is_bool_datatype,
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
from lms.common.field.lms_datatype import (LMS_DataType, is_bool_datatype,
|
|
4
|
+
is_bytes_datatype, is_list_datatype,
|
|
5
|
+
is_number_datatype)
|
|
6
|
+
from lms.common.field.lms_field import LMS_Field
|
|
6
7
|
from lms.fileio.io import FileReader, FileWriter
|
|
7
|
-
from lms.message.definitions.field.lms_field import LMS_Field
|
|
8
8
|
from lms.titleconfig.definitions.value import ValueDefinition
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def read_field(reader: FileReader, definition: ValueDefinition) -> LMS_Field:
|
|
12
|
-
# String is excluded as their reading varies between
|
|
12
|
+
# String is excluded as their reading varies between MSBT/MSBF
|
|
13
13
|
match definition.datatype:
|
|
14
14
|
case LMS_DataType.UINT8:
|
|
15
15
|
value = reader.read_uint8()
|
|
@@ -16,6 +16,7 @@ ALIAS_MAP = {
|
|
|
16
16
|
"byte": "BYTES",
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
|
|
19
20
|
def is_number_datatype(value: object, datatype: LMS_DataType) -> TypeGuard[int | float]:
|
|
20
21
|
return datatype in (
|
|
21
22
|
LMS_DataType.UINT8,
|
|
@@ -57,16 +58,10 @@ class LMS_DataType(Enum):
|
|
|
57
58
|
|
|
58
59
|
FLOAT32 = 6
|
|
59
60
|
|
|
60
|
-
# Unknown 16 bit type (value of 6) has yet to be documented
|
|
61
|
-
# Might be some sort of 2 byte integer, float, or may be an array.
|
|
62
|
-
# We won't ever know cause no game (yet that has been found) has utilized this type
|
|
63
|
-
# Thanks Nintendo.
|
|
64
|
-
...
|
|
65
|
-
|
|
66
61
|
STRING = 8
|
|
67
62
|
LIST = 9
|
|
68
63
|
|
|
69
|
-
# Interface types
|
|
64
|
+
# - Interface types -
|
|
70
65
|
# These types act as an abstraction for a real LMS_Datatype
|
|
71
66
|
# The actual value isn't important since they are not real types, but are instantiated from a config
|
|
72
67
|
BOOL = "bool"
|
|
@@ -107,8 +102,8 @@ class LMS_DataType(Enum):
|
|
|
107
102
|
|
|
108
103
|
@property
|
|
109
104
|
def stream_size(self) -> int:
|
|
110
|
-
"""
|
|
111
|
-
|
|
105
|
+
"""Size of the datatype, only for fixed integers."""
|
|
106
|
+
sizes = {
|
|
112
107
|
LMS_DataType.UINT8: 1,
|
|
113
108
|
LMS_DataType.UINT16: 2,
|
|
114
109
|
LMS_DataType.UINT32: 4,
|
|
@@ -116,10 +111,14 @@ class LMS_DataType(Enum):
|
|
|
116
111
|
LMS_DataType.INT16: 2,
|
|
117
112
|
LMS_DataType.INT32: 4,
|
|
118
113
|
LMS_DataType.FLOAT32: 4,
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
return sizes[self]
|
|
118
|
+
except KeyError:
|
|
119
|
+
raise TypeError(
|
|
120
|
+
f"Stream size is not defined for '{self.to_string()}'."
|
|
121
|
+
)
|
|
123
122
|
|
|
124
123
|
@classmethod
|
|
125
124
|
def from_string(cls, string: str):
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Iterator
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
|
|
6
|
-
from lms.common.lms_datatype import LMS_DataType
|
|
6
|
+
from lms.common.field.lms_datatype import LMS_DataType
|
|
7
7
|
from lms.titleconfig.definitions.value import ValueDefinition
|
|
8
8
|
|
|
9
9
|
FLOAT32_MIN = -3.4028235e38
|
|
@@ -11,6 +11,7 @@ FLOAT32_MAX = 3.4028235e38
|
|
|
11
11
|
|
|
12
12
|
type FieldValue = int | str | float | bool | bytes
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
@dataclass(frozen=True)
|
|
15
16
|
class LMS_FieldMap:
|
|
16
17
|
"""
|
|
@@ -44,9 +45,9 @@ class LMS_FieldMap:
|
|
|
44
45
|
for definition in definitions:
|
|
45
46
|
match definition.datatype:
|
|
46
47
|
case LMS_DataType.LIST:
|
|
47
|
-
field_map[definition.name] =
|
|
48
|
+
field_map[definition.name] = definition.list_items[0]
|
|
48
49
|
case LMS_DataType.BOOL:
|
|
49
|
-
field_map[definition.name] =
|
|
50
|
+
field_map[definition.name] = False
|
|
50
51
|
case LMS_DataType.FLOAT32:
|
|
51
52
|
field_map[definition.name] = 0.0
|
|
52
53
|
case _:
|
|
@@ -97,15 +98,16 @@ class LMS_Field:
|
|
|
97
98
|
"""
|
|
98
99
|
|
|
99
100
|
def __init__(
|
|
100
|
-
self, value: int | str | float | bytes | bool, definition: ValueDefinition
|
|
101
|
+
self, value: int | str | float | bytes | bool, definition: ValueDefinition,
|
|
101
102
|
):
|
|
102
|
-
|
|
103
|
+
_verify_value_from_definition(value, definition)
|
|
103
104
|
self._definition = definition
|
|
104
105
|
self._value = value
|
|
105
106
|
|
|
106
107
|
def __repr__(self):
|
|
107
108
|
if self.datatype is LMS_DataType.LIST:
|
|
108
109
|
return f"LMS_Field(value={self._value!r}, list_items={self.list_items!r})"
|
|
110
|
+
|
|
109
111
|
return f"LMS_Field(value={self._value!r}, type={self.datatype!r})"
|
|
110
112
|
|
|
111
113
|
@property
|
|
@@ -135,11 +137,11 @@ class LMS_Field:
|
|
|
135
137
|
|
|
136
138
|
@value.setter
|
|
137
139
|
def value(self, new_value: int | str | float | bytes | bool):
|
|
138
|
-
|
|
140
|
+
_verify_value_from_definition(new_value, self._definition)
|
|
139
141
|
self._value = new_value
|
|
140
142
|
|
|
141
143
|
|
|
142
|
-
def
|
|
144
|
+
def _verify_value_from_definition(
|
|
143
145
|
value: int | str | float | bytes | bool, definition: ValueDefinition
|
|
144
146
|
) -> None:
|
|
145
147
|
datatype = definition.datatype
|
|
@@ -161,33 +163,32 @@ def _verify_value(
|
|
|
161
163
|
)
|
|
162
164
|
else:
|
|
163
165
|
return
|
|
164
|
-
case LMS_DataType.FLOAT32 if isinstance(value, float):
|
|
165
|
-
_verify_number_is_in_range(value, FLOAT32_MIN, FLOAT32_MAX, definition)
|
|
166
|
-
return
|
|
167
166
|
case _ if isinstance(value, int):
|
|
168
|
-
|
|
169
|
-
if datatype.signed:
|
|
170
|
-
max_value = 2 ** (bits - 1)
|
|
171
|
-
min_value = -max_value
|
|
172
|
-
else:
|
|
173
|
-
min_value, max_value = 0, (2 ** bits) - 1
|
|
174
|
-
|
|
175
|
-
_verify_number_is_in_range(value, min_value, max_value, definition)
|
|
167
|
+
_verify_number_datatype(value, definition.datatype)
|
|
176
168
|
return
|
|
177
169
|
|
|
178
170
|
raise TypeError(
|
|
179
|
-
f"The value provided for '{definition.name}' type '{type(value)}' should be '{datatype.builtin_type}'."
|
|
171
|
+
f"The value provided for '{definition.name}' type '{type(value)}' should be '{datatype.builtin_type}' (DATATYPE {definition.datatype})."
|
|
180
172
|
)
|
|
181
173
|
|
|
182
174
|
|
|
183
|
-
def
|
|
175
|
+
def _verify_number_datatype(
|
|
184
176
|
value: int | float,
|
|
185
|
-
|
|
186
|
-
max_value: int | float,
|
|
187
|
-
definition: ValueDefinition,
|
|
177
|
+
datatype: LMS_DataType,
|
|
188
178
|
):
|
|
179
|
+
if datatype is LMS_DataType.FLOAT32:
|
|
180
|
+
max_value = FLOAT32_MAX
|
|
181
|
+
min_value = FLOAT32_MIN
|
|
182
|
+
else:
|
|
183
|
+
bits = datatype.stream_size * 8
|
|
184
|
+
if datatype.signed:
|
|
185
|
+
max_value = 2 ** (bits - 1)
|
|
186
|
+
min_value = -max_value
|
|
187
|
+
else:
|
|
188
|
+
min_value, max_value = 0, (2 ** bits) - 1
|
|
189
|
+
|
|
189
190
|
if not min_value <= value <= max_value:
|
|
190
191
|
raise ValueError(
|
|
191
|
-
f"""The value '{value}' of type '{
|
|
192
|
+
f"""The value '{value}' of type '{datatype.name}' provided for this field
|
|
192
193
|
is out of range of ({min_value}, {max_value})"""
|
|
193
194
|
)
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
from lms.common import lms_exceptions
|
|
2
|
+
from lms.common.lms_constants import BIG_ENDIAN_BOM, SECTION_DATA_START, LITTLE_ENDIAN_BOM, SIZE_OFFSET
|
|
2
3
|
from lms.common.lms_fileinfo import LMS_FileInfo
|
|
3
4
|
from lms.fileio.encoding import FileEncoding
|
|
4
5
|
from lms.fileio.io import FileReader, FileWriter
|
|
5
6
|
|
|
6
|
-
DATA_START = 0x20
|
|
7
|
-
LITTLE_ENDIAN_BOM = b"\xff\xfe"
|
|
8
|
-
BIG_ENDIAN_BOM = b"\xfe\xff"
|
|
9
|
-
|
|
10
7
|
|
|
11
8
|
def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
|
|
12
9
|
magic = reader.read_string_len(8)
|
|
@@ -32,10 +29,10 @@ def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
|
|
|
32
29
|
file_size = reader.read_uint32()
|
|
33
30
|
|
|
34
31
|
reader.seek(0, 2)
|
|
35
|
-
if file_size != reader.tell():
|
|
36
|
-
raise lms_exceptions.LMS_MisalignedSizeError(f"File size is misaligned
|
|
32
|
+
if file_size != (real_size := reader.tell()):
|
|
33
|
+
raise lms_exceptions.LMS_MisalignedSizeError(f"File size is misaligned!` Got {file_size} expected {real_size}.")
|
|
37
34
|
|
|
38
|
-
reader.seek(
|
|
35
|
+
reader.seek(SECTION_DATA_START)
|
|
39
36
|
|
|
40
37
|
return LMS_FileInfo(
|
|
41
38
|
is_big_endian,
|
|
@@ -62,4 +59,9 @@ def write_file_info(writer: FileWriter, magic: str, file_info: LMS_FileInfo) ->
|
|
|
62
59
|
writer.write_bytes(b"\x00\x00")
|
|
63
60
|
writer.write_bytes(b"\x00" * 4)
|
|
64
61
|
writer.write_bytes(b"\x00" * 10)
|
|
65
|
-
writer.seek(
|
|
62
|
+
writer.seek(SECTION_DATA_START)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def write_file_size(writer: FileWriter):
|
|
66
|
+
writer.seek(SIZE_OFFSET)
|
|
67
|
+
writer.write_uint32(writer.get_stream_size())
|
|
@@ -27,21 +27,23 @@ def read_labels(reader: FileReader) -> tuple[dict, int]:
|
|
|
27
27
|
return sorted_labels, slot_count
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def write_labels(writer: FileWriter, labels: list[str], slot_count: int) -> None:
|
|
30
|
+
def write_labels(writer: FileWriter, labels: list[str], slot_count: int, index_map: dict[str, int] = None) -> None:
|
|
31
31
|
writer.write_uint32(slot_count)
|
|
32
32
|
|
|
33
33
|
hash_slots = {}
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
|
|
35
|
+
add_indices = index_map is None
|
|
36
|
+
|
|
37
|
+
if index_map is None:
|
|
38
|
+
index_map = {}
|
|
39
|
+
|
|
36
40
|
for i, label in enumerate(labels):
|
|
37
|
-
|
|
41
|
+
slot = _calculate_hash(label, slot_count)
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
hash_slots[hash] = [label]
|
|
41
|
-
else:
|
|
42
|
-
hash_slots[hash].append(label)
|
|
43
|
+
hash_slots.setdefault(slot, []).append(label)
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
if add_indices:
|
|
46
|
+
index_map[label] = i
|
|
45
47
|
|
|
46
48
|
label_offsets = slot_count * 8 + 4
|
|
47
49
|
|
|
@@ -66,8 +68,15 @@ def write_labels(writer: FileWriter, labels: list[str], slot_count: int) -> None
|
|
|
66
68
|
writer.write_uint32(index_map[label])
|
|
67
69
|
|
|
68
70
|
|
|
69
|
-
# See https://nintendo-formats.com/libs/lms/overview.html#hash-tables
|
|
70
71
|
def _calculate_hash(label: str, slot_count: int) -> int:
|
|
72
|
+
"""
|
|
73
|
+
Calucate the hash of a label
|
|
74
|
+
|
|
75
|
+
:param label: the label to calculate the hash for.
|
|
76
|
+
:param slot_count: the number of slots.
|
|
77
|
+
|
|
78
|
+
Taken directly from https://nintendo-formats.com/libs/lms/overview.html#hash-tables
|
|
79
|
+
"""
|
|
71
80
|
hash = 0
|
|
72
81
|
for character in label:
|
|
73
82
|
hash = hash * 0x492 + ord(character)
|
|
@@ -2,6 +2,9 @@ from typing import Any, Callable, Generator
|
|
|
2
2
|
|
|
3
3
|
from lms.fileio.io import FileReader, FileWriter
|
|
4
4
|
|
|
5
|
+
SECTION_PADDING_BYTE = b"\xAB"
|
|
6
|
+
SECTION_ALIGNMENT = 16
|
|
7
|
+
|
|
5
8
|
|
|
6
9
|
def read_section_data(
|
|
7
10
|
reader: FileReader, section_count: int
|
|
@@ -14,7 +17,7 @@ def read_section_data(
|
|
|
14
17
|
reader.skip(8)
|
|
15
18
|
end = reader.tell() + size
|
|
16
19
|
|
|
17
|
-
yield
|
|
20
|
+
yield magic, size
|
|
18
21
|
|
|
19
22
|
reader.seek(end)
|
|
20
23
|
reader.align(16)
|
|
@@ -55,4 +58,4 @@ def _write_end_data(writer: FileWriter, data_start: int, size_offset: int) -> No
|
|
|
55
58
|
writer.seek(size_offset)
|
|
56
59
|
writer.write_uint32(size)
|
|
57
60
|
writer.seek(end)
|
|
58
|
-
writer.write_alignment(
|
|
61
|
+
writer.write_alignment(SECTION_PADDING_BYTE, SECTION_ALIGNMENT)
|
|
@@ -6,6 +6,8 @@ from typing import BinaryIO, Generator
|
|
|
6
6
|
from io import BytesIO, IOBase
|
|
7
7
|
from lms.fileio.encoding import FileEncoding
|
|
8
8
|
|
|
9
|
+
PADDING_BYTE = b"\x00"
|
|
10
|
+
|
|
9
11
|
STRUCT_TYPES = {
|
|
10
12
|
"little": {
|
|
11
13
|
"uint8": "<B",
|
|
@@ -90,6 +92,14 @@ class FileReader:
|
|
|
90
92
|
def read_float32(self) -> float:
|
|
91
93
|
return struct.unpack(self._get_datatype("float"), self._stream.read(4))[0]
|
|
92
94
|
|
|
95
|
+
def read_string_offset(self, section_start: int) -> str:
|
|
96
|
+
offset = self.read_uint32()
|
|
97
|
+
last_position = self.tell()
|
|
98
|
+
self.seek(section_start + offset)
|
|
99
|
+
string = self.read_encoded_string()
|
|
100
|
+
self.seek(last_position)
|
|
101
|
+
return string
|
|
102
|
+
|
|
93
103
|
def read_string_len(self, length: int) -> str:
|
|
94
104
|
return self._stream.read(length).decode("UTF-8")
|
|
95
105
|
|