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.
Files changed (85) hide show
  1. pylibms-4.2.1/MANIFEST.in +1 -0
  2. {pylibms-4.1.7 → pylibms-4.2.1}/PKG-INFO +28 -7
  3. {pylibms-4.1.7 → pylibms-4.2.1}/PyLibMS.egg-info/PKG-INFO +28 -7
  4. {pylibms-4.1.7 → pylibms-4.2.1}/README.md +27 -6
  5. {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/field/io.py +20 -6
  6. {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/definitions/flowchart.py +1 -0
  7. {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/definitions/node.py +22 -26
  8. {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/definitions/node_type.py +22 -2
  9. {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/flw3.py +26 -12
  10. {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/msbf.py +4 -8
  11. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/definitions/nodes.py +9 -2
  12. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Tomodachi Life NA-EU.yaml +6 -0
  13. {pylibms-4.1.7 → pylibms-4.2.1}/pyproject.toml +1 -1
  14. pylibms-4.1.7/MANIFEST.in +0 -2
  15. {pylibms-4.1.7 → pylibms-4.2.1}/LICENSE +0 -0
  16. {pylibms-4.1.7 → pylibms-4.2.1}/PyLibMS.egg-info/SOURCES.txt +0 -0
  17. {pylibms-4.1.7 → pylibms-4.2.1}/PyLibMS.egg-info/dependency_links.txt +0 -0
  18. {pylibms-4.1.7 → pylibms-4.2.1}/PyLibMS.egg-info/requires.txt +0 -0
  19. {pylibms-4.1.7 → pylibms-4.2.1}/PyLibMS.egg-info/top_level.txt +0 -0
  20. {pylibms-4.1.7 → pylibms-4.2.1}/lms/__init__.py +0 -0
  21. {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/__init__.py +0 -0
  22. {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/field/__init__.py +0 -0
  23. {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/field/lms_datatype.py +0 -0
  24. {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/field/lms_field.py +0 -0
  25. {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/lms_constants.py +0 -0
  26. {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/lms_exceptions.py +0 -0
  27. {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/lms_fileinfo.py +0 -0
  28. {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/stream/fileinfo.py +0 -0
  29. {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/stream/hashtable.py +0 -0
  30. {pylibms-4.1.7 → pylibms-4.2.1}/lms/common/stream/section.py +0 -0
  31. {pylibms-4.1.7 → pylibms-4.2.1}/lms/fileio/encoding.py +0 -0
  32. {pylibms-4.1.7 → pylibms-4.2.1}/lms/fileio/io.py +0 -0
  33. {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/__init__.py +0 -0
  34. {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/definitions/__init__.py +0 -0
  35. {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/definitions/lms_nodeexceptions.py +0 -0
  36. {pylibms-4.1.7 → pylibms-4.2.1}/lms/flowchart/msbfio.py +0 -0
  37. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/__init__.py +0 -0
  38. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/definitions/__init__.py +0 -0
  39. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/definitions/field/__init__.py +0 -0
  40. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/lms_messagetext.py +0 -0
  41. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/msbt.py +0 -0
  42. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/msbtentry.py +0 -0
  43. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/msbtio.py +0 -0
  44. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/section/__init__.py +0 -0
  45. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/section/atr1.py +0 -0
  46. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/section/nli1.py +0 -0
  47. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/section/tsy1.py +0 -0
  48. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/section/txt2.py +0 -0
  49. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/tag/__init__.py +0 -0
  50. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/tag/io/param_io.py +0 -0
  51. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/tag/io/tag_io.py +0 -0
  52. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/tag/lms_tag.py +0 -0
  53. {pylibms-4.1.7 → pylibms-4.2.1}/lms/message/tag/lms_tagexceptions.py +0 -0
  54. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/__init__.py +0 -0
  55. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/definitions/__init__.py +0 -0
  56. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/definitions/attribute.py +0 -0
  57. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/definitions/color.py +0 -0
  58. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/definitions/style.py +0 -0
  59. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/definitions/tag.py +0 -0
  60. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/msbp.py +0 -0
  61. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/msbpread.py +0 -0
  62. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/ali2.py +0 -0
  63. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/ati2.py +0 -0
  64. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/clr1.py +0 -0
  65. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/string.py +0 -0
  66. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/syl3.py +0 -0
  67. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/tag2.py +0 -0
  68. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/tgg2.py +0 -0
  69. {pylibms-4.1.7 → pylibms-4.2.1}/lms/project/section/tgp2.py +0 -0
  70. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/__init__.py +0 -0
  71. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/config.py +0 -0
  72. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/definitions/__init__.py +0 -0
  73. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/definitions/attribute.py +0 -0
  74. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/definitions/tags.py +0 -0
  75. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/definitions/value.py +0 -0
  76. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Badge Arcade.yaml +0 -0
  77. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Brain Age Concentration Training.yaml +0 -0
  78. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Kirby Planet Robobot.yaml +0 -0
  79. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Super Mario 3D Land.yaml +0 -0
  80. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Super Mario 3D World + Bowsers Fury.yaml +0 -0
  81. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Super Mario Odyssey.yaml +0 -0
  82. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml +0 -0
  83. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/The Legend of Zelda a Link Between Worlds.yaml +0 -0
  84. {pylibms-4.1.7 → pylibms-4.2.1}/lms/titleconfig/presets/Tomodachi Life Living The Dream.yaml +0 -0
  85. {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.7
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
  [![Python Versions](https://img.shields.io/pypi/pyversions/PyLibMS)](https://pypi.org/project/PyLibMS/)
24
24
  [![PyPI version](https://badge.fury.io/py/PyLibMS.svg)](https://badge.fury.io/py/PyLibMS)
25
25
 
26
- PyLibMS is a library built for Python 3.10 and above for the libMessageStudio (LMS) proprietary file formats from Nintendo.
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/MSBF
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
- msbt = read_msbt_path("Game.msbt")
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
- write_msbt_path("Out_Game.msbt")
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.7
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
  [![Python Versions](https://img.shields.io/pypi/pyversions/PyLibMS)](https://pypi.org/project/PyLibMS/)
24
24
  [![PyPI version](https://badge.fury.io/py/PyLibMS.svg)](https://badge.fury.io/py/PyLibMS)
25
25
 
26
- PyLibMS is a library built for Python 3.10 and above for the libMessageStudio (LMS) proprietary file formats from Nintendo.
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/MSBF
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
- msbt = read_msbt_path("Game.msbt")
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
- write_msbt_path("Out_Game.msbt")
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
  [![Python Versions](https://img.shields.io/pypi/pyversions/PyLibMS)](https://pypi.org/project/PyLibMS/)
3
3
  [![PyPI version](https://badge.fury.io/py/PyLibMS.svg)](https://badge.fury.io/py/PyLibMS)
4
4
 
5
- PyLibMS is a library built for Python 3.10 and above for the libMessageStudio (LMS) proprietary file formats from Nintendo.
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/MSBF
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
- msbt = read_msbt_path("Game.msbt")
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
- write_msbt_path("Out_Game.msbt")
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) -> LMS_Field:
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
- index = reader.read_uint8()
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
- value = bool(reader.read_uint8())
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
- writer.write_uint8(field.list_items.index(field.value))
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
- writer.write_uint8(bool(field.value))
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}")
@@ -16,6 +16,7 @@ class LMS_Flowchart:
16
16
  for node in entry_point.get_descendents():
17
17
  self._nodes[node.id] = node
18
18
 
19
+
19
20
  def __iter__(self) -> Iterator[LMS_BaseNode]:
20
21
  return iter(self._nodes.values())
21
22
 
@@ -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 = stream_next_id
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, sliced_datatype[self._parameter_type][i])
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, stream_next_id=None, flowchart_name: str = ""):
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, next_flowchart_id: int, unknown_short0a: 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.lms_exceptions import LMS_Error
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, stream_next_id, file_index, message_index, msbt)
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, stream_next_id, definition)
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, stream_next_id)
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
- next_flowchart_id = read_next_node_id(reader)
93
+ stream_next_id = read_next_node_id(reader)
89
94
  unknown_short0a = reader.read_uint16()
90
- node = LMS_JumpNode(i, next_flowchart_id, unknown_short0a)
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[node.next_node_id]
119
+ node.next_flowchart = node_map[next_node_id]
113
120
  continue
114
121
 
115
- if node.next_node_id is None:
122
+ if next_node_id is None:
116
123
  continue
117
124
 
118
- node.set_next_node(node_map[node.next_node_id])
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
- result[param_definition.name] = read_field(reader, param_definition)
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
- write_field(writer, field)
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._flowcharts:
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"], data.get("description", ""), datatype_from_dict,
88
+ definition = ValueDefinition(parameter["name"], description, datatype_from_dict,
87
89
  list_items)
88
90
  else:
89
- definition = ValueDefinition(parameter["name"], data["description"],
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.7"
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
@@ -1,2 +0,0 @@
1
- include README.md LICENSE
2
- recursive-include lms/titleconfig/presets *.yaml
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