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,236 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Property Registry - Type Dispatch for Property Reading.
|
|
3
|
+
|
|
4
|
+
This module provides the central registry for reading properties from binary data.
|
|
5
|
+
It dispatches to the appropriate property class based on the type name in the header.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import typing as t
|
|
11
|
+
|
|
12
|
+
from ..common.binary_reader import BinaryReader
|
|
13
|
+
from ..common.exceptions import UnknownPropertyError
|
|
14
|
+
from .base import NameTable, Property, PropertyHeader, read_property_header
|
|
15
|
+
from .byte_property import ByteProperty
|
|
16
|
+
from .compound import ArrayProperty, MapProperty, StructProperty
|
|
17
|
+
from .primitives import (
|
|
18
|
+
BoolProperty,
|
|
19
|
+
DoubleProperty,
|
|
20
|
+
FloatProperty,
|
|
21
|
+
Int8Property,
|
|
22
|
+
Int16Property,
|
|
23
|
+
Int64Property,
|
|
24
|
+
IntProperty,
|
|
25
|
+
NameProperty,
|
|
26
|
+
ObjectProperty,
|
|
27
|
+
SoftObjectProperty,
|
|
28
|
+
StrProperty,
|
|
29
|
+
UInt16Property,
|
|
30
|
+
UInt32Property,
|
|
31
|
+
UInt64Property,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Type alias for property reader functions
|
|
35
|
+
PropertyReader = t.Callable[[BinaryReader, PropertyHeader, bool], Property]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Registry mapping type names to their reader classes
|
|
39
|
+
PROPERTY_REGISTRY: dict[str, type[Property]] = {
|
|
40
|
+
# Numeric properties
|
|
41
|
+
"Int8Property": Int8Property,
|
|
42
|
+
"Int16Property": Int16Property,
|
|
43
|
+
"IntProperty": IntProperty,
|
|
44
|
+
"Int64Property": Int64Property,
|
|
45
|
+
"UInt16Property": UInt16Property,
|
|
46
|
+
"UInt32Property": UInt32Property,
|
|
47
|
+
"UInt64Property": UInt64Property,
|
|
48
|
+
"FloatProperty": FloatProperty,
|
|
49
|
+
"DoubleProperty": DoubleProperty,
|
|
50
|
+
# Boolean
|
|
51
|
+
"BoolProperty": BoolProperty,
|
|
52
|
+
# String/Name
|
|
53
|
+
"StrProperty": StrProperty,
|
|
54
|
+
"NameProperty": NameProperty,
|
|
55
|
+
# Object reference
|
|
56
|
+
"ObjectProperty": ObjectProperty,
|
|
57
|
+
# Soft object reference (asset paths)
|
|
58
|
+
"SoftObjectProperty": SoftObjectProperty,
|
|
59
|
+
# Byte (can be raw or enum)
|
|
60
|
+
"ByteProperty": ByteProperty,
|
|
61
|
+
# Compound types
|
|
62
|
+
"ArrayProperty": ArrayProperty,
|
|
63
|
+
"StructProperty": StructProperty,
|
|
64
|
+
"MapProperty": MapProperty,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def read_property(
|
|
69
|
+
reader: BinaryReader,
|
|
70
|
+
is_asa: bool = False,
|
|
71
|
+
name_table: NameTable = None,
|
|
72
|
+
worldsave_format: bool = False,
|
|
73
|
+
) -> Property | None:
|
|
74
|
+
"""
|
|
75
|
+
Read a single property from the binary reader.
|
|
76
|
+
|
|
77
|
+
Reads the property header, then dispatches to the appropriate
|
|
78
|
+
property type's read method.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
reader: The binary reader positioned at the property header.
|
|
82
|
+
is_asa: True for ASA format, False for ASE.
|
|
83
|
+
name_table: Optional name table for world saves.
|
|
84
|
+
- list: ASE format (1-based index)
|
|
85
|
+
- dict: ASA format (hash key)
|
|
86
|
+
worldsave_format: True for ASA WorldSave SQLite object format.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The parsed Property object, or None if the terminating "None" property
|
|
90
|
+
was encountered.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
UnknownPropertyError: If the property type is not recognized.
|
|
94
|
+
"""
|
|
95
|
+
header = read_property_header(reader, is_asa, name_table=name_table, worldsave_format=worldsave_format)
|
|
96
|
+
|
|
97
|
+
# Check for terminator (read_property_header returns None for "None")
|
|
98
|
+
if header is None:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
# Look up the property type in the registry
|
|
102
|
+
property_class = PROPERTY_REGISTRY.get(header.type_name)
|
|
103
|
+
|
|
104
|
+
if property_class is None:
|
|
105
|
+
raise UnknownPropertyError(
|
|
106
|
+
property_type=header.type_name,
|
|
107
|
+
position=reader.position,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Read the property value
|
|
111
|
+
# In WorldSave format, all properties need the flag and name_table
|
|
112
|
+
if worldsave_format:
|
|
113
|
+
return property_class.read(reader, header, is_asa, name_table=name_table, worldsave_format=worldsave_format)
|
|
114
|
+
elif header.type_name in (
|
|
115
|
+
"ArrayProperty",
|
|
116
|
+
"StructProperty",
|
|
117
|
+
"MapProperty",
|
|
118
|
+
"ByteProperty",
|
|
119
|
+
"NameProperty",
|
|
120
|
+
"ObjectProperty",
|
|
121
|
+
):
|
|
122
|
+
# These types need name_table even in non-worldsave mode
|
|
123
|
+
return property_class.read(reader, header, is_asa, name_table=name_table, worldsave_format=worldsave_format)
|
|
124
|
+
else:
|
|
125
|
+
return property_class.read(reader, header, is_asa)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def read_properties(
|
|
129
|
+
reader: BinaryReader,
|
|
130
|
+
is_asa: bool = False,
|
|
131
|
+
name_table: NameTable = None,
|
|
132
|
+
worldsave_format: bool = False,
|
|
133
|
+
) -> list[Property]:
|
|
134
|
+
"""
|
|
135
|
+
Read all properties until the "None" terminator.
|
|
136
|
+
|
|
137
|
+
This reads properties in a loop until encountering a property
|
|
138
|
+
named "None", which signals the end of the property list.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
reader: The binary reader positioned at the first property.
|
|
142
|
+
is_asa: True for ASA format, False for ASE.
|
|
143
|
+
name_table: Optional name table for world saves.
|
|
144
|
+
- list: ASE format (1-based index)
|
|
145
|
+
- dict: ASA format (hash key)
|
|
146
|
+
worldsave_format: True for ASA WorldSave SQLite object format.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of parsed Property objects.
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
UnknownPropertyError: If any property type is not recognized.
|
|
153
|
+
"""
|
|
154
|
+
properties: list[Property] = []
|
|
155
|
+
|
|
156
|
+
while True:
|
|
157
|
+
prop = read_property(reader, is_asa, name_table=name_table, worldsave_format=worldsave_format)
|
|
158
|
+
if prop is None:
|
|
159
|
+
break
|
|
160
|
+
properties.append(prop)
|
|
161
|
+
|
|
162
|
+
return properties
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def read_properties_as_dict(
|
|
166
|
+
reader: BinaryReader,
|
|
167
|
+
is_asa: bool = False,
|
|
168
|
+
) -> dict[str, Property | list[Property]]:
|
|
169
|
+
"""
|
|
170
|
+
Read all properties and return as a dictionary.
|
|
171
|
+
|
|
172
|
+
Properties with the same name (different indices) are grouped into lists.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
reader: The binary reader positioned at the first property.
|
|
176
|
+
is_asa: True for ASA format, False for ASE.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Dictionary mapping property names to Property objects or lists of them.
|
|
180
|
+
"""
|
|
181
|
+
properties = read_properties(reader, is_asa)
|
|
182
|
+
result: dict[str, Property | list[Property]] = {}
|
|
183
|
+
|
|
184
|
+
for prop in properties:
|
|
185
|
+
if prop.name in result:
|
|
186
|
+
existing = result[prop.name]
|
|
187
|
+
if isinstance(existing, list):
|
|
188
|
+
existing.append(prop)
|
|
189
|
+
else:
|
|
190
|
+
result[prop.name] = [existing, prop]
|
|
191
|
+
else:
|
|
192
|
+
result[prop.name] = prop
|
|
193
|
+
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def get_property_value(
|
|
198
|
+
properties: dict[str, Property | list[Property]],
|
|
199
|
+
name: str,
|
|
200
|
+
default: t.Any = None,
|
|
201
|
+
index: int | None = None,
|
|
202
|
+
) -> t.Any:
|
|
203
|
+
"""
|
|
204
|
+
Get a property value from a properties dictionary.
|
|
205
|
+
|
|
206
|
+
Helper function for easily extracting values from parsed properties.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
properties: Dictionary of properties from read_properties_as_dict.
|
|
210
|
+
name: The property name to look up.
|
|
211
|
+
default: Default value if property not found.
|
|
212
|
+
index: Specific index to retrieve if there are multiple properties
|
|
213
|
+
with the same name. None returns the first/only one.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
The property value, or the default if not found.
|
|
217
|
+
"""
|
|
218
|
+
if name not in properties:
|
|
219
|
+
return default
|
|
220
|
+
|
|
221
|
+
prop_or_list = properties[name]
|
|
222
|
+
|
|
223
|
+
if isinstance(prop_or_list, list):
|
|
224
|
+
if index is not None:
|
|
225
|
+
# Find the property with the matching index
|
|
226
|
+
for prop in prop_or_list:
|
|
227
|
+
if prop.index == index:
|
|
228
|
+
return prop.value
|
|
229
|
+
return default
|
|
230
|
+
else:
|
|
231
|
+
# Return the first one
|
|
232
|
+
return prop_or_list[0].value if prop_or_list else default
|
|
233
|
+
else:
|
|
234
|
+
if index is not None and prop_or_list.index != index:
|
|
235
|
+
return default
|
|
236
|
+
return prop_or_list.value
|
arkparser/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ARK Struct System.
|
|
3
|
+
|
|
4
|
+
This module provides struct types for structured data within properties.
|
|
5
|
+
|
|
6
|
+
Structs come in two forms:
|
|
7
|
+
1. Native structs: Fixed binary format (Vector, Color, etc.)
|
|
8
|
+
2. Property-based structs: List of properties terminated by "None"
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
from arkparser.structs import read_struct, Vector, Color
|
|
12
|
+
|
|
13
|
+
# Read a struct by type name
|
|
14
|
+
struct = read_struct(reader, "Vector", is_asa=False)
|
|
15
|
+
|
|
16
|
+
# Access struct data
|
|
17
|
+
if isinstance(struct, Vector):
|
|
18
|
+
print(f"Position: ({struct.x}, {struct.y}, {struct.z})")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .base import NativeStruct, Struct
|
|
22
|
+
from .colors import Color, LinearColor
|
|
23
|
+
from .misc import CustomItemDataRef, Guid, UniqueNetIdRepl
|
|
24
|
+
from .property_list import StructPropertyList
|
|
25
|
+
from .registry import (
|
|
26
|
+
ARRAY_NAME_TO_STRUCT_TYPE,
|
|
27
|
+
STRUCT_REGISTRY,
|
|
28
|
+
get_array_struct_type,
|
|
29
|
+
is_native_struct,
|
|
30
|
+
read_struct,
|
|
31
|
+
)
|
|
32
|
+
from .vectors import IntPoint, IntVector, Quat, Rotator, Vector, Vector2D
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
# Base
|
|
36
|
+
"Struct",
|
|
37
|
+
"NativeStruct",
|
|
38
|
+
# Vectors
|
|
39
|
+
"Vector",
|
|
40
|
+
"Vector2D",
|
|
41
|
+
"Rotator",
|
|
42
|
+
"Quat",
|
|
43
|
+
"IntPoint",
|
|
44
|
+
"IntVector",
|
|
45
|
+
# Colors
|
|
46
|
+
"Color",
|
|
47
|
+
"LinearColor",
|
|
48
|
+
# Misc
|
|
49
|
+
"UniqueNetIdRepl",
|
|
50
|
+
"Guid",
|
|
51
|
+
"CustomItemDataRef",
|
|
52
|
+
# Property-based
|
|
53
|
+
"StructPropertyList",
|
|
54
|
+
# Registry
|
|
55
|
+
"STRUCT_REGISTRY",
|
|
56
|
+
"ARRAY_NAME_TO_STRUCT_TYPE",
|
|
57
|
+
"read_struct",
|
|
58
|
+
"is_native_struct",
|
|
59
|
+
"get_array_struct_type",
|
|
60
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Struct Base Classes.
|
|
3
|
+
|
|
4
|
+
Structs in ARK save files come in two forms:
|
|
5
|
+
1. Native structs: Fixed binary format, known by struct type name
|
|
6
|
+
2. Property-based structs: List of properties terminated by "None"
|
|
7
|
+
|
|
8
|
+
This module provides the abstract base class and common types.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import typing as t
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
if t.TYPE_CHECKING:
|
|
18
|
+
from ..common.binary_reader import BinaryReader
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Struct(ABC):
|
|
22
|
+
"""
|
|
23
|
+
Abstract base class for all struct types.
|
|
24
|
+
|
|
25
|
+
Structs are typed data containers used within StructProperty values.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def struct_type(self) -> str:
|
|
31
|
+
"""The struct type name (e.g., 'Vector', 'Color')."""
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def is_native(self) -> bool:
|
|
37
|
+
"""True if this is a native (fixed format) struct."""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def to_dict(self) -> dict[str, t.Any]:
|
|
42
|
+
"""Convert the struct to a dictionary for serialization."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def read(cls, reader: BinaryReader, is_asa: bool = False, **kwargs: t.Any) -> Struct:
|
|
48
|
+
"""Read the struct from binary data."""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class NativeStruct(Struct):
|
|
54
|
+
"""
|
|
55
|
+
Base class for native structs with fixed binary formats.
|
|
56
|
+
|
|
57
|
+
Native structs have a known, fixed structure that doesn't use the
|
|
58
|
+
property system. Examples: Vector, Rotator, Color, etc.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def is_native(self) -> bool:
|
|
63
|
+
return True
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Color Structs.
|
|
3
|
+
|
|
4
|
+
Color-related native structs for storing RGBA color values.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import typing as t
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
from .base import NativeStruct
|
|
13
|
+
|
|
14
|
+
if t.TYPE_CHECKING:
|
|
15
|
+
from ..common.binary_reader import BinaryReader
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Color(NativeStruct):
|
|
20
|
+
"""
|
|
21
|
+
BGRA Color (8-bit per channel).
|
|
22
|
+
|
|
23
|
+
Format: b, g, r, a as UInt8 (4 bytes total).
|
|
24
|
+
Note: Byte order is BGRA, not RGBA!
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
b: int = 0
|
|
28
|
+
g: int = 0
|
|
29
|
+
r: int = 0
|
|
30
|
+
a: int = 255
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def struct_type(self) -> str:
|
|
34
|
+
return "Color"
|
|
35
|
+
|
|
36
|
+
def to_dict(self) -> dict[str, int]:
|
|
37
|
+
return {"r": self.r, "g": self.g, "b": self.b, "a": self.a}
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def rgba(self) -> tuple[int, int, int, int]:
|
|
41
|
+
"""Get color as (r, g, b, a) tuple."""
|
|
42
|
+
return (self.r, self.g, self.b, self.a)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def hex(self) -> str:
|
|
46
|
+
"""Get color as hex string (#RRGGBB or #RRGGBBAA)."""
|
|
47
|
+
if self.a == 255:
|
|
48
|
+
return f"#{self.r:02x}{self.g:02x}{self.b:02x}"
|
|
49
|
+
return f"#{self.r:02x}{self.g:02x}{self.b:02x}{self.a:02x}"
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def read(cls, reader: BinaryReader, is_asa: bool = False, **kwargs: t.Any) -> Color:
|
|
53
|
+
"""Read a Color from the archive."""
|
|
54
|
+
return cls(
|
|
55
|
+
b=reader.read_uint8(),
|
|
56
|
+
g=reader.read_uint8(),
|
|
57
|
+
r=reader.read_uint8(),
|
|
58
|
+
a=reader.read_uint8(),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class LinearColor(NativeStruct):
|
|
64
|
+
"""
|
|
65
|
+
Linear RGBA Color (32-bit float per channel).
|
|
66
|
+
|
|
67
|
+
Format: r, g, b, a as Float (16 bytes total).
|
|
68
|
+
Values are typically in range [0.0, 1.0] but can exceed for HDR.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
r: float = 0.0
|
|
72
|
+
g: float = 0.0
|
|
73
|
+
b: float = 0.0
|
|
74
|
+
a: float = 1.0
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def struct_type(self) -> str:
|
|
78
|
+
return "LinearColor"
|
|
79
|
+
|
|
80
|
+
def to_dict(self) -> dict[str, float]:
|
|
81
|
+
return {"r": self.r, "g": self.g, "b": self.b, "a": self.a}
|
|
82
|
+
|
|
83
|
+
def to_color(self) -> Color:
|
|
84
|
+
"""Convert to 8-bit Color (clamped to 0-255)."""
|
|
85
|
+
return Color(
|
|
86
|
+
r=max(0, min(255, int(self.r * 255))),
|
|
87
|
+
g=max(0, min(255, int(self.g * 255))),
|
|
88
|
+
b=max(0, min(255, int(self.b * 255))),
|
|
89
|
+
a=max(0, min(255, int(self.a * 255))),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def read(cls, reader: BinaryReader, is_asa: bool = False, **kwargs: t.Any) -> LinearColor:
|
|
94
|
+
"""Read a LinearColor from the archive.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
reader: Binary reader positioned at the struct data.
|
|
98
|
+
is_asa: True for ASA format.
|
|
99
|
+
|
|
100
|
+
Note: For ASA indexed struct properties, the array index prefix is
|
|
101
|
+
already handled by the struct registry before this method is called.
|
|
102
|
+
"""
|
|
103
|
+
return cls(
|
|
104
|
+
r=reader.read_float(),
|
|
105
|
+
g=reader.read_float(),
|
|
106
|
+
b=reader.read_float(),
|
|
107
|
+
a=reader.read_float(),
|
|
108
|
+
)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Miscellaneous Native Structs.
|
|
3
|
+
|
|
4
|
+
Various native structs that don't fit into other categories.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import typing as t
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
from .base import NativeStruct
|
|
13
|
+
|
|
14
|
+
if t.TYPE_CHECKING:
|
|
15
|
+
from ..common.binary_reader import BinaryReader
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class UniqueNetIdRepl(NativeStruct):
|
|
20
|
+
"""
|
|
21
|
+
Unique Network ID for player identification.
|
|
22
|
+
|
|
23
|
+
Used for Steam IDs and other platform identifiers.
|
|
24
|
+
|
|
25
|
+
ASE Format:
|
|
26
|
+
- Int32 unknown
|
|
27
|
+
- String net_id (e.g., "2533274977850953" for Xbox)
|
|
28
|
+
|
|
29
|
+
ASA Format:
|
|
30
|
+
- Byte unknown
|
|
31
|
+
- String value_type (platform, e.g., "RedpointEOS")
|
|
32
|
+
- Byte length
|
|
33
|
+
- Bytes value (raw ID bytes, converted to hex string)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
unknown: int = 0
|
|
37
|
+
net_id: str = ""
|
|
38
|
+
value_type: str = "" # ASA only: platform type like "RedpointEOS"
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def struct_type(self) -> str:
|
|
42
|
+
return "UniqueNetIdRepl"
|
|
43
|
+
|
|
44
|
+
def to_dict(self) -> dict[str, t.Any]:
|
|
45
|
+
result = {"unknown": self.unknown, "net_id": self.net_id}
|
|
46
|
+
if self.value_type:
|
|
47
|
+
result["value_type"] = self.value_type
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def steam_id(self) -> str | None:
|
|
52
|
+
"""Extract Steam ID if this is a Steam network ID."""
|
|
53
|
+
if self.net_id.startswith("steam:"):
|
|
54
|
+
return self.net_id[6:]
|
|
55
|
+
return self.net_id if self.net_id else None
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def read(cls, reader: BinaryReader, is_asa: bool = False, **kwargs: t.Any) -> UniqueNetIdRepl:
|
|
59
|
+
"""Read a UniqueNetIdRepl from the archive."""
|
|
60
|
+
if is_asa:
|
|
61
|
+
# ASA format: byte unknown + string value_type + byte length + bytes value
|
|
62
|
+
unknown = reader.read_uint8()
|
|
63
|
+
value_type = reader.read_string()
|
|
64
|
+
length = reader.read_uint8()
|
|
65
|
+
value_bytes = reader.read_bytes(length)
|
|
66
|
+
net_id = value_bytes.hex()
|
|
67
|
+
return cls(unknown=unknown, net_id=net_id, value_type=value_type)
|
|
68
|
+
else:
|
|
69
|
+
# ASE format: int32 unknown + string net_id
|
|
70
|
+
unknown = reader.read_int32()
|
|
71
|
+
net_id = reader.read_string()
|
|
72
|
+
return cls(unknown=unknown, net_id=net_id)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class Guid(NativeStruct):
|
|
77
|
+
"""
|
|
78
|
+
GUID/UUID structure.
|
|
79
|
+
|
|
80
|
+
16-byte globally unique identifier.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
value: str = "" # Stored as hex string
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def struct_type(self) -> str:
|
|
87
|
+
return "Guid"
|
|
88
|
+
|
|
89
|
+
def to_dict(self) -> dict[str, str]:
|
|
90
|
+
return {"guid": self.value}
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def read(cls, reader: BinaryReader, is_asa: bool = False, **kwargs: t.Any) -> Guid:
|
|
94
|
+
"""Read a Guid from the archive."""
|
|
95
|
+
guid_value = reader.read_guid()
|
|
96
|
+
return cls(value=str(guid_value))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class CustomItemDataRef(NativeStruct):
|
|
101
|
+
"""
|
|
102
|
+
Reference to custom item data.
|
|
103
|
+
|
|
104
|
+
Used for storing references to item-specific custom data.
|
|
105
|
+
Format: 4 x Int32 values (16 bytes)
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
value1: int = 0
|
|
109
|
+
value2: int = 0
|
|
110
|
+
value3: int = 0
|
|
111
|
+
value4: int = 0
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def struct_type(self) -> str:
|
|
115
|
+
return "CustomItemDataRef"
|
|
116
|
+
|
|
117
|
+
def to_dict(self) -> dict[str, int]:
|
|
118
|
+
return {
|
|
119
|
+
"value1": self.value1,
|
|
120
|
+
"value2": self.value2,
|
|
121
|
+
"value3": self.value3,
|
|
122
|
+
"value4": self.value4,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def read(cls, reader: BinaryReader, is_asa: bool = False, **kwargs: t.Any) -> CustomItemDataRef:
|
|
127
|
+
"""Read a CustomItemDataRef from the archive."""
|
|
128
|
+
return cls(
|
|
129
|
+
value1=reader.read_int32(),
|
|
130
|
+
value2=reader.read_int32(),
|
|
131
|
+
value3=reader.read_int32(),
|
|
132
|
+
value4=reader.read_int32(),
|
|
133
|
+
)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Property-Based Struct.
|
|
3
|
+
|
|
4
|
+
This struct type contains a list of properties rather than a fixed binary format.
|
|
5
|
+
It's used for complex game objects and custom data structures.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import typing as t
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
|
|
13
|
+
from .base import Struct
|
|
14
|
+
|
|
15
|
+
if t.TYPE_CHECKING:
|
|
16
|
+
from ..common.binary_reader import BinaryReader
|
|
17
|
+
from ..properties.base import Property
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class StructPropertyList(Struct):
|
|
22
|
+
"""
|
|
23
|
+
A struct that contains a list of properties.
|
|
24
|
+
|
|
25
|
+
This is the fallback for any struct type that isn't a known native type.
|
|
26
|
+
The properties are read until a "None" terminator is encountered.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
_struct_type: str = "PropertyList"
|
|
30
|
+
properties: list[Property] = field(default_factory=list)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def struct_type(self) -> str:
|
|
34
|
+
return self._struct_type
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def is_native(self) -> bool:
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
def to_dict(self) -> dict[str, t.Any]:
|
|
41
|
+
"""Convert properties to a dictionary."""
|
|
42
|
+
result: dict[str, t.Any] = {}
|
|
43
|
+
for prop in self.properties:
|
|
44
|
+
if prop.name in result:
|
|
45
|
+
# Handle duplicate property names (array-like)
|
|
46
|
+
existing = result[prop.name]
|
|
47
|
+
if isinstance(existing, list):
|
|
48
|
+
existing.append(prop.value)
|
|
49
|
+
else:
|
|
50
|
+
result[prop.name] = [existing, prop.value]
|
|
51
|
+
else:
|
|
52
|
+
result[prop.name] = prop.value
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
def get_property(self, name: str, index: int = 0) -> Property | None:
|
|
56
|
+
"""Get a property by name and optional index."""
|
|
57
|
+
for prop in self.properties:
|
|
58
|
+
if prop.name == name and prop.index == index:
|
|
59
|
+
return prop
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
def get_value(self, name: str, default: t.Any = None, index: int = 0) -> t.Any:
|
|
63
|
+
"""Get a property value by name."""
|
|
64
|
+
prop = self.get_property(name, index)
|
|
65
|
+
return prop.value if prop else default
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def read(
|
|
69
|
+
cls,
|
|
70
|
+
reader: BinaryReader,
|
|
71
|
+
is_asa: bool = False,
|
|
72
|
+
struct_type: str = "PropertyList",
|
|
73
|
+
name_table: list[str] | None = None,
|
|
74
|
+
worldsave_format: bool = False,
|
|
75
|
+
) -> StructPropertyList:
|
|
76
|
+
"""
|
|
77
|
+
Read a property-based struct from the archive.
|
|
78
|
+
|
|
79
|
+
Note: This imports the registry at runtime to avoid circular imports.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
reader: The binary reader.
|
|
83
|
+
is_asa: True for ASA format.
|
|
84
|
+
struct_type: The struct type name.
|
|
85
|
+
name_table: Optional name table for world saves (version 6+).
|
|
86
|
+
worldsave_format: True for ASA WorldSave SQLite object format.
|
|
87
|
+
"""
|
|
88
|
+
# Import here to avoid circular dependency
|
|
89
|
+
from ..properties.registry import read_properties
|
|
90
|
+
|
|
91
|
+
# Note: For ASA, the extra_byte that precedes struct data is read by the
|
|
92
|
+
# calling code (StructProperty.read or ArrayProperty.read), not here.
|
|
93
|
+
# We just read the properties directly.
|
|
94
|
+
|
|
95
|
+
properties = read_properties(
|
|
96
|
+
reader,
|
|
97
|
+
is_asa,
|
|
98
|
+
name_table=name_table,
|
|
99
|
+
worldsave_format=worldsave_format,
|
|
100
|
+
)
|
|
101
|
+
return cls(_struct_type=struct_type, properties=properties)
|