arkparser 0.7.0__tar.gz → 0.7.1__tar.gz

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 (63) hide show
  1. {arkparser-0.7.0 → arkparser-0.7.1}/PKG-INFO +10 -1
  2. {arkparser-0.7.0 → arkparser-0.7.1}/README.md +9 -0
  3. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/common/types.py +11 -0
  4. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/data_models.py +5 -1
  5. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/export.py +14 -18
  6. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/files/world_save.py +5 -7
  7. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/game_objects/game_object.py +11 -1
  8. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser.egg-info/PKG-INFO +10 -1
  9. {arkparser-0.7.0 → arkparser-0.7.1}/pyproject.toml +1 -1
  10. {arkparser-0.7.0 → arkparser-0.7.1}/LICENSE +0 -0
  11. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/__init__.py +0 -0
  12. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/common/__init__.py +0 -0
  13. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/common/binary_reader.py +0 -0
  14. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/common/exceptions.py +0 -0
  15. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/common/map_config.py +0 -0
  16. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/common/normalization.py +0 -0
  17. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/common/version_detection.py +0 -0
  18. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/files/__init__.py +0 -0
  19. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/files/base.py +0 -0
  20. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/files/cloud_inventory.py +0 -0
  21. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/files/profile.py +0 -0
  22. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/files/tribe.py +0 -0
  23. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/game_objects/__init__.py +0 -0
  24. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/game_objects/container.py +0 -0
  25. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/game_objects/location.py +0 -0
  26. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/properties/__init__.py +0 -0
  27. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/properties/base.py +0 -0
  28. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/properties/byte_property.py +0 -0
  29. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/properties/compound.py +0 -0
  30. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/properties/primitives.py +0 -0
  31. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/properties/registry.py +0 -0
  32. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/structs/__init__.py +0 -0
  33. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/structs/base.py +0 -0
  34. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/structs/colors.py +0 -0
  35. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/structs/misc.py +0 -0
  36. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/structs/property_list.py +0 -0
  37. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/structs/registry.py +0 -0
  38. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser/structs/vectors.py +0 -0
  39. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser.egg-info/SOURCES.txt +0 -0
  40. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser.egg-info/dependency_links.txt +0 -0
  41. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser.egg-info/requires.txt +0 -0
  42. {arkparser-0.7.0 → arkparser-0.7.1}/arkparser.egg-info/top_level.txt +0 -0
  43. {arkparser-0.7.0 → arkparser-0.7.1}/setup.cfg +0 -0
  44. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_asa_header_position.py +0 -0
  45. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_asa_name_table.py +0 -0
  46. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_ase_cluster_drift.py +0 -0
  47. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_binary_reader.py +0 -0
  48. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_binary_reader_layouts.py +0 -0
  49. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_cloud_export.py +0 -0
  50. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_cloud_inventory.py +0 -0
  51. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_cryopod_export.py +0 -0
  52. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_current_stats.py +0 -0
  53. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_data_models.py +0 -0
  54. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_export.py +0 -0
  55. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_game_objects.py +0 -0
  56. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_golden_exports.py +0 -0
  57. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_lazy_properties.py +0 -0
  58. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_profile.py +0 -0
  59. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_review_fixes.py +0 -0
  60. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_tribe.py +0 -0
  61. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_v13_property_layouts.py +0 -0
  62. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_version_detection.py +0 -0
  63. {arkparser-0.7.0 → arkparser-0.7.1}/tests/test_world_save.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arkparser
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Pure Python parser for ARK: Survival Evolved and ARK: Survival Ascended save files
5
5
  Author: Vertyco
6
6
  License-Expression: MIT
@@ -681,6 +681,15 @@ Schema policy:
681
681
 
682
682
  ASA worldsave property layouts differ between **v13** (`TheIsland_WP` and older single-player saves, legacy `AsaSavegameToolkit`-style `dataSize + position + typeRef + byte` body) and **v14+** (current production ASA, marker-based body). Both are parsed by version-aware property readers; `WorldSave.version` is the source of truth.
683
683
 
684
+ ### Parse diagnostics (logging)
685
+
686
+ The parser logs through the standard `logging` module on the `arkparser.*` logger hierarchy. At DEBUG level it reports recoverable parse anomalies, for example ASE objects whose property block terminates early (the run of successfully parsed properties is kept and the remaining bytes are preserved on `GameObject.extra_data`):
687
+
688
+ ```python
689
+ import logging
690
+ logging.getLogger("arkparser").setLevel(logging.DEBUG)
691
+ ```
692
+
684
693
  ### Known limitation: ASA cryopod property blocks are partially decoded
685
694
 
