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
arkparser/export.py ADDED
@@ -0,0 +1,485 @@
1
+ """
2
+ Export functions for producing C#-compatible JSON outputs.
3
+
4
+ Generates the 7 ASV export formats from parsed save data:
5
+ - ASV_Tamed: Tamed creature list
6
+ - ASV_Wild: Wild creature list
7
+ - ASV_Players: Player profiles
8
+ - ASV_Tribes: Tribe data
9
+ - ASV_Structures: Placed structures
10
+ - ASV_TribeLogs: Tribe log entries
11
+ - ASV_MapStructures: All structures with map coordinates
12
+
13
+ Each function accepts a parsed savegame (ASE or ASA) plus optional
14
+ map config and returns a list of dictionaries ready for JSON serialization.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import typing as t
21
+ from pathlib import Path
22
+
23
+ from arkparser.common.map_config import MapConfig, get_map_config
24
+ from arkparser.models.creature import TamedCreature, WildCreature
25
+ from arkparser.models.player import Player
26
+ from arkparser.models.stats import Location
27
+ from arkparser.models.structure import Structure
28
+ from arkparser.models.tribe import Tribe as TribeModel
29
+
30
+
31
+ def _apply_map(loc: Location | None, map_config: MapConfig | None) -> Location | None:
32
+ """Attach map config to a location for GPS conversion."""
33
+ if loc and map_config:
34
+ return loc.with_map(map_config)
35
+ return loc
36
+
37
+
38
+ # -------------------------------------------------------------------------
39
+ # Tamed Creatures (ASV_Tamed)
40
+ # -------------------------------------------------------------------------
41
+
42
+
43
+ def export_tamed(
44
+ save: t.Any,
45
+ map_config: MapConfig | None = None,
46
+ ) -> list[dict[str, t.Any]]:
47
+ """
48
+ Export tamed creatures in ASV_Tamed format.
49
+
50
+ Args:
51
+ save: A parsed savegame (WorldSave — auto-detects ASE/ASA).
52
+ map_config: Optional map config for GPS coordinates.
53
+
54
+ Returns:
55
+ List of tamed creature dictionaries.
56
+ """
57
+ results: list[dict[str, t.Any]] = []
58
+
59
+ tamed_objs = save.tamed_objects if hasattr(save, "tamed_objects") else []
60
+ objects = save.objects if hasattr(save, "objects") else {}
61
+ obj_lookup = _build_lookup(objects)
62
+
63
+ for obj in tamed_objs:
64
+ status = _find_status_component(obj, obj_lookup)
65
+ creature = TamedCreature.from_game_object(obj, status)
66
+ data = creature.to_dict()
67
+
68
+ if creature.location and map_config:
69
+ mapped_loc = creature.location.with_map(map_config)
70
+ if mapped_loc.latitude is not None:
71
+ data["lat"] = mapped_loc.latitude
72
+ if mapped_loc.longitude is not None:
73
+ data["lon"] = mapped_loc.longitude
74
+
75
+ inventory = _get_inventory_items(obj, obj_lookup)
76
+ data["inventory"] = inventory
77
+
78
+ results.append(data)
79
+
80
+ return results
81
+
82
+
83
+ # -------------------------------------------------------------------------
84
+ # Wild Creatures (ASV_Wild)
85
+ # -------------------------------------------------------------------------
86
+
87
+
88
+ def export_wild(
89
+ save: t.Any,
90
+ map_config: MapConfig | None = None,
91
+ ) -> list[dict[str, t.Any]]:
92
+ """
93
+ Export wild creatures in ASV_Wild format.
94
+
95
+ Args:
96
+ save: A parsed savegame.
97
+ map_config: Optional map config for GPS coordinates.
98
+
99
+ Returns:
100
+ List of wild creature dictionaries.
101
+ """
102
+ results: list[dict[str, t.Any]] = []
103
+
104
+ wild_objs = save.wild_objects if hasattr(save, "wild_objects") else []
105
+ objects = save.objects if hasattr(save, "objects") else {}
106
+ obj_lookup = _build_lookup(objects)
107
+
108
+ for obj in wild_objs:
109
+ status = _find_status_component(obj, obj_lookup)
110
+ creature = WildCreature.from_game_object(obj, status)
111
+ data = creature.to_dict()
112
+
113
+ if creature.location and map_config:
114
+ mapped_loc = creature.location.with_map(map_config)
115
+ if mapped_loc.latitude is not None:
116
+ data["lat"] = mapped_loc.latitude
117
+ if mapped_loc.longitude is not None:
118
+ data["lon"] = mapped_loc.longitude
119
+
120
+ results.append(data)
121
+
122
+ return results
123
+
124
+
125
+ # -------------------------------------------------------------------------
126
+ # Players (ASV_Players)
127
+ # -------------------------------------------------------------------------
128
+
129
+
130
+ def export_players(
131
+ save: t.Any,
132
+ map_config: MapConfig | None = None,
133
+ ) -> list[dict[str, t.Any]]:
134
+ """
135
+ Export player profiles in ASV_Players format.
136
+
137
+ Args:
138
+ save: A parsed savegame with profile data.
139
+ map_config: Optional map config for GPS coordinates.
140
+
141
+ Returns:
142
+ List of player dictionaries.
143
+ """
144
+ results: list[dict[str, t.Any]] = []
145
+
146
+ profiles = save.profiles if hasattr(save, "profiles") else []
147
+
148
+ for profile in profiles:
149
+ profile_obj = None
150
+ status_obj = None
151
+
152
+ if hasattr(profile, "profile"):
153
+ profile_obj = profile.profile
154
+ elif hasattr(profile, "objects") and profile.objects:
155
+ profile_obj = profile.objects[0]
156
+
157
+ if hasattr(profile, "objects"):
158
+ for obj in profile.objects:
159
+ cs = str(getattr(obj, "class_name", ""))
160
+ if "StatusComponent" in cs or "CharacterStatus" in cs:
161
+ status_obj = obj
162
+ break
163
+
164
+ if profile_obj is None:
165
+ continue
166
+
167
+ player = Player.from_game_object(profile_obj, status_obj)
168
+ data = player.to_dict()
169
+ results.append(data)
170
+
171
+ return results
172
+
173
+
174
+ # -------------------------------------------------------------------------
175
+ # Tribes (ASV_Tribes)
176
+ # -------------------------------------------------------------------------
177
+
178
+
179
+ def export_tribes(
180
+ save: t.Any,
181
+ ) -> list[dict[str, t.Any]]:
182
+ """
183
+ Export tribe data in ASV_Tribes format.
184
+
185
+ Args:
186
+ save: A parsed savegame with tribe data.
187
+
188
+ Returns:
189
+ List of tribe dictionaries.
190
+ """
191
+ results: list[dict[str, t.Any]] = []
192
+
193
+ tribes = save.tribes if hasattr(save, "tribes") else []
194
+
195
+ for tribe_parser in tribes:
196
+ tribe_obj = None
197
+ if hasattr(tribe_parser, "tribe"):
198
+ tribe_obj = tribe_parser.tribe
199
+ elif hasattr(tribe_parser, "objects") and tribe_parser.objects:
200
+ tribe_obj = tribe_parser.objects[0]
201
+
202
+ if tribe_obj is None:
203
+ continue
204
+
205
+ tribe_model = TribeModel.from_game_object(tribe_obj)
206
+ data = tribe_model.to_dict()
207
+ results.append(data)
208
+
209
+ return results
210
+
211
+
212
+ # -------------------------------------------------------------------------
213
+ # Structures (ASV_Structures)
214
+ # -------------------------------------------------------------------------
215
+
216
+
217
+ def export_structures(
218
+ save: t.Any,
219
+ map_config: MapConfig | None = None,
220
+ ) -> list[dict[str, t.Any]]:
221
+ """
222
+ Export placed structures in ASV_Structures format.
223
+
224
+ Args:
225
+ save: A parsed savegame.
226
+ map_config: Optional map config for GPS coordinates.
227
+
228
+ Returns:
229
+ List of structure dictionaries.
230
+ """
231
+ results: list[dict[str, t.Any]] = []
232
+
233
+ struct_objs = save.structure_objects if hasattr(save, "structure_objects") else []
234
+
235
+ for obj in struct_objs:
236
+ structure = Structure.from_game_object(obj)
237
+ data = structure.to_dict()
238
+
239
+ if structure.location and map_config:
240
+ mapped_loc = structure.location.with_map(map_config)
241
+ if mapped_loc.latitude is not None:
242
+ data["lat"] = mapped_loc.latitude
243
+ if mapped_loc.longitude is not None:
244
+ data["lon"] = mapped_loc.longitude
245
+
246
+ results.append(data)
247
+
248
+ return results
249
+
250
+
251
+ # -------------------------------------------------------------------------
252
+ # Tribe Logs (ASV_TribeLogs)
253
+ # -------------------------------------------------------------------------
254
+
255
+
256
+ def export_tribe_logs(
257
+ save: t.Any,
258
+ ) -> list[dict[str, t.Any]]:
259
+ """
260
+ Export tribe log entries in ASV_TribeLogs format.
261
+
262
+ Args:
263
+ save: A parsed savegame with tribe data.
264
+
265
+ Returns:
266
+ List of tribe log dictionaries (one per tribe).
267
+ """
268
+ results: list[dict[str, t.Any]] = []
269
+
270
+ tribes = save.tribes if hasattr(save, "tribes") else []
271
+
272
+ for tribe_parser in tribes:
273
+ tribe_obj = None
274
+ if hasattr(tribe_parser, "tribe"):
275
+ tribe_obj = tribe_parser.tribe
276
+ elif hasattr(tribe_parser, "objects") and tribe_parser.objects:
277
+ tribe_obj = tribe_parser.objects[0]
278
+
279
+ if tribe_obj is None:
280
+ continue
281
+
282
+ tribe_model = TribeModel.from_game_object(tribe_obj)
283
+ results.append(
284
+ {
285
+ "tribeid": tribe_model.tribe_id,
286
+ "tribe": tribe_model.name,
287
+ "logs": [entry.to_dict() for entry in tribe_model.log],
288
+ }
289
+ )
290
+
291
+ return results
292
+
293
+
294
+ # -------------------------------------------------------------------------
295
+ # Map Structures (ASV_MapStructures) — structures with GPS coords
296
+ # -------------------------------------------------------------------------
297
+
298
+
299
+ def export_map_structures(
300
+ save: t.Any,
301
+ map_config: MapConfig | None = None,
302
+ ) -> list[dict[str, t.Any]]:
303
+ """
304
+ Export structures with map coordinates in ASV_MapStructures format.
305
+
306
+ Same as ASV_Structures but intended for map visualisation with GPS coords.
307
+
308
+ Args:
309
+ save: A parsed savegame.
310
+ map_config: Map config for GPS coordinate conversion.
311
+
312
+ Returns:
313
+ List of structure dictionaries with GPS coordinates.
314
+ """
315
+ return export_structures(save, map_config)
316
+
317
+
318
+ # -------------------------------------------------------------------------
319
+ # Full Export — all 7 formats at once
320
+ # -------------------------------------------------------------------------
321
+
322
+
323
+ def export_all(
324
+ save: t.Any,
325
+ map_config: MapConfig | None = None,
326
+ ) -> dict[str, list[dict[str, t.Any]]]:
327
+ """
328
+ Export all 7 ASV formats at once.
329
+
330
+ Args:
331
+ save: A parsed savegame.
332
+ map_config: Optional map config for GPS coordinates.
333
+
334
+ Returns:
335
+ Dictionary with keys ASV_Tamed, ASV_Wild, ASV_Players,
336
+ ASV_Tribes, ASV_Structures, ASV_TribeLogs, ASV_MapStructures.
337
+ """
338
+ return {
339
+ "ASV_Tamed": export_tamed(save, map_config),
340
+ "ASV_Wild": export_wild(save, map_config),
341
+ "ASV_Players": export_players(save, map_config),
342
+ "ASV_Tribes": export_tribes(save),
343
+ "ASV_Structures": export_structures(save, map_config),
344
+ "ASV_TribeLogs": export_tribe_logs(save),
345
+ "ASV_MapStructures": export_map_structures(save, map_config),
346
+ }
347
+
348
+
349
+ def export_to_files(
350
+ save: t.Any,
351
+ output_dir: str | Path,
352
+ map_config: MapConfig | None = None,
353
+ ) -> list[Path]:
354
+ """
355
+ Export all 7 ASV formats to JSON files.
356
+
357
+ Args:
358
+ save: A parsed savegame.
359
+ output_dir: Directory to write JSON files to.
360
+ map_config: Optional map config for GPS coordinates.
361
+
362
+ Returns:
363
+ List of paths to the created files.
364
+ """
365
+ out = Path(output_dir)
366
+ out.mkdir(parents=True, exist_ok=True)
367
+
368
+ all_data = export_all(save, map_config)
369
+ created: list[Path] = []
370
+
371
+ for name, data in all_data.items():
372
+ path = out / f"{name}.json"
373
+ path.write_text(json.dumps(data, indent=2, default=str), encoding="utf-8")
374
+ created.append(path)
375
+
376
+ return created
377
+
378
+
379
+ # -------------------------------------------------------------------------
380
+ # Helpers
381
+ # -------------------------------------------------------------------------
382
+
383
+
384
+ def _build_lookup(objects: t.Any) -> dict[t.Any, t.Any]:
385
+ """Build an object lookup dict from various container formats."""
386
+ if isinstance(objects, dict):
387
+ return objects
388
+ if isinstance(objects, list):
389
+ result: dict[t.Any, t.Any] = {}
390
+ for obj in objects:
391
+ key = getattr(obj, "guid", None) or id(obj)
392
+ result[key] = obj
393
+ return result
394
+ return {}
395
+
396
+
397
+ def _find_status_component(obj: t.Any, lookup: dict[t.Any, t.Any]) -> t.Any:
398
+ """
399
+ Find the status component object for a creature.
400
+
401
+ Looks for MyCharacterStatusComponent reference or component link.
402
+ """
403
+ # Check components dict
404
+ if hasattr(obj, "components"):
405
+ for key, comp in obj.components.items():
406
+ if "status" in str(key).lower():
407
+ return comp
408
+
409
+ # Check property reference
410
+ ref = None
411
+ if hasattr(obj, "get_property_value"):
412
+ ref = obj.get_property_value("MyCharacterStatusComponent", default=None)
413
+
414
+ if ref is None:
415
+ return None
416
+
417
+ # Resolve reference
418
+ guid = None
419
+ if hasattr(ref, "guid") and ref.guid:
420
+ guid = ref.guid
421
+ elif hasattr(ref, "object_id") and ref.object_id >= 0:
422
+ if isinstance(lookup, list):
423
+ return lookup[ref.object_id] if ref.object_id < len(lookup) else None
424
+
425
+ if guid and guid in lookup:
426
+ return lookup[guid]
427
+
428
+ return None
429
+
430
+
431
+ def _get_inventory_items(obj: t.Any, lookup: dict[t.Any, t.Any]) -> list[dict[str, t.Any]]:
432
+ """Get inventory items for a creature."""
433
+ items: list[dict[str, t.Any]] = []
434
+
435
+ inv_ref = None
436
+ if hasattr(obj, "get_property_value"):
437
+ inv_ref = obj.get_property_value("MyInventoryComponent", default=None)
438
+
439
+ if inv_ref is None:
440
+ return items
441
+
442
+ # Resolve inventory object
443
+ inv_obj = None
444
+ guid = None
445
+ if hasattr(inv_ref, "guid") and inv_ref.guid:
446
+ guid = inv_ref.guid
447
+
448
+ if guid and guid in lookup:
449
+ inv_obj = lookup[guid]
450
+
451
+ if inv_obj is None:
452
+ return items
453
+
454
+ # Get item references from inventory
455
+ item_refs = None
456
+ if hasattr(inv_obj, "get_property_value"):
457
+ item_refs = inv_obj.get_property_value("InventoryItems", default=None)
458
+
459
+ if not isinstance(item_refs, list):
460
+ return items
461
+
462
+ for ref in item_refs:
463
+ item_guid = None
464
+ if hasattr(ref, "guid") and ref.guid:
465
+ item_guid = ref.guid
466
+
467
+ if item_guid and item_guid in lookup:
468
+ item_obj = lookup[item_guid]
469
+ item_class = str(getattr(item_obj, "class_name", ""))
470
+ qty = 1
471
+ is_bp = False
472
+ if hasattr(item_obj, "get_property_value"):
473
+ q = item_obj.get_property_value("ItemQuantity", default=1)
474
+ qty = int(q) if q else 1
475
+ is_bp = item_obj.get_property_value("bIsBlueprint", default=False)
476
+
477
+ items.append(
478
+ {
479
+ "itemId": item_class,
480
+ "qty": qty,
481
+ "blueprint": bool(is_bp),
482
+ }
483
+ )
484
+
485
+ return items
@@ -0,0 +1,25 @@
1
+ """
2
+ File parsers for ARK save data.
3
+
4
+ This module provides parsers for various ARK save file formats:
5
+ - Profile: Player profile data (.arkprofile)
6
+ - Tribe: Tribe data (.arktribe)
7
+ - CloudInventory: Obelisk/cloud inventory data (no extension)
8
+ - WorldSave: World save data (.ark) — auto-detects ASE binary and ASA SQLite
9
+
10
+ All parsers support both ASE and ASA formats with automatic detection.
11
+ """
12
+
13
+ from .base import ArkFile
14
+ from .cloud_inventory import CloudInventory
15
+ from .profile import Profile
16
+ from .tribe import Tribe
17
+ from .world_save import WorldSave
18
+
19
+ __all__ = [
20
+ "ArkFile",
21
+ "Profile",
22
+ "Tribe",
23
+ "CloudInventory",
24
+ "WorldSave",
25
+ ]