PyLibMS 4.3.1__tar.gz → 4.3.3__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 (83) hide show
  1. {pylibms-4.3.1 → pylibms-4.3.3}/PKG-INFO +7 -7
  2. {pylibms-4.3.1 → pylibms-4.3.3}/PyLibMS.egg-info/PKG-INFO +7 -7
  3. {pylibms-4.3.1 → pylibms-4.3.3}/README.md +6 -6
  4. {pylibms-4.3.1 → pylibms-4.3.3}/lms/common/lms_constants.py +2 -0
  5. {pylibms-4.3.1 → pylibms-4.3.3}/lms/common/stream/fileinfo.py +2 -2
  6. {pylibms-4.3.1 → pylibms-4.3.3}/lms/flowchart/definitions/flowchart.py +2 -5
  7. {pylibms-4.3.1 → pylibms-4.3.3}/lms/flowchart/definitions/node.py +17 -10
  8. {pylibms-4.3.1 → pylibms-4.3.3}/lms/flowchart/msbf.py +16 -11
  9. {pylibms-4.3.1 → pylibms-4.3.3}/lms/flowchart/msbfio.py +3 -9
  10. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/config.py +17 -10
  11. {pylibms-4.3.1 → pylibms-4.3.3}/pyproject.toml +1 -1
  12. {pylibms-4.3.1 → pylibms-4.3.3}/LICENSE +0 -0
  13. {pylibms-4.3.1 → pylibms-4.3.3}/MANIFEST.in +0 -0
  14. {pylibms-4.3.1 → pylibms-4.3.3}/PyLibMS.egg-info/SOURCES.txt +0 -0
  15. {pylibms-4.3.1 → pylibms-4.3.3}/PyLibMS.egg-info/dependency_links.txt +0 -0
  16. {pylibms-4.3.1 → pylibms-4.3.3}/PyLibMS.egg-info/requires.txt +0 -0
  17. {pylibms-4.3.1 → pylibms-4.3.3}/PyLibMS.egg-info/top_level.txt +0 -0
  18. {pylibms-4.3.1 → pylibms-4.3.3}/lms/__init__.py +0 -0
  19. {pylibms-4.3.1 → pylibms-4.3.3}/lms/common/__init__.py +0 -0
  20. {pylibms-4.3.1 → pylibms-4.3.3}/lms/common/field/__init__.py +0 -0
  21. {pylibms-4.3.1 → pylibms-4.3.3}/lms/common/field/io.py +0 -0
  22. {pylibms-4.3.1 → pylibms-4.3.3}/lms/common/field/lms_datatype.py +0 -0
  23. {pylibms-4.3.1 → pylibms-4.3.3}/lms/common/field/lms_field.py +0 -0
  24. {pylibms-4.3.1 → pylibms-4.3.3}/lms/common/lms_exceptions.py +0 -0
  25. {pylibms-4.3.1 → pylibms-4.3.3}/lms/common/lms_fileinfo.py +0 -0
  26. {pylibms-4.3.1 → pylibms-4.3.3}/lms/common/stream/hashtable.py +0 -0
  27. {pylibms-4.3.1 → pylibms-4.3.3}/lms/common/stream/section.py +0 -0
  28. {pylibms-4.3.1 → pylibms-4.3.3}/lms/fileio/encoding.py +0 -0
  29. {pylibms-4.3.1 → pylibms-4.3.3}/lms/fileio/io.py +0 -0
  30. {pylibms-4.3.1 → pylibms-4.3.3}/lms/flowchart/__init__.py +0 -0
  31. {pylibms-4.3.1 → pylibms-4.3.3}/lms/flowchart/definitions/__init__.py +0 -0
  32. {pylibms-4.3.1 → pylibms-4.3.3}/lms/flowchart/definitions/lms_nodeexceptions.py +0 -0
  33. {pylibms-4.3.1 → pylibms-4.3.3}/lms/flowchart/definitions/node_type.py +0 -0
  34. {pylibms-4.3.1 → pylibms-4.3.3}/lms/flowchart/flw3.py +0 -0
  35. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/__init__.py +0 -0
  36. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/definitions/__init__.py +0 -0
  37. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/definitions/field/__init__.py +0 -0
  38. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/lms_messagetext.py +0 -0
  39. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/msbt.py +0 -0
  40. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/msbtentry.py +0 -0
  41. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/msbtio.py +0 -0
  42. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/section/__init__.py +0 -0
  43. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/section/atr1.py +0 -0
  44. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/section/nli1.py +0 -0
  45. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/section/tsy1.py +0 -0
  46. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/section/txt2.py +0 -0
  47. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/tag/__init__.py +0 -0
  48. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/tag/io/param_io.py +0 -0
  49. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/tag/io/tag_io.py +0 -0
  50. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/tag/lms_tag.py +0 -0
  51. {pylibms-4.3.1 → pylibms-4.3.3}/lms/message/tag/lms_tagexceptions.py +0 -0
  52. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/__init__.py +0 -0
  53. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/definitions/__init__.py +0 -0
  54. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/definitions/attribute.py +0 -0
  55. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/definitions/color.py +0 -0
  56. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/definitions/style.py +0 -0
  57. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/definitions/tag.py +0 -0
  58. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/msbp.py +0 -0
  59. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/msbpread.py +0 -0
  60. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/section/ali2.py +0 -0
  61. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/section/ati2.py +0 -0
  62. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/section/clr1.py +0 -0
  63. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/section/string.py +0 -0
  64. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/section/syl3.py +0 -0
  65. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/section/tag2.py +0 -0
  66. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/section/tgg2.py +0 -0
  67. {pylibms-4.3.1 → pylibms-4.3.3}/lms/project/section/tgp2.py +0 -0
  68. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/__init__.py +0 -0
  69. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/definitions/__init__.py +0 -0
  70. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/definitions/attribute.py +0 -0
  71. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/definitions/nodes.py +0 -0
  72. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/definitions/tags.py +0 -0
  73. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/definitions/value.py +0 -0
  74. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/presets/Badge Arcade.yaml +0 -0
  75. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/presets/Brain Age Concentration Training.yaml +0 -0
  76. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/presets/Kirby Planet Robobot.yaml +0 -0
  77. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/presets/Super Mario 3D Land.yaml +0 -0
  78. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/presets/Super Mario 3D World + Bowsers Fury.yaml +0 -0
  79. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/presets/Super Mario Odyssey.yaml +0 -0
  80. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml +0 -0
  81. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/presets/The Legend of Zelda a Link Between Worlds.yaml +0 -0
  82. {pylibms-4.3.1 → pylibms-4.3.3}/lms/titleconfig/presets/Tomodachi Life Living The Dream.yaml +0 -0
  83. {pylibms-4.3.1 → pylibms-4.3.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyLibMS
3
- Version: 4.3.1
3
+ Version: 4.3.3
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
@@ -28,13 +28,13 @@ Dynamic: license-file
28
28
  PyLibMS is a library built in Python 3.10 and above for reading and writing of libMessageStudio (LMS) proprietary file
29
29
  formats from Nintendo.
30
30
 
31
- The following is supported
32
- | Format | Read | Write | Configuration Support |
33
- |-------------|------|-------|-------------------------------------------------------------------------------------------|
34
- | `MSBT` | ✅ | ✅ | Support for full attribute (`ATR1`) decoding. Tag decoding 1:1 with the official Nintendo `.mstxt` file format. |
35
- | `MSBF` | ✅ | ✅ | Support for custom node definitions, attaching names and descriptions to nodes. Altering parameter datatypes for flexibility between games. |
36
- | `MSBP` | ✅ | ❌ | Generating configuration files for use with `MSBT` files. |
31
+ The following is supported:
37
32
 
33
+ | Format | Read | Write | Configuration Support |
34
+ |--------|------|-------|---------------------------------------------------------------------------------------------------------------------------------------------|
35
+ | `MSBT` | ✅ | ✅ | Support for full attribute (`ATR1`) decoding. Tag decoding 1:1 matching with official Nintendo `.mstxt` file format. |
36
+ | `MSBF` | ✅ | ✅ | Support for custom node definitions, attaching names and descriptions to nodes. Altering parameter datatypes for flexibility between games. |
37
+ | `MSBP` | ✅ | ❌ | Generating configuration files for use with `MSBT` files. |
38
38
 
39
39
  This library is designed to support LMS revision >=3.0. These include files generated for late **Wii** titles,
40
40
  **Nintendo 3DS**, **Wii U**, **Mobile**, **Nintendo Switch** and **Nintendo Switch 2** games.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyLibMS
3
- Version: 4.3.1
3
+ Version: 4.3.3
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
@@ -28,13 +28,13 @@ Dynamic: license-file
28
28
  PyLibMS is a library built in Python 3.10 and above for reading and writing of libMessageStudio (LMS) proprietary file
29
29
  formats from Nintendo.
30
30
 
31
- The following is supported
32
- | Format | Read | Write | Configuration Support |
33
- |-------------|------|-------|-------------------------------------------------------------------------------------------|
34
- | `MSBT` | ✅ | ✅ | Support for full attribute (`ATR1`) decoding. Tag decoding 1:1 with the official Nintendo `.mstxt` file format. |
35
- | `MSBF` | ✅ | ✅ | Support for custom node definitions, attaching names and descriptions to nodes. Altering parameter datatypes for flexibility between games. |
36
- | `MSBP` | ✅ | ❌ | Generating configuration files for use with `MSBT` files. |
31
+ The following is supported:
37
32
 
33
+ | Format | Read | Write | Configuration Support |
34
+ |--------|------|-------|---------------------------------------------------------------------------------------------------------------------------------------------|
35
+ | `MSBT` | ✅ | ✅ | Support for full attribute (`ATR1`) decoding. Tag decoding 1:1 matching with official Nintendo `.mstxt` file format. |
36
+ | `MSBF` | ✅ | ✅ | Support for custom node definitions, attaching names and descriptions to nodes. Altering parameter datatypes for flexibility between games. |
37
+ | `MSBP` | ✅ | ❌ | Generating configuration files for use with `MSBT` files. |
38
38
 
39
39
  This library is designed to support LMS revision >=3.0. These include files generated for late **Wii** titles,
40
40
  **Nintendo 3DS**, **Wii U**, **Mobile**, **Nintendo Switch** and **Nintendo Switch 2** games.
@@ -7,13 +7,13 @@
7
7
  PyLibMS is a library built in Python 3.10 and above for reading and writing of libMessageStudio (LMS) proprietary file
8
8
  formats from Nintendo.
9
9
 
10
- The following is supported
11
- | Format | Read | Write | Configuration Support |
12
- |-------------|------|-------|-------------------------------------------------------------------------------------------|
13
- | `MSBT` | ✅ | ✅ | Support for full attribute (`ATR1`) decoding. Tag decoding 1:1 with the official Nintendo `.mstxt` file format. |
14
- | `MSBF` | ✅ | ✅ | Support for custom node definitions, attaching names and descriptions to nodes. Altering parameter datatypes for flexibility between games. |
15
- | `MSBP` | ✅ | ❌ | Generating configuration files for use with `MSBT` files. |
10
+ The following is supported:
16
11
 
12
+ | Format | Read | Write | Configuration Support |
13
+ |--------|------|-------|---------------------------------------------------------------------------------------------------------------------------------------------|
14
+ | `MSBT` | ✅ | ✅ | Support for full attribute (`ATR1`) decoding. Tag decoding 1:1 matching with official Nintendo `.mstxt` file format. |
15
+ | `MSBF` | ✅ | ✅ | Support for custom node definitions, attaching names and descriptions to nodes. Altering parameter datatypes for flexibility between games. |
16
+ | `MSBP` | ✅ | ❌ | Generating configuration files for use with `MSBT` files. |
17
17
 
18
18
  This library is designed to support LMS revision >=3.0. These include files generated for late **Wii** titles,
19
19
  **Nintendo 3DS**, **Wii U**, **Mobile**, **Nintendo Switch** and **Nintendo Switch 2** games.
@@ -1,4 +1,6 @@
1
1
  # Common constant definitions between files
2
+ LMS_MINIMUM_VERSION = 3
3
+
2
4
  PADDING_BYTE = b"\x00"
3
5
 
4
6
  SIZE_OFFSET = 0x12
@@ -3,7 +3,7 @@ from lms.common.lms_constants import (
3
3
  BIG_ENDIAN_BOM,
4
4
  SECTION_DATA_START,
5
5
  LITTLE_ENDIAN_BOM,
6
- SIZE_OFFSET,
6
+ SIZE_OFFSET, LMS_MINIMUM_VERSION,
7
7
  )
8
8
  from lms.common.lms_fileinfo import LMS_FileInfo
9
9
  from lms.fileio.encoding import FileEncoding
@@ -29,7 +29,7 @@ def read_file_info(reader: FileReader, expected_magic: str) -> LMS_FileInfo:
29
29
 
30
30
  version = reader.read_uint8()
31
31
 
32
- if version < 3:
32
+ if version < LMS_MINIMUM_VERSION:
33
33
  raise lms_exceptions.LMS_UnsupportedFileVersionError(
34
34
  "Only version 3+ files are supported!"
35
35
  )
@@ -1,5 +1,5 @@
1
1
  from types import MappingProxyType
2
- from typing import Callable, Iterator
2
+ from typing import Iterator
3
3
 
4
4
  from lms.flowchart.definitions.node import LMS_EntryNode, LMS_BranchNode, LMS_BaseNode
5
5
 
@@ -7,12 +7,9 @@ from lms.flowchart.definitions.node import LMS_EntryNode, LMS_BranchNode, LMS_Ba
7
7
  class LMS_Flowchart:
8
8
  """Class that represents a flowchart in a MSBF file."""
9
9
 
10
- def __init__(
11
- self, entry_point: LMS_EntryNode, id_generator: Callable[[], int]
12
- ) -> None:
10
+ def __init__(self, entry_point: LMS_EntryNode) -> None:
13
11
  self._entry_node = entry_point
14
12
  self._nodes: dict[int, LMS_BaseNode] = {}
15
- self._id_generator = id_generator
16
13
 
17
14
  if entry_point is not None:
18
15
  for node in entry_point.get_descendents():
@@ -185,7 +185,7 @@ class LMS_MessageNode(LMS_BaseNode):
185
185
 
186
186
  @property
187
187
  def msbt(self) -> MSBT | None:
188
- """MSBT instance the node references. Optional, and must be set with `set_msbt` or by providing a MSBT file on reading."""
188
+ """MSBT instance the node references. Optional, is set by providing a MSBT file on reading."""
189
189
  return self._msbt
190
190
 
191
191
  @property
@@ -313,26 +313,33 @@ class LMS_BranchNode(LMS_BaseNode):
313
313
  case_map.setdefault(branch, []).append(case)
314
314
 
315
315
  if self.definition is not None:
316
- if self.definition.case_options:
316
+ definition: NodeDefinition = self.definition
317
+
318
+ if definition.case_options:
317
319
  for case, branch in self._branches.items():
318
- case_message = self.definition.case_options.get(
319
- case, f"Case {case}"
320
+ case_format = definition.case_options.get(
321
+ case, None,
320
322
  )
321
323
 
322
- result.append(((case,), case_message, branch))
324
+ if case_format is None:
325
+ formatted_message = f"Case {case}"
326
+ else:
327
+ formatted_message = self._format_case_message(case, case_format)
328
+
329
+ result.append(((case,), formatted_message, branch))
323
330
 
324
331
  return result
325
332
 
326
- if self.definition.case_format is not None:
333
+ if definition.case_format is not None:
327
334
  if compress_shared_references:
328
335
  for branch, cases in case_map.items():
329
336
  case_message = self._format_case_message(
330
- merge_shared_cases(cases)
337
+ merge_shared_cases(cases), definition.case_format
331
338
  )
332
339
  result.append((tuple(cases), case_message, branch))
333
340
  else:
334
341
  for case, branch in self._branches.items():
335
- case_message = self._format_case_message(case)
342
+ case_message = self._format_case_message(case, definition.case_format)
336
343
  result.append(((case,), case_message, branch))
337
344
 
338
345
  return result
@@ -352,8 +359,8 @@ class LMS_BranchNode(LMS_BaseNode):
352
359
 
353
360
  return result
354
361
 
355
- def _format_case_message(self, case_prefix: str):
356
- prefix = string.Template(self._definition.case_format)
362
+ def _format_case_message(self, case_prefix: str, case_format: str):
363
+ prefix = string.Template(case_format)
357
364
 
358
365
  # Unpack dict for formatting $case and $param formats
359
366
  # Allows inserting parameter values dynamically into the case message
@@ -22,14 +22,17 @@ class MSBF:
22
22
  """
23
23
 
24
24
  MAGIC = "MsgFlwBn"
25
-
26
25
  DEFAULT_SLOT_COUNT = 59
27
26
 
28
27
  def __init__(
29
28
  self, info: LMS_FileInfo | None = None, flowcharts: list[LMS_Flowchart] = None
30
29
  ):
31
30
  self._info = info if info is not None else LMS_FileInfo()
32
- self._shared_references: dict[int, LMS_BaseNode] = {}
31
+
32
+ # While unlikely to vary, and have yet to find a file that changes the slot count (unlike MSBT)
33
+ # it is wiser to store an editable attribute just in case a game decides to vary the amount.
34
+ self.slot_count = MSBF.DEFAULT_SLOT_COUNT
35
+
33
36
  self._flowcharts = flowcharts or []
34
37
  self._nodes: dict[int, LMS_BaseNode] = {}
35
38
  self._global_node_id = 0
@@ -69,11 +72,6 @@ class MSBF:
69
72
  """Global node map for the file."""
70
73
  return MappingProxyType(self._nodes)
71
74
 
72
- @property
73
- def shared_references(self) -> MappingProxyType[int, LMS_BaseNode]:
74
- """Shared references of nodes between flowcharts."""
75
- return self._shared_references
76
-
77
75
  @property
78
76
  def flowcharts(self) -> MappingProxyType[str, LMS_Flowchart]:
79
77
  """The flowcharts of the MSBF instance."""
@@ -81,12 +79,20 @@ class MSBF:
81
79
  {flowchart.name: flowchart for flowchart in self._flowcharts}
82
80
  )
83
81
 
82
+ def get_flowchart_references(self, node_id: int) -> set[LMS_Flowchart]:
83
+ """
84
+ Determines which flowcharts contain this node.
85
+
86
+ :param node_id: The node to find references for.
87
+ """
88
+ return {flowchart for flowchart in self._flowcharts if node_id in flowchart.nodes}
89
+
84
90
  def register_node(self, node: LMS_BaseNode, flowchart: LMS_Flowchart = None) -> None:
85
91
  """
86
92
  Registers a node to the MSBF instance.
87
93
 
88
94
  :param node: The node to register.
89
- :param flowchart: optional argument that registers the node to that flowchart.
95
+ :param flowchart: Optional argument that registers the node to that flowchart.
90
96
  """
91
97
  if not isinstance(node, LMS_BaseNode):
92
98
  raise TypeError(
@@ -98,7 +104,7 @@ class MSBF:
98
104
  f"Node of ID '{node.id}' is already registered to this flowchart!"
99
105
  )
100
106
 
101
- node.id = self._id_generator()
107
+ node.id = self._generate_next_id()
102
108
  self._nodes[node.id] = node
103
109
 
104
110
  if flowchart is not None:
@@ -143,12 +149,11 @@ class MSBF:
143
149
  if entry_point is None:
144
150
  entry_point = LMS_EntryNode(self._generate_next_id(), flowchart_name)
145
151
 
146
- flowchart = LMS_Flowchart(entry_point, self._generate_next_id)
152
+ flowchart = LMS_Flowchart(entry_point)
147
153
  self._flowcharts.append(flowchart)
148
154
 
149
155
  for flowchart in self._flowcharts:
150
156
  for node_id, node in flowchart.nodes.items():
151
- self._shared_references.setdefault(node.id, set()).add(flowchart)
152
157
  self._nodes[node_id] = node
153
158
 
154
159
  self._nodes = dict(sorted(self._nodes.items()))
@@ -59,13 +59,12 @@ def read_msbf(
59
59
  file_info = read_file_info(reader, MSBF.MAGIC)
60
60
 
61
61
  msbf = MSBF(file_info)
62
-
63
62
  for magic, size in read_section_data(reader, file_info.section_count):
64
63
  match magic:
65
64
  case "FLW3":
66
65
  entry_nodes = read_flw3(reader, config, msbt)
67
66
  case "FEN1":
68
- labels, _ = read_labels(reader)
67
+ labels, slot_count = read_labels(reader)
69
68
  case "REF1":
70
69
  raise LMS_UnsupportedSectionError("""REF1 was found in the MSBF file! Please report this file as an issue
71
70
  in the PyLibMS repository https://github.com/AbdyyEee/PylibMS""")
@@ -74,6 +73,7 @@ def read_msbf(
74
73
  entry_nodes[i].flowchart_name = label
75
74
  msbf.add_flowchart(label, entry_nodes[i])
76
75
 
76
+ msbf.slot_count = slot_count
77
77
  return msbf
78
78
 
79
79
 
@@ -115,12 +115,6 @@ def write_msbf(file: MSBF) -> bytes:
115
115
  # Saved here so ids aren't mutated in the current state
116
116
  stream_ids = {node: stream_id for stream_id, node in enumerate(file.nodes.values())}
117
117
 
118
- for chart in file:
119
- if amount := len(chart.get_dangling_nodes()):
120
- raise LMS_Error(
121
- f"Unable to write the flowchart '{chart.name}'! There are {amount} dangling nodes."
122
- )
123
-
124
118
  index_map = {
125
119
  flowchart.name: stream_ids[flowchart.entry_point] for flowchart in file
126
120
  }
@@ -131,7 +125,7 @@ def write_msbf(file: MSBF) -> bytes:
131
125
  "FEN1",
132
126
  write_labels,
133
127
  list(file.flowcharts.keys()),
134
- MSBF.DEFAULT_SLOT_COUNT,
128
+ file.slot_count,
135
129
  index_map,
136
130
  )
137
131
  write_file_size(writer)
@@ -218,7 +218,7 @@ class TitleConfig:
218
218
  game = parsed_content.get("game")
219
219
 
220
220
  attribute_configs = {}
221
- for config in parsed_content[cls.ATTR_KEY]:
221
+ for config in parsed_content.get(cls.ATTR_KEY, []):
222
222
  definitions = [
223
223
  ValueDefinition.from_dict(value_def)
224
224
  for value_def in config["definitions"]
@@ -227,21 +227,28 @@ class TitleConfig:
227
227
  config["name"], config.get("description", ""), definitions
228
228
  )
229
229
 
230
+ tag_config = None
230
231
  tag_definitions: dict[int, list[TagDefinition]] = {}
231
- group_map = parsed_content[cls.TAG_KEY]["groups"]
232
+ tag_content = parsed_content.get(cls.TAG_KEY, None)
232
233
 
233
- for tag_def in parsed_content[cls.TAG_KEY]["tags"]:
234
- definition = TagDefinition.from_dict(tag_def, group_map)
235
- if definition.group_id not in tag_definitions:
236
- tag_definitions[definition.group_id] = []
237
- tag_definitions[definition.group_id].append(definition)
234
+ if tag_content is not None:
235
+ group_map = tag_content["groups"]
238
236
 
239
- tag_config = TagConfig(group_map, tag_definitions)
237
+ for tag_def in tag_content["tags"]:
238
+ definition = TagDefinition.from_dict(tag_def, group_map)
239
+ if definition.group_id not in tag_definitions:
240
+ tag_definitions[definition.group_id] = []
241
+ tag_definitions[definition.group_id].append(definition)
242
+
243
+ tag_config = TagConfig(group_map, tag_definitions)
240
244
 
241
245
  branch_nodes: dict[int, NodeDefinition | tuple[NodeDefinition, ...]] = {}
242
246
  event_nodes: dict[int, tuple[NodeDefinition, ...]] = {}
243
247
 
244
- for definition in parsed_content[cls.NODE_KEY]["branch"]:
248
+ node_data = parsed_content.get(cls.NODE_KEY, [])
249
+ branch_data, event_data = node_data.get("branch", []), node_data.get("event", [])
250
+
251
+ for definition in branch_data:
245
252
  node_id = definition["id"]
246
253
 
247
254
  node_definition = NodeDefinition.from_dict(
@@ -259,7 +266,7 @@ class TitleConfig:
259
266
  else:
260
267
  branch_nodes[node_id] = (existing, node_definition)
261
268
 
262
- for definition in parsed_content[cls.NODE_KEY]["event"]:
269
+ for definition in event_data:
263
270
  node_id = definition["id"]
264
271
 
265
272
  node_definition = NodeDefinition.from_dict(
@@ -7,7 +7,7 @@ include = ["lms*"]
7
7
 
8
8
  [project]
9
9
  name = "PyLibMS"
10
- version = "4.3.1"
10
+ version = "4.3.3"
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
File without changes