arkparser 0.5.4__tar.gz → 0.7.0__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 (65) hide show
  1. {arkparser-0.5.4 → arkparser-0.7.0}/PKG-INFO +51 -11
  2. {arkparser-0.5.4 → arkparser-0.7.0}/README.md +50 -10
  3. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/__init__.py +98 -98
  4. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/common/binary_reader.py +98 -5
  5. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/common/normalization.py +15 -2
  6. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/data_models.py +2 -2
  7. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/export.py +425 -130
  8. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/files/world_save.py +378 -43
  9. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/game_objects/container.py +149 -20
  10. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/game_objects/game_object.py +77 -0
  11. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/properties/base.py +32 -10
  12. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/properties/compound.py +1 -1
  13. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/properties/primitives.py +5 -5
  14. arkparser-0.7.0/arkparser/properties/registry.py +434 -0
  15. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser.egg-info/PKG-INFO +51 -11
  16. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser.egg-info/SOURCES.txt +2 -0
  17. {arkparser-0.5.4 → arkparser-0.7.0}/pyproject.toml +86 -86
  18. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_binary_reader.py +19 -1
  19. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_cryopod_export.py +1 -1
  20. arkparser-0.7.0/tests/test_export.py +177 -0
  21. arkparser-0.7.0/tests/test_golden_exports.py +78 -0
  22. arkparser-0.7.0/tests/test_lazy_properties.py +164 -0
  23. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_review_fixes.py +1 -1
  24. arkparser-0.5.4/arkparser/properties/registry.py +0 -256
  25. arkparser-0.5.4/tests/test_export.py +0 -92
  26. {arkparser-0.5.4 → arkparser-0.7.0}/LICENSE +0 -0
  27. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/common/__init__.py +0 -0
  28. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/common/exceptions.py +0 -0
  29. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/common/map_config.py +0 -0
  30. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/common/types.py +0 -0
  31. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/common/version_detection.py +0 -0
  32. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/files/__init__.py +0 -0
  33. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/files/base.py +0 -0
  34. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/files/cloud_inventory.py +0 -0
  35. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/files/profile.py +0 -0
  36. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/files/tribe.py +0 -0
  37. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/game_objects/__init__.py +0 -0
  38. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/game_objects/location.py +0 -0
  39. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/properties/__init__.py +0 -0
  40. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/properties/byte_property.py +0 -0
  41. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/structs/__init__.py +0 -0
  42. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/structs/base.py +0 -0
  43. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/structs/colors.py +0 -0
  44. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/structs/misc.py +0 -0
  45. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/structs/property_list.py +0 -0
  46. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/structs/registry.py +0 -0
  47. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser/structs/vectors.py +0 -0
  48. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser.egg-info/dependency_links.txt +0 -0
  49. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser.egg-info/requires.txt +0 -0
  50. {arkparser-0.5.4 → arkparser-0.7.0}/arkparser.egg-info/top_level.txt +0 -0
  51. {arkparser-0.5.4 → arkparser-0.7.0}/setup.cfg +0 -0
  52. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_asa_header_position.py +0 -0
  53. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_asa_name_table.py +0 -0
  54. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_ase_cluster_drift.py +0 -0
  55. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_binary_reader_layouts.py +0 -0
  56. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_cloud_export.py +0 -0
  57. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_cloud_inventory.py +0 -0
  58. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_current_stats.py +0 -0
  59. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_data_models.py +0 -0
  60. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_game_objects.py +0 -0
  61. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_profile.py +0 -0
  62. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_tribe.py +0 -0
  63. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_v13_property_layouts.py +0 -0
  64. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_version_detection.py +0 -0
  65. {arkparser-0.5.4 → arkparser-0.7.0}/tests/test_world_save.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arkparser
3
- Version: 0.5.4
3
+ Version: 0.7.0
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
@@ -58,6 +58,7 @@ A pure-Python library for parsing ARK: Survival Evolved (ASE) and ARK: Survival
58
58
  - **Dual format**: automatic ASE (v5-12) / ASA (v13-14+, SQLite) detection
59
59
  - **Legacy-parity export**: drop-in JSON output matching `ASVExport.exe` schema, plus parser-only extras under descriptive snake_case keys (no namespace prefix; never overloading a legacy key)
60
60
  - **Fast**: pure-Python `BinaryReader` (`int.from_bytes` + `struct.Struct` unpackers, slots-based dataclasses), a 30 MB ASE save (65k objects) loads in ~3s on CPython 3.14
61
+ - **Memory-bounded**: streaming JSON export plus an opt-in lazy parse mode (`lazy_properties=True`, ASE and ASA) that materializes property blocks on demand and evicts them as records stream to disk; a 1.8 GB / 1.79M-object busy-PvE ASE save exports in 2.5 GB peak RSS instead of 7.1 GB, and a 217k-object ASA save in 307 MB instead of 853 MB (and slightly faster)
61
62
 
62
63
  ## Installation
63
64
 
@@ -132,6 +133,38 @@ print(f"Parse errors: {save.parse_error_count}")
132
133
  print(f"Is ASA: {save.is_asa}")