686
695
  ASA cryopods (`PrimalItem_WeaponEmptyCryopod_C`, `SoulTrap`, `Vivarium`, `DinoBall`) store the embedded creature snapshot inside the item's `CustomItemDatas.CustomDataBytes.ByteArrays[0]` as a **zlib + custom-RLE compressed `AsaDataStore` blob**. The parser surfaces the snapshot via the simpler `CustomDataStrings` / `CustomDataFloats` accessors, exposing **species, tamed name, level, color regions, and `CurrentStatusValues[0..11]`** on each cryopodded creature.
@@ -647,6 +647,15 @@ Schema policy:
647
647
 
648
648
  ASA worldsave property layouts differ between **v13** (`TheIsland_WP` and older single-player saves, legacy `AsaSavegameToolkit`-style `dataSize + position + typeRef + byte` body) and **v14+** (current production ASA, marker-based body). Both are parsed by version-aware property readers; `WorldSave.version` is the source of truth.
649
649
 
650
+ ### Parse diagnostics (logging)
651
+
652
+ The parser logs through the standard `logging` module on the `arkparser.*` logger hierarchy. At DEBUG level it reports recoverable parse anomalies, for example ASE objects whose property block terminates early (the run of successfully parsed properties is kept and the remaining bytes are preserved on `GameObject.extra_data`):
653
+
654
+ ```python
655
+ import logging
656
+ logging.getLogger("arkparser").setLevel(logging.DEBUG)
657
+ ```
658
+
650
659
  ### Known limitation: ASA cryopod property blocks are partially decoded
651
660
 
652
661
  ASA cryopods (`PrimalItem_WeaponEmptyCryopod_C`, `SoulTrap`, `Vivarium`, `DinoBall`) store the embedded creature snapshot inside the item's `CustomItemDatas.CustomDataBytes.ByteArrays[0]` as a **zlib + custom-RLE compressed `AsaDataStore` blob**. The parser surfaces the snapshot via the simpler `CustomDataStrings` / `CustomDataFloats` accessors, exposing **species, tamed name, level, color regions, and `CurrentStatusValues[0..11]`** on each cryopodded creature.
@@ -21,6 +21,17 @@ from dataclasses import dataclass
21
21
  # The "None" name is used as a terminator in property lists
22
22
  NAME_NONE = "None"
23
23
 
24
+ # Class-name / blueprint fragments that mark a creature-storage item.
25
+ # Single source of truth for cryopod detection: in-world iteration
26
+ # (WorldSave.iter_cryopod_creatures), inventory filtering (export), and
27
+ # uploaded-item checks (UploadedItem.is_cryopod) all match against this.
28
+ CRYOPOD_CLASS_PATTERNS: tuple[str, ...] = (
29
+ "Cryopod",
30
+ "SoulTrap",
31
+ "Vivarium",
32
+ "DinoBall",
33
+ )
34
+
24
35
 
25
36
  # =============================================================================
26
37
  # ArkName (FName)
@@ -15,10 +15,14 @@ from dataclasses import dataclass, field
15
15
 
16
16
  from .common.binary_reader import BinaryReader
17
17
  from .common.normalization import normalize_indexed_data, normalize_indexed_list
18
+ from .common.types import CRYOPOD_CLASS_PATTERNS
18
19
  from .properties.registry import read_properties
19
20
 
20
21
  logger = logging.getLogger(__name__)
21
22
 
23
+ # Uploaded items match against lowercased blueprint paths.
24
+ _CRYOPOD_PATTERNS_LOWER: tuple[str, ...] = tuple(p.lower() for p in CRYOPOD_CLASS_PATTERNS)
25
+
22
26
  # Trailing UE actor spawn-instance suffix on cryopod class names, e.g.
23
27
  # "Raptor_Character_BP_C_2145673735". ARK stores the full instance name in the
24
28
  # cryopod CustomDataStrings/SoftClasses blob; the worldsave name-table path
@@ -820,7 +824,7 @@ class UploadedItem:
820
824
  def is_cryopod(self) -> bool:
821
825
  """Check if this item is a cryopod (or similar creature storage item)."""
822
826
  bp_lower = self.blueprint.lower()
823
- return any(x in bp_lower for x in ["cryopod", "soultrap", "vivarium", "dinoball"])
827
+ return any(x in bp_lower for x in _CRYOPOD_PATTERNS_LOWER)
824
828
 
825
829
  @property
826
830
  def cryopod_creature(self) -> CryopodCreature | None:
@@ -44,21 +44,14 @@ from pathlib import Path
44
44
  from arkparser.common.exceptions import ArkParseError
45
45
  from arkparser.common.map_config import MapConfig
46
46
  from arkparser.common.normalization import normalize_indexed_data, normalize_indexed_list
