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.
Files changed (46) hide show
  1. arkparser/__init__.py +117 -0
  2. arkparser/common/__init__.py +72 -0
  3. arkparser/common/binary_reader.py +402 -0
  4. arkparser/common/exceptions.py +99 -0
  5. arkparser/common/map_config.py +166 -0
  6. arkparser/common/types.py +249 -0
  7. arkparser/common/version_detection.py +195 -0
  8. arkparser/data_models.py +801 -0
  9. arkparser/export.py +485 -0
  10. arkparser/files/__init__.py +25 -0
  11. arkparser/files/base.py +309 -0
  12. arkparser/files/cloud_inventory.py +259 -0
  13. arkparser/files/profile.py +205 -0
  14. arkparser/files/tribe.py +155 -0
  15. arkparser/files/world_save.py +699 -0
  16. arkparser/game_objects/__init__.py +32 -0
  17. arkparser/game_objects/container.py +180 -0
  18. arkparser/game_objects/game_object.py +273 -0
  19. arkparser/game_objects/location.py +87 -0
  20. arkparser/models/__init__.py +29 -0
  21. arkparser/models/character.py +227 -0
  22. arkparser/models/creature.py +642 -0
  23. arkparser/models/item.py +207 -0
  24. arkparser/models/player.py +263 -0
  25. arkparser/models/stats.py +226 -0
  26. arkparser/models/structure.py +176 -0
  27. arkparser/models/tribe.py +291 -0
  28. arkparser/properties/__init__.py +77 -0
  29. arkparser/properties/base.py +329 -0
  30. arkparser/properties/byte_property.py +230 -0
  31. arkparser/properties/compound.py +1125 -0
  32. arkparser/properties/primitives.py +803 -0
  33. arkparser/properties/registry.py +236 -0
  34. arkparser/py.typed +0 -0
  35. arkparser/structs/__init__.py +60 -0
  36. arkparser/structs/base.py +63 -0
  37. arkparser/structs/colors.py +108 -0
  38. arkparser/structs/misc.py +133 -0
  39. arkparser/structs/property_list.py +101 -0
  40. arkparser/structs/registry.py +140 -0
  41. arkparser/structs/vectors.py +221 -0
  42. arkparser-0.1.0.dist-info/METADATA +833 -0
  43. arkparser-0.1.0.dist-info/RECORD +46 -0
  44. arkparser-0.1.0.dist-info/WHEEL +5 -0
  45. arkparser-0.1.0.dist-info/licenses/LICENSE +21 -0
  46. 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
@@ -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