133
134
  ```
134
135
 
136
+ ### Low-memory parsing (large saves)
137
+
138
+ By default every object's property block is parsed up front, which on a busy
139
+ multi-GB PvE save means a multi-GB resident object graph. `lazy_properties=True`
140
+ parses object headers eagerly but defers each property block until something
141
+ reads it, then the export drivers evict it again once the record has been
142
+ written:
143
+
144
+ ```python
145
+ from arkparser import WorldSave, export_to_files
146
+ from arkparser.common import get_map_config
147
+
148
+ save = WorldSave.load("path/to/Fjordur.ark", lazy_properties=True) # ASE
149
+ save = WorldSave.load("path/to/TheIsland_WP.ark", lazy_properties=True) # ASA
150
+ export_to_files(save, "output/", get_map_config("fjordur.ark"))
151
+ ```
152
+
153
+ Output is identical to eager mode (validated record-for-record). ASE retains
154
+ the file reader and re-seeks each object's block on demand; ASA retains the
155
+ SQLite connection (one held read transaction) and re-fetches row blobs by
156
+ GUID. On ASA v14+ saves the export pipeline additionally uses partial decodes:
157
+ a verified byte-exact skip walk that parses only the property names a record
158
+ needs, with any out-of-whitelist read transparently upgrading to the full
159
+ block.
160
+
161
+ Measured load + export, same machine: a 1.8 GB Fjordur PvE ASE save (1.79M
162
+ objects) drops from ~7.1 GB peak RSS / 348 s eager to ~2.5 GB / 287 s lazy; a
163
+ 233 MB ASA TheIsland save (217k objects) drops from 853 MB / 38 s eager to
164
+ 307 MB / ~36 s lazy. Property access on a lazy save is transparent: any
165
+ `get_property_value` call materializes the block on demand, so all getters and
166
+ exports work unchanged.
167
+
135
168
  ### Cryopod-stored Creatures
136
169
 
137
170
  Tamed creatures stored inside in-world cryopods aren't returned by `get_tamed_creatures()`. Iterate them via `WorldSave.iter_cryopod_creatures()`:
@@ -218,7 +251,7 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
218
251
  | `isClone` | legacy | `bIsClone` or `bIsCloneDino` |
219
252
  | `tamedServer` | legacy | `TamedOnServerName` |
220
253
  | `uploadedServer` | legacy | `UploadedFromServerName` |
221
- | `maturation` | legacy | `str(int(BabyAge * 100))` — integer maturation percent (a baby with no `BabyAge` is `0`, a newborn). Note: legacy emits the full float string (e.g. `"7.5131035"`); arkparser truncates to the integer percent. Semantically equal and the downstream consumer coerces to `int` either way. |
254
+ | `maturation` | legacy | `str(int(BabyAge * 100))`, integer maturation percent (a baby with no `BabyAge` is `0`, a newborn). Note: legacy emits the full float string (e.g. `"7.5131035"`); arkparser truncates to the integer percent. Semantically equal and the downstream consumer coerces to `int` either way. |
222
255
  | `traits` | legacy | `CreatureTraits` as a list of `{"trait": <class>}` objects (matches `ASVExport`'s shape, not a flat string list) |
223
256
  | `inventory` | legacy | items from `MyInventoryComponent.InventoryItems`. Each entry carries `itemId`, `qty`, `blueprint`, plus a full snake_case property dump flattened in at the top level (`id`, `rating`, `durability`, `quality`, `damage`, `armor`, `durability_max`, `hypo`, `hyper`, `clip_size`, `weight`, `crafter`, `crafter_tribe`, `skill_bonus`, `loaded_ammo`, `spoils_at`, `spoiled_at`, `c0`..`c5`, `egg_*`, etc). `item_stat_values` is unpacked into the universal 8-slot ARK map (slot 0 `gen_quality`, 1 `armor`, 2 `durability_max`, 3 `damage`, 4 `clip_size`, 5 `hypo`, 6 `weight`, 7 `hyperthermal_insulation`); raw uint16s scaled by the per-blueprint multiplier (which lives in the UE blueprint, not the save). Default / unset values are filtered (no `craft_queue=0`, `skin=-1`, `color_pre_skin=[0]*6`, NaN spoil timers, etc). When the item is a **cryopod / soultrap / vivarium / dinoball** with an embedded creature, the entry is enriched with `dino_id` (combined 64-bit id matching the corresponding `ASV_Tamed` record), `dino_creature` (species / class name), and `dino_name` (`TamedName` if set). Cryopods stored in containers (cryofridges, vaults, dedicated storage) get the same enrichment. |
224
257
  | `father_id`, `mother_id` | added | combined dino id from the first `DinoAncestors` entry (`null` when missing) |
@@ -281,7 +314,7 @@ ARK keeps player data in **two distinct places**, and the parser builds `ASV_Pla
281
314
 
282
315
  The export pipeline (`export_players`) loops over `save.profiles` (a list the caller assembles). For each entry:
283
316
 
284
- - If it's a `Profile` instance → `_player_from_profile` runs: fills core identity (name, gender, level, stats, tribe id, engram count, experience, active datetime from `LastLoginTime`). Leaves `lat`/`lon`/`ccc` as zero, `inventory` empty, pawn-state flags (`is_sleeping`, `is_dead`, `chibi_levels`, …) absent.
317
+ - If it's a `Profile` instance → `_player_from_profile` runs: fills core identity (name, gender, level, stats, tribe id, engram count, experience, active datetime from `LastLoginTime`). It joins `PlayerDataID == LinkedPlayerDataID` against the world save's player pawns, so when a live pawn exists `lat`/`lon`/`ccc` come from that pawn's `.location`; players with no live pawn (dead / logged out) keep `lat`/`lon`/`ccc` at zero. `inventory` is empty and pawn-state flags (`is_sleeping`, `is_dead`, `chibi_levels`, …) absent (the profile carries neither).
285
318
  - Otherwise it's treated as a wrapped `(profile, objects)` pair pointing at an in-world `PlayerPawn` → `_player_from_object` runs: fills location, inventory, pawn-state flags, body/hair cosmetics, death timestamps, and `active` from `SavedLastTimeHadController`.
286
319
 
287
320
  For the richest output, hand `export_players` **both**, assemble a wrapper for each player that carries the `Profile` (for offline identity) *and* the in-world pawn (when present) so the merged record gets identity + live state. The current validation script only passes `Profile` instances, which is why fields like `is_sleeping` / `body_colors` / `current_weapon` show up empty in the validation output even though the parser supports them.
@@ -303,7 +336,7 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
303
336
  | `netAddress` | legacy (now populated) | Last client IP ARK persisted (`SavedNetworkAddress` in profile `MyData`). Legacy ASVExport reads the same field (ContentPlayer.cs:157 ASE / :341 ASA). `""` when the profile lacks it (e.g. never-played placeholders). ASA stores a clean IPv4/IPv6 string; some ASE saves store an engine-truncated value (e.g. `"[2001"`) reproduced verbatim, matching legacy. |
304
337
  | `steamid`, `dataFile` | legacy | platform net id and `{steamid}.arkprofile` filename |
305
338
  | `active` | legacy (now populated) | ISO 8601 datetime of last login, converted from profile `LastLoginTime` or in-world pawn `SavedLastTimeHadController`. Legacy schema reserved the field but the old C# exporter only filled it for in-world pawns; the parser fills it for profiles too. `null` when neither source is present. |