47
+ from arkparser.common.types import CRYOPOD_CLASS_PATTERNS
47
48
  from arkparser.data_models import CryopodCreature
48
49
  from arkparser.files import CloudInventory, Profile, Tribe
49
50
 
50
51
  logger = logging.getLogger(__name__)
51
52
 
52
- _CRYOPOD_CLASS_PATTERNS: tuple[str, ...] = (
53
- "Cryopod",
54
- "SoulTrap",
55
- "Vivarium",
56
- "DinoBall",
57
- )
58
-
59
-
60
53
  def _is_cryopod_class(class_name: str) -> bool:
61
- return any(p in class_name for p in _CRYOPOD_CLASS_PATTERNS)
54
+ return any(p in class_name for p in CRYOPOD_CLASS_PATTERNS)
62
55
 
63
56
 
64
57
  def _decode_inventory_cryopod(item_obj: t.Any) -> CryopodCreature | None:
@@ -152,6 +145,10 @@ _LEGACY_STAT_ORDER: tuple[str, ...] = (
152
145
  )
153
146
  _EXTRA_STAT_ORDER: tuple[str, ...] = ("torp", "water", "temp", "fort")
154
147
  _FLAT_STAT_ORDER: tuple[str, ...] = _LEGACY_STAT_ORDER + _EXTRA_STAT_ORDER
148
+ # The order tuples are hand-maintained views of _STAT_NAMES; catch drift at
149
+ # import time before it silently misaligns exported stat JSON.
150
+ assert len(_FLAT_STAT_ORDER) == len(_STAT_NAMES), "stat order tuples out of sync"
151
+ assert set(_FLAT_STAT_ORDER) == set(_STAT_NAMES), "stat order tuples out of sync"
155
152
 
156
153
 
157
154
  def _prop(obj: t.Any, name: str, default: t.Any = None, index: int | None = None) -> t.Any:
@@ -2519,8 +2516,7 @@ def _tribe_from_object(
2519
2516
  tribe_id = _int(_prop(obj, "TribeID")) or _int(_prop(obj, "TribeId"))
2520
2517
  owner_id = _int(_prop(obj, "OwnerPlayerDataID")) or _int(_prop(obj, "OwnerPlayerDataId"))
2521
2518
  members: list[dict[str, t.Any]] = []
2522
- i = 0
2523
- while True:
2519
+ for i in range(_MAX_TRIBE_MEMBERS):
2524
2520
  pid = _prop(obj, "MembersPlayerDataID", index=i)
2525
2521
  if pid is None:
2526
2522
  break
@@ -2536,15 +2532,14 @@ def _tribe_from_object(
2536
2532
  "steamid": (profile.unique_id or "") if profile is not None else "",
2537
2533
  }
2538
2534
  )
2539
- i += 1
2535
+ assert len(members) < _MAX_TRIBE_MEMBERS, "tribe member list exceeded bound"
2540
2536
  alliances: list[int] = []
2541
- j = 0
2542
- while True:
2537
+ for j in range(_MAX_TRIBE_MEMBERS):
2543
2538
  val = _prop(obj, "TribeAlliances", index=j)
2544
2539
  if val is None:
2545
2540
  break
2546
2541
  alliances.append(_int(val))
2547
- j += 1
2542
+ assert len(alliances) < _MAX_TRIBE_MEMBERS, "tribe alliance list exceeded bound"
2548
2543
  c = counts.get(tribe_id, {})
