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,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Player profile parser for .arkprofile files.
|
|
3
|
+
|
|
4
|
+
Profile files contain player character data including:
|
|
5
|
+
- Character name and stats
|
|
6
|
+
- Level and experience
|
|
7
|
+
- Engrams learned
|
|
8
|
+
- Inventory items (if saved)
|
|
9
|
+
- Tribe affiliation
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import typing as t
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
from .base import ArkFile
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Profile(ArkFile):
|
|
22
|
+
"""
|
|
23
|
+
Parser for .arkprofile player profile files.
|
|
24
|
+
|
|
25
|
+
The main object has class name "PrimalPlayerData" or "PrimalPlayerDataBP_C".
|
|
26
|
+
All player data is nested inside a "MyData" struct property.
|
|
27
|
+
|
|
28
|
+
Example usage:
|
|
29
|
+
>>> profile = Profile.load("examples/ase/map_save/2533274977850953.arkprofile")
|
|
30
|
+
>>> print(f"Player: {profile.player_name}")
|
|
31
|
+
>>> print(f"Level: {profile.level}")
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
VALID_VERSIONS: t.ClassVar[tuple[int, ...]] = (1, 5, 6, 7)
|
|
35
|
+
MAIN_CLASS_NAME: t.ClassVar[str] = "PrimalPlayerData"
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def main_object(self):
|
|
39
|
+
"""Get the main player data object (handles both class name variants).
|
|
40
|
+
|
|
41
|
+
ASE class names: "PrimalPlayerData", "PrimalPlayerDataBP_C"
|
|
42
|
+
ASA class names: "/Game/PrimalEarth/CoreBlueprints/PrimalPlayerDataBP.PrimalPlayerDataBP_C"
|
|
43
|
+
"""
|
|
44
|
+
for obj in self.objects:
|
|
45
|
+
# Check for various naming conventions
|
|
46
|
+
if "PrimalPlayerData" in obj.class_name:
|
|
47
|
+
return obj
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def _player_data(self) -> dict[str, t.Any]:
|
|
52
|
+
"""Get the nested MyData struct as a dictionary."""
|
|
53
|
+
player_data = self.get_property_value("MyData")
|
|
54
|
+
if player_data is None:
|
|
55
|
+
return {}
|
|
56
|
+
return player_data if isinstance(player_data, dict) else {}
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def _persistent_stats(self) -> dict[str, t.Any]:
|
|
60
|
+
"""Get the nested MyPersistentCharacterStats struct."""
|
|
61
|
+
stats = self._player_data.get("MyPersistentCharacterStats")
|
|
62
|
+
if stats is None:
|
|
63
|
+
return {}
|
|
64
|
+
return stats if isinstance(stats, dict) else {}
|
|
65
|
+
|
|
66
|
+
# Convenience properties for common player data
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def player_name(self) -> str | None:
|
|
70
|
+
"""Get the player's character name."""
|
|
71
|
+
return self._player_data.get("PlayerName")
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def player_id(self) -> int | None:
|
|
75
|
+
"""Get the player's unique ID."""
|
|
76
|
+
return self._player_data.get("PlayerDataID")
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def unique_id(self) -> str | None:
|
|
80
|
+
"""Get the player's network unique ID (Steam ID, Xbox ID, etc.)."""
|
|
81
|
+
unique_id = self._player_data.get("UniqueID")
|
|
82
|
+
if unique_id and isinstance(unique_id, dict):
|
|
83
|
+
return str(unique_id.get("net_id", ""))
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def tribe_id(self) -> int | None:
|
|
88
|
+
"""
|
|
89
|
+
Get the player's tribe ID (0 if not in a tribe).
|
|
90
|
+
|
|
91
|
+
Note: ASE uses "TribeId" (lowercase d), ASA uses "TribeID" (uppercase D).
|
|
92
|
+
"""
|
|
93
|
+
# ASE uses "TribeId", ASA uses "TribeID"
|
|
94
|
+
return self._player_data.get("TribeId") or self._player_data.get("TribeID")
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def tribe_name(self) -> str | None:
|
|
98
|
+
"""
|
|
99
|
+
Get the player's tribe name.
|
|
100
|
+
|
|
101
|
+
Note: Tribe name is NOT stored in the player profile file.
|
|
102
|
+
This property will always return None. To get the tribe name,
|
|
103
|
+
load the corresponding .arktribe file using the tribe_id.
|
|
104
|
+
"""
|
|
105
|
+
return None # Tribe name is not stored in profile files
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def level(self) -> int:
|
|
109
|
+
"""
|
|
110
|
+
Get the player's current level.
|
|
111
|
+
|
|
112
|
+
Calculated from CharacterStatusComponent_ExtraCharacterLevel + 1.
|
|
113
|
+
"""
|
|
114
|
+
extra_level = self._persistent_stats.get("CharacterStatusComponent_ExtraCharacterLevel", 0)
|
|
115
|
+
return (extra_level or 0) + 1
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def experience(self) -> float:
|
|
119
|
+
"""Get the player's total experience points."""
|
|
120
|
+
return self._persistent_stats.get("CharacterStatusComponent_ExperiencePoints", 0.0) or 0.0
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def total_engram_points(self) -> int:
|
|
124
|
+
"""Get total engram points spent."""
|
|
125
|
+
return self._persistent_stats.get("PlayerState_TotalEngramPoints", 0) or 0
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def engram_blueprints(self) -> list[str]:
|
|
129
|
+
"""Get list of learned engram blueprint paths."""
|
|
130
|
+
engrams = self._persistent_stats.get("EngramBlueprints")
|
|
131
|
+
if not engrams:
|
|
132
|
+
engrams = self._persistent_stats.get("PlayerState_EngramBlueprints", [])
|
|
133
|
+
return [str(e) for e in engrams] if engrams else []
|
|
134
|
+
|
|
135
|
+
def get_stat(self, stat_index: int) -> dict[str, t.Any]:
|
|
136
|
+
"""
|
|
137
|
+
Get a specific stat value by index.
|
|
138
|
+
|
|
139
|
+
Stat indices:
|
|
140
|
+
0: Health
|
|
141
|
+
1: Stamina
|
|
142
|
+
2: Torpidity
|
|
143
|
+
3: Oxygen
|
|
144
|
+
4: Food
|
|
145
|
+
5: Water
|
|
146
|
+
6: Temperature
|
|
147
|
+
7: Weight
|
|
148
|
+
8: Melee Damage
|
|
149
|
+
9: Movement Speed
|
|
150
|
+
10: Fortitude
|
|
151
|
+
11: Crafting Skill
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Dict with base and added values
|
|
155
|
+
"""
|
|
156
|
+
added = 0
|
|
157
|
+
stats = self._persistent_stats
|
|
158
|
+
points_key = "CharacterStatusComponent_NumberOfLevelUpPointsApplied"
|
|
159
|
+
points_value = stats.get(points_key)
|
|
160
|
+
|
|
161
|
+
if isinstance(points_value, list):
|
|
162
|
+
if 0 <= stat_index < len(points_value):
|
|
163
|
+
raw_value = points_value[stat_index]
|
|
164
|
+
if isinstance(raw_value, int):
|
|
165
|
+
added = raw_value
|
|
166
|
+
elif isinstance(points_value, int):
|
|
167
|
+
if stat_index == 0:
|
|
168
|
+
added = points_value
|
|
169
|
+
|
|
170
|
+
indexed_prefix = f"{points_key}["
|
|
171
|
+
for key, value in stats.items():
|
|
172
|
+
if not isinstance(key, str) or not key.startswith(indexed_prefix):
|
|
173
|
+
continue
|
|
174
|
+
if not isinstance(value, int):
|
|
175
|
+
continue
|
|
176
|
+
suffix = key[len(indexed_prefix) :]
|
|
177
|
+
if not suffix.endswith("]"):
|
|
178
|
+
continue
|
|
179
|
+
index_text = suffix[:-1]
|
|
180
|
+
if not index_text.isdigit():
|
|
181
|
+
continue
|
|
182
|
+
if int(index_text) == stat_index:
|
|
183
|
+
added = value
|
|
184
|
+
break
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
"stat_index": stat_index,
|
|
188
|
+
"added": added,
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
def to_dict(self) -> dict[str, t.Any]:
|
|
192
|
+
"""Convert to dictionary with player-specific fields."""
|
|
193
|
+
base_dict = super().to_dict()
|
|
194
|
+
base_dict.update(
|
|
195
|
+
{
|
|
196
|
+
"player_name": self.player_name,
|
|
197
|
+
"player_id": self.player_id,
|
|
198
|
+
"unique_id": self.unique_id,
|
|
199
|
+
"tribe_id": self.tribe_id,
|
|
200
|
+
"tribe_name": self.tribe_name,
|
|
201
|
+
"experience": self.experience,
|
|
202
|
+
"total_engram_points": self.total_engram_points,
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
return base_dict
|
arkparser/files/tribe.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tribe data parser for .arktribe files.
|
|
3
|
+
|
|
4
|
+
Tribe files contain:
|
|
5
|
+
- Tribe name and ID
|
|
6
|
+
- Member list with ranks
|
|
7
|
+
- Tribe log entries
|
|
8
|
+
- Governance settings
|
|
9
|
+
- Alliance information
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import typing as t
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
from .base import ArkFile
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Tribe(ArkFile):
|
|
22
|
+
"""
|
|
23
|
+
Parser for .arktribe tribe data files.
|
|
24
|
+
|
|
25
|
+
The main object has class name "PrimalTribeData".
|
|
26
|
+
All tribe data is nested inside a "TribeData" struct property.
|
|
27
|
+
|
|
28
|
+
Example usage:
|
|
29
|
+
>>> tribe = Tribe.load("examples/ase/map_save/1446520645.arktribe")
|
|
30
|
+
>>> print(f"Tribe: {tribe.name}")
|
|
31
|
+
>>> print(f"Members: {tribe.member_count}")
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
VALID_VERSIONS: t.ClassVar[tuple[int, ...]] = (1, 5, 6, 7)
|
|
35
|
+
MAIN_CLASS_NAME: t.ClassVar[str] = "PrimalTribeData"
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def _tribe_data(self) -> dict[str, t.Any]:
|
|
39
|
+
"""Get the nested TribeData struct as a dictionary."""
|
|
40
|
+
tribe_data = self.get_property_value("TribeData")
|
|
41
|
+
if tribe_data is None:
|
|
42
|
+
return {}
|
|
43
|
+
return tribe_data if isinstance(tribe_data, dict) else {}
|
|
44
|
+
|
|
45
|
+
# Convenience properties for tribe data
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def tribe_id(self) -> int | None:
|
|
49
|
+
"""Get the tribe's unique ID.
|
|
50
|
+
|
|
51
|
+
Note: ASE uses 'TribeId' while ASA uses 'TribeID' (different capitalization).
|
|
52
|
+
"""
|
|
53
|
+
# ASA uses TribeID, ASE uses TribeId
|
|
54
|
+
return self._tribe_data.get("TribeID") or self._tribe_data.get("TribeId")
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def name(self) -> str | None:
|
|
58
|
+
"""Get the tribe's name."""
|
|
59
|
+
return self._tribe_data.get("TribeName")
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def owner_player_id(self) -> int | None:
|
|
63
|
+
"""Get the player ID of the tribe owner.
|
|
64
|
+
|
|
65
|
+
Note: ASE uses 'OwnerPlayerDataID' while ASA uses 'OwnerPlayerDataId' (different capitalization).
|
|
66
|
+
"""
|
|
67
|
+
# ASE uses OwnerPlayerDataID, ASA uses OwnerPlayerDataId
|
|
68
|
+
return self._tribe_data.get("OwnerPlayerDataID") or self._tribe_data.get("OwnerPlayerDataId")
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def member_ids(self) -> list[int]:
|
|
72
|
+
"""Get list of member player IDs."""
|
|
73
|
+
return self._tribe_data.get("MembersPlayerDataID", [])
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def member_names(self) -> list[str]:
|
|
77
|
+
"""Get list of member player names."""
|
|
78
|
+
return self._tribe_data.get("MembersPlayerName", [])
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def member_ranks(self) -> list[int]:
|
|
82
|
+
"""Get list of member rank indices."""
|
|
83
|
+
return self._tribe_data.get("MembersRankGroups", [])
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def member_count(self) -> int:
|
|
87
|
+
"""Get the number of tribe members."""
|
|
88
|
+
return len(self.member_ids)
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def log_entries(self) -> list[str]:
|
|
92
|
+
"""Get tribe log entries as strings."""
|
|
93
|
+
return self._tribe_data.get("TribeLog", [])
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def rank_groups(self) -> list[dict[str, t.Any]]:
|
|
97
|
+
"""
|
|
98
|
+
Get tribe rank group definitions.
|
|
99
|
+
|
|
100
|
+
Each rank has:
|
|
101
|
+
- Name
|
|
102
|
+
- Permission flags
|
|
103
|
+
"""
|
|
104
|
+
rank_names = self._tribe_data.get("TribeRankGroupNames", [])
|
|
105
|
+
# Could also get permissions from TribeRankGroupPermissions
|
|
106
|
+
return [{"name": name} for name in rank_names]
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def alliance_ids(self) -> list[int]:
|
|
110
|
+
"""Get IDs of allied tribes."""
|
|
111
|
+
return self._tribe_data.get("TribeAlliances", [])
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def government_type(self) -> int:
|
|
115
|
+
"""Get tribe government type (0=Player Owned, 1=Tribe Owned, 2=Personal Owned)."""
|
|
116
|
+
return self._tribe_data.get("TribeGovernment", 0)
|
|
117
|
+
|
|
118
|
+
def get_members(self) -> list[dict[str, t.Any]]:
|
|
119
|
+
"""
|
|
120
|
+
Get detailed member information.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of dicts with member data (id, name, rank, etc.)
|
|
124
|
+
"""
|
|
125
|
+
ids = self.member_ids
|
|
126
|
+
names = self.member_names
|
|
127
|
+
ranks = self.member_ranks
|
|
128
|
+
|
|
129
|
+
members = []
|
|
130
|
+
for i in range(len(ids)):
|
|
131
|
+
member = {
|
|
132
|
+
"player_id": ids[i] if i < len(ids) else None,
|
|
133
|
+
"name": names[i] if i < len(names) else None,
|
|
134
|
+
"rank": ranks[i] if i < len(ranks) else 0,
|
|
135
|
+
}
|
|
136
|
+
members.append(member)
|
|
137
|
+
|
|
138
|
+
return members
|
|
139
|
+
|
|
140
|
+
def to_dict(self) -> dict[str, t.Any]:
|
|
141
|
+
"""Convert to dictionary with tribe-specific fields."""
|
|
142
|
+
base_dict = super().to_dict()
|
|
143
|
+
base_dict.update(
|
|
144
|
+
{
|
|
145
|
+
"tribe_id": self.tribe_id,
|
|
146
|
+
"name": self.name,
|
|
147
|
+
"owner_player_id": self.owner_player_id,
|
|
148
|
+
"member_count": self.member_count,
|
|
149
|
+
"members": self.get_members(),
|
|
150
|
+
"log_entries": self.log_entries,
|
|
151
|
+
"alliance_ids": self.alliance_ids,
|
|
152
|
+
"government_type": self.government_type,
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
return base_dict
|