talespire-encoding 1.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- talespire_encoding-1.2.2.dist-info/METADATA +144 -0
- talespire_encoding-1.2.2.dist-info/RECORD +11 -0
- talespire_encoding-1.2.2.dist-info/WHEEL +5 -0
- talespire_encoding-1.2.2.dist-info/top_level.txt +1 -0
- ts_encoding/__init__.py +17 -0
- ts_encoding/assets.py +152 -0
- ts_encoding/common.py +210 -0
- ts_encoding/creature_bp.py +355 -0
- ts_encoding/exceptions.py +31 -0
- ts_encoding/slab.py +258 -0
- ts_encoding/slab_testing.py +164 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: talespire-encoding
|
|
3
|
+
Version: 1.2.2
|
|
4
|
+
Summary: Encoding and decoding tools for TaleSpire data
|
|
5
|
+
Author: Baldrax
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Baldrax/TaleSpire-Encoding-Python
|
|
8
|
+
Project-URL: Repository, https://github.com/Baldrax/TaleSpire-Encoding-Python
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest; extra == "dev"
|
|
13
|
+
|
|
14
|
+
# TaleSpire-Encoding-Python
|
|
15
|
+
Encoding/Decoding tools for TaleSpire
|
|
16
|
+
|
|
17
|
+
This repository is just getting started.
|
|
18
|
+
|
|
19
|
+
It currently contains two encoding types:
|
|
20
|
+
- Slabs (v1, v2)
|
|
21
|
+
- Creature Blueprints (v1, v2)
|
|
22
|
+
|
|
23
|
+
## Installation:
|
|
24
|
+
With pip you can install specific releases.
|
|
25
|
+
```
|
|
26
|
+
pip install git+https://github.com/Baldrax/TaleSpire-Encoding-Python.git@vX.X.X
|
|
27
|
+
```
|
|
28
|
+
Replace the version with the version you want to install:
|
|
29
|
+
- [Latest Releases](https://github.com/Baldrax/TaleSpire-Encoding-Python/releases)
|
|
30
|
+
|
|
31
|
+
## Slabs Example usage:
|
|
32
|
+
```python
|
|
33
|
+
from ts_encoding.slab import TSSlab
|
|
34
|
+
|
|
35
|
+
example_slab_code = ("H4sIAAAAAAAACjv369xFJgZGBgYGgUWHGX9Pme/S4z7T7pZ"
|
|
36
|
+
"doRonUGwCSIKhgRFMS0L4DTwMDCcYwOIsDBDADOZLQsRB8g"
|
|
37
|
+
"xQPgOcDwD7jJ0vaAAAAA==")
|
|
38
|
+
|
|
39
|
+
slab = TSSlab()
|
|
40
|
+
slab.decode_slab(example_slab_code)
|
|
41
|
+
|
|
42
|
+
# The slab contents are now in a dictionary `slab.data`
|
|
43
|
+
from pprint import pprint
|
|
44
|
+
pprint(slab.data)
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
{'layout_count': 1,
|
|
48
|
+
'layouts': [{'instance_count': 9,
|
|
49
|
+
'instances': [{'degrees': 90.0,
|
|
50
|
+
'pos_x': 0.0,
|
|
51
|
+
'pos_y': 0.0,
|
|
52
|
+
'pos_z': 4.0},
|
|
53
|
+
{'degrees': 0.0,
|
|
54
|
+
'pos_x': 4.0,
|
|
55
|
+
'pos_y': 0.0,
|
|
56
|
+
'pos_z': 4.0},
|
|
57
|
+
{'degrees': 0.0,
|
|
58
|
+
'pos_x': 2.0,
|
|
59
|
+
'pos_y': 0.0,
|
|
60
|
+
'pos_z': 4.0},
|
|
61
|
+
{'degrees': 270.0,
|
|
62
|
+
'pos_x': 0.0,
|
|
63
|
+
'pos_y': 0.0,
|
|
64
|
+
'pos_z': 2.0},
|
|
65
|
+
{'degrees': 180.0,
|
|
66
|
+
'pos_x': 0.0,
|
|
67
|
+
'pos_y': 0.0,
|
|
68
|
+
'pos_z': 0.0},
|
|
69
|
+
{'degrees': 0.0,
|
|
70
|
+
'pos_x': 4.0,
|
|
71
|
+
'pos_y': 0.0,
|
|
72
|
+
'pos_z': 2.0},
|
|
73
|
+
{'degrees': 0.0,
|
|
74
|
+
'pos_x': 2.0,
|
|
75
|
+
'pos_y': 0.0,
|
|
76
|
+
'pos_z': 2.0},
|
|
77
|
+
{'degrees': 0.0,
|
|
78
|
+
'pos_x': 4.0,
|
|
79
|
+
'pos_y': 0.0,
|
|
80
|
+
'pos_z': 0.0},
|
|
81
|
+
{'degrees': 0.0,
|
|
82
|
+
'pos_x': 2.0,
|
|
83
|
+
'pos_y': 0.0,
|
|
84
|
+
'pos_z': 0.0}],
|
|
85
|
+
'reserved': 0,
|
|
86
|
+
'uuid': '01c3a210-94fb-449f-8c47-993eda3e7126'}],
|
|
87
|
+
'magic_num': 3520002766,
|
|
88
|
+
'num_creatures': 0,
|
|
89
|
+
'version': 2}
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
# To encode the data
|
|
93
|
+
new_slab_code = slab.encode_slab()
|
|
94
|
+
# The new_slab_code can be pasted into TaleSpire
|
|
95
|
+
|
|
96
|
+
# To create the data start a new TSSlab and edit the data dictionary.
|
|
97
|
+
# Each uuid is a new layout in the dictionary it is currently up to you
|
|
98
|
+
# to format the dictionary properly so that it encodes correctly.
|
|
99
|
+
# In future versions there may be tools to help build the layouts.
|
|
100
|
+
|
|
101
|
+
# Here is an example of creating a single grass tile
|
|
102
|
+
new_slab = TSSlab()
|
|
103
|
+
new_slab.data["layout_count"] = 1
|
|
104
|
+
grass_layout = {
|
|
105
|
+
"instance_count": 1, # The number of grass tiles
|
|
106
|
+
"instances": [{
|
|
107
|
+
"degrees": 0.0, # Yaw rotation in degrees 0-360 it is up to you to give valid values
|
|
108
|
+
"pos_x": 0.0, # X position within slab
|
|
109
|
+
"pos_y": 0.0, # Y position within slab
|
|
110
|
+
"pos_z": 0.0}], # Z position within slab
|
|
111
|
+
"reserved": 0, # Always set to 0
|
|
112
|
+
"uuid": "01c3a210-94fb-449f-8c47-993eda3e7126" # Asset UUID
|
|
113
|
+
}
|
|
114
|
+
new_slab.data["layouts"].append(grass_layout)
|
|
115
|
+
new_slab_code = new_slab.encode_slab()
|
|
116
|
+
|
|
117
|
+
# You can paste that new_slab_code into TaleSpire to see your grass tile
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Creature Blueprint Example usage:
|
|
121
|
+
```python
|
|
122
|
+
from ts_encoding.creature_bp import TSCreature
|
|
123
|
+
from pprint import pprint
|
|
124
|
+
|
|
125
|
+
url_from_TS = ("talespire://creature-blueprint/"
|
|
126
|
+
"AgAMV2hpdGUgTWVlcGxlAQAAACMAYnI6MDAwMDAwMDAwMDAwMDAwMD"
|
|
127
|
+
"AwMDAwMDAwMDQ3MDQxMDABAAAAAI49u_vNJQRDrZctETEJKR8ABAAA"
|
|
128
|
+
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQQAAIEEAACBBAAAgQQ"
|
|
129
|
+
"AAIEEAACBBAAAgQQAAIEEAACBBAAAgQQAAIEEAACBBAAAgQQAAIEEA"
|
|
130
|
+
"ACBBAAAgQQAAIEEAACBBAAAA")
|
|
131
|
+
|
|
132
|
+
bp = TSCreature()
|
|
133
|
+
bp.decode_url(url_from_TS)
|
|
134
|
+
pprint(bp.data)
|
|
135
|
+
|
|
136
|
+
# You can change any of the data in `bp.data`
|
|
137
|
+
bp.data["name"] = "New Name"
|
|
138
|
+
bp.data["stats"][0] = {"max": 100.0, "value": 100.0} # Changes hp to 100
|
|
139
|
+
|
|
140
|
+
# To re-encode the data
|
|
141
|
+
new_url = bp.encode_url()
|
|
142
|
+
|
|
143
|
+
print(new_url) # You can copy/paste this new URL into TaleSpire
|
|
144
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
ts_encoding/__init__.py,sha256=9V8f47HbY_y2d0P7ilE4n0chgE4e2UKuOuL9MDwwzVs,322
|
|
2
|
+
ts_encoding/assets.py,sha256=7jtDLbvZcpj0Ka0Jng0SmiN07vaTAPuSTT5Vbz0-b4A,5216
|
|
3
|
+
ts_encoding/common.py,sha256=vTNXa2gYU7ib0yXtu1oO_aIwMmLTk9KequcwzLSGiTk,6745
|
|
4
|
+
ts_encoding/creature_bp.py,sha256=_fISg6jayCxrkX6eW038Gn5c9hLgNO9Qkh5lYd3eFlU,13745
|
|
5
|
+
ts_encoding/exceptions.py,sha256=E13sbDOaK8iUBUjMsn_oKPPyYOVjeevY9qu8iihgwwQ,892
|
|
6
|
+
ts_encoding/slab.py,sha256=uv6f-9owrPm4Y2f-tcDBrIv5eIIEomrMPGU7lgqyDFA,9708
|
|
7
|
+
ts_encoding/slab_testing.py,sha256=sumuW0h9PzpUZfOOMpIy118_EJJNwE0QXfIPEif8klM,18552
|
|
8
|
+
talespire_encoding-1.2.2.dist-info/METADATA,sha256=01fx0BOEwVDiluN_9VNOs5-w6h3QLPkQrew6yHXh3PE,5186
|
|
9
|
+
talespire_encoding-1.2.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
talespire_encoding-1.2.2.dist-info/top_level.txt,sha256=CrhA5IN_Ee5Brotf1rSdvULycalNETRGm2coN5quVbA,12
|
|
11
|
+
talespire_encoding-1.2.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ts_encoding
|
ts_encoding/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
__version__ = "1.2.2"
|
|
2
|
+
|
|
3
|
+
from .exceptions import (
|
|
4
|
+
SlabExceedsSizeLimit,
|
|
5
|
+
BadSlabCode,
|
|
6
|
+
UnsupportedSlabVersion,
|
|
7
|
+
InvalidTaleSpireDirectory,
|
|
8
|
+
InvalidAssetType
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"SlabExceedsSizeLimit",
|
|
13
|
+
"BadSlabCode",
|
|
14
|
+
"UnsupportedSlabVersion",
|
|
15
|
+
"InvalidTaleSpireDirectory",
|
|
16
|
+
"InvalidAssetType"
|
|
17
|
+
]
|
ts_encoding/assets.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tools for reading in TaleSpire assets.
|
|
3
|
+
These require a path to a valid TaleSpire install.
|
|
4
|
+
It utilizes the `index.json` files within the installed location to get asset information.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from ts_encoding import InvalidTaleSpireDirectory, InvalidAssetType
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_asset_index_paths(ts_basedir: Path | str) -> list[Path]:
|
|
16
|
+
"""
|
|
17
|
+
Given the base TaleSpire Directory get a list of all the paths to index.json files.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
ts_basedir: The base directory that TaleSpire is installed in.
|
|
21
|
+
"""
|
|
22
|
+
ts_base_path = Path(str(ts_basedir))
|
|
23
|
+
taleweaver_dir = ts_base_path / "Taleweaver"
|
|
24
|
+
|
|
25
|
+
if taleweaver_dir.is_dir():
|
|
26
|
+
return list(taleweaver_dir.rglob("index.json"))
|
|
27
|
+
else:
|
|
28
|
+
raise InvalidTaleSpireDirectory(f"The supplied TaleSpire directory is not valid:\n\t{ts_base_path}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def read_index_file(index_file: Path | str) -> dict:
|
|
32
|
+
"""
|
|
33
|
+
Read in the given index file and return it as a dictionary.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
index_file: The TaleSpire index.json file to be read.
|
|
37
|
+
"""
|
|
38
|
+
index_file_path = Path(str(index_file))
|
|
39
|
+
with index_file_path.open("r", encoding="utf-8") as f:
|
|
40
|
+
index_dict = json.load(f)
|
|
41
|
+
|
|
42
|
+
return index_dict
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_index_dicts(ts_basedir: Path | str) -> dict:
|
|
46
|
+
"""
|
|
47
|
+
Given the base TaleSpire directory return a dictionary containing
|
|
48
|
+
all the content pack asset indexes as dictionaries.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
ts_basedir: The base directory that TaleSpire is installed in.
|
|
52
|
+
"""
|
|
53
|
+
index_files = get_asset_index_paths(ts_basedir)
|
|
54
|
+
|
|
55
|
+
index_dicts = {}
|
|
56
|
+
|
|
57
|
+
for index_file in index_files:
|
|
58
|
+
index_dict = read_index_file(index_file)
|
|
59
|
+
index_name = index_dict["Name"]
|
|
60
|
+
index_dicts[index_name] = {"path": str(index_file), "index": index_dict}
|
|
61
|
+
|
|
62
|
+
return index_dicts
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TSAssetLib:
|
|
66
|
+
|
|
67
|
+
# This is the default list of asset loaded as well as the valid types excepted.
|
|
68
|
+
default_asset_filter = ["Tiles", "Props", "Creatures", "Music"]
|
|
69
|
+
|
|
70
|
+
def __init__(self, ts_basedir, asset_filter: list[str] | None = None):
|
|
71
|
+
"""
|
|
72
|
+
TaleSpire Asset Library
|
|
73
|
+
This reads in all the TaleSpire assets and stores them both in index form and by UUID.
|
|
74
|
+
|
|
75
|
+
A filter can be set to limit what types of assets are stored.
|
|
76
|
+
Valid types are: ["Tiles", "Props", "Creatures", "Music"]
|
|
77
|
+
The default is all asset types.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
ts_basedir: The base directory that TaleSpire is installed in.
|
|
81
|
+
asset_filter: A list of asset types to use as a filter.
|
|
82
|
+
"""
|
|
83
|
+
self.index_dicts = get_index_dicts(ts_basedir)
|
|
84
|
+
self.asset_filter = asset_filter if asset_filter else self.default_asset_filter
|
|
85
|
+
self.index_names = list(self.index_dicts.keys())
|
|
86
|
+
self.asset_uuid_dict: dict[str, TSAsset] = {}
|
|
87
|
+
self._build_asset_uuid_dict()
|
|
88
|
+
|
|
89
|
+
def _build_asset_uuid_dict(self) -> None:
|
|
90
|
+
for index_name in self.index_names:
|
|
91
|
+
index_path = Path(self.index_dicts[index_name]["path"])
|
|
92
|
+
index_dict = self.index_dicts[index_name]["index"]
|
|
93
|
+
|
|
94
|
+
icon_atlases = []
|
|
95
|
+
for atlas_entry in index_dict["IconsAtlases"]:
|
|
96
|
+
icon_atlases.append(atlas_entry["Path"])
|
|
97
|
+
|
|
98
|
+
for asset_type in self.asset_filter:
|
|
99
|
+
asset_type = asset_type.title()
|
|
100
|
+
if asset_type not in self.default_asset_filter:
|
|
101
|
+
raise InvalidAssetType(
|
|
102
|
+
f"Invalid Asset Filter: {asset_type}\nValid types are: {self.default_asset_filter}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
for asset_dict in self.index_dicts[index_name]["index"][asset_type]:
|
|
106
|
+
if "Icon" in asset_dict:
|
|
107
|
+
asset = TSIconAsset(asset_dict, asset_type)
|
|
108
|
+
atlas_index = asset_dict["Icon"]["AtlasIndex"]
|
|
109
|
+
asset.icon_atlas = str(index_path.parent / icon_atlases[atlas_index])
|
|
110
|
+
else:
|
|
111
|
+
asset = TSAsset(asset_dict, asset_type)
|
|
112
|
+
|
|
113
|
+
self.asset_uuid_dict[asset.id] = asset
|
|
114
|
+
|
|
115
|
+
def asset(self, asset_uuid: str) -> TSAsset:
|
|
116
|
+
"""
|
|
117
|
+
Given a UUID return the asset.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
asset_uuid: The TaleSpire asset UUID
|
|
121
|
+
"""
|
|
122
|
+
asset = self.asset_uuid_dict.get(asset_uuid, None)
|
|
123
|
+
return asset
|
|
124
|
+
|
|
125
|
+
def assets(self) -> list[TSAsset]:
|
|
126
|
+
"""Returns a list of all the assets in the library."""
|
|
127
|
+
return list(self.asset_uuid_dict.values())
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TSAsset:
|
|
131
|
+
|
|
132
|
+
def __init__(self, asset_dict, asset_type):
|
|
133
|
+
self.asset_dict = asset_dict
|
|
134
|
+
self.asset_type = asset_type
|
|
135
|
+
self.id = asset_dict["Id"].lower()
|
|
136
|
+
self.name = asset_dict["Name"]
|
|
137
|
+
self.deprecated = asset_dict["IsDeprecated"] == 1
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class TSIconAsset(TSAsset):
|
|
141
|
+
|
|
142
|
+
def __init__(self, asset_dict, asset_type):
|
|
143
|
+
super().__init__(asset_dict, asset_type)
|
|
144
|
+
self.icon_atlas_index = asset_dict["Icon"]["AtlasIndex"]
|
|
145
|
+
self.icon_atlas = ""
|
|
146
|
+
atlas_region = asset_dict["Icon"]["Region"]
|
|
147
|
+
self.atlas_region = (
|
|
148
|
+
atlas_region["x"],
|
|
149
|
+
atlas_region["y"],
|
|
150
|
+
atlas_region["width"],
|
|
151
|
+
atlas_region["height"]
|
|
152
|
+
)
|
ts_encoding/common.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base TS Encoding classes and functions for use in the specific encoders.
|
|
3
|
+
|
|
4
|
+
URL Schemes are documented here:
|
|
5
|
+
https://talespire.com/url-scheme
|
|
6
|
+
|
|
7
|
+
Standard Byte order is little-endian
|
|
8
|
+
"""
|
|
9
|
+
import base64
|
|
10
|
+
import struct
|
|
11
|
+
import uuid
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TSCodingBase:
|
|
15
|
+
"""
|
|
16
|
+
A Base Class to Decode and Encode a TaleSpire content.
|
|
17
|
+
|
|
18
|
+
For Decoding:
|
|
19
|
+
The class stores the binary data and an offset which represents the current position/index we are at in the
|
|
20
|
+
binary data. Each unpack method then increments the offset by the proper amount so we can decode the next
|
|
21
|
+
step without having to feed in the offset index.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.data = {}
|
|
26
|
+
self._init_data()
|
|
27
|
+
self._version = 0
|
|
28
|
+
self._encode_version = 2
|
|
29
|
+
self._code = None
|
|
30
|
+
self._binary_data = None
|
|
31
|
+
self._offset = 0
|
|
32
|
+
|
|
33
|
+
def _init_data(self) -> None:
|
|
34
|
+
"""
|
|
35
|
+
This is intended to be overridden by the subclass.
|
|
36
|
+
The data dictionary should be initialized to a default state declaring all the data needed.
|
|
37
|
+
"""
|
|
38
|
+
self.data = {
|
|
39
|
+
"version": 1,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def _decode(self) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Preps self._code into self._binary_data then runs self._decode_steps()
|
|
45
|
+
It is up to the subclass to set self._code
|
|
46
|
+
"""
|
|
47
|
+
self._binary_data = base64.b64decode(self._code) # Decode the encoded string into binary data
|
|
48
|
+
self._offset = 0 # Reset the offset index of the binary data
|
|
49
|
+
self._decode_steps()
|
|
50
|
+
|
|
51
|
+
def _decode_steps(self) -> None:
|
|
52
|
+
"""
|
|
53
|
+
This is meant to be overridden by the subclass.
|
|
54
|
+
Keep these steps as simple as possible, 1 or 2 lines or break them out into another method.
|
|
55
|
+
When the steps are complete all the binary data should be unpacked into `self.data`
|
|
56
|
+
"""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
def _encode(self) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Resets self._binary_data, runs self._encode_steps and encodes the new binary data to self._code
|
|
62
|
+
It is up to the subclass to reveal self._code to the user or application.
|
|
63
|
+
"""
|
|
64
|
+
self._binary_data = bytearray()
|
|
65
|
+
self._encode_steps()
|
|
66
|
+
self._code = base64.b64encode(self._binary_data)
|
|
67
|
+
|
|
68
|
+
def _encode_steps(self) -> None:
|
|
69
|
+
"""
|
|
70
|
+
This is meant to be overridden by the subclass.
|
|
71
|
+
Keep these steps as simple as possible, 1 or 2 lines or break them out into another method.
|
|
72
|
+
When the steps are complete everything in `self.data` should be packed into `self._binary_data`
|
|
73
|
+
"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
def _unpack_u8(self) -> int:
|
|
77
|
+
"""Unpacks a u8 - Unsigned 8-bit integer (1 byte)"""
|
|
78
|
+
result, = struct.unpack_from("<B", self._binary_data, self._offset)
|
|
79
|
+
self._offset += 1
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
def _unpack_u16(self) -> int:
|
|
83
|
+
"""Unpacks a u16 - Unsigned Short Integer (2 bytes)"""
|
|
84
|
+
result, = struct.unpack_from("<H", self._binary_data, self._offset)
|
|
85
|
+
self._offset += 2
|
|
86
|
+
return result
|
|
87
|
+
|
|
88
|
+
def _unpack_u32(self) -> int:
|
|
89
|
+
"""Unpacks a u32 - Unsigned 32-bit Integer (4 bytes)"""
|
|
90
|
+
result, = struct.unpack_from("<I", self._binary_data, self._offset)
|
|
91
|
+
self._offset += 4
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
def _unpack_u64(self) -> int:
|
|
95
|
+
"""Unpacks a u64 - Unsigned 64-bit integer (8 bytes)"""
|
|
96
|
+
result, = struct.unpack_from("<Q", self._binary_data, self._offset)
|
|
97
|
+
self._offset += 8
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
def _unpack_utf8(self, num_bytes: int) -> str:
|
|
101
|
+
"""
|
|
102
|
+
Unpacks a UTF-8 string of fixed length.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
num_bytes (int): Number of bytes to read from the stream.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
str: Decoded UTF-8 string
|
|
109
|
+
"""
|
|
110
|
+
result, = struct.unpack_from(f"<{num_bytes}s", self._binary_data, self._offset)
|
|
111
|
+
self._offset += num_bytes
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
def _unpack_i32(self) -> int:
|
|
115
|
+
"""Unpacks an i32 - Signed 32-bit integer (4 bytes)"""
|
|
116
|
+
result, = struct.unpack_from("<i", self._binary_data, self._offset)
|
|
117
|
+
self._offset += 4
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
def _unpack_uuid(self) -> str:
|
|
121
|
+
"""Unpacks a UUID - 128-bit identifier (16 bytes)"""
|
|
122
|
+
result, = struct.unpack_from(f"16s", self._binary_data, self._offset)
|
|
123
|
+
self._offset += 16
|
|
124
|
+
return str(uuid.UUID(bytes=result))
|
|
125
|
+
|
|
126
|
+
def _unpack_slab_uuid(self) -> str:
|
|
127
|
+
"""Unpacks a slab UUID - 128-bit identifier (16 bytes, mixed-endian layout)"""
|
|
128
|
+
fields = struct.unpack_from("<IHH8B", self._binary_data, self._offset)
|
|
129
|
+
self._offset += 16
|
|
130
|
+
asset_uuid = uuid.UUID(
|
|
131
|
+
fields=(
|
|
132
|
+
fields[0],
|
|
133
|
+
fields[1],
|
|
134
|
+
fields[2],
|
|
135
|
+
fields[3],
|
|
136
|
+
fields[4],
|
|
137
|
+
int.from_bytes(fields[5:], byteorder="big")
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
return str(asset_uuid)
|
|
141
|
+
|
|
142
|
+
def _pack_u8(self, value: int) -> None:
|
|
143
|
+
"""
|
|
144
|
+
Packs a u8 - Unsigned 8-bit integer (1 byte)
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
value: The integer value to pack.
|
|
148
|
+
"""
|
|
149
|
+
self._binary_data.extend(struct.pack("<B", value))
|
|
150
|
+
|
|
151
|
+
def _pack_u16(self, value: int) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Packs a u16 - Unsigned 16-bit integer (2 bytes)
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
value: The integer value to pack.
|
|
157
|
+
"""
|
|
158
|
+
self._binary_data.extend(struct.pack("<H", value))
|
|
159
|
+
|
|
160
|
+
def _pack_u32(self, value: int) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Packs a u32 - Unsigned 32-bit integer (4 bytes)
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
value: The integer value to pack.
|
|
166
|
+
"""
|
|
167
|
+
self._binary_data.extend(struct.pack("<I", value))
|
|
168
|
+
|
|
169
|
+
def _pack_u64(self, value: int) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Packs a u64 - Unsigned 64-bit Integer (8 bytes)
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
value: The integer value to pack.
|
|
175
|
+
"""
|
|
176
|
+
self._binary_data.extend(struct.pack("<Q", value))
|
|
177
|
+
|
|
178
|
+
def _pack_uuid(self, uuid_str: str) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Packs a UUID - 128-bit identifier (16 bytes)
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
uuid_str: The UUID string.
|
|
184
|
+
"""
|
|
185
|
+
self._binary_data.extend(uuid.UUID(uuid_str).bytes)
|
|
186
|
+
|
|
187
|
+
def _pack_slab_uuid(self, uuid_str: str):
|
|
188
|
+
"""
|
|
189
|
+
Packs a Slab UUID - 128-bit identifier (16 bytes, mixed-endian layout)
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
uuid_str: The UUID String.
|
|
193
|
+
"""
|
|
194
|
+
fields = uuid.UUID(uuid_str).fields
|
|
195
|
+
|
|
196
|
+
node_bytes = fields[5].to_bytes(6, byteorder="big")
|
|
197
|
+
|
|
198
|
+
self._binary_data.extend(struct.pack(
|
|
199
|
+
"<IHH8B",
|
|
200
|
+
fields[0], fields[1], fields[2], fields[3], fields[4], *node_bytes
|
|
201
|
+
))
|
|
202
|
+
|
|
203
|
+
def _pack_i32(self, value: int):
|
|
204
|
+
"""
|
|
205
|
+
Packs an i32 - Signed 32-bit integer (4 bytes)
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
value: The integer to pack.
|
|
209
|
+
"""
|
|
210
|
+
self._binary_data.extend(struct.pack("<i", value))
|