PyLibMS 4.2.0__py3-none-any.whl → 4.2.2__py3-none-any.whl
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.
- lms/flowchart/definitions/flowchart.py +1 -0
- lms/flowchart/definitions/node.py +112 -33
- lms/flowchart/definitions/node_type.py +5 -5
- lms/flowchart/flw3.py +340 -336
- lms/titleconfig/definitions/nodes.py +8 -0
- lms/titleconfig/presets/Tomodachi Life NA-EU.yaml +6 -1
- {pylibms-4.2.0.dist-info → pylibms-4.2.2.dist-info}/METADATA +2 -2
- {pylibms-4.2.0.dist-info → pylibms-4.2.2.dist-info}/RECORD +11 -11
- {pylibms-4.2.0.dist-info → pylibms-4.2.2.dist-info}/WHEEL +0 -0
- {pylibms-4.2.0.dist-info → pylibms-4.2.2.dist-info}/licenses/LICENSE +0 -0
- {pylibms-4.2.0.dist-info → pylibms-4.2.2.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import string
|
|
3
4
|
from collections import deque
|
|
4
5
|
from types import MappingProxyType
|
|
5
6
|
from typing import Generator
|
|
@@ -27,15 +28,14 @@ class LMS_BaseNode:
|
|
|
27
28
|
id: int | None,
|
|
28
29
|
node_type: LMS_NodeType,
|
|
29
30
|
parameter_type: LMS_NodeParameterType,
|
|
30
|
-
parameter_value: LMS_NodeParameter | None
|
|
31
|
-
stream_next_id: int | None = None):
|
|
31
|
+
parameter_value: LMS_NodeParameter | None):
|
|
32
32
|
self.id = id
|
|
33
33
|
self._node_type = node_type
|
|
34
34
|
|
|
35
35
|
self._parameter_type = parameter_type
|
|
36
36
|
self._parameter_value = parameter_value
|
|
37
37
|
|
|
38
|
-
self._stream_next_id: int | None =
|
|
38
|
+
self._stream_next_id: int | None = None
|
|
39
39
|
self._next_node: LMS_BaseNode | None = None
|
|
40
40
|
|
|
41
41
|
@property
|
|
@@ -76,14 +76,6 @@ class LMS_BaseNode:
|
|
|
76
76
|
if not isinstance(value, self._parameter_type.builtin_type):
|
|
77
77
|
raise TypeError(f"Wrong value provided! Expected {self._parameter_type.builtin_type} got {type(value)}")
|
|
78
78
|
|
|
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
79
|
match self._parameter_type:
|
|
88
80
|
case LMS_NodeParameterType.STRING:
|
|
89
81
|
self._parameter_value = value
|
|
@@ -92,7 +84,8 @@ class LMS_BaseNode:
|
|
|
92
84
|
self._parameter_value = value
|
|
93
85
|
case _:
|
|
94
86
|
for i, parameter in enumerate(value):
|
|
95
|
-
verify_number_from_datatype(parameter,
|
|
87
|
+
verify_number_from_datatype(parameter,
|
|
88
|
+
self._parameter_type.sliced_datatype[self._parameter_type][i])
|
|
96
89
|
|
|
97
90
|
self._parameter_value = value
|
|
98
91
|
|
|
@@ -100,8 +93,6 @@ class LMS_BaseNode:
|
|
|
100
93
|
"""Set the next node for the instance.
|
|
101
94
|
|
|
102
95
|
: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
96
|
self._next_node = node
|
|
106
97
|
return node
|
|
107
98
|
|
|
@@ -143,7 +134,6 @@ class LMS_MessageNode(LMS_BaseNode):
|
|
|
143
134
|
|
|
144
135
|
def __init__(self,
|
|
145
136
|
id: int | None,
|
|
146
|
-
stream_next_node: int,
|
|
147
137
|
msbt_index: int,
|
|
148
138
|
label_index: int,
|
|
149
139
|
msbt: MSBT | None = None
|
|
@@ -153,7 +143,6 @@ class LMS_MessageNode(LMS_BaseNode):
|
|
|
153
143
|
LMS_NodeType.MESSAGE,
|
|
154
144
|
LMS_NodeParameterType.NONE,
|
|
155
145
|
None,
|
|
156
|
-
stream_next_node
|
|
157
146
|
)
|
|
158
147
|
|
|
159
148
|
self.msbt_index = msbt_index
|
|
@@ -173,7 +162,7 @@ class LMS_MessageNode(LMS_BaseNode):
|
|
|
173
162
|
@classmethod
|
|
174
163
|
def new_msbt(cls, file_index: int, entry: MSBTEntry, msbt: MSBT):
|
|
175
164
|
"""
|
|
176
|
-
Creates a new LMS_MessageNode given a MSBT object.
|
|
165
|
+
Creates a new ``LMS_MessageNode`` given a MSBT object.
|
|
177
166
|
|
|
178
167
|
:param file_index: index of the MSBT file in a folder/archive.
|
|
179
168
|
:param entry: the MSBTEntry object to reference.
|
|
@@ -211,13 +200,20 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
211
200
|
LMS_NodeType.BRANCH,
|
|
212
201
|
parameter_type,
|
|
213
202
|
parameter_value,
|
|
214
|
-
NO_NEXT_NODE
|
|
215
203
|
)
|
|
216
204
|
|
|
217
205
|
self._condition_id = condition_id
|
|
218
206
|
self._branches: dict[int, LMS_BaseNode | None] = {}
|
|
219
207
|
self._definition = definition
|
|
220
208
|
|
|
209
|
+
def __repr__(self):
|
|
210
|
+
if self._definition is None:
|
|
211
|
+
return super().__repr__()
|
|
212
|
+
return f"{self._definition.name}({ {field.name: field.value for field in self._parameter_value} } {self.id}"
|
|
213
|
+
|
|
214
|
+
def set_next_node(self, node: LMS_BaseNode | None) -> LMS_BaseNode | None:
|
|
215
|
+
raise NotImplementedError("Use function set_branch_case to alter the flow of a branch node!")
|
|
216
|
+
|
|
221
217
|
@classmethod
|
|
222
218
|
def new(cls,
|
|
223
219
|
parameter_type: LMS_NodeParameterType,
|
|
@@ -275,6 +271,67 @@ class LMS_BranchNode(LMS_BaseNode):
|
|
|
275
271
|
"""The node branches."""
|
|
276
272
|
return MappingProxyType(self._branches)
|
|
277
273
|
|
|
274
|
+
def get_case_options(self, compress_shared_references: bool = False) -> list[
|
|
275
|
+
tuple[tuple[int, ...], str, LMS_BaseNode | None]]:
|
|
276
|
+
"""
|
|
277
|
+
Retrieves the cases from this node formatted with the proper messages.
|
|
278
|
+
|
|
279
|
+
If a definition is provided, and either ``case_format`` or ``case_options`` are provied, the resulting
|
|
280
|
+
message will utilize those values to format the message.
|
|
281
|
+
|
|
282
|
+
:param compress_shared_references: whether to compress shared references into one case.
|
|
283
|
+
"""
|
|
284
|
+
result, case_map = [], {}
|
|
285
|
+
|
|
286
|
+
for case, branch in self._branches.items():
|
|
287
|
+
case_map.setdefault(branch, []).append(case)
|
|
288
|
+
|
|
289
|
+
if self.definition is not None:
|
|
290
|
+
|
|
291
|
+
if self.definition.case_options:
|
|
292
|
+
for case, branch in self._branches.items():
|
|
293
|
+
case_message = self.definition.case_options.get(case, f"Case {case}")
|
|
294
|
+
|
|
295
|
+
result.append(((case,), case_message, branch))
|
|
296
|
+
|
|
297
|
+
return result
|
|
298
|
+
|
|
299
|
+
if self.definition.case_format is not None:
|
|
300
|
+
if compress_shared_references:
|
|
301
|
+
for branch, cases in case_map.items():
|
|
302
|
+
case_message = self._format_case_message(merge_shared_cases(cases))
|
|
303
|
+
result.append((tuple(cases), case_message, branch))
|
|
304
|
+
else:
|
|
305
|
+
for case, branch in self._branches.items():
|
|
306
|
+
case_message = self._format_case_message(case)
|
|
307
|
+
result.append(((case,), case_message, branch))
|
|
308
|
+
|
|
309
|
+
return result
|
|
310
|
+
|
|
311
|
+
if compress_shared_references:
|
|
312
|
+
for branch, cases in case_map.items():
|
|
313
|
+
case_message = f"Case {cases[0]}" if len(cases) == 1 else f"Cases {merge_shared_cases(cases)}"
|
|
314
|
+
result.append((tuple(cases), case_message, branch))
|
|
315
|
+
else:
|
|
316
|
+
for case, branch in self._branches.items():
|
|
317
|
+
case_message = f"Case {case}"
|
|
318
|
+
result.append((case,), case_message, branch)
|
|
319
|
+
|
|
320
|
+
return result
|
|
321
|
+
|
|
322
|
+
def _format_case_message(self, case_prefix: str):
|
|
323
|
+
prefix = string.Template(self._definition.case_format)
|
|
324
|
+
|
|
325
|
+
# Unpack dict for formatting $case and $param formats
|
|
326
|
+
# Allows inserting parameter values dynamically into the case message
|
|
327
|
+
string_map = {}
|
|
328
|
+
for param_definition in self.definition.parameter_definitions:
|
|
329
|
+
string_map[param_definition.name] = self.parameter_value[
|
|
330
|
+
param_definition.name].value
|
|
331
|
+
|
|
332
|
+
string_map["case"] = case_prefix
|
|
333
|
+
return prefix.substitute(**string_map)
|
|
334
|
+
|
|
278
335
|
def add_branch(self, node: LMS_BaseNode | None) -> None:
|
|
279
336
|
"""
|
|
280
337
|
Adds a branch to the node.
|
|
@@ -345,31 +402,34 @@ class LMS_EventNode(LMS_BaseNode):
|
|
|
345
402
|
parameter_type: LMS_NodeParameterType,
|
|
346
403
|
parameter_value: LMS_FieldMap | int | str | tuple[int, ...],
|
|
347
404
|
action_id: int,
|
|
348
|
-
stream_next_id: int | None = None,
|
|
349
405
|
definition: NodeDefinition | None = None,
|
|
350
406
|
):
|
|
351
407
|
super().__init__(
|
|
352
408
|
id,
|
|
353
409
|
LMS_NodeType.EVENT,
|
|
354
410
|
parameter_type,
|
|
355
|
-
parameter_value
|
|
356
|
-
stream_next_id)
|
|
411
|
+
parameter_value)
|
|
357
412
|
|
|
358
|
-
self.
|
|
413
|
+
self._event_id = action_id
|
|
359
414
|
self._definition = definition
|
|
360
415
|
|
|
416
|
+
def __repr__(self):
|
|
417
|
+
if self._definition is None:
|
|
418
|
+
return super().__repr__()
|
|
419
|
+
return f"{self._definition.name}({ {field.name: field.value for field in self._parameter_value} } {self.id}"
|
|
420
|
+
|
|
361
421
|
@classmethod
|
|
362
|
-
def new(cls, parameter_type: LMS_NodeParameterType, parameter_value: int | str | tuple[int, ...],
|
|
422
|
+
def new(cls, parameter_type: LMS_NodeParameterType, parameter_value: int | str | tuple[int, ...], event_id: int):
|
|
363
423
|
"""
|
|
364
424
|
Instantiates a new event node.
|
|
365
425
|
|
|
366
426
|
:param parameter_type: the parameter type.
|
|
367
427
|
:param parameter_value: the parameter value.
|
|
368
|
-
:param
|
|
428
|
+
:param event_id: the event_id of the node.
|
|
369
429
|
|
|
370
430
|
"""
|
|
371
431
|
verify_parameter_structure(parameter_value, parameter_type)
|
|
372
|
-
return cls(None, parameter_type, parameter_value,
|
|
432
|
+
return cls(None, parameter_type, parameter_value, event_id)
|
|
373
433
|
|
|
374
434
|
@classmethod
|
|
375
435
|
def new_from_definition(cls,
|
|
@@ -399,9 +459,9 @@ class LMS_EventNode(LMS_BaseNode):
|
|
|
399
459
|
return self._definition.name
|
|
400
460
|
|
|
401
461
|
@property
|
|
402
|
-
def
|
|
462
|
+
def event_id(self) -> int:
|
|
403
463
|
"""The identifier to determine which action to run."""
|
|
404
|
-
return self.
|
|
464
|
+
return self._event_id
|
|
405
465
|
|
|
406
466
|
|
|
407
467
|
class LMS_EntryNode(LMS_BaseNode):
|
|
@@ -409,13 +469,12 @@ class LMS_EntryNode(LMS_BaseNode):
|
|
|
409
469
|
Class that represents an entry node. Node that is at the start of a flowchart.
|
|
410
470
|
"""
|
|
411
471
|
|
|
412
|
-
def __init__(self, id: int,
|
|
472
|
+
def __init__(self, id: int, flowchart_name: str = ""):
|
|
413
473
|
super().__init__(
|
|
414
474
|
id,
|
|
415
475
|
LMS_NodeType.ENTRY,
|
|
416
476
|
LMS_NodeParameterType.NONE,
|
|
417
|
-
None
|
|
418
|
-
stream_next_id)
|
|
477
|
+
None)
|
|
419
478
|
|
|
420
479
|
self.flowchart_name = flowchart_name
|
|
421
480
|
|
|
@@ -425,13 +484,12 @@ class LMS_JumpNode(LMS_BaseNode):
|
|
|
425
484
|
Class that represents a jump node. Node that jumps to another flowchart.
|
|
426
485
|
"""
|
|
427
486
|
|
|
428
|
-
def __init__(self, id: int,
|
|
487
|
+
def __init__(self, id: int, unknown_short0a: int):
|
|
429
488
|
super().__init__(
|
|
430
489
|
id,
|
|
431
490
|
LMS_NodeType.JUMP,
|
|
432
491
|
LMS_NodeParameterType.NONE,
|
|
433
|
-
None
|
|
434
|
-
next_flowchart_id)
|
|
492
|
+
None)
|
|
435
493
|
|
|
436
494
|
self.next_flowchart: LMS_EntryNode | None = None
|
|
437
495
|
|
|
@@ -478,3 +536,24 @@ def verify_parameter_structure(value: int | tuple[int, ...] | str, parameter_typ
|
|
|
478
536
|
if isinstance(value, tuple) and len(value) != parameter_type.value_count:
|
|
479
537
|
raise node_exceptions.LMS_NodeMissingParameterValueError(
|
|
480
538
|
f"Parameter type {parameter_type} expects {parameter_type.value_count} parameter values, got {len(value)}!")
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def merge_shared_cases(cases: list[int]) -> str:
|
|
542
|
+
cases = sorted(cases)
|
|
543
|
+
|
|
544
|
+
result = []
|
|
545
|
+
start = cases[0]
|
|
546
|
+
end = cases[0]
|
|
547
|
+
|
|
548
|
+
for case in cases[1:]:
|
|
549
|
+
if case == end + 1:
|
|
550
|
+
end = case
|
|
551
|
+
continue
|
|
552
|
+
|
|
553
|
+
result.append(str(start) if start == end else f"{start}-{end}")
|
|
554
|
+
|
|
555
|
+
start = case
|
|
556
|
+
end = case
|
|
557
|
+
|
|
558
|
+
result.append(str(start) if start == end else f"{start}-{end}")
|
|
559
|
+
return ", ".join(result)
|
|
@@ -54,21 +54,21 @@ class LMS_NodeParameterType(IntEnum):
|
|
|
54
54
|
NONE = -1
|
|
55
55
|
|
|
56
56
|
@property
|
|
57
|
-
def sliced_datatype(self):
|
|
58
|
-
"""The parameter type split into the raw datatypes. Not valid for
|
|
57
|
+
def sliced_datatype(self) -> tuple[LMS_DataType, ...]:
|
|
58
|
+
"""The parameter type split into the raw datatypes. Not valid for STRING."""
|
|
59
59
|
if self in [
|
|
60
60
|
LMS_NodeParameterType.NONE,
|
|
61
|
-
LMS_NodeParameterType.PARAM_32_0,
|
|
62
|
-
LMS_NodeParameterType.PARAM_32_1,
|
|
63
61
|
LMS_NodeParameterType.STRING]:
|
|
64
62
|
raise TypeError(f"There is no sliced datatypes for {self} parameter types!")
|
|
65
63
|
|
|
66
64
|
return {
|
|
65
|
+
LMS_NodeParameterType.PARAM_32_0: (LMS_DataType.UINT32,),
|
|
67
66
|
LMS_NodeParameterType.PARAM_16_16: (LMS_DataType.UINT16, LMS_DataType.UINT16),
|
|
68
67
|
LMS_NodeParameterType.PARAM_16_8_8: (LMS_DataType.UINT16, LMS_DataType.UINT8, LMS_DataType.UINT8),
|
|
69
68
|
LMS_NodeParameterType.PARAM_8_8_16: (LMS_DataType.UINT8, LMS_DataType.UINT8, LMS_DataType.UINT16),
|
|
70
69
|
LMS_NodeParameterType.PARAM_8_8_8_8: (LMS_DataType.UINT8, LMS_DataType.UINT8, LMS_DataType.UINT8,
|
|
71
|
-
LMS_DataType.UINT8)
|
|
70
|
+
LMS_DataType.UINT8),
|
|
71
|
+
LMS_NodeParameterType.PARAM_32_1: (LMS_DataType.UINT32,)
|
|
72
72
|
}[self]
|
|
73
73
|
|
|
74
74
|
@property
|
lms/flowchart/flw3.py
CHANGED
|
@@ -1,336 +1,340 @@
|
|
|
1
|
-
from lms.common.field.io import read_field, write_field
|
|
2
|
-
from lms.common.field.lms_datatype import LMS_DataType
|
|
3
|
-
from lms.common.field.lms_field import LMS_FieldMap, LMS_Field
|
|
4
|
-
from lms.common.lms_exceptions import LMS_Error
|
|
5
|
-
from lms.fileio.io import FileReader, FileWriter
|
|
6
|
-
from lms.flowchart.definitions.node import LMS_MessageNode, LMS_EntryNode, \
|
|
7
|
-
LMS_BranchNode, \
|
|
8
|
-
LMS_EventNode, LMS_JumpNode, LMS_BaseNode, LMS_NodeParameter
|
|
9
|
-
from lms.flowchart.definitions.node_type import LMS_NodeType, LMS_NodeParameterType
|
|
10
|
-
from lms.message.msbt import MSBT
|
|
11
|
-
from lms.titleconfig.definitions.nodes import NodeConfig, NodeDefinition
|
|
12
|
-
|
|
13
|
-
NO_NEXT_NODE = 0xFFFF
|
|
14
|
-
FLW3_HEADER_SIZE = 16
|
|
15
|
-
NODE_SIZE = 16
|
|
16
|
-
|
|
17
|
-
VARIABLE_WIDTH_DATATYPES = (LMS_DataType.BOOL, LMS_DataType.LIST)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def read_flw3(reader: FileReader, config: NodeConfig | None, msbt: MSBT | None, ) -> tuple[
|
|
21
|
-
int, dict[int, LMS_EntryNode]]:
|
|
22
|
-
section_start = reader.tell()
|
|
23
|
-
node_count = reader.read_uint16()
|
|
24
|
-
branch_table_id_count = reader.read_uint16()
|
|
25
|
-
|
|
26
|
-
# Huge padding section skip
|
|
27
|
-
reader.skip(12)
|
|
28
|
-
|
|
29
|
-
nodes: list[LMS_BaseNode] = []
|
|
30
|
-
entry_nodes: list[LMS_EntryNode] = []
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
reader.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
reader.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
reader.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
reader.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
reader.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
reader.seek(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
reader.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
reader.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
for node in nodes
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
case LMS_NodeParameterType.
|
|
175
|
-
return reader.
|
|
176
|
-
case LMS_NodeParameterType.
|
|
177
|
-
return reader.
|
|
178
|
-
case LMS_NodeParameterType.
|
|
179
|
-
return reader.
|
|
180
|
-
case LMS_NodeParameterType.
|
|
181
|
-
return reader.
|
|
182
|
-
case
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
writer.write_uint16(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
for
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
writer.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
writer.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
writer.write_uint16(
|
|
244
|
-
|
|
245
|
-
writer.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
writer.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
writer.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
writer.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
writer.
|
|
277
|
-
writer.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
writer.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
writer.
|
|
323
|
-
writer.
|
|
324
|
-
case LMS_NodeParameterType.
|
|
325
|
-
writer.
|
|
326
|
-
writer.write_uint8(value[1])
|
|
327
|
-
writer.
|
|
328
|
-
case LMS_NodeParameterType.
|
|
329
|
-
writer.write_uint8(value[0])
|
|
330
|
-
writer.write_uint8(value[1])
|
|
331
|
-
writer.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
1
|
+
from lms.common.field.io import read_field, write_field
|
|
2
|
+
from lms.common.field.lms_datatype import LMS_DataType
|
|
3
|
+
from lms.common.field.lms_field import LMS_FieldMap, LMS_Field
|
|
4
|
+
from lms.common.lms_exceptions import LMS_Error
|
|
5
|
+
from lms.fileio.io import FileReader, FileWriter
|
|
6
|
+
from lms.flowchart.definitions.node import LMS_MessageNode, LMS_EntryNode, \
|
|
7
|
+
LMS_BranchNode, \
|
|
8
|
+
LMS_EventNode, LMS_JumpNode, LMS_BaseNode, LMS_NodeParameter
|
|
9
|
+
from lms.flowchart.definitions.node_type import LMS_NodeType, LMS_NodeParameterType
|
|
10
|
+
from lms.message.msbt import MSBT
|
|
11
|
+
from lms.titleconfig.definitions.nodes import NodeConfig, NodeDefinition
|
|
12
|
+
|
|
13
|
+
NO_NEXT_NODE = 0xFFFF
|
|
14
|
+
FLW3_HEADER_SIZE = 16
|
|
15
|
+
NODE_SIZE = 16
|
|
16
|
+
|
|
17
|
+
VARIABLE_WIDTH_DATATYPES = (LMS_DataType.BOOL, LMS_DataType.LIST)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def read_flw3(reader: FileReader, config: NodeConfig | None, msbt: MSBT | None, ) -> tuple[
|
|
21
|
+
int, dict[int, LMS_EntryNode]]:
|
|
22
|
+
section_start = reader.tell()
|
|
23
|
+
node_count = reader.read_uint16()
|
|
24
|
+
branch_table_id_count = reader.read_uint16()
|
|
25
|
+
|
|
26
|
+
# Huge padding section skip
|
|
27
|
+
reader.skip(12)
|
|
28
|
+
|
|
29
|
+
nodes: list[LMS_BaseNode] = []
|
|
30
|
+
entry_nodes: list[LMS_EntryNode] = []
|
|
31
|
+
|
|
32
|
+
next_id_map: dict[LMS_BaseNode, int | None] = {}
|
|
33
|
+
branch_metadata: dict[LMS_BranchNode, dict] = {}
|
|
34
|
+
|
|
35
|
+
for i in range(node_count):
|
|
36
|
+
node_data = reader.tell() + 8
|
|
37
|
+
node_type = LMS_NodeType(reader.read_uint8())
|
|
38
|
+
parameter_type = LMS_NodeParameterType(reader.read_int8())
|
|
39
|
+
|
|
40
|
+
reader.skip(2)
|
|
41
|
+
|
|
42
|
+
parameter_offset = reader.tell()
|
|
43
|
+
match node_type:
|
|
44
|
+
case LMS_NodeType.MESSAGE:
|
|
45
|
+
reader.skip(4)
|
|
46
|
+
|
|
47
|
+
stream_next_id = read_next_node_id(reader)
|
|
48
|
+
file_index = reader.read_uint16()
|
|
49
|
+
message_index = reader.read_uint16()
|
|
50
|
+
node = LMS_MessageNode(i, file_index, message_index, msbt)
|
|
51
|
+
reader.skip(2)
|
|
52
|
+
case LMS_NodeType.BRANCH:
|
|
53
|
+
# There is no next node id as the next node is determined by the branches
|
|
54
|
+
# Skip parameter value alongside that
|
|
55
|
+
reader.skip(2 + 4)
|
|
56
|
+
|
|
57
|
+
condition_id = reader.read_uint16()
|
|
58
|
+
case_count = reader.read_uint16()
|
|
59
|
+
table_index = reader.read_uint16()
|
|
60
|
+
end = reader.tell()
|
|
61
|
+
|
|
62
|
+
reader.seek(parameter_offset)
|
|
63
|
+
definition = get_config_definition(config, LMS_NodeType.BRANCH, parameter_type, condition_id)
|
|
64
|
+
parameter_value = evaluate_node_parameter(reader, section_start, parameter_type, definition)
|
|
65
|
+
|
|
66
|
+
node = LMS_BranchNode(i, parameter_type, parameter_value, condition_id, definition)
|
|
67
|
+
|
|
68
|
+
# Save the metadata for the branch node so that the branches itself can be set later on
|
|
69
|
+
branch_metadata[node] = case_count, table_index
|
|
70
|
+
reader.seek(end)
|
|
71
|
+
case LMS_NodeType.EVENT:
|
|
72
|
+
reader.skip(4)
|
|
73
|
+
|
|
74
|
+
stream_next_id = read_next_node_id(reader)
|
|
75
|
+
event_id = reader.read_uint16()
|
|
76
|
+
end = reader.tell() + 4
|
|
77
|
+
|
|
78
|
+
reader.seek(parameter_offset)
|
|
79
|
+
|
|
80
|
+
definition = get_config_definition(config, LMS_NodeType.EVENT, parameter_type, event_id)
|
|
81
|
+
parameter_value = evaluate_node_parameter(reader, section_start, parameter_type, definition)
|
|
82
|
+
|
|
83
|
+
node = LMS_EventNode(i, parameter_type, parameter_value, event_id, definition)
|
|
84
|
+
reader.seek(end)
|
|
85
|
+
case LMS_NodeType.ENTRY:
|
|
86
|
+
reader.seek(node_data)
|
|
87
|
+
stream_next_id = read_next_node_id(reader)
|
|
88
|
+
node = LMS_EntryNode(i)
|
|
89
|
+
entry_nodes.append(node)
|
|
90
|
+
reader.skip(6)
|
|
91
|
+
case LMS_NodeType.JUMP:
|
|
92
|
+
reader.seek(node_data)
|
|
93
|
+
stream_next_id = read_next_node_id(reader)
|
|
94
|
+
unknown_short0a = reader.read_uint16()
|
|
95
|
+
node = LMS_JumpNode(i, unknown_short0a)
|
|
96
|
+
reader.skip(4)
|
|
97
|
+
|
|
98
|
+
next_id_map[node] = stream_next_id
|
|
99
|
+
nodes.append(node)
|
|
100
|
+
|
|
101
|
+
node_map: dict[int, LMS_BaseNode] = {node.id: node for node in nodes}
|
|
102
|
+
branch_ids = [read_next_node_id(reader) for _ in range(branch_table_id_count)]
|
|
103
|
+
|
|
104
|
+
for node in nodes:
|
|
105
|
+
next_node_id = next_id_map[node]
|
|
106
|
+
|
|
107
|
+
if isinstance(node, LMS_BranchNode):
|
|
108
|
+
case_count, starting_index = branch_metadata[node]
|
|
109
|
+
|
|
110
|
+
# Slice the portion of IDs linked to this branch node and add the node objects themselves
|
|
111
|
+
for branch_id in branch_ids[starting_index:starting_index + case_count]:
|
|
112
|
+
if branch_id is None:
|
|
113
|
+
node.add_branch(None)
|
|
114
|
+
else:
|
|
115
|
+
node.add_branch(node_map[branch_id])
|
|
116
|
+
|
|
117
|
+
continue
|
|
118
|
+
elif isinstance(node, LMS_JumpNode):
|
|
119
|
+
node.next_flowchart = node_map[next_node_id]
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
if next_node_id is None:
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
node.set_next_node(node_map[next_node_id])
|
|
126
|
+
|
|
127
|
+
return node_count, entry_nodes
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def read_next_node_id(reader: FileReader) -> int | None:
|
|
131
|
+
return None if (id := reader.read_uint16()) == NO_NEXT_NODE else id
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_config_definition(config: NodeConfig,
|
|
135
|
+
node_type: LMS_NodeType.BRANCH | LMS_NodeType.EVENT,
|
|
136
|
+
parameter_type: LMS_NodeParameterType,
|
|
137
|
+
id: int) -> NodeDefinition:
|
|
138
|
+
return None if config is None else config.get_definition(id, node_type, parameter_type)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def evaluate_node_parameter(reader: FileReader,
|
|
142
|
+
section_start: int,
|
|
143
|
+
parameter_type: LMS_NodeParameterType,
|
|
144
|
+
definition: NodeDefinition | None) -> LMS_NodeParameter:
|
|
145
|
+
# How functions may utilize node parameters vary, such that they may cast values as they wish.
|
|
146
|
+
# By default, PyLibMS interprets the parameter types using signed integers for simplicity.
|
|
147
|
+
# If the user believes a better interpretation fits, then they may utilize a node configuration
|
|
148
|
+
# and cast individual parameters to any LMS_Datatype.
|
|
149
|
+
# This is my understanding of how node parameters work, but updating may be necessary as more research surfaces.
|
|
150
|
+
# -
|
|
151
|
+
# It also noted that by default LIST types are allocated a single byte for its index, it is unknown if
|
|
152
|
+
# some games may utilize a whole 4 byte stack as an index and/or 2 bytes depending on the parameter type.
|
|
153
|
+
# For now, by PyLibMS will read a single byte for its index until proven otherwise.
|
|
154
|
+
if definition is not None:
|
|
155
|
+
result = {}
|
|
156
|
+
|
|
157
|
+
if parameter_type is LMS_NodeParameterType.STRING:
|
|
158
|
+
param_definition = definition.parameter_definitions[0]
|
|
159
|
+
value = reader.read_string_offset(section_start)
|
|
160
|
+
|
|
161
|
+
return LMS_FieldMap({
|
|
162
|
+
param_definition.name: LMS_Field(value, param_definition)
|
|
163
|
+
})
|
|
164
|
+
|
|
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])
|
|
169
|
+
|
|
170
|
+
return LMS_FieldMap(result)
|
|
171
|
+
else:
|
|
172
|
+
# Default parameter reading
|
|
173
|
+
match parameter_type:
|
|
174
|
+
case LMS_NodeParameterType.PARAM_32_0 | LMS_NodeParameterType.PARAM_32_1:
|
|
175
|
+
return reader.read_uint32()
|
|
176
|
+
case LMS_NodeParameterType.PARAM_16_16:
|
|
177
|
+
return reader.read_uint16(), reader.read_uint16()
|
|
178
|
+
case LMS_NodeParameterType.PARAM_16_8_8:
|
|
179
|
+
return reader.read_uint16(), reader.read_uint8(), reader.read_uint8()
|
|
180
|
+
case LMS_NodeParameterType.PARAM_8_8_16:
|
|
181
|
+
return reader.read_uint8(), reader.read_uint8(), reader.read_uint16()
|
|
182
|
+
case LMS_NodeParameterType.PARAM_8_8_8_8:
|
|
183
|
+
return reader.read_uint8(), reader.read_uint8(), reader.read_uint8(), reader.read_uint8()
|
|
184
|
+
case LMS_NodeParameterType.STRING:
|
|
185
|
+
return reader.read_string_offset(section_start)
|
|
186
|
+
case _:
|
|
187
|
+
raise LMS_Error(f"Invalid parameter type of '{parameter_type}'!")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def write_flw3(writer: FileWriter, nodes: list[LMS_BaseNode], stream_ids: dict[LMS_BaseNode, int]) -> None:
|
|
191
|
+
node_count = len(nodes)
|
|
192
|
+
|
|
193
|
+
branch_table: list[int] = []
|
|
194
|
+
string_table: list[str] = []
|
|
195
|
+
string_offsets: list[int] = []
|
|
196
|
+
|
|
197
|
+
writer.write_uint16(node_count)
|
|
198
|
+
branch_id_offset = writer.tell()
|
|
199
|
+
|
|
200
|
+
# Placeholder value for branch table count
|
|
201
|
+
writer.write_uint16(0)
|
|
202
|
+
|
|
203
|
+
writer.write_bytes(b'\x00' * 12)
|
|
204
|
+
for node in nodes:
|
|
205
|
+
writer.write_int8(node.type)
|
|
206
|
+
writer.write_int8(node.parameter_type)
|
|
207
|
+
|
|
208
|
+
match node:
|
|
209
|
+
case LMS_MessageNode():
|
|
210
|
+
writer.write_bytes(b"\x00" * 2)
|
|
211
|
+
writer.write_bytes(b"\x00" * 4)
|
|
212
|
+
|
|
213
|
+
write_next_node_id(writer, None if node.next_node is None else stream_ids[node.next_node])
|
|
214
|
+
|
|
215
|
+
writer.write_uint16(node.msbt_index)
|
|
216
|
+
writer.write_uint16(node.label_index)
|
|
217
|
+
|
|
218
|
+
writer.write_bytes(b"\x00\x00")
|
|
219
|
+
case LMS_BranchNode():
|
|
220
|
+
writer.write_bytes(b"\x00\x00")
|
|
221
|
+
|
|
222
|
+
if node.parameter_type is LMS_NodeParameterType.STRING:
|
|
223
|
+
string_offsets.append(writer.tell())
|
|
224
|
+
|
|
225
|
+
if node.definition is not None:
|
|
226
|
+
parameter_definition = node.definition.parameter_definitions[0]
|
|
227
|
+
field = node.parameter_value[parameter_definition.name]
|
|
228
|
+
string_table.append(field.value)
|
|
229
|
+
else:
|
|
230
|
+
string_table.append(node.parameter_value)
|
|
231
|
+
|
|
232
|
+
writer.write_uint32(0)
|
|
233
|
+
else:
|
|
234
|
+
write_node_parameter(writer, node.parameter_value, node.parameter_type)
|
|
235
|
+
|
|
236
|
+
starting_index = len(branch_table)
|
|
237
|
+
for branch in node.branches.values():
|
|
238
|
+
if branch is None:
|
|
239
|
+
branch_table.append(NO_NEXT_NODE)
|
|
240
|
+
else:
|
|
241
|
+
branch_table.append(stream_ids[branch])
|
|
242
|
+
|
|
243
|
+
writer.write_uint16(NO_NEXT_NODE)
|
|
244
|
+
|
|
245
|
+
writer.write_uint16(node.condition_id)
|
|
246
|
+
writer.write_uint16(node.case_count)
|
|
247
|
+
writer.write_uint16(starting_index)
|
|
248
|
+
case LMS_EventNode():
|
|
249
|
+
writer.write_bytes(b"\x00\x00")
|
|
250
|
+
|
|
251
|
+
if node.parameter_type is LMS_NodeParameterType.STRING:
|
|
252
|
+
string_offsets.append(writer.tell())
|
|
253
|
+
|
|
254
|
+
if isinstance(node.parameter_value, LMS_FieldMap):
|
|
255
|
+
parameter_definition = node.definition.parameter_definitions[0]
|
|
256
|
+
field = node.parameter_value[parameter_definition.name]
|
|
257
|
+
string_table.append(field.value)
|
|
258
|
+
else:
|
|
259
|
+
string_table.append(node.parameter_value)
|
|
260
|
+
|
|
261
|
+
writer.write_uint32(0)
|
|
262
|
+
else:
|
|
263
|
+
write_node_parameter(writer, node.parameter_value, node.parameter_type)
|
|
264
|
+
|
|
265
|
+
write_next_node_id(writer, None if node.next_node is None else stream_ids[node.next_node])
|
|
266
|
+
writer.write_uint16(node.event_id)
|
|
267
|
+
|
|
268
|
+
writer.write_bytes(b"\x00" * 4)
|
|
269
|
+
case LMS_EntryNode():
|
|
270
|
+
writer.write_bytes(b"\x00" * 2)
|
|
271
|
+
writer.write_bytes(b"\x00" * 4)
|
|
272
|
+
|
|
273
|
+
write_next_node_id(writer, None if node.next_node is None else stream_ids[node.next_node])
|
|
274
|
+
writer.skip(6)
|
|
275
|
+
case LMS_JumpNode():
|
|
276
|
+
writer.write_bytes(b"\x00" * 2)
|
|
277
|
+
writer.write_bytes(b"\x00" * 4)
|
|
278
|
+
|
|
279
|
+
write_next_node_id(writer, stream_ids[node.next_flowchart])
|
|
280
|
+
writer.write_uint16(node.unknown_short0a)
|
|
281
|
+
writer.skip(4)
|
|
282
|
+
|
|
283
|
+
for node_id in branch_table:
|
|
284
|
+
writer.write_uint16(node_id)
|
|
285
|
+
|
|
286
|
+
string_offset = FLW3_HEADER_SIZE + (NODE_SIZE * node_count) + (2 * len(branch_table))
|
|
287
|
+
|
|
288
|
+
for offset, string in zip(string_offsets, string_table):
|
|
289
|
+
write_offset = writer.tell()
|
|
290
|
+
|
|
291
|
+
writer.seek(offset)
|
|
292
|
+
writer.write_uint32(string_offset)
|
|
293
|
+
|
|
294
|
+
writer.seek(write_offset)
|
|
295
|
+
writer.write_encoded_string(string)
|
|
296
|
+
|
|
297
|
+
string_offset += len(string) * writer.encoding.width + len(writer.encoding.terminator)
|
|
298
|
+
|
|
299
|
+
end = writer.tell()
|
|
300
|
+
writer.seek(branch_id_offset)
|
|
301
|
+
writer.write_uint16(len(branch_table))
|
|
302
|
+
writer.seek(end)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def write_node_parameter(writer: FileWriter,
|
|
306
|
+
value: LMS_NodeParameter,
|
|
307
|
+
parameter_type: LMS_NodeParameterType) -> None:
|
|
308
|
+
if isinstance(value, LMS_FieldMap):
|
|
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)
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
match parameter_type:
|
|
319
|
+
case LMS_NodeParameterType.PARAM_32_0 | LMS_NodeParameterType.PARAM_32_1:
|
|
320
|
+
writer.write_uint32(value)
|
|
321
|
+
case LMS_NodeParameterType.PARAM_16_16:
|
|
322
|
+
writer.write_uint16(value[0])
|
|
323
|
+
writer.write_uint16(value[1])
|
|
324
|
+
case LMS_NodeParameterType.PARAM_16_8_8:
|
|
325
|
+
writer.write_uint16(value[0])
|
|
326
|
+
writer.write_uint8(value[1])
|
|
327
|
+
writer.write_uint8(value[2])
|
|
328
|
+
case LMS_NodeParameterType.PARAM_8_8_16:
|
|
329
|
+
writer.write_uint8(value[0])
|
|
330
|
+
writer.write_uint8(value[1])
|
|
331
|
+
writer.write_uint16(value[2])
|
|
332
|
+
case LMS_NodeParameterType.PARAM_8_8_8_8:
|
|
333
|
+
writer.write_uint8(value[0])
|
|
334
|
+
writer.write_uint8(value[1])
|
|
335
|
+
writer.write_uint8(value[2])
|
|
336
|
+
writer.write_uint8(value[3])
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def write_next_node_id(writer: FileWriter, id: int | None) -> None:
|
|
340
|
+
writer.write_uint16(NO_NEXT_NODE if id is None else id)
|
|
@@ -56,6 +56,7 @@ class NodeDefinition:
|
|
|
56
56
|
parameter_type: LMS_NodeParameterType
|
|
57
57
|
parameter_definitions: tuple[ValueDefinition, ...]
|
|
58
58
|
case_format: str | None
|
|
59
|
+
case_options: dict[int, str]
|
|
59
60
|
next_node_dependency: bool = False
|
|
60
61
|
|
|
61
62
|
@classmethod
|
|
@@ -95,6 +96,12 @@ class NodeDefinition:
|
|
|
95
96
|
converted_parameters.append(definition)
|
|
96
97
|
|
|
97
98
|
# case_format is only for branch nodesa
|
|
99
|
+
if "case_format" in data and "case_options" in data:
|
|
100
|
+
raise ValueError("There may only be one of case_format and options in the definition.")
|
|
101
|
+
|
|
102
|
+
if (options := data.get("case_options", None)) is not None and type == LMS_NodeType.EVENT:
|
|
103
|
+
raise ValueError("There may only be options for branch nodes!")
|
|
104
|
+
|
|
98
105
|
if (case_format := data.get("case_format", None)) is not None and type == LMS_NodeType.EVENT:
|
|
99
106
|
raise ValueError("There may only be case_format for branch nodes!")
|
|
100
107
|
|
|
@@ -106,5 +113,6 @@ class NodeDefinition:
|
|
|
106
113
|
parameter_type=parameter_type,
|
|
107
114
|
parameter_definitions=converted_parameters,
|
|
108
115
|
case_format=case_format,
|
|
116
|
+
case_options=options,
|
|
109
117
|
next_node_dependency=data.get("next_node_dependency", False)
|
|
110
118
|
)
|
|
@@ -8,7 +8,7 @@ node_definitions:
|
|
|
8
8
|
- name: exp
|
|
9
9
|
- id: 1
|
|
10
10
|
name: SwitchVariable
|
|
11
|
-
case_format: "var ==
|
|
11
|
+
case_format: "var == $case"
|
|
12
12
|
parameter_type: string
|
|
13
13
|
parameters:
|
|
14
14
|
- name: var
|
|
@@ -29,6 +29,11 @@ node_definitions:
|
|
|
29
29
|
parameter_type: string
|
|
30
30
|
parameters:
|
|
31
31
|
- name: var
|
|
32
|
+
- id: 82
|
|
33
|
+
name: SetFlag
|
|
34
|
+
parameter_type: p32_1
|
|
35
|
+
parameters:
|
|
36
|
+
- name: flag
|
|
32
37
|
- id: 90
|
|
33
38
|
name: LoadSEGroup
|
|
34
39
|
parameter_type: string
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyLibMS
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.2
|
|
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
|
|
@@ -107,7 +107,7 @@ pip install -e .
|
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
# Credits & Sources
|
|
110
|
-
|
|
110
|
+
* [SS-Decomp](https://github.com/zeldaret/ss): Referenced some parts of their MSBF decomp when adding the format to [Nintendo-File-Formats](https://nintendo-formats.com) and this library.
|
|
111
111
|
* [Nintendo-File-Formats](https://nintendo-formats.com) by Kinnay: For existing information on the MSBT and MSBP file
|
|
112
112
|
formats.
|
|
113
113
|
* [Trippixyz](https://github.com/Trippixyz): For helping me get started general decompilation of the formats and general
|
|
@@ -13,14 +13,14 @@ lms/common/stream/section.py,sha256=WAqqj4FlUS8puB0iAnjzwNRtJ0YdHVK_DSrj051TZwU,
|
|
|
13
13
|
lms/fileio/encoding.py,sha256=8OmDxpVLu613TH6xb47Szb3bo7gqNeCh2xJjnNI3Ds0,1283
|
|
14
14
|
lms/fileio/io.py,sha256=ATr1s_m420KngK8HYYz5dSFkR8EhLJbk7U8-l_8xSss,6636
|
|
15
15
|
lms/flowchart/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
lms/flowchart/flw3.py,sha256=
|
|
16
|
+
lms/flowchart/flw3.py,sha256=JFQfmppKAUMcQvO6fKCRPpx335r_xcUV92OrrnFiBQU,14617
|
|
17
17
|
lms/flowchart/msbf.py,sha256=-IehJxdWqZwNz50p_Z4oELUTMJHmKZs-d7Awx7e7sew,3196
|
|
18
18
|
lms/flowchart/msbfio.py,sha256=KrR5rUZ_3NK6UfwfPhbrFFFg9LeGuXGE6CRVqlXB75o,4366
|
|
19
19
|
lms/flowchart/definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
lms/flowchart/definitions/flowchart.py,sha256=
|
|
20
|
+
lms/flowchart/definitions/flowchart.py,sha256=H0DZSLboWvPvDHf7J-G_2Krnv-sE7eQ0ZIJOpGZYJs4,3555
|
|
21
21
|
lms/flowchart/definitions/lms_nodeexceptions.py,sha256=hlcqW6ERMVyAf2nqxaiSyFdXDfwthFADK79ggS_B3GU,181
|
|
22
|
-
lms/flowchart/definitions/node.py,sha256=
|
|
23
|
-
lms/flowchart/definitions/node_type.py,sha256=
|
|
22
|
+
lms/flowchart/definitions/node.py,sha256=aSYnvUHEcf7Bb0u23YbeC93H0DNpE3ZHgaBakC-uQno,19897
|
|
23
|
+
lms/flowchart/definitions/node_type.py,sha256=W6_whMPwTxC11YxV8x8YMAxHT9Psq8_XOITiUPwRCRg,4113
|
|
24
24
|
lms/message/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
lms/message/lms_messagetext.py,sha256=A6iXkumfhD_r55BnJAdWEWmYKiM-2iJre0v7oU3JcZs,5971
|
|
26
26
|
lms/message/msbt.py,sha256=WPtTDQQn1_n8uNny9YWBh5HsY-TW8YgAF2Hv5G-PBZM,7804
|
|
@@ -58,7 +58,7 @@ lms/titleconfig/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
58
58
|
lms/titleconfig/config.py,sha256=SDryeNLLRKDEaDL6ffqmHE_Q0EB3g7jznKP8FGM9sEk,12107
|
|
59
59
|
lms/titleconfig/definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
60
|
lms/titleconfig/definitions/attribute.py,sha256=1Wcnn9FFicUHVWRtnx1S7LP-hmtE2MxIvtgncpsWX5c,299
|
|
61
|
-
lms/titleconfig/definitions/nodes.py,sha256=
|
|
61
|
+
lms/titleconfig/definitions/nodes.py,sha256=bzo6InVCDkRQykrXFVdihACU2bGOqsADeSD0gDJimXc,5105
|
|
62
62
|
lms/titleconfig/definitions/tags.py,sha256=84aM-QgPFxonllkzRwTvPR76CvV_LJpypFv4ABNsJ7o,2916
|
|
63
63
|
lms/titleconfig/definitions/value.py,sha256=_Py26KzhQ93v83k7k6_ywO0U1WTWFUzyodtQdWjBxSI,576
|
|
64
64
|
lms/titleconfig/presets/Badge Arcade.yaml,sha256=OotJblQ210dNPiwBpdAI1qwbEYCRQn4jDI7boUSrCao,11854
|
|
@@ -70,9 +70,9 @@ lms/titleconfig/presets/Super Mario Odyssey.yaml,sha256=mV4BsLPzupEPy2RmPUxXlz0U
|
|
|
70
70
|
lms/titleconfig/presets/The Legend of Zelda Echos of Wisdom.yaml,sha256=6AoSNRNORjMzMBp2ais_ujRdLLeOe7FB0YlozZJNvHg,2878
|
|
71
71
|
lms/titleconfig/presets/The Legend of Zelda a Link Between Worlds.yaml,sha256=IY8wPIWMA4ElnN_dtV455beBucntxEBeeJXxUUIrnuw,9785
|
|
72
72
|
lms/titleconfig/presets/Tomodachi Life Living The Dream.yaml,sha256=hJNb0_KdUAZrztyphaTeaZJl_os7HpmdglLgly_eerU,82382
|
|
73
|
-
lms/titleconfig/presets/Tomodachi Life NA-EU.yaml,sha256=
|
|
74
|
-
pylibms-4.2.
|
|
75
|
-
pylibms-4.2.
|
|
76
|
-
pylibms-4.2.
|
|
77
|
-
pylibms-4.2.
|
|
78
|
-
pylibms-4.2.
|
|
73
|
+
lms/titleconfig/presets/Tomodachi Life NA-EU.yaml,sha256=0W9JayobbFCieqDufnedpjRMbOWR2nOdzR-YbJmxs4M,188361
|
|
74
|
+
pylibms-4.2.2.dist-info/licenses/LICENSE,sha256=IGTA9rMb-XpTMPJOzC6z0rRxSzysj3DlV5IQyDd06Xo,1061
|
|
75
|
+
pylibms-4.2.2.dist-info/METADATA,sha256=bYI_CFMKZVa2QSmqCfQugtlmIXgw50G4ALdIbSTEoRo,4208
|
|
76
|
+
pylibms-4.2.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
77
|
+
pylibms-4.2.2.dist-info/top_level.txt,sha256=SWQqb3sjP5r5Ls1Ol4E_PIbsv1sggK4DCkkLEzXNRTU,4
|
|
78
|
+
pylibms-4.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|