306
- | `lat`, `lon`, `ccc` | legacy (now populated) | In-world position when the record was built from a `PlayerPawn` GameObject. Records sourced from profile / cluster files keep the legacy `0` / `"0 0 0"` placeholders since profiles carry no world position. |
339
+ | `lat`, `lon`, `ccc` | legacy (now populated) | In-world position. Built directly from a `PlayerPawn` GameObject when one is passed, and for profile-sourced records the parser joins `PlayerDataID == LinkedPlayerDataID` to the world save's player pawns and pulls the matched pawn's `.location`. Records whose player has no live pawn (dead / logged out / cluster-only) keep the legacy `0` / `"0 0 0"` placeholders since there is no world position to report. |
307
340
  | `inventory` | legacy (now populated) | Items from the pawn's `MyInventoryComponent` when built from an in-world pawn. Empty list otherwise. |
308
341
  | `engram_points` | added | `TotalEngramPoints` |
309
342
  | `experience` | added | `ExperiencePoints` (status), integer |
@@ -322,17 +355,17 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
322
355
 
323
356
  #### `ASV_Tribes` schema
324
357
 
325
- `ASV_Tribes` (and `ASV_TribeLogs` / `ASV_Players`, which iterate the same list) is a **superset** of the loaded `.arktribe` files, mirroring legacy `ContentContainer`: it seeds two sentinels (`[ASV Unclaimed]` id `2000000000`, `[ASV Abandoned]` id `-2147483648`), adds every file-backed tribe, then synthesizes a stub tribe for each player profile not already in a tribe (`Tribe of <name>`, id = `PlayerDataID`), each distinct structure `TargetingTeam` (`>= 50000`), and each distinct in-world tame `TargetingTeam` — deduped by id. Cross-server tribes that exist **only** in cluster cloud-inventory files (no map presence) are not synthesized.
358
+ `ASV_Tribes` (and `ASV_TribeLogs` / `ASV_Players`, which iterate the same list) is a **superset** of the loaded `.arktribe` files, mirroring legacy `ContentContainer`: it seeds two sentinels (`[ASV Unclaimed]` id `2000000000`, `[ASV Abandoned]` id `-2147483648`), adds every file-backed tribe, then synthesizes a stub tribe for each player profile not already in a tribe (`Tribe of <name>`, id = `PlayerDataID`), each distinct structure `TargetingTeam` (`>= 50000`), and each distinct in-world tame `TargetingTeam`, deduped by id. Cross-server tribes that exist **only** in cluster cloud-inventory files (no map presence) are not synthesized.
326
359
 
327
360
  | Field | Origin | Source / formula |
328
361
  |---|---|---|
329
362
  | `tribeid` | legacy | `TribeID` / parser `Tribe.tribe_id`, or a synthesized stub/sentinel id (see above) |
330
363
  | `tribe` | legacy | tribe name (file `TribeName`; for stubs, `OwnerName`/`TamerString`; or `Tribe of <character>` for a solo) |
