arkparser 0.4.5__tar.gz → 0.5.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.
- {arkparser-0.4.5 → arkparser-0.5.0}/PKG-INFO +15 -17
- {arkparser-0.4.5 → arkparser-0.5.0}/README.md +14 -16
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/__init__.py +1 -1
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/data_models.py +27 -3
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/export.py +795 -288
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/files/profile.py +19 -3
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/files/world_save.py +50 -52
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/properties/compound.py +11 -2
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/properties/registry.py +18 -5
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser.egg-info/PKG-INFO +15 -17
- {arkparser-0.4.5 → arkparser-0.5.0}/pyproject.toml +1 -1
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_cloud_inventory.py +0 -1
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_profile.py +0 -1
- arkparser-0.5.0/tests/test_review_fixes.py +271 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_tribe.py +0 -1
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_world_save.py +1 -1
- arkparser-0.4.5/tests/test_review_fixes.py +0 -138
- {arkparser-0.4.5 → arkparser-0.5.0}/LICENSE +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/common/__init__.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/common/binary_reader.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/common/exceptions.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/common/map_config.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/common/normalization.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/common/types.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/common/version_detection.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/files/__init__.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/files/base.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/files/cloud_inventory.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/files/tribe.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/game_objects/__init__.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/game_objects/container.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/game_objects/game_object.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/game_objects/location.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/properties/__init__.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/properties/base.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/properties/byte_property.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/properties/primitives.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/structs/__init__.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/structs/base.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/structs/colors.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/structs/misc.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/structs/property_list.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/structs/registry.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser/structs/vectors.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser.egg-info/SOURCES.txt +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser.egg-info/dependency_links.txt +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser.egg-info/requires.txt +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/arkparser.egg-info/top_level.txt +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/setup.cfg +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_asa_header_position.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_asa_name_table.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_ase_cluster_drift.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_binary_reader.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_binary_reader_layouts.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_cloud_export.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_cryopod_export.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_current_stats.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_data_models.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_export.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_game_objects.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_v13_property_layouts.py +0 -0
- {arkparser-0.4.5 → arkparser-0.5.0}/tests/test_version_detection.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arkparser
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -56,7 +56,7 @@ A pure-Python library for parsing ARK: Survival Evolved (ASE) and ARK: Survival
|
|
|
56
56
|
- **Cloud Inventory / Obelisk**: uploaded creatures, items, cryopod contents
|
|
57
57
|
- **World Saves** (`.ark`): full map state (creatures, structures, items, players)
|
|
58
58
|
- **Dual format**: automatic ASE (v5-12) / ASA (v13-14+, SQLite) detection
|
|
59
|
-
- **Legacy-parity export**: drop-in JSON output matching `ASVExport.exe` schema, plus parser-only extras under
|
|
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
61
|
|
|
62
62
|
## Installation
|
|
@@ -212,14 +212,14 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
|
|
|
212
212
|
| `mut-f`, `mut-m` | legacy | **Ancestor-line totals.** `RandomMutationsFemale` and `RandomMutationsMale`, single integers counting the total number of mutations that occurred down the maternal and paternal ancestry lines respectively. These are *not* per-stat, they share the `-m` token with the per-stat mutation block below but mean a different thing. Kept under the legacy names for ASVExport parity. |
|
|
213
213
|
| `cryo` | legacy | `True` for creatures embedded inside cryopod / soultrap / vivarium / dinoball items in the world save, `False` for actor-in-world tames. `export_tamed` walks `WorldSave.iter_cryopod_creatures()` and emits one ASV_Tamed record per embedded creature in addition to the actor-in-world tames; on busy PvE servers cryopodded tames are the majority of the roster (e.g. 10,277 of 11,054 on a live Ragnarok_WP). Cluster-uploaded tames also surface here (via `export_cluster_uploads`) with `cryo=True`. |
|
|
214
214
|
| `ccc` | legacy | `"{x} {y} {z}"` from `LocationData` |
|
|
215
|
-
| `dinoid` | legacy | string form of `id` |
|
|
215
|
+
| `dinoid` | legacy | string form of the dino id. **ASA**: decimal of the combined 64-bit `id`. **ASE**: the two halves concatenated as signed-int32 decimals (`str(DinoID1) + str(DinoID2)`), matching `ASVExport`. |
|
|
216
216
|
| `isMating` | legacy | `bEnableTamedMating` |
|
|
217
217
|
| `isNeutered` | legacy | `bNeutered` |
|
|
218
218
|
| `isClone` | legacy | `bIsClone` or `bIsCloneDino` |
|
|
219
219
|
| `tamedServer` | legacy | `TamedOnServerName` |
|
|
220
220
|
| `uploadedServer` | legacy | `UploadedFromServerName` |
|
|
221
|
-
| `maturation` | legacy | `str(int(BabyAge * 100))` (
|
|
222
|
-
| `traits` | legacy | `CreatureTraits`
|
|
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. |
|
|
222
|
+
| `traits` | legacy | `CreatureTraits` as a list of `{"trait": <class>}` objects (matches `ASVExport`'s shape, not a flat string list) |
|
|
223
223
|
| `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
224
|
| `father_id`, `mother_id` | added | combined dino id from the first `DinoAncestors` entry (`null` when missing) |
|
|
225
225
|
| `father_name`, `mother_name` | added | name strings from the first `DinoAncestors` entry |
|
|
@@ -256,7 +256,7 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
|
|
|
256
256
|
|
|
257
257
|
| Field | Origin | Source / formula |
|
|
258
258
|
|---|---|---|
|
|
259
|
-
| `id`, `dinoid` | legacy | `(DinoID1 << 32) \| DinoID2`
|
|
259
|
+
| `id`, `dinoid` | legacy | `id` = `(DinoID1 << 32) \| DinoID2`. `dinoid` = its string form on **ASA**, or `str(DinoID1) + str(DinoID2)` (signed int32) on **ASE**, matching `ASVExport`. |
|
|
260
260
|
| `creature` | legacy | `GameObject.class_name` |
|
|
261
261
|
| `sex` | legacy | `"Female"` if `bIsFemale` else `"Male"` |
|
|
262
262
|
| `lvl` | legacy | `BaseCharacterLevel` (status) |
|
|
@@ -297,11 +297,8 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
|
|
|
297
297
|
| `tribe` | legacy | tribe name |
|
|
298
298
|
| `sex` | legacy | `"Female"` / `"Male"` |
|
|
299
299
|
| `lvl` | legacy | `BaseCharacterLevel + ExtraCharacterLevel` |
|
|
300
|
-
| `lat`, `lon` | legacy | `0.0` (profile/cluster files carry no in-world location) |
|
|
301
300
|
| `hp` .. `craft`, `fort` | legacy | `NumberOfLevelUpPointsApplied[i]` (10 visible stats) |
|
|
302
301
|
| `torp`, `temp` | added | same source, indices legacy never emitted |
|
|
303
|
-
| `active` | legacy | last-active datetime; currently `null` (placeholder for parity) |
|
|
304
|
-
| `ccc` | legacy | `"0 0 0"` (no in-world position from profile/cluster source) |
|
|
305
302
|
| `achievements` | legacy | reserved array (currently empty for parity) |
|
|
306
303
|
| `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. |
|
|
307
304
|
| `steamid`, `dataFile` | legacy | platform net id and `{steamid}.arkprofile` filename |
|
|
@@ -325,12 +322,14 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
|
|
|
325
322
|
|
|
326
323
|
#### `ASV_Tribes` schema
|
|
327
324
|
|
|
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.
|
|
326
|
+
|
|
328
327
|
| Field | Origin | Source / formula |
|
|
329
328
|
|---|---|---|
|
|
330
|
-
| `tribeid` | legacy | `TribeID` / parser `Tribe.tribe_id
|
|
331
|
-
| `tribe` | legacy | tribe name |
|
|
332
|
-
| `players` | legacy | member count |
|
|
333
|
-
| `members` | legacy | list of `{ign, lvl, playerid, playername, steamid}
|
|
329
|
+
| `tribeid` | legacy | `TribeID` / parser `Tribe.tribe_id`, or a synthesized stub/sentinel id (see above) |
|
|
330
|
+
| `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 |
|
|
332
|
+
| `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=""`. |
|
|
334
333
|
| `tames`, `structures` | legacy | counts derived from `WorldSave` (creatures + structures whose `TargetingTeam` matches) |
|
|
335
334
|
| `uploadedTames` | legacy | reserved (currently `0`) |
|
|
336
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. |
|
|
@@ -352,9 +351,9 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
|
|
|
352
351
|
| Field | Origin | Source / formula |
|
|
353
352
|
|---|---|---|
|
|
354
353
|
| `id` | added | `GameObject.id`, internal numeric identifier. Cross-references the values in other records' `linked_structures` / `saddle_structures` / `attached_dino_id` lists. |
|
|
355
|
-
| `tribeid` | legacy | `TargetingTeam` |
|
|
356
|
-
| `tribe` | legacy | `OwnerName` |
|
|
357
|
-
| `struct` | legacy | `GameObject.class_name` |
|
|
354
|
+
| `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
|
+
| `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. |
|
|
358
357
|
| `name` | legacy | `BoxName` (empty when it matches the class name, mirroring legacy's no-rename strip) |
|
|
359
358
|
| `locked` | legacy | `bIsPinLocked` or `bIsLocked` |
|
|
360
359
|
| `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. |
|
|
@@ -362,7 +361,6 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
|
|
|
362
361
|
| `lat`, `lon`, `ccc` | legacy | location via `MapConfig`, **rounded to 2 decimals** (parser-only nicety, not legacy parity) |
|
|
363
362
|
| `isSwitchedOn` | legacy | `bContainerActivated`, emitted only when the structure is powered (`bIsPowered` or `bHasFuel`); omitted otherwise. Mirrors legacy `ContentStructure.cs` / `ContentPack.cs` (`IsSwitchedOn.HasValue`). |
|
|
364
363
|
| `decay_reset` | added | `bHasResetDecayTime` |
|
|
365
|
-
| `last_ally_in_range_seconds` | added | raw `LastInAllyRangeTime` / `LastInAllyRangeTimeSerialized` / `LastInAllyRangeSerialized` (in-game seconds, float) |
|
|
366
364
|
| `last_ally_in_range` | added | ISO 8601 datetime with local TZ. `null` when the save lacks the anchors. |
|
|
367
365
|
| `painting_id` | added | `UniquePaintingId` |
|
|
368
366
|
| `feeding_inclusions` | added | `FeedingDinoList` class names when `DinoFeedingListType == 1` (ASA feeding troughs) |
|
|
@@ -22,7 +22,7 @@ A pure-Python library for parsing ARK: Survival Evolved (ASE) and ARK: Survival
|
|
|
22
22
|
- **Cloud Inventory / Obelisk**: uploaded creatures, items, cryopod contents
|
|
23
23
|
- **World Saves** (`.ark`): full map state (creatures, structures, items, players)
|
|
24
24
|
- **Dual format**: automatic ASE (v5-12) / ASA (v13-14+, SQLite) detection
|
|
25
|
-
- **Legacy-parity export**: drop-in JSON output matching `ASVExport.exe` schema, plus parser-only extras under
|
|
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
27
|
|
|
28
28
|
## Installation
|
|
@@ -178,14 +178,14 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
|
|
|
178
178
|
| `mut-f`, `mut-m` | legacy | **Ancestor-line totals.** `RandomMutationsFemale` and `RandomMutationsMale`, single integers counting the total number of mutations that occurred down the maternal and paternal ancestry lines respectively. These are *not* per-stat, they share the `-m` token with the per-stat mutation block below but mean a different thing. Kept under the legacy names for ASVExport parity. |
|
|
179
179
|
| `cryo` | legacy | `True` for creatures embedded inside cryopod / soultrap / vivarium / dinoball items in the world save, `False` for actor-in-world tames. `export_tamed` walks `WorldSave.iter_cryopod_creatures()` and emits one ASV_Tamed record per embedded creature in addition to the actor-in-world tames; on busy PvE servers cryopodded tames are the majority of the roster (e.g. 10,277 of 11,054 on a live Ragnarok_WP). Cluster-uploaded tames also surface here (via `export_cluster_uploads`) with `cryo=True`. |
|
|
180
180
|
| `ccc` | legacy | `"{x} {y} {z}"` from `LocationData` |
|
|
181
|
-
| `dinoid` | legacy | string form of `id` |
|
|
181
|
+
| `dinoid` | legacy | string form of the dino id. **ASA**: decimal of the combined 64-bit `id`. **ASE**: the two halves concatenated as signed-int32 decimals (`str(DinoID1) + str(DinoID2)`), matching `ASVExport`. |
|
|
182
182
|
| `isMating` | legacy | `bEnableTamedMating` |
|
|
183
183
|
| `isNeutered` | legacy | `bNeutered` |
|
|
184
184
|
| `isClone` | legacy | `bIsClone` or `bIsCloneDino` |
|
|
185
185
|
| `tamedServer` | legacy | `TamedOnServerName` |
|
|
186
186
|
| `uploadedServer` | legacy | `UploadedFromServerName` |
|
|
187
|
-
| `maturation` | legacy | `str(int(BabyAge * 100))` (
|
|
188
|
-
| `traits` | legacy | `CreatureTraits`
|
|
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. |
|
|
188
|
+
| `traits` | legacy | `CreatureTraits` as a list of `{"trait": <class>}` objects (matches `ASVExport`'s shape, not a flat string list) |
|
|
189
189
|
| `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
190
|
| `father_id`, `mother_id` | added | combined dino id from the first `DinoAncestors` entry (`null` when missing) |
|
|
191
191
|
| `father_name`, `mother_name` | added | name strings from the first `DinoAncestors` entry |
|
|
@@ -222,7 +222,7 @@ The legacy ASVExport.exe emitted only the visible 8 stats (`hp`, `stam`, `melee`
|
|
|
222
222
|
|
|
223
223
|
| Field | Origin | Source / formula |
|
|
224
224
|
|---|---|---|
|
|
225
|
-
| `id`, `dinoid` | legacy | `(DinoID1 << 32) \| DinoID2`
|
|
225
|
+
| `id`, `dinoid` | legacy | `id` = `(DinoID1 << 32) \| DinoID2`. `dinoid` = its string form on **ASA**, or `str(DinoID1) + str(DinoID2)` (signed int32) on **ASE**, matching `ASVExport`. |
|
|
226
226
|
| `creature` | legacy | `GameObject.class_name` |
|
|
227
227
|
| `sex` | legacy | `"Female"` if `bIsFemale` else `"Male"` |
|
|
228
228
|
| `lvl` | legacy | `BaseCharacterLevel` (status) |
|
|
@@ -263,11 +263,8 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
|
|
|
263
263
|
| `tribe` | legacy | tribe name |
|
|
264
264
|
| `sex` | legacy | `"Female"` / `"Male"` |
|
|
265
265
|
| `lvl` | legacy | `BaseCharacterLevel + ExtraCharacterLevel` |
|
|
266
|
-
| `lat`, `lon` | legacy | `0.0` (profile/cluster files carry no in-world location) |
|
|
267
266
|
| `hp` .. `craft`, `fort` | legacy | `NumberOfLevelUpPointsApplied[i]` (10 visible stats) |
|
|
268
267
|
| `torp`, `temp` | added | same source, indices legacy never emitted |
|
|
269
|
-
| `active` | legacy | last-active datetime; currently `null` (placeholder for parity) |
|
|
270
|
-
| `ccc` | legacy | `"0 0 0"` (no in-world position from profile/cluster source) |
|
|
271
268
|
| `achievements` | legacy | reserved array (currently empty for parity) |
|
|
272
269
|
| `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. |
|
|
273
270
|
| `steamid`, `dataFile` | legacy | platform net id and `{steamid}.arkprofile` filename |
|
|
@@ -291,12 +288,14 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
|
|
|
291
288
|
|
|
292
289
|
#### `ASV_Tribes` schema
|
|
293
290
|
|
|
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.
|
|
292
|
+
|
|
294
293
|
| Field | Origin | Source / formula |
|
|
295
294
|
|---|---|---|
|
|
296
|
-
| `tribeid` | legacy | `TribeID` / parser `Tribe.tribe_id
|
|
297
|
-
| `tribe` | legacy | tribe name |
|
|
298
|
-
| `players` | legacy | member count |
|
|
299
|
-
| `members` | legacy | list of `{ign, lvl, playerid, playername, steamid}
|
|
295
|
+
| `tribeid` | legacy | `TribeID` / parser `Tribe.tribe_id`, or a synthesized stub/sentinel id (see above) |
|
|
296
|
+
| `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 |
|
|
298
|
+
| `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=""`. |
|
|
300
299
|
| `tames`, `structures` | legacy | counts derived from `WorldSave` (creatures + structures whose `TargetingTeam` matches) |
|
|
301
300
|
| `uploadedTames` | legacy | reserved (currently `0`) |
|
|
302
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. |
|
|
@@ -318,9 +317,9 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
|
|
|
318
317
|
| Field | Origin | Source / formula |
|
|
319
318
|
|---|---|---|
|
|
320
319
|
| `id` | added | `GameObject.id`, internal numeric identifier. Cross-references the values in other records' `linked_structures` / `saddle_structures` / `attached_dino_id` lists. |
|
|
321
|
-
| `tribeid` | legacy | `TargetingTeam` |
|
|
322
|
-
| `tribe` | legacy | `OwnerName` |
|
|
323
|
-
| `struct` | legacy | `GameObject.class_name` |
|
|
320
|
+
| `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
|
+
| `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. |
|
|
324
323
|
| `name` | legacy | `BoxName` (empty when it matches the class name, mirroring legacy's no-rename strip) |
|
|
325
324
|
| `locked` | legacy | `bIsPinLocked` or `bIsLocked` |
|
|
326
325
|
| `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. |
|
|
@@ -328,7 +327,6 @@ For the richest output, hand `export_players` **both**, assemble a wrapper for e
|
|
|
328
327
|
| `lat`, `lon`, `ccc` | legacy | location via `MapConfig`, **rounded to 2 decimals** (parser-only nicety, not legacy parity) |
|
|
329
328
|
| `isSwitchedOn` | legacy | `bContainerActivated`, emitted only when the structure is powered (`bIsPowered` or `bHasFuel`); omitted otherwise. Mirrors legacy `ContentStructure.cs` / `ContentPack.cs` (`IsSwitchedOn.HasValue`). |
|
|
330
329
|
| `decay_reset` | added | `bHasResetDecayTime` |
|
|
331
|
-
| `last_ally_in_range_seconds` | added | raw `LastInAllyRangeTime` / `LastInAllyRangeTimeSerialized` / `LastInAllyRangeSerialized` (in-game seconds, float) |
|
|
332
330
|
| `last_ally_in_range` | added | ISO 8601 datetime with local TZ. `null` when the save lacks the anchors. |
|
|
333
331
|
| `painting_id` | added | `UniquePaintingId` |
|
|
334
332
|
| `feeding_inclusions` | added | `FeedingDinoList` class names when `DinoFeedingListType == 1` (ASA feeding troughs) |
|
|
@@ -8,6 +8,7 @@ property data from ARK save files.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import logging
|
|
11
|
+
import math
|
|
11
12
|
import typing as t
|
|
12
13
|
from dataclasses import dataclass, field
|
|
13
14
|
|
|
@@ -18,6 +19,21 @@ from .properties.registry import read_properties
|
|
|
18
19
|
logger = logging.getLogger(__name__)
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
def _finite(value: t.Any, default: float) -> float:
|
|
23
|
+
"""Coerce to a finite float; NaN / inf / non-numeric collapse to ``default``.
|
|
24
|
+
|
|
25
|
+
Non-finite floats are invalid JSON tokens that crash strict serializers
|
|
26
|
+
(``json.dumps(allow_nan=False)``, JS ``JSON.parse``, pydantic). Legacy
|
|
27
|
+
ASVExport substitutes ``0.0001`` for a NaN item rating (ContentItem.cs:62);
|
|
28
|
+
other non-finite floats fall back to ``0.0``.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
result = float(value)
|
|
32
|
+
except (TypeError, ValueError):
|
|
33
|
+
return default
|
|
34
|
+
return result if math.isfinite(result) else default
|
|
35
|
+
|
|
36
|
+
|
|
21
37
|
@dataclass
|
|
22
38
|
class DinoStats:
|
|
23
39
|
"""Statistics for a creature."""
|
|
@@ -201,7 +217,7 @@ class UploadedCreature:
|
|
|
201
217
|
dino_id1=data.get("DinoID1", 0),
|
|
202
218
|
dino_id2=data.get("DinoID2", 0),
|
|
203
219
|
level=level,
|
|
204
|
-
experience=data.get("DinoExperiencePoints", 0.0),
|
|
220
|
+
experience=_finite(data.get("DinoExperiencePoints", 0.0), 0.0),
|
|
205
221
|
stats=stats,
|
|
206
222
|
upload_time=data.get("UploadTime", 0),
|
|
207
223
|
version=data.get("Version", 0.0),
|
|
@@ -348,6 +364,14 @@ class CryopodCreature:
|
|
|
348
364
|
props_dict[key] = p.value
|
|
349
365
|
obj["properties"] = props_dict
|
|
350
366
|
except Exception:
|
|
367
|
+
# Don't let one drifted object silently zero out: log it so
|
|
368
|
+
# cryopod decode regressions surface instead of returning
|
|
369
|
+
# empty stats with no diagnostic (was a blind swallow).
|
|
370
|
+
logger.debug(
|
|
371
|
+
"Failed to parse cryopod object properties at offset %s",
|
|
372
|
+
obj.get("props_offset"),
|
|
373
|
+
exc_info=True,
|
|
374
|
+
)
|
|
351
375
|
obj["properties"] = {}
|
|
352
376
|
|
|
353
377
|
# Find creature object (first one) and status component
|
|
@@ -735,8 +759,8 @@ class UploadedItem:
|
|
|
735
759
|
item_id2=item_id.get("ItemID2", 0) if isinstance(item_id, dict) else 0,
|
|
736
760
|
quantity=ark_tribute.get("ItemQuantity", 1) or 1,
|
|
737
761
|
quality_index=ark_tribute.get("ItemQualityIndex", 0),
|
|
738
|
-
durability=ark_tribute.get("ItemDurability", 0.0),
|
|
739
|
-
rating=ark_tribute.get("ItemRating", 0.0),
|
|
762
|
+
durability=_finite(ark_tribute.get("ItemDurability", 0.0), 0.0),
|
|
763
|
+
rating=_finite(ark_tribute.get("ItemRating", 0.0), 0.0001),
|
|
740
764
|
slot_index=ark_tribute.get("SlotIndex", 0),
|
|
741
765
|
is_blueprint=ark_tribute.get("bIsBlueprint", False),
|
|
742
766
|
is_engram=ark_tribute.get("bIsEngram", False),
|