PyLibMS 4.1.7__tar.gz → 4.2.0__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 (84) hide show
  1. {pylibms-4.1.7 → pylibms-4.2.0}/PKG-INFO +28 -7
  2. {pylibms-4.1.7 → pylibms-4.2.0}/PyLibMS.egg-info/PKG-INFO +28 -7
  3. {pylibms-4.1.7 → pylibms-4.2.0}/README.md +27 -6
  4. {pylibms-4.1.7 → pylibms-4.2.0}/lms/common/field/io.py +20 -6
  5. {pylibms-4.1.7 → pylibms-4.2.0}/lms/flowchart/definitions/node_type.py +22 -2
  6. {pylibms-4.1.7 → pylibms-4.2.0}/lms/flowchart/flw3.py +14 -4
  7. {pylibms-4.1.7 → pylibms-4.2.0}/lms/flowchart/msbf.py +4 -8
  8. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/definitions/nodes.py +9 -2
  9. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/presets/Tomodachi Life NA-EU.yaml +1 -0
  10. {pylibms-4.1.7 → pylibms-4.2.0}/pyproject.toml +1 -1
  11. {pylibms-4.1.7 → pylibms-4.2.0}/LICENSE +0 -0
  12. {pylibms-4.1.7 → pylibms-4.2.0}/MANIFEST.in +0 -0
  13. {pylibms-4.1.7 → pylibms-4.2.0}/PyLibMS.egg-info/SOURCES.txt +0 -0
  14. {pylibms-4.1.7 → pylibms-4.2.0}/PyLibMS.egg-info/dependency_links.txt +0 -0
  15. {pylibms-4.1.7 → pylibms-4.2.0}/PyLibMS.egg-info/requires.txt +0 -0
  16. {pylibms-4.1.7 → pylibms-4.2.0}/PyLibMS.egg-info/top_level.txt +0 -0
  17. {pylibms-4.1.7 → pylibms-4.2.0}/lms/__init__.py +0 -0
  18. {pylibms-4.1.7 → pylibms-4.2.0}/lms/common/__init__.py +0 -0
  19. {pylibms-4.1.7 → pylibms-4.2.0}/lms/common/field/__init__.py +0 -0
  20. {pylibms-4.1.7 → pylibms-4.2.0}/lms/common/field/lms_datatype.py +0 -0
  21. {pylibms-4.1.7 → pylibms-4.2.0}/lms/common/field/lms_field.py +0 -0
  22. {pylibms-4.1.7 → pylibms-4.2.0}/lms/common/lms_constants.py +0 -0
  23. {pylibms-4.1.7 → pylibms-4.2.0}/lms/common/lms_exceptions.py +0 -0
  24. {pylibms-4.1.7 → pylibms-4.2.0}/lms/common/lms_fileinfo.py +0 -0
  25. {pylibms-4.1.7 → pylibms-4.2.0}/lms/common/stream/fileinfo.py +0 -0
  26. {pylibms-4.1.7 → pylibms-4.2.0}/lms/common/stream/hashtable.py +0 -0
  27. {pylibms-4.1.7 → pylibms-4.2.0}/lms/common/stream/section.py +0 -0
  28. {pylibms-4.1.7 → pylibms-4.2.0}/lms/fileio/encoding.py +0 -0
  29. {pylibms-4.1.7 → pylibms-4.2.0}/lms/fileio/io.py +0 -0
  30. {pylibms-4.1.7 → pylibms-4.2.0}/lms/flowchart/__init__.py +0 -0
  31. {pylibms-4.1.7 → pylibms-4.2.0}/lms/flowchart/definitions/__init__.py +0 -0
  32. {pylibms-4.1.7 → pylibms-4.2.0}/lms/flowchart/definitions/flowchart.py +0 -0
  33. {pylibms-4.1.7 → pylibms-4.2.0}/lms/flowchart/definitions/lms_nodeexceptions.py +0 -0
  34. {pylibms-4.1.7 → pylibms-4.2.0}/lms/flowchart/definitions/node.py +0 -0
  35. {pylibms-4.1.7 → pylibms-4.2.0}/lms/flowchart/msbfio.py +0 -0
  36. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/__init__.py +0 -0
  37. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/definitions/__init__.py +0 -0
  38. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/definitions/field/__init__.py +0 -0
  39. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/lms_messagetext.py +0 -0
  40. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/msbt.py +0 -0
  41. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/msbtentry.py +0 -0
  42. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/msbtio.py +0 -0
  43. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/section/__init__.py +0 -0
  44. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/section/atr1.py +0 -0
  45. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/section/nli1.py +0 -0
  46. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/section/tsy1.py +0 -0
  47. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/section/txt2.py +0 -0
  48. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/tag/__init__.py +0 -0
  49. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/tag/io/param_io.py +0 -0
  50. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/tag/io/tag_io.py +0 -0
  51. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/tag/lms_tag.py +0 -0
  52. {pylibms-4.1.7 → pylibms-4.2.0}/lms/message/tag/lms_tagexceptions.py +0 -0
  53. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/__init__.py +0 -0
  54. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/definitions/__init__.py +0 -0
  55. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/definitions/attribute.py +0 -0
  56. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/definitions/color.py +0 -0
  57. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/definitions/style.py +0 -0
  58. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/definitions/tag.py +0 -0
  59. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/msbp.py +0 -0
  60. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/msbpread.py +0 -0
  61. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/section/ali2.py +0 -0
  62. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/section/ati2.py +0 -0
  63. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/section/clr1.py +0 -0
  64. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/section/string.py +0 -0
  65. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/section/syl3.py +0 -0
  66. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/section/tag2.py +0 -0
  67. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/section/tgg2.py +0 -0
  68. {pylibms-4.1.7 → pylibms-4.2.0}/lms/project/section/tgp2.py +0 -0
  69. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/__init__.py +0 -0
  70. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/config.py +0 -0
  71. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/definitions/__init__.py +0 -0
  72. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/definitions/attribute.py +0 -0
  73. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/definitions/tags.py +0 -0
  74. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/definitions/value.py +0 -0
  75. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/presets/Badge Arcade.yaml +0 -0
  76. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/presets/Brain Age Concentration Training.yaml +0 -0
  77. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/presets/Kirby Planet Robobot.yaml +0 -0
  78. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/presets/Super Mario 3D Land.yaml +0 -0
  79. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/presets/Super Mario 3D World + Bowsers Fury.yaml +0 -0
  80. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/presets/Super Mario Odyssey.yaml +0 -0
  81. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml +0 -0
  82. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/presets/The Legend of Zelda a Link Between Worlds.yaml +0 -0
  83. {pylibms-4.1.7 → pylibms-4.2.0}/lms/titleconfig/presets/Tomodachi Life Living The Dream.yaml +0 -0
  84. {pylibms-4.1.7 → pylibms-4.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyLibMS
3
- Version: 4.1.7
3
+ Version: 4.2.0
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.0
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}")
@@ -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):
58
+ """The parameter type split into the raw datatypes. Not valid for PARAM_32_0, PARAM_32_1, and STRING."""
59
+ if self in [
60
+ LMS_NodeParameterType.NONE,
61
+ LMS_NodeParameterType.PARAM_32_0,
62
+ LMS_NodeParameterType.PARAM_32_1,
63
+ LMS_NodeParameterType.STRING]:
64
+ raise TypeError(f"There is no sliced datatypes for {self} parameter types!")
65
+
66
+ return {
67
+ LMS_NodeParameterType.PARAM_16_16: (LMS_DataType.UINT16, LMS_DataType.UINT16),
68
+ LMS_NodeParameterType.PARAM_16_8_8: (LMS_DataType.UINT16, LMS_DataType.UINT8, LMS_DataType.UINT8),
69
+ LMS_NodeParameterType.PARAM_8_8_16: (LMS_DataType.UINT8, LMS_DataType.UINT8, LMS_DataType.UINT16),
70
+ LMS_NodeParameterType.PARAM_8_8_8_8: (LMS_DataType.UINT8, LMS_DataType.UINT8, LMS_DataType.UINT8,
71
+ LMS_DataType.UINT8)
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]]:
@@ -155,8 +158,10 @@ def evaluate_node_parameter(reader: FileReader,
155
158
  param_definition.name: LMS_Field(value, param_definition)
156
159
  })
