arkparser 0.1.0__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.
- arkparser/__init__.py +117 -0
- arkparser/common/__init__.py +72 -0
- arkparser/common/binary_reader.py +402 -0
- arkparser/common/exceptions.py +99 -0
- arkparser/common/map_config.py +166 -0
- arkparser/common/types.py +249 -0
- arkparser/common/version_detection.py +195 -0
- arkparser/data_models.py +801 -0
- arkparser/export.py +485 -0
- arkparser/files/__init__.py +25 -0
- arkparser/files/base.py +309 -0
- arkparser/files/cloud_inventory.py +259 -0
- arkparser/files/profile.py +205 -0
- arkparser/files/tribe.py +155 -0
- arkparser/files/world_save.py +699 -0
- arkparser/game_objects/__init__.py +32 -0
- arkparser/game_objects/container.py +180 -0
- arkparser/game_objects/game_object.py +273 -0
- arkparser/game_objects/location.py +87 -0
- arkparser/models/__init__.py +29 -0
- arkparser/models/character.py +227 -0
- arkparser/models/creature.py +642 -0
- arkparser/models/item.py +207 -0
- arkparser/models/player.py +263 -0
- arkparser/models/stats.py +226 -0
- arkparser/models/structure.py +176 -0
- arkparser/models/tribe.py +291 -0
- arkparser/properties/__init__.py +77 -0
- arkparser/properties/base.py +329 -0
- arkparser/properties/byte_property.py +230 -0
- arkparser/properties/compound.py +1125 -0
- arkparser/properties/primitives.py +803 -0
- arkparser/properties/registry.py +236 -0
- arkparser/py.typed +0 -0
- arkparser/structs/__init__.py +60 -0
- arkparser/structs/base.py +63 -0
- arkparser/structs/colors.py +108 -0
- arkparser/structs/misc.py +133 -0
- arkparser/structs/property_list.py +101 -0
- arkparser/structs/registry.py +140 -0
- arkparser/structs/vectors.py +221 -0
- arkparser-0.1.0.dist-info/METADATA +833 -0
- arkparser-0.1.0.dist-info/RECORD +46 -0
- arkparser-0.1.0.dist-info/WHEEL +5 -0
- arkparser-0.1.0.dist-info/licenses/LICENSE +21 -0
- arkparser-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Property Base Classes.
|
|
3
|
+
|
|
4
|
+
Properties are the core data storage mechanism in ARK save files.
|
|
5
|
+
Each game object has a list of properties that store its state.
|
|
6
|
+
|
|
7
|
+
Property Format:
|
|
8
|
+
+--------+--------+--------+--------+
|
|
9
|
+
| Name | Name | Int32 | Int32 |
|
|
10
|
+
| name | type | size | index |
|
|
11
|
+
+--------+--------+--------+--------+
|
|
12
|
+
|
|
13
|
+
The property list is terminated by a property with name "None".
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import typing as t
|
|
19
|
+
from abc import ABC, abstractmethod
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
|
|
22
|
+
if t.TYPE_CHECKING:
|
|
23
|
+
from ..common.binary_reader import BinaryReader
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class Property(ABC):
|
|
28
|
+
"""
|
|
29
|
+
Base class for all ARK properties.
|
|
30
|
+
|
|
31
|
+
Properties store named, typed values on game objects.
|
|
32
|
+
Each property has:
|
|
33
|
+
- name: The property name (e.g., "Health", "TamedName")
|
|
34
|
+
- type_name: The property type (e.g., "FloatProperty", "StrProperty")
|
|
35
|
+
- index: Array index for properties with the same name
|
|
36
|
+
- value: The property value (type depends on property type)
|
|
37
|
+
|
|
38
|
+
Subclasses implement reading the value based on the type.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
index: int = 0
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def type_name(self) -> str:
|
|
47
|
+
"""The property type name (e.g., 'FloatProperty')."""
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def value(self) -> t.Any:
|
|
53
|
+
"""The property value."""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
def __repr__(self) -> str:
|
|
57
|
+
idx_str = f", index={self.index}" if self.index != 0 else ""
|
|
58
|
+
return f"{self.__class__.__name__}(name={self.name!r}{idx_str}, value={self.value!r})"
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def read(
|
|
62
|
+
cls,
|
|
63
|
+
reader: BinaryReader,
|
|
64
|
+
header: PropertyHeader,
|
|
65
|
+
is_asa: bool = False,
|
|
66
|
+
**kwargs: t.Any,
|
|
67
|
+
) -> Property:
|
|
68
|
+
"""
|
|
69
|
+
Read a property value from binary data.
|
|
70
|
+
|
|
71
|
+
Subclasses must implement this to parse their specific value format.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
reader: The binary reader positioned at the property value.
|
|
75
|
+
header: The property header (name, type, data_size, index).
|
|
76
|
+
is_asa: True for ASA format, False for ASE.
|
|
77
|
+
**kwargs: Additional keyword arguments (name_table, worldsave_format, etc.).
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
The parsed property instance.
|
|
81
|
+
"""
|
|
82
|
+
raise NotImplementedError(f"{cls.__name__} must implement read()")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class PropertyHeader:
|
|
87
|
+
"""
|
|
88
|
+
Property header data read before the value.
|
|
89
|
+
|
|
90
|
+
This is used internally during parsing to pass header
|
|
91
|
+
information to property constructors.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
name: str
|
|
95
|
+
type_name: str
|
|
96
|
+
data_size: int
|
|
97
|
+
index: int
|
|
98
|
+
|
|
99
|
+
def __repr__(self) -> str:
|
|
100
|
+
return f"PropertyHeader(name={self.name!r}, type={self.type_name!r}, size={self.data_size}, index={self.index})"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# Type alias for name table - can be either list (ASE) or dict (ASA)
|
|
104
|
+
NameTable = list[str] | dict[int, str] | None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _read_name_from_list_table(reader: BinaryReader, name_table: list[str]) -> str:
|
|
108
|
+
"""
|
|
109
|
+
Read a name using a list-based name table (ASE world saves).
|
|
110
|
+
|
|
111
|
+
Name table format:
|
|
112
|
+
- Int32 index: Index into name table (1-based)
|
|
113
|
+
- Int32 instance: Instance number (0 = no suffix, otherwise append _{instance-1})
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
reader: The binary reader.
|
|
117
|
+
name_table: The name table list (1-based indexing).
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
The full name string with instance suffix if applicable.
|
|
121
|
+
"""
|
|
122
|
+
index = reader.read_int32()
|
|
123
|
+
|
|
124
|
+
# Convert from 1-based to 0-based index
|
|
125
|
+
internal_index = index - 1
|
|
126
|
+
|
|
127
|
+
if internal_index < 0 or internal_index >= len(name_table):
|
|
128
|
+
# Invalid index - return placeholder
|
|
129
|
+
return f"__INVALID_NAME_INDEX_{index}__"
|
|
130
|
+
|
|
131
|
+
name = name_table[internal_index]
|
|
132
|
+
|
|
133
|
+
# Read instance number
|
|
134
|
+
instance = reader.read_int32()
|
|
135
|
+
|
|
136
|
+
# Instance 0 means no suffix, otherwise append _{instance-1}
|
|
137
|
+
if instance > 0:
|
|
138
|
+
return f"{name}_{instance - 1}"
|
|
139
|
+
return name
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _read_name_from_dict_table(reader: BinaryReader, name_table: dict[int, str]) -> str:
|
|
143
|
+
"""
|
|
144
|
+
Read a name using a dict-based name table (ASA world saves).
|
|
145
|
+
|
|
146
|
+
ASA name table format:
|
|
147
|
+
- Int32 id: Hash key into name table dictionary
|
|
148
|
+
- Int32 instance: Instance number (0 = no suffix, otherwise append _{instance-1})
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
reader: The binary reader.
|
|
152
|
+
name_table: The name table dictionary (hash keys).
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The full name string with instance suffix if applicable.
|
|
156
|
+
"""
|
|
157
|
+
name_id = reader.read_int32()
|
|
158
|
+
|
|
159
|
+
if name_id not in name_table:
|
|
160
|
+
# Unknown name - return placeholder
|
|
161
|
+
return f"__UNKNOWN_NAME_{name_id}__"
|
|
162
|
+
|
|
163
|
+
name = name_table[name_id]
|
|
164
|
+
|
|
165
|
+
# Read instance number
|
|
166
|
+
instance = reader.read_int32()
|
|
167
|
+
|
|
168
|
+
# Instance 0 means no suffix, otherwise append _{instance-1}
|
|
169
|
+
if instance > 0:
|
|
170
|
+
return f"{name}_{instance - 1}"
|
|
171
|
+
return name
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def read_name(reader: BinaryReader, name_table: NameTable = None) -> str:
|
|
175
|
+
"""
|
|
176
|
+
Read a name from the archive.
|
|
177
|
+
|
|
178
|
+
If a name table is provided, reads from the table.
|
|
179
|
+
Otherwise, reads a raw string.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
reader: The binary reader.
|
|
183
|
+
name_table: Optional name table for world saves.
|
|
184
|
+
- list: ASE format (1-based index)
|
|
185
|
+
- dict: ASA format (hash key)
|
|
186
|
+
- None: Read raw string
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
The name string.
|
|
190
|
+
"""
|
|
191
|
+
if name_table is None:
|
|
192
|
+
return reader.read_string()
|
|
193
|
+
elif isinstance(name_table, dict):
|
|
194
|
+
return _read_name_from_dict_table(reader, name_table)
|
|
195
|
+
else:
|
|
196
|
+
return _read_name_from_list_table(reader, name_table)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def read_property_header(
|
|
200
|
+
reader: BinaryReader,
|
|
201
|
+
is_asa: bool = False,
|
|
202
|
+
name_table: NameTable = None,
|
|
203
|
+
worldsave_format: bool = False,
|
|
204
|
+
) -> PropertyHeader | None:
|
|
205
|
+
"""
|
|
206
|
+
Read a property header from the archive.
|
|
207
|
+
|
|
208
|
+
Returns None if the property name is "None" (end of property list).
|
|
209
|
+
|
|
210
|
+
ASE Property header format:
|
|
211
|
+
- Name: property name (string or name table index)
|
|
212
|
+
- Name: property type (string or name table index)
|
|
213
|
+
- Int32: data size (size of value in bytes)
|
|
214
|
+
- Int32: index (for array elements with same name)
|
|
215
|
+
|
|
216
|
+
ASA Property header format (CloudInventory, Profile, Tribe):
|
|
217
|
+
- Name: property name
|
|
218
|
+
- Name: property type
|
|
219
|
+
- Int32: data_size
|
|
220
|
+
- Int32: index
|
|
221
|
+
|
|
222
|
+
ASA WorldSave Property header format (SQLite objects):
|
|
223
|
+
- Name ID (Int32) + Name Instance (Int32)
|
|
224
|
+
- Type ID (Int32) + 8 zero bytes
|
|
225
|
+
- Int32: data_size
|
|
226
|
+
- Byte: terminator (usually 0)
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
reader: The binary reader positioned at the property header.
|
|
230
|
+
is_asa: True for ASA format, False for ASE.
|
|
231
|
+
name_table: Optional name table for world saves.
|
|
232
|
+
- list: ASE format (1-based index)
|
|
233
|
+
- dict: ASA format (hash key)
|
|
234
|
+
worldsave_format: True for ASA WorldSave SQLite object format.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
PropertyHeader or None if this is the terminator.
|
|
238
|
+
"""
|
|
239
|
+
if worldsave_format:
|
|
240
|
+
return _read_worldsave_property_header(reader, name_table)
|
|
241
|
+
|
|
242
|
+
# Read property name
|
|
243
|
+
name = read_name(reader, name_table)
|
|
244
|
+
|
|
245
|
+
# Check for terminator
|
|
246
|
+
if name == "None" or name == "" or name is None:
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
# Read property type
|
|
250
|
+
type_name = read_name(reader, name_table)
|
|
251
|
+
|
|
252
|
+
# Both ASE and ASA have data_size and index in the header
|
|
253
|
+
data_size = reader.read_int32()
|
|
254
|
+
index = reader.read_int32()
|
|
255
|
+
|
|
256
|
+
return PropertyHeader(
|
|
257
|
+
name=name,
|
|
258
|
+
type_name=type_name,
|
|
259
|
+
data_size=data_size,
|
|
260
|
+
index=index,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _read_worldsave_property_header(
|
|
265
|
+
reader: BinaryReader,
|
|
266
|
+
name_table: dict[int, str] | None,
|
|
267
|
+
) -> PropertyHeader | None:
|
|
268
|
+
"""
|
|
269
|
+
Read a property header in ASA WorldSave format.
|
|
270
|
+
|
|
271
|
+
WorldSave property header format (from TypeScript docs):
|
|
272
|
+
Common header:
|
|
273
|
+
- Name ID (Int32): Hash key into name table
|
|
274
|
+
- Name Instance (Int32): Usually 0, for array indices use name_instance - 1
|
|
275
|
+
- Type ID (Int32): Hash key for type name
|
|
276
|
+
- Type Instance (Int32): Usually 0
|
|
277
|
+
|
|
278
|
+
After the common header, the format depends on the property type:
|
|
279
|
+
- Simple properties (Int, Float, etc.): 4 zeros + Length(4) + Terminator(1) + Value
|
|
280
|
+
- ArrayProperty: ArrayHeader(4) + ElementTypeID(4) + ... (complex structure)
|
|
281
|
+
- StructProperty: similar complex structure
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
reader: The binary reader.
|
|
285
|
+
name_table: The name table dictionary (hash keys to names).
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
PropertyHeader or None if this is the terminator.
|
|
289
|
+
"""
|
|
290
|
+
if name_table is None:
|
|
291
|
+
raise ValueError("WorldSave format requires a name table")
|
|
292
|
+
|
|
293
|
+
# Read name: ID (4) + Instance (4)
|
|
294
|
+
name_id = reader.read_int32()
|
|
295
|
+
name_instance = reader.read_int32()
|
|
296
|
+
|
|
297
|
+
# Lookup name
|
|
298
|
+
name = name_table.get(name_id, f"__UNKNOWN_NAME_{name_id}__")
|
|
299
|
+
|
|
300
|
+
# Apply instance suffix (for array-like properties with same name)
|
|
301
|
+
index = 0
|
|
302
|
+
if name_instance > 0:
|
|
303
|
+
index = name_instance - 1
|
|
304
|
+
# Don't append to name - use index field instead
|
|
305
|
+
|
|
306
|
+
# Check for terminator
|
|
307
|
+
if name == "None":
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
# Read type: ID (4) + Instance (4)
|
|
311
|
+
# Type instance is almost always 0 (padding), but must be read
|
|
312
|
+
type_id = reader.read_int32()
|
|
313
|
+
_type_instance = reader.read_int32() # Usually 0
|
|
314
|
+
type_name = name_table.get(type_id, f"__UNKNOWN_TYPE_{type_id}__")
|
|
315
|
+
|
|
316
|
+
# NOTE: The property-type-specific data (including data_size and terminator)
|
|
317
|
+
# is read by individual property readers. For the header, we return data_size=0
|
|
318
|
+
# and the actual readers will determine the size from their own format.
|
|
319
|
+
# This is because:
|
|
320
|
+
# - Simple properties: 4 zeros + Length(4) + Term(1) + Value
|
|
321
|
+
# - ArrayProperty: ArrayHeader + ElementTypeID + 8zeros + ByteLength + Term + Count
|
|
322
|
+
# - StructProperty: similar complex format
|
|
323
|
+
|
|
324
|
+
return PropertyHeader(
|
|
325
|
+
name=name,
|
|
326
|
+
type_name=type_name,
|
|
327
|
+
data_size=0, # Will be determined by property-specific reader
|
|
328
|
+
index=index,
|
|
329
|
+
)
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Byte Property Type.
|
|
3
|
+
|
|
4
|
+
ByteProperty is special because it can represent either:
|
|
5
|
+
1. A raw byte value (UInt8)
|
|
6
|
+
2. An enum value (string name from an enum type)
|
|
7
|
+
|
|
8
|
+
The enum name is stored after the index, before the value.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import typing as t
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
|
|
16
|
+
from .base import Property, PropertyHeader, read_name
|
|
17
|
+
|
|
18
|
+
if t.TYPE_CHECKING:
|
|
19
|
+
from ..common.binary_reader import BinaryReader
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ByteProperty(Property):
|
|
24
|
+
"""
|
|
25
|
+
Byte property - can be either a raw byte or an enum value.
|
|
26
|
+
|
|
27
|
+
Format after header:
|
|
28
|
+
- String enum_name (e.g., "None" for raw byte, or enum type name)
|
|
29
|
+
- ASA: 1 unknown byte
|
|
30
|
+
- If enum_name == "None": UInt8 value
|
|
31
|
+
- Else: String enum_value (the actual enum constant name)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
name: str
|
|
35
|
+
index: int = 0
|
|
36
|
+
enum_name: str = "None" # "None" means raw byte, otherwise enum type name
|
|
37
|
+
_byte_value: int | None = None # Raw byte value (if enum_name == "None")
|
|
38
|
+
_enum_value: str | None = None # Enum constant name (if enum_name != "None")
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def type_name(self) -> str:
|
|
42
|
+
return "ByteProperty"
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def value(self) -> int | str:
|
|
46
|
+
"""Returns byte value (int) or enum value (str)."""
|
|
47
|
+
if self._byte_value is not None:
|
|
48
|
+
return self._byte_value
|
|
49
|
+
return self._enum_value or ""
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def is_enum(self) -> bool:
|
|
53
|
+
"""True if this is an enum value, False if raw byte."""
|
|
54
|
+
return self.enum_name != "None"
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def byte_value(self) -> int | None:
|
|
58
|
+
"""The raw byte value, or None if this is an enum."""
|
|
59
|
+
return self._byte_value
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def enum_value(self) -> str | None:
|
|
63
|
+
"""The enum constant name, or None if this is a raw byte."""
|
|
64
|
+
return self._enum_value
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def read(
|
|
68
|
+
cls,
|
|
69
|
+
reader: BinaryReader,
|
|
70
|
+
header: PropertyHeader,
|
|
71
|
+
is_asa: bool = False,
|
|
72
|
+
name_table: list[str] | None = None,
|
|
73
|
+
worldsave_format: bool = False,
|
|
74
|
+
) -> ByteProperty:
|
|
75
|
+
"""
|
|
76
|
+
Read a ByteProperty from the archive.
|
|
77
|
+
|
|
78
|
+
WorldSave format: 8 zeros + length + array_index + byte_value
|
|
79
|
+
(Note: In WorldSave format, ByteProperty is always a raw byte)
|
|
80
|
+
|
|
81
|
+
ASE format:
|
|
82
|
+
- enum_name (name) - "None" for raw byte, or enum type name
|
|
83
|
+
- If enum_name == "None": UInt8 value
|
|
84
|
+
- Else: enum_value (name) - the enum constant
|
|
85
|
+
|
|
86
|
+
ASA format (string-based files like cloud inventory):
|
|
87
|
+
Header: name, type, data_size, index
|
|
88
|
+
|
|
89
|
+
For raw byte (data_size=0, index=1):
|
|
90
|
+
- enum_name (1 byte null = empty string)
|
|
91
|
+
- extra_byte (1)
|
|
92
|
+
- byte_value (1)
|
|
93
|
+
|
|
94
|
+
For raw byte (index=1 in header):
|
|
95
|
+
- extra_byte (1)
|
|
96
|
+
- If extra_byte & 0x01: array_index (int32)
|
|
97
|
+
- byte_value (1)
|
|
98
|
+
|
|
99
|
+
For enum (index > 1 = enum_name_length):
|
|
100
|
+
- enum_name string (using header.index as length, null-terminated)
|
|
101
|
+
- extra1 (int32)
|
|
102
|
+
- blueprint_path (string)
|
|
103
|
+
- zeros (int32)
|
|
104
|
+
- data_size (int32)
|
|
105
|
+
- extra_byte (1)
|
|
106
|
+
- If extra_byte & 0x01: array_index (int32)
|
|
107
|
+
- enum_value (string)
|
|
108
|
+
"""
|
|
109
|
+
if worldsave_format:
|
|
110
|
+
# WorldSave ByteProperty has two formats, determined by the first int32
|
|
111
|
+
# after the 16-byte common header:
|
|
112
|
+
#
|
|
113
|
+
# Raw byte (marker == 0):
|
|
114
|
+
# marker(4) + data_size(4) + flag(1) + value(1) = 10 bytes
|
|
115
|
+
#
|
|
116
|
+
# Enum (marker == 1):
|
|
117
|
+
# marker(4) + enum_type_name(8) + marker2(4) + blueprint_name(8)
|
|
118
|
+
# + zeros(4) + data_size(4) + flag(1) + enum_value_name(8) = 41 bytes
|
|
119
|
+
marker = reader.read_int32()
|
|
120
|
+
|
|
121
|
+
if marker == 0:
|
|
122
|
+
# Raw byte format (same as simple prefix)
|
|
123
|
+
_data_size = reader.read_int32()
|
|
124
|
+
flag = reader.read_uint8()
|
|
125
|
+
# Simple prefix: if flag bit 0 is set, read array_index
|
|
126
|
+
if flag & 0x01:
|
|
127
|
+
_array_index = reader.read_int32()
|
|
128
|
+
byte_value = reader.read_uint8()
|
|
129
|
+
return cls(
|
|
130
|
+
name=header.name,
|
|
131
|
+
index=header.index,
|
|
132
|
+
enum_name="None",
|
|
133
|
+
_byte_value=byte_value,
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
# Enum format (marker == 1): sub-header + enum value
|
|
137
|
+
if not (name_table and isinstance(name_table, dict)):
|
|
138
|
+
raise ValueError("ByteProperty enum format requires a name table")
|
|
139
|
+
|
|
140
|
+
enum_type_id = reader.read_int32()
|
|
141
|
+
_enum_type_inst = reader.read_int32()
|
|
142
|
+
enum_type_name = name_table.get(enum_type_id, f"__UNKNOWN_{enum_type_id}__")
|
|
143
|
+
|
|
144
|
+
_marker2 = reader.read_int32() # Usually 1
|
|
145
|
+
_blueprint_id = reader.read_int32()
|
|
146
|
+
_blueprint_inst = reader.read_int32()
|
|
147
|
+
_zeros = reader.read_int32()
|
|
148
|
+
_data_size = reader.read_int32()
|
|
149
|
+
_flag = reader.read_uint8()
|
|
150
|
+
|
|
151
|
+
enum_value_id = reader.read_int32()
|
|
152
|
+
enum_value_inst = reader.read_int32()
|
|
153
|
+
enum_value = name_table.get(enum_value_id, f"__UNKNOWN_{enum_value_id}__")
|
|
154
|
+
if enum_value_inst > 0:
|
|
155
|
+
enum_value = f"{enum_value}_{enum_value_inst - 1}"
|
|
156
|
+
|
|
157
|
+
return cls(
|
|
158
|
+
name=header.name,
|
|
159
|
+
index=header.index,
|
|
160
|
+
enum_name=enum_type_name,
|
|
161
|
+
_enum_value=enum_value,
|
|
162
|
+
)
|
|
163
|
+
elif is_asa:
|
|
164
|
+
# For ASA, header.index indicates the type:
|
|
165
|
+
# - index == 1: raw byte (no enum_name)
|
|
166
|
+
# - index > 1: enum type name length (null-terminated string)
|
|
167
|
+
enum_name_len = header.index
|
|
168
|
+
|
|
169
|
+
if enum_name_len == 1:
|
|
170
|
+
# Raw byte value - no enum_name at all
|
|
171
|
+
# Format: extra_byte + optional array_index + byte_value
|
|
172
|
+
extra_byte = reader.read_uint8()
|
|
173
|
+
array_index = 0
|
|
174
|
+
if extra_byte & 0x01:
|
|
175
|
+
array_index = reader.read_int32()
|
|
176
|
+
byte_value = reader.read_uint8()
|
|
177
|
+
return cls(
|
|
178
|
+
name=header.name,
|
|
179
|
+
index=array_index,
|
|
180
|
+
enum_name="None",
|
|
181
|
+
_byte_value=byte_value,
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
# Enum value - has enum_name string followed by more data
|
|
185
|
+
enum_name_bytes = reader.read_bytes(enum_name_len)
|
|
186
|
+
enum_name = enum_name_bytes[:-1].decode("latin-1")
|
|
187
|
+
|
|
188
|
+
# extra1 (int32)
|
|
189
|
+
_extra1 = reader.read_int32()
|
|
190
|
+
# blueprint_path (string)
|
|
191
|
+
_blueprint_path = reader.read_string()
|
|
192
|
+
# zeros (int32)
|
|
193
|
+
_zeros = reader.read_int32()
|
|
194
|
+
# data_size (int32)
|
|
195
|
+
_data_size = reader.read_int32()
|
|
196
|
+
# extra_byte (1)
|
|
197
|
+
extra_byte = reader.read_uint8()
|
|
198
|
+
array_index = 0
|
|
199
|
+
if extra_byte & 0x01:
|
|
200
|
+
array_index = reader.read_int32()
|
|
201
|
+
# enum_value (string)
|
|
202
|
+
enum_value = reader.read_string()
|
|
203
|
+
return cls(
|
|
204
|
+
name=header.name,
|
|
205
|
+
index=array_index,
|
|
206
|
+
enum_name=enum_name,
|
|
207
|
+
_enum_value=enum_value,
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
# ASE format with enum_name (uses name table if present)
|
|
211
|
+
enum_name = read_name(reader, name_table)
|
|
212
|
+
|
|
213
|
+
if enum_name == "None":
|
|
214
|
+
# Raw byte value
|
|
215
|
+
byte_value = reader.read_uint8()
|
|
216
|
+
return cls(
|
|
217
|
+
name=header.name,
|
|
218
|
+
index=header.index,
|
|
219
|
+
enum_name=enum_name,
|
|
220
|
+
_byte_value=byte_value,
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
# Enum value - read the enum constant name (also uses name table)
|
|
224
|
+
enum_value = read_name(reader, name_table)
|
|
225
|
+
return cls(
|
|
226
|
+
name=header.name,
|
|
227
|
+
index=header.index,
|
|
228
|
+
enum_name=enum_name,
|
|
229
|
+
_enum_value=enum_value,
|
|
230
|
+
)
|