2549
2544
  data: dict[str, t.Any] = {
2550
2545
  "tribeid": tribe_id,
@@ -2569,14 +2564,15 @@ def _tribe_object_logs(obj: t.Any) -> list[str]:
2569
2564
  if isinstance(log_val, list):
2570
2565
  return [e for e in log_val if isinstance(e, str) and e.strip()]
2571
2566
  out: list[str] = []
2572
- k = 0
2573
- while True:
2567
+ entries = 0
2568
+ for k in range(_MAX_TRIBE_MEMBERS):
2574
2569
  v = _prop(obj, "TribeLog", index=k)
2575
2570
  if v is None:
2576
2571
  break
2572
+ entries += 1
2577
2573
  if isinstance(v, str) and v.strip():
2578
2574
  out.append(v)
2579
- k += 1
2575
+ assert entries < _MAX_TRIBE_MEMBERS, "tribe log list exceeded bound"
2580
2576
  return out
2581
2577
 
2582
2578
 
@@ -34,6 +34,7 @@ from uuid import UUID
34
34
  from ..common.binary_reader import BinaryReader, guid_str_le
35
35
  from ..common.exceptions import ArkParseError, CorruptDataError
36
36
  from ..common.normalization import normalize_indexed_data, normalize_indexed_list
37
+ from ..common.types import CRYOPOD_CLASS_PATTERNS
37
38
  from ..data_models import CryopodCreature
38
39
  from ..game_objects.container import GameObjectContainer
39
40
  from ..game_objects.game_object import MAX_OBJECT_COUNT, GameObject
@@ -428,13 +429,10 @@ class WorldSave:
428
429
  """Return creature nest objects (wyvern, drake, etc.)."""
429
430
  return self.container.get_nests()
430
431
 
431
- # Class-name fragments that indicate a creature-storage item. Mirrors
432
- # data_models.UploadedItem.is_cryopod (which matches against blueprint
433
- # paths); the in-world version matches against the GameObject's
434
- # ``class_name`` directly.
435
- _CRYOPOD_PATTERNS: t.ClassVar[tuple[str, ...]] = (
436
- "Cryopod", "SoulTrap", "Vivarium", "DinoBall",
437
- )
432
+ # Shared with data_models.UploadedItem.is_cryopod (blueprint paths) and
433
+ # the export inventory filter; the in-world version matches against the
434
+ # GameObject's ``class_name`` directly.
435
+ _CRYOPOD_PATTERNS: t.ClassVar[tuple[str, ...]] = CRYOPOD_CLASS_PATTERNS
438
436
 
439
437
  def iter_cryopod_creatures(self) -> t.Iterator[tuple[GameObject, CryopodCreature]]:
440
438
  """Yield ``(item_obj, CryopodCreature)`` for every *filled* cryopod
@@ -7,6 +7,7 @@ Game objects represent creatures, items, structures, players, and other entities
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import logging
10
11
  import re
11
12
  import typing as t
12
13
  from collections import defaultdict
@@ -16,6 +17,8 @@ from ..common.exceptions import CorruptDataError
16
17
  from ..properties.registry import read_properties, read_property
17
18
  from .location import LocationData
18
19
 
20
+ logger = logging.getLogger(__name__)
21
+
19
22
  if t.TYPE_CHECKING:
20
23
  from ..common.binary_reader import BinaryReader
21
24
  from ..properties.base import Property
@@ -420,11 +423,18 @@ class GameObject:
420
423
  if prop is None:
421
424
  break
422
425
  properties.append(prop)
423
- except Exception:
426
+ except Exception as exc:
424
427
  self.properties = properties
425
428
  self._props_loaded = True
426
429
  if next_object is None:
427
430
  raise
431
+ logger.debug(
432
+ "property parse stopped early for %s after %d properties, "
433
+ "remainder kept as extra_data: %s",
434
+ self.class_name,
435
+ len(properties),
436
+ exc,
437
+ )
428
438
 
429
439
  next_offset = properties_block_offset + next_object.properties_offset
430
440
  remaining = next_offset - reader.position
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arkparser
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Pure Python parser for ARK: Survival Evolved and ARK: Survival Ascended save files
5
5
  Author: Vertyco
6
6
  License-Expression: MIT
@@ -681,6 +681,15 @@ Schema policy:
681
681
 
682
682
  ASA worldsave property layouts differ between **v13** (`TheIsland_WP` and older single-player saves, legacy `AsaSavegameToolkit`-style `dataSize + position + typeRef + byte` body) and **v14+** (current production ASA, marker-based body). Both are parsed by version-aware property readers; `WorldSave.version` is the source of truth.
683
683
 
684
+ ### Parse diagnostics (logging)
685
+
686
+ The parser logs through the standard `logging` module on the `arkparser.*` logger hierarchy. At DEBUG level it reports recoverable parse anomalies, for example ASE objects whose property block terminates early (the run of successfully parsed properties is kept and the remaining bytes are preserved on `GameObject.extra_data`):
687
+
688
+ ```python
689
+ import logging
690
+ logging.getLogger("arkparser").setLevel(logging.DEBUG)
691
+ ```
692
+
684
693
  ### Known limitation: ASA cryopod property blocks are partially decoded
685
694
 
686
695
  ASA cryopods (`PrimalItem_WeaponEmptyCryopod_C`, `SoulTrap`, `Vivarium`, `DinoBall`) store the embedded creature snapshot inside the item's `CustomItemDatas.CustomDataBytes.ByteArrays[0]` as a **zlib + custom-RLE compressed `AsaDataStore` blob**. The parser surfaces the snapshot via the simpler `CustomDataStrings` / `CustomDataFloats` accessors, exposing **species, tamed name, level, color regions, and `CurrentStatusValues[0..11]`** on each cryopodded creature.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "arkparser"
7
- version = "0.7.0"
7
+ version = "0.7.1"
8
8
  description = "Pure Python parser for ARK: Survival Evolved and ARK: Survival Ascended save files"
9
9
  readme = "README.md"
10
10
  license = "MIT"
File without changes
File without changes
File without changes