157
160
 
158
- for param_definition in definition.parameter_definitions:
159
- result[param_definition.name] = read_field(reader, param_definition)
161
+ for i, param_definition in enumerate(definition.parameter_definitions):
162
+ stream_map = {1: reader.read_uint8, 2: reader.read_uint16, 3: reader.read_uint32}
163
+ result[param_definition.name] = read_field(reader, param_definition,
164
+ stream_map[parameter_type.sliced_datatype[i].stream_size])
160
165
 
161
166
  return LMS_FieldMap(result)
162
167
  else:
@@ -297,8 +302,13 @@ def write_node_parameter(writer: FileWriter,
297
302
  value: LMS_NodeParameter,
298
303
  parameter_type: LMS_NodeParameterType) -> None:
299
304
  if isinstance(value, LMS_FieldMap):
300
- for field in value:
301
- write_field(writer, field)
305
+ for i, field in enumerate(value):
306
+ # In MSBF files there could be a case where naturally one bit datatypes take up 2/4 bits.
307
+ if field.datatype in VARIABLE_WIDTH_DATATYPES:
308
+ stream_map = {1: writer.write_uint8, 2: writer.write_uint16, 3: writer.write_uint32}
309
+ write_field(writer, field, stream_map[parameter_type.sliced_datatype[i].stream_size])
310
+ else:
311
+ write_field(writer, field)
302
312
  return
303
313
 
304
314
  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
@@ -7,7 +7,7 @@ include = ["lms*"]
7
7
 
8
8
  [project]
9
9
  name = "PyLibMS"
10
- version = "4.1.7"
10
+ version = "4.2.0"
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"
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