331
- | `players` | legacy | count of players **allocated** to this tribe (profiles whose explicit team / membership / solo id resolves here, plus member back-fill), matching legacy `Players.Count` — not the raw `.arktribe` member count |
364
+ | `players` | legacy | count of players **allocated** to this tribe (profiles whose explicit team / membership / solo id resolves here, plus member back-fill), matching legacy `Players.Count`, not the raw `.arktribe` member count |
332
365
  | `members` | legacy | list of `{ign, lvl, playerid, playername, steamid}` built from the allocated players. `lvl` and `steamid` populate from the matching `.arkprofile`; members with no profile (back-filled from the tribe's member list) carry `lvl=0`, `steamid=""`. |
333
366
  | `tames`, `structures` | legacy | counts derived from `WorldSave` (creatures + structures whose `TargetingTeam` matches) |
334
367
  | `uploadedTames` | legacy | reserved (currently `0`) |
335
- | `active` | legacy | ISO 8601 datetime of the most recent tribe log entry, converted from in-game "Day N, HH:MM:SS" via the save's anchor. `null` when no parseable log entries or anchors are missing. |
368
+ | `active` | legacy | ISO 8601 datetime, legacy `ContentTribe.LastActive`: max of the tribe file's write time (`.arktribe` mtime; world-save mtime for in-save tribes) and the allocated members' last-active times (`LastLoginTime` via the save anchor), discarding future values. `null` when no past candidate exists. Tribe-log "Day N" stamps are game-calendar time and are deliberately not used. |
336
369
  | `dataFile` | legacy | `"{tribeid}.arktribe"` filename pattern. |
337
370
  | `owner_id` | added | `OwnerPlayerDataID` / parser `Tribe.owner_player_id` |
338
371
  | `owner_name` | added | `OwnerPlayerName` (object form only; parser-tribe form has no equivalent) |
@@ -353,7 +386,7 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
353
386
  | `id` | added | `GameObject.id`, internal numeric identifier. Cross-references the values in other records' `linked_structures` / `saddle_structures` / `attached_dino_id` lists. |
354
387
  | `tribeid` | legacy | `TargetingTeam` for player-owned structures (`>= 50000`). Unowned structures fall to the synthetic `[ASV Abandoned]` tribe id `-2147483648` (legacy `int.MinValue`), mirroring `ContentContainer.cs`. |
355
388
  | `tribe` | legacy | resolved owning-tribe name: the loaded `.arktribe` `TribeName`, else the structure's `OwnerName` / `TamerString`; `"[ASV Abandoned]"` for unowned. (Legacy emits the resolved tribe name, not the raw `OwnerName`.) |
356
- | `struct` | legacy | `GameObject.class_name`. Unowned map elements / crates / debug actors (`Button_*`, `*Vein_*`, `*Nest_*`, `*Beaver*`, `BeeHive_C`, `ArtifactCrate_*`, `TributeTerminal_*`, `SupplyCrate_*`) are excluded — surfaced via `ASV_MapStructures` instead, matching legacy's abandoned-structure filter. |
389
+ | `struct` | legacy | `GameObject.class_name`. Unowned map elements / crates / debug actors (`Button_*`, `*Vein_*`, `*Nest_*`, `*Beaver*`, `BeeHive_C`, `ArtifactCrate_*`, `TributeTerminal_*`, `SupplyCrate_*`) are excluded; surfaced via `ASV_MapStructures` instead, matching legacy's abandoned-structure filter. |
357
390
  | `name` | legacy | `BoxName` (empty when it matches the class name, mirroring legacy's no-rename strip) |
358
391
  | `locked` | legacy | `bIsPinLocked` or `bIsLocked` |
359
392
  | `created` | legacy (richer) | ISO 8601 datetime with the local TZ of the parser machine, computed `save.file_mtime + (OriginalCreationTime - game_time)` (mirrors legacy `ContentContainer.GetApproxDateTimeOf`). `null` when the anchors are missing. |
@@ -412,7 +445,7 @@ data = export_all(save, map_config, cluster="path/to/cluster")
412
445
  # .arkprofile filename stem.
413
446
  ```
414
447
 
415
- Cluster items are spliced into the owning player's `inventory`; no separate `ASV_ClusterItems` file is emitted. The match works because every cluster file and the player's `.arkprofile` share a stem — the Steam id on ASE, the hex platform UUID on ASA — so the join is keyed on the profile's source filename stem. (`Profile.unique_id` equals that stem only on ASE; on ASA it is the numeric net id, not the UUID filename, so loading profiles from real file paths — which sets `Profile.source_path` — is required for the ASA splice.)
448
+ Cluster items are spliced into the owning player's `inventory`; no separate `ASV_ClusterItems` file is emitted. The match works because every cluster file and the player's `.arkprofile` share a stem (the Steam id on ASE, the hex platform UUID on ASA) so the join is keyed on the profile's source filename stem. (`Profile.unique_id` equals that stem only on ASE; on ASA it is the numeric net id, not the UUID filename, so loading profiles from real file paths (which sets `Profile.source_path`) is required for the ASA splice.)
416
449
 
417
450
  Pre-loaded `CloudInventory` instances also work (`cluster=[inv1, inv2, ...]`).
418
451
 
@@ -530,7 +563,9 @@ Key methods:
530
563
 
531
564
  | Method | Returns | Description |
532
565
  |---|---|---|
533
- | `load(source, load_properties=True, max_objects=None)` | `WorldSave` | Load and parse a world save |
566
+ | `load(source, load_properties=True, max_objects=None, lazy_properties=False)` | `WorldSave` | Load and parse a world save (`lazy_properties`: on-demand property parsing, ASE + ASA, see Quick Start) |
567
+ | `materialize_object(obj, names=None)` | `None` | Parse one lazy object's deferred property block (called automatically on property access; `names` is an ASA v14+ partial-decode hint) |
568
+ | `evict_materialized()` | `int` | Release every property block materialized since the last call (lazy saves; no-op eager) |
534
569
  | `get_creatures()` | `list[GameObject]` | All creatures |
535
570
  | `get_tamed_creatures()` / `get_wild_creatures()` | `list[GameObject]` | Filtered creature sets |
536
571
  | `get_structures()` | `list[GameObject]` | Tribe-owned placed structures |
@@ -558,7 +593,7 @@ Key methods:
558
593
  | `properties` | `list[Property]` | Parsed property list |
559
594
  | `parent` / `components` | `GameObject \| None` / `dict[str, GameObject]` | Relationships |
560
595
 
561
- Lookup helpers: `get_property(name, index=None)`, `get_property_value(name, default=None, index=None)`, `get_properties_by_name(name)`, `has_property(name)`, `to_dict()`.
596
+ Lookup helpers: `get_property(name, index=None)`, `get_property_value(name, default=None, index=None)`, `get_properties_by_name(name)`, `has_property(name)`, `to_dict()`. On lazy saves every helper materializes the deferred property block transparently; `evict_properties()` releases it again (re-access re-parses, so eviction is always safe).
562
597
 
563
598
  #### GameObjectContainer (`arkparser.GameObjectContainer`)
564
599
 
@@ -674,6 +709,11 @@ python -m pytest tests/ -v
674
709
 
675
710
  Tests live in `tests/`. Byte-level layout tests (`test_v13_property_layouts.py`, `test_binary_reader_layouts.py`) pin the canonical v13/v14 property body byte sequences with no file-fixture dependency. Integration tests (`test_world_save.py`, `test_export.py`, etc.) skip cleanly when their referenced save files are not present under `references/examples/`.
676
711
 
712
+ Two suites guard export output against drift:
713
+
714
+ - **Golden manifests** (`tests/test_golden_exports.py` + `tests/golden/*.json`): a per-map fingerprint of `export_all` (record count, field-key union, order-independent sha256) for 17 real saves (10 ASE map dumps + 7 ASA). Any change to parsing or export that alters output fails the matching map. Regenerate intentionally with `ARKPARSER_UPDATE_GOLDEN=1`.
715
+ - **Lazy parity** (`tests/test_lazy_properties.py`): proves `lazy_properties=True` yields property-for-property identical objects (ASE and ASA), that evict/re-materialize round-trips, and that full lazy exports match the committed eager goldens. The ASA partial-decode skip walk is additionally verified byte-exact against the full parser over every object of every local ASA fixture by `references/scripts/verify_partial_walk.py`.
716
+
677
717
  ## Credits
678
718
 
679
719
  Built by reverse-engineering ARK save formats with heavy reference to [ASV (Ark Save Visualizer)](https://github.com/miragedmuk/ASV) by **miragedmuk**. The C# implementation in ASV is the primary reference for porting binary parsing logic to Python.
@@ -24,6 +24,7 @@ A pure-Python library for parsing ARK: Survival Evolved (ASE) and ARK: Survival
24
24
  - **Dual format**: automatic ASE (v5-12) / ASA (v13-14+, SQLite) detection
25
25
  - **Legacy-parity export**: drop-in JSON output matching `ASVExport.exe` schema, plus parser-only extras under descriptive snake_case keys (no namespace prefix; never overloading a legacy key)
26
26
  - **Fast**: pure-Python `BinaryReader` (`int.from_bytes` + `struct.Struct` unpackers, slots-based dataclasses), a 30 MB ASE save (65k objects) loads in ~3s on CPython 3.14
27
+ - **Memory-bounded**: streaming JSON export plus an opt-in lazy parse mode (`lazy_properties=True`, ASE and ASA) that materializes property blocks on demand and evicts them as records stream to disk; a 1.8 GB / 1.79M-object busy-PvE ASE save exports in 2.5 GB peak RSS instead of 7.1 GB, and a 217k-object ASA save in 307 MB instead of 853 MB (and slightly faster)
27
28
 
28
29
  ## Installation
29
30
 
@@ -98,6 +99,38 @@ print(f"Parse errors: {save.parse_error_count}")
98
99
  print(f"Is ASA: {save.is_asa}")
99
100
  ```
100
101
 
102
+ ### Low-memory parsing (large saves)
103
+
104
+ By default every object's property block is parsed up front, which on a busy
105
+ multi-GB PvE save means a multi-GB resident object graph. `lazy_properties=True`
106
+ parses object headers eagerly but defers each property block until something
107
+ reads it, then the export drivers evict it again once the record has been
108
+ written:
109
+
110
+ ```python
111
+ from arkparser import WorldSave, export_to_files
112
+ from arkparser.common import get_map_config
113
+
114
+ save = WorldSave.load("path/to/Fjordur.ark", lazy_properties=True) # ASE
115
+ save = WorldSave.load("path/to/TheIsland_WP.ark", lazy_properties=True) # ASA
116
+ export_to_files(save, "output/", get_map_config("fjordur.ark"))
117
+ ```
118
+
119
+ Output is identical to eager mode (validated record-for-record). ASE retains
120
+ the file reader and re-seeks each object's block on demand; ASA retains the
121
+ SQLite connection (one held read transaction) and re-fetches row blobs by
122
+ GUID. On ASA v14+ saves the export pipeline additionally uses partial decodes:
123
+ a verified byte-exact skip walk that parses only the property names a record
124
+ needs, with any out-of-whitelist read transparently upgrading to the full
125
+ block.
126
+
127
+ Measured load + export, same machine: a 1.8 GB Fjordur PvE ASE save (1.79M
128
+ objects) drops from ~7.1 GB peak RSS / 348 s eager to ~2.5 GB / 287 s lazy; a
129
+ 233 MB ASA TheIsland save (217k objects) drops from 853 MB / 38 s eager to
130
+ 307 MB / ~36 s lazy. Property access on a lazy save is transparent: any
131
+ `get_property_value` call materializes the block on demand, so all getters and
132
+ exports work unchanged.
133
+
101
134
  ### Cryopod-stored Creatures
102
135
 
103
136
  Tamed creatures stored inside in-world cryopods aren't returned by `get_tamed_creatures()`. Iterate them via `WorldSave.iter_cryopod_creatures()`:
@@ -184,7 +217,7 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
184
217
  | `isClone` | legacy | `bIsClone` or `bIsCloneDino` |
185
218
  | `tamedServer` | legacy | `TamedOnServerName` |
186
219
  | `uploadedServer` | legacy | `UploadedFromServerName` |
187
- | `maturation` | legacy | `str(int(BabyAge * 100))` — integer maturation percent (a baby with no `BabyAge` is `0`, a newborn). Note: legacy emits the full float string (e.g. `"7.5131035"`); arkparser truncates to the integer percent. Semantically equal and the downstream consumer coerces to `int` either way. |
220
+ | `maturation` | legacy | `str(int(BabyAge * 100))`, integer maturation percent (a baby with no `BabyAge` is `0`, a newborn). Note: legacy emits the full float string (e.g. `"7.5131035"`); arkparser truncates to the integer percent. Semantically equal and the downstream consumer coerces to `int` either way. |
188
221
  | `traits` | legacy | `CreatureTraits` as a list of `{"trait": <class>}` objects (matches `ASVExport`'s shape, not a flat string list) |
189
222
  | `inventory` | legacy | items from `MyInventoryComponent.InventoryItems`. Each entry carries `itemId`, `qty`, `blueprint`, plus a full snake_case property dump flattened in at the top level (`id`, `rating`, `durability`, `quality`, `damage`, `armor`, `durability_max`, `hypo`, `hyper`, `clip_size`, `weight`, `crafter`, `crafter_tribe`, `skill_bonus`, `loaded_ammo`, `spoils_at`, `spoiled_at`, `c0`..`c5`, `egg_*`, etc). `item_stat_values` is unpacked into the universal 8-slot ARK map (slot 0 `gen_quality`, 1 `armor`, 2 `durability_max`, 3 `damage`, 4 `clip_size`, 5 `hypo`, 6 `weight`, 7 `hyperthermal_insulation`); raw uint16s scaled by the per-blueprint multiplier (which lives in the UE blueprint, not the save). Default / unset values are filtered (no `craft_queue=0`, `skin=-1`, `color_pre_skin=[0]*6`, NaN spoil timers, etc). When the item is a **cryopod / soultrap / vivarium / dinoball** with an embedded creature, the entry is enriched with `dino_id` (combined 64-bit id matching the corresponding `ASV_Tamed` record), `dino_creature` (species / class name), and `dino_name` (`TamedName` if set). Cryopods stored in containers (cryofridges, vaults, dedicated storage) get the same enrichment. |
190
223
  | `father_id`, `mother_id` | added | combined dino id from the first `DinoAncestors` entry (`null` when missing) |
@@ -247,7 +280,7 @@ ARK keeps player data in **two distinct places**, and the parser builds `ASV_Pla
247
280
 
248
281
  The export pipeline (`export_players`) loops over `save.profiles` (a list the caller assembles). For each entry:
249
282
 
250
- - If it's a `Profile` instance → `_player_from_profile` runs: fills core identity (name, gender, level, stats, tribe id, engram count, experience, active datetime from `LastLoginTime`). Leaves `lat`/`lon`/`ccc` as zero, `inventory` empty, pawn-state flags (`is_sleeping`, `is_dead`, `chibi_levels`, …) absent.
283
+ - If it's a `Profile` instance → `_player_from_profile` runs: fills core identity (name, gender, level, stats, tribe id, engram count, experience, active datetime from `LastLoginTime`). It joins `PlayerDataID == LinkedPlayerDataID` against the world save's player pawns, so when a live pawn exists `lat`/`lon`/`ccc` come from that pawn's `.location`; players with no live pawn (dead / logged out) keep `lat`/`lon`/`ccc` at zero. `inventory` is empty and pawn-state flags (`is_sleeping`, `is_dead`, `chibi_levels`, …) absent (the profile carries neither).
251
284
  - Otherwise it's treated as a wrapped `(profile, objects)` pair pointing at an in-world `PlayerPawn` → `_player_from_object` runs: fills location, inventory, pawn-state flags, body/hair cosmetics, death timestamps, and `active` from `SavedLastTimeHadController`.
252
285
 
253
286
  For the richest output, hand `export_players` **both**, assemble a wrapper for each player that carries the `Profile` (for offline identity) *and* the in-world pawn (when present) so the merged record gets identity + live state. The current validation script only passes `Profile` instances, which is why fields like `is_sleeping` / `body_colors` / `current_weapon` show up empty in the validation output even though the parser supports them.
@@ -269,7 +302,7 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
269
302
  | `netAddress` | legacy (now populated) | Last client IP ARK persisted (`SavedNetworkAddress` in profile `MyData`). Legacy ASVExport reads the same field (ContentPlayer.cs:157 ASE / :341 ASA). `""` when the profile lacks it (e.g. never-played placeholders). ASA stores a clean IPv4/IPv6 string; some ASE saves store an engine-truncated value (e.g. `"[2001"`) reproduced verbatim, matching legacy. |
270
303
  | `steamid`, `dataFile` | legacy | platform net id and `{steamid}.arkprofile` filename |
271
304
  | `active` | legacy (now populated) | ISO 8601 datetime of last login, converted from profile `LastLoginTime` or in-world pawn `SavedLastTimeHadController`. Legacy schema reserved the field but the old C# exporter only filled it for in-world pawns; the parser fills it for profiles too. `null` when neither source is present. |
272
- | `lat`, `lon`, `ccc` | legacy (now populated) | In-world position when the record was built from a `PlayerPawn` GameObject. Records sourced from profile / cluster files keep the legacy `0` / `"0 0 0"` placeholders since profiles carry no world position. |
305
+ | `lat`, `lon`, `ccc` | legacy (now populated) | In-world position. Built directly from a `PlayerPawn` GameObject when one is passed, and for profile-sourced records the parser joins `PlayerDataID == LinkedPlayerDataID` to the world save's player pawns and pulls the matched pawn's `.location`. Records whose player has no live pawn (dead / logged out / cluster-only) keep the legacy `0` / `"0 0 0"` placeholders since there is no world position to report. |
273
306
  | `inventory` | legacy (now populated) | Items from the pawn's `MyInventoryComponent` when built from an in-world pawn. Empty list otherwise. |
274
307
  | `engram_points` | added | `TotalEngramPoints` |
275
308
  | `experience` | added | `ExperiencePoints` (status), integer |
@@ -288,17 +321,17 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
288
321
 
289
322
  #### `ASV_Tribes` schema
290
323
 
291
- `ASV_Tribes` (and `ASV_TribeLogs` / `ASV_Players`, which iterate the same list) is a **superset** of the loaded `.arktribe` files, mirroring legacy `ContentContainer`: it seeds two sentinels (`[ASV Unclaimed]` id `2000000000`, `[ASV Abandoned]` id `-2147483648`), adds every file-backed tribe, then synthesizes a stub tribe for each player profile not already in a tribe (`Tribe of <name>`, id = `PlayerDataID`), each distinct structure `TargetingTeam` (`>= 50000`), and each distinct in-world tame `TargetingTeam` — deduped by id. Cross-server tribes that exist **only** in cluster cloud-inventory files (no map presence) are not synthesized.
324
+ `ASV_Tribes` (and `ASV_TribeLogs` / `ASV_Players`, which iterate the same list) is a **superset** of the loaded `.arktribe` files, mirroring legacy `ContentContainer`: it seeds two sentinels (`[ASV Unclaimed]` id `2000000000`, `[ASV Abandoned]` id `-2147483648`), adds every file-backed tribe, then synthesizes a stub tribe for each player profile not already in a tribe (`Tribe of <name>`, id = `PlayerDataID`), each distinct structure `TargetingTeam` (`>= 50000`), and each distinct in-world tame `TargetingTeam`, deduped by id. Cross-server tribes that exist **only** in cluster cloud-inventory files (no map presence) are not synthesized.
292
325
 
293
326
  | Field | Origin | Source / formula |
294
327
  |---|---|---|
295
328
  | `tribeid` | legacy | `TribeID` / parser `Tribe.tribe_id`, or a synthesized stub/sentinel id (see above) |
296
329
  | `tribe` | legacy | tribe name (file `TribeName`; for stubs, `OwnerName`/`TamerString`; or `Tribe of <character>` for a solo) |
297
- | `players` | legacy | count of players **allocated** to this tribe (profiles whose explicit team / membership / solo id resolves here, plus member back-fill), matching legacy `Players.Count` — not the raw `.arktribe` member count |
330
+ | `players` | legacy | count of players **allocated** to this tribe (profiles whose explicit team / membership / solo id resolves here, plus member back-fill), matching legacy `Players.Count`, not the raw `.arktribe` member count |
298
331
  | `members` | legacy | list of `{ign, lvl, playerid, playername, steamid}` built from the allocated players. `lvl` and `steamid` populate from the matching `.arkprofile`; members with no profile (back-filled from the tribe's member list) carry `lvl=0`, `steamid=""`. |
299
332
  | `tames`, `structures` | legacy | counts derived from `WorldSave` (creatures + structures whose `TargetingTeam` matches) |
300
333
  | `uploadedTames` | legacy | reserved (currently `0`) |
301
- | `active` | legacy | ISO 8601 datetime of the most recent tribe log entry, converted from in-game "Day N, HH:MM:SS" via the save's anchor. `null` when no parseable log entries or anchors are missing. |
334
+ | `active` | legacy | ISO 8601 datetime, legacy `ContentTribe.LastActive`: max of the tribe file's write time (`.arktribe` mtime; world-save mtime for in-save tribes) and the allocated members' last-active times (`LastLoginTime` via the save anchor), discarding future values. `null` when no past candidate exists. Tribe-log "Day N" stamps are game-calendar time and are deliberately not used. |
302
335
  | `dataFile` | legacy | `"{tribeid}.arktribe"` filename pattern. |
303
336
  | `owner_id` | added | `OwnerPlayerDataID` / parser `Tribe.owner_player_id` |
304
337
  | `owner_name` | added | `OwnerPlayerName` (object form only; parser-tribe form has no equivalent) |
@@ -319,7 +352,7 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
319
352
  | `id` | added | `GameObject.id`, internal numeric identifier. Cross-references the values in other records' `linked_structures` / `saddle_structures` / `attached_dino_id` lists. |
320
353
  | `tribeid` | legacy | `TargetingTeam` for player-owned structures (`>= 50000`). Unowned structures fall to the synthetic `[ASV Abandoned]` tribe id `-2147483648` (legacy `int.MinValue`), mirroring `ContentContainer.cs`. |
321
354
  | `tribe` | legacy | resolved owning-tribe name: the loaded `.arktribe` `TribeName`, else the structure's `OwnerName` / `TamerString`; `"[ASV Abandoned]"` for unowned. (Legacy emits the resolved tribe name, not the raw `OwnerName`.) |
322
- | `struct` | legacy | `GameObject.class_name`. Unowned map elements / crates / debug actors (`Button_*`, `*Vein_*`, `*Nest_*`, `*Beaver*`, `BeeHive_C`, `ArtifactCrate_*`, `TributeTerminal_*`, `SupplyCrate_*`) are excluded — surfaced via `ASV_MapStructures` instead, matching legacy's abandoned-structure filter. |
355
+ | `struct` | legacy | `GameObject.class_name`. Unowned map elements / crates / debug actors (`Button_*`, `*Vein_*`, `*Nest_*`, `*Beaver*`, `BeeHive_C`, `ArtifactCrate_*`, `TributeTerminal_*`, `SupplyCrate_*`) are excluded; surfaced via `ASV_MapStructures` instead, matching legacy's abandoned-structure filter. |
323
356
  | `name` | legacy | `BoxName` (empty when it matches the class name, mirroring legacy's no-rename strip) |
324
357
  | `locked` | legacy | `bIsPinLocked` or `bIsLocked` |
325
358
  | `created` | legacy (richer) | ISO 8601 datetime with the local TZ of the parser machine, computed `save.file_mtime + (OriginalCreationTime - game_time)` (mirrors legacy `ContentContainer.GetApproxDateTimeOf`). `null` when the anchors are missing. |
@@ -378,7 +411,7 @@ data = export_all(save, map_config, cluster="path/to/cluster")
378
411
  # .arkprofile filename stem.
379
412
  ```
380
413
 
381
- Cluster items are spliced into the owning player's `inventory`; no separate `ASV_ClusterItems` file is emitted. The match works because every cluster file and the player's `.arkprofile` share a stem — the Steam id on ASE, the hex platform UUID on ASA — so the join is keyed on the profile's source filename stem. (`Profile.unique_id` equals that stem only on ASE; on ASA it is the numeric net id, not the UUID filename, so loading profiles from real file paths — which sets `Profile.source_path` — is required for the ASA splice.)
414
+ Cluster items are spliced into the owning player's `inventory`; no separate `ASV_ClusterItems` file is emitted. The match works because every cluster file and the player's `.arkprofile` share a stem (the Steam id on ASE, the hex platform UUID on ASA) so the join is keyed on the profile's source filename stem. (`Profile.unique_id` equals that stem only on ASE; on ASA it is the numeric net id, not the UUID filename, so loading profiles from real file paths (which sets `Profile.source_path`) is required for the ASA splice.)
382
415
 
383
416
  Pre-loaded `CloudInventory` instances also work (`cluster=[inv1, inv2, ...]`).
384
417
 
@@ -496,7 +529,9 @@ Key methods:
496
529
 
497
530
  | Method | Returns | Description |
498
531
  |---|---|---|
499
- | `load(source, load_properties=True, max_objects=None)` | `WorldSave` | Load and parse a world save |
532
+ | `load(source, load_properties=True, max_objects=None, lazy_properties=False)` | `WorldSave` | Load and parse a world save (`lazy_properties`: on-demand property parsing, ASE + ASA, see Quick Start) |
533
+ | `materialize_object(obj, names=None)` | `None` | Parse one lazy object's deferred property block (called automatically on property access; `names` is an ASA v14+ partial-decode hint) |
534
+ | `evict_materialized()` | `int` | Release every property block materialized since the last call (lazy saves; no-op eager) |
500
535
  | `get_creatures()` | `list[GameObject]` | All creatures |
501
536
  | `get_tamed_creatures()` / `get_wild_creatures()` | `list[GameObject]` | Filtered creature sets |
502
537
  | `get_structures()` | `list[GameObject]` | Tribe-owned placed structures |
@@ -524,7 +559,7 @@ Key methods:
524
559
  | `properties` | `list[Property]` | Parsed property list |
525
560
  | `parent` / `components` | `GameObject \| None` / `dict[str, GameObject]` | Relationships |
526
561
 
527
- Lookup helpers: `get_property(name, index=None)`, `get_property_value(name, default=None, index=None)`, `get_properties_by_name(name)`, `has_property(name)`, `to_dict()`.
562
+ Lookup helpers: `get_property(name, index=None)`, `get_property_value(name, default=None, index=None)`, `get_properties_by_name(name)`, `has_property(name)`, `to_dict()`. On lazy saves every helper materializes the deferred property block transparently; `evict_properties()` releases it again (re-access re-parses, so eviction is always safe).
528
563
 
529
564
  #### GameObjectContainer (`arkparser.GameObjectContainer`)
530
565
 
@@ -640,6 +675,11 @@ python -m pytest tests/ -v
640
675
 
641
676
  Tests live in `tests/`. Byte-level layout tests (`test_v13_property_layouts.py`, `test_binary_reader_layouts.py`) pin the canonical v13/v14 property body byte sequences with no file-fixture dependency. Integration tests (`test_world_save.py`, `test_export.py`, etc.) skip cleanly when their referenced save files are not present under `references/examples/`.
642
677
 
678
+ Two suites guard export output against drift:
679
+
680
+ - **Golden manifests** (`tests/test_golden_exports.py` + `tests/golden/*.json`): a per-map fingerprint of `export_all` (record count, field-key union, order-independent sha256) for 17 real saves (10 ASE map dumps + 7 ASA). Any change to parsing or export that alters output fails the matching map. Regenerate intentionally with `ARKPARSER_UPDATE_GOLDEN=1`.
681
+ - **Lazy parity** (`tests/test_lazy_properties.py`): proves `lazy_properties=True` yields property-for-property identical objects (ASE and ASA), that evict/re-materialize round-trips, and that full lazy exports match the committed eager goldens. The ASA partial-decode skip walk is additionally verified byte-exact against the full parser over every object of every local ASA fixture by `references/scripts/verify_partial_walk.py`.
682
+
643
683
  ## Credits
644
684
 
645
685
  Built by reverse-engineering ARK save formats with heavy reference to [ASV (Ark Save Visualizer)](https://github.com/miragedmuk/ASV) by **miragedmuk**. The C# implementation in ASV is the primary reference for porting binary parsing logic to Python.
@@ -1,98 +1,98 @@
1
- """
2
- ARK Save Parser - Parse ARK: Survival Evolved/Ascended save files.
3
-
4
- This package provides tools to parse various ARK save file formats:
5
-
6
- - Profile: Player profile data (.arkprofile)
7
- - Tribe: Tribe data (.arktribe)
8
- - CloudInventory: Obelisk/cloud inventory data (no extension)
9
- - WorldSave: World save data (.ark), auto-detects ASE binary and ASA SQLite
10
-
11
- Supports both ASE (ARK: Survival Evolved) and ASA (ARK: Survival Ascended)
12
- formats with automatic detection.
13
-
14
- Example usage:
15
- >>> from arkparser import Profile, Tribe, CloudInventory, WorldSave
16
- >>> profile = Profile.load("path/to/profile.arkprofile")
17
- >>> tribe = Tribe.load("path/to/tribe.arktribe")
18
- >>> inv = CloudInventory.load("path/to/obelisk_file")
19
- >>> save = WorldSave.load("path/to/TheIsland.ark") # ASE
20
- >>> save = WorldSave.load("path/to/Extinction_WP.ark") # ASA
21
- """
22
-
23
- from arkparser.common.exceptions import ArkParseError
24
- from arkparser.common.map_config import MapConfig, get_map_config, get_map_config_by_name
25
- from arkparser.common.version_detection import (
26
- ArkFileFormat,
27
- ArkFileType,
28
- detect_file_type,
29
- detect_format,
30
- )
31
- from arkparser.data_models import (
32
- CryopodCreature,
33
- DinoStats,
34
- UploadedCreature,
35
- UploadedItem,
36
- )
37
- from arkparser.export import (
38
- export_all,
39
- export_cloud_inventory,
40
- export_cluster_items,
41
- export_cluster_uploads,
42
- export_map_structures,
43
- export_players,
44
- export_structures,
45
- export_tamed,
46
- export_to_files,
47
- export_tribe_logs,
48
- export_tribes,
49
- export_wild,
50
- )
51
- from arkparser.files import CloudInventory, Profile, Tribe, WorldSave
52
- from arkparser.game_objects import GameObject, GameObjectContainer, LocationData
53
-
54
- # Convenience alias - users may know this as "Obelisk" from the game
55
- Obelisk = CloudInventory
56
-
57
- __all__ = [
58
- # File parsers
59
- "Profile",
60
- "Tribe",
61
- "CloudInventory",
62
- "Obelisk",
63
- "WorldSave",
64
- # Cloud-inventory data models
65
- "UploadedCreature",
66
- "UploadedItem",
67
- "CryopodCreature",
68
- "DinoStats",
69
- # Game objects
70
- "GameObject",
71
- "GameObjectContainer",
72
- "LocationData",
73
- # Map config
74
- "MapConfig",
75
- "get_map_config",
76
- "get_map_config_by_name",
77
- # Export
78
- "export_all",
79
- "export_tamed",
80
- "export_wild",
81
- "export_players",
82
- "export_tribes",
83
- "export_structures",
84
- "export_tribe_logs",
85
- "export_map_structures",
86
- "export_cluster_uploads",
87
- "export_cluster_items",
88
- "export_cloud_inventory",
89
- "export_to_files",
90
- # Utilities
91
- "detect_format",
92
- "detect_file_type",
93
- "ArkFileFormat",
94
- "ArkFileType",
95
- "ArkParseError",
96
- ]
97
-
98
- __version__ = "0.5.4"
1
+ """
2
+ ARK Save Parser - Parse ARK: Survival Evolved/Ascended save files.
3
+
4
+ This package provides tools to parse various ARK save file formats:
5
+
6
+ - Profile: Player profile data (.arkprofile)
7
+ - Tribe: Tribe data (.arktribe)
8
+ - CloudInventory: Obelisk/cloud inventory data (no extension)
9
+ - WorldSave: World save data (.ark), auto-detects ASE binary and ASA SQLite
10
+
11
+ Supports both ASE (ARK: Survival Evolved) and ASA (ARK: Survival Ascended)
12
+ formats with automatic detection.
13
+
14
+ Example usage:
15
+ >>> from arkparser import Profile, Tribe, CloudInventory, WorldSave
16
+ >>> profile = Profile.load("path/to/profile.arkprofile")
17
+ >>> tribe = Tribe.load("path/to/tribe.arktribe")
18
+ >>> inv = CloudInventory.load("path/to/obelisk_file")
19
+ >>> save = WorldSave.load("path/to/TheIsland.ark") # ASE
20
+ >>> save = WorldSave.load("path/to/Extinction_WP.ark") # ASA
21
+ """
22
+
23
+ from arkparser.common.exceptions import ArkParseError
24
+ from arkparser.common.map_config import MapConfig, get_map_config, get_map_config_by_name
25
+ from arkparser.common.version_detection import (
26
+ ArkFileFormat,
27
+ ArkFileType,
28
+ detect_file_type,
29
+ detect_format,
30
+ )
31
+ from arkparser.data_models import (
32
+ CryopodCreature,
33
+ DinoStats,
34
+ UploadedCreature,
35
+ UploadedItem,
36
+ )
37
+ from arkparser.export import (
38
+ export_all,
39
+ export_cloud_inventory,
40
+ export_cluster_items,
41
+ export_cluster_uploads,
42
+ export_map_structures,
43
+ export_players,
44
+ export_structures,
45
+ export_tamed,
46
+ export_to_files,
47
+ export_tribe_logs,
48
+ export_tribes,
49
+ export_wild,
50
+ )
51
+ from arkparser.files import CloudInventory, Profile, Tribe, WorldSave
52
+ from arkparser.game_objects import GameObject, GameObjectContainer, LocationData
53
+
54
+ # Convenience alias - users may know this as "Obelisk" from the game
55
+ Obelisk = CloudInventory
56
+
57
+ __all__ = [
58
+ # File parsers
59
+ "Profile",
60
+ "Tribe",
61
+ "CloudInventory",
62
+ "Obelisk",
63
+ "WorldSave",
64
+ # Cloud-inventory data models
65
+ "UploadedCreature",
66
+ "UploadedItem",
67
+ "CryopodCreature",
68
+ "DinoStats",
69
+ # Game objects
70
+ "GameObject",
71
+ "GameObjectContainer",
72
+ "LocationData",
73
+ # Map config
74
+ "MapConfig",
75
+ "get_map_config",
76
+ "get_map_config_by_name",
77
+ # Export
78
+ "export_all",
79
+ "export_tamed",
80
+ "export_wild",
81
+ "export_players",
82
+ "export_tribes",
83
+ "export_structures",
84
+ "export_tribe_logs",
85
+ "export_map_structures",
86
+ "export_cluster_uploads",
87
+ "export_cluster_items",
88
+ "export_cloud_inventory",
89
+ "export_to_files",
90
+ # Utilities
91
+ "detect_format",
92
+ "detect_file_type",
93
+ "ArkFileFormat",
94
+ "ArkFileType",
95
+ "ArkParseError",
96
+ ]
97
+
98
+ __version__ = "0.7.0"