brickedit 5.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brickedit/__init__.py +12 -0
- brickedit/brick.py +146 -0
- brickedit/brm.py +336 -0
- brickedit/brv.py +495 -0
- brickedit/bt/__init__.py +4 -0
- brickedit/bt/base.py +56 -0
- brickedit/bt/classes.py +2072 -0
- brickedit/bt/inner_properties.py +24 -0
- brickedit/bt/meta.py +76 -0
- brickedit/exceptions.py +2 -0
- brickedit/id.py +16 -0
- brickedit/p/__init__.py +3 -0
- brickedit/p/base.py +83 -0
- brickedit/p/classes.py +1115 -0
- brickedit/p/meta.py +260 -0
- brickedit/var.py +28 -0
- brickedit/vec.py +249 -0
- brickedit/vhelper/__init__.py +4 -0
- brickedit/vhelper/color.py +290 -0
- brickedit/vhelper/helper.py +223 -0
- brickedit/vhelper/mat.py +69 -0
- brickedit/vhelper/time.py +48 -0
- brickedit/vhelper/units.py +53 -0
- brickedit-5.0.0.dist-info/METADATA +50 -0
- brickedit-5.0.0.dist-info/RECORD +28 -0
- brickedit-5.0.0.dist-info/WHEEL +5 -0
- brickedit-5.0.0.dist-info/licenses/LICENSE +674 -0
- brickedit-5.0.0.dist-info/top_level.txt +1 -0
brickedit/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""BrickEdit is a python package for working with the .BRV and .BRM file formats, belonging to the game Brick Rigs.
|
|
2
|
+
It can read, write, and manipulate the contents of .BRV and .BRM files."""
|
|
3
|
+
from .vec import *
|
|
4
|
+
from .var import *
|
|
5
|
+
from .var import BRICKEDIT_VERSION_FULL as __version__
|
|
6
|
+
from .id import *
|
|
7
|
+
from .brick import *
|
|
8
|
+
from .brv import *
|
|
9
|
+
from .brm import *
|
|
10
|
+
from . import p
|
|
11
|
+
from . import bt
|
|
12
|
+
from . import vhelper
|
brickedit/brick.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from typing import Optional, Self, Callable
|
|
3
|
+
from collections.abc import Hashable
|
|
4
|
+
|
|
5
|
+
from . import bt
|
|
6
|
+
from .id import ID as _ID
|
|
7
|
+
from .exceptions import BrickError
|
|
8
|
+
from .vec import Vec3
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Brick:
|
|
12
|
+
|
|
13
|
+
__slots__ = ('_meta', 'ref', 'pos', 'rot', 'ppatch')
|
|
14
|
+
|
|
15
|
+
def __init__(self,
|
|
16
|
+
ref: _ID,
|
|
17
|
+
meta: bt.BrickMeta,
|
|
18
|
+
pos: Optional[Vec3] = None,
|
|
19
|
+
rot: Optional[Vec3] = None,
|
|
20
|
+
ppatch: Optional[dict[str, Hashable]] = None
|
|
21
|
+
):
|
|
22
|
+
self.ref = ref
|
|
23
|
+
self._meta = meta
|
|
24
|
+
self.pos = pos if pos is not None else Vec3(0, 0, 0)
|
|
25
|
+
self.rot = rot if rot is not None else Vec3(0, 0, 0)
|
|
26
|
+
self.ppatch: dict[str, Hashable] = {} if ppatch is None else ppatch
|
|
27
|
+
|
|
28
|
+
def meta(self) -> bt.BrickMeta:
|
|
29
|
+
"""Returns the BrickMeta of this brick."""
|
|
30
|
+
return self._meta
|
|
31
|
+
|
|
32
|
+
def get_property(self, p: str) -> Hashable:
|
|
33
|
+
"""Gets a property of the brick. If it has been modified, returns the modified value.
|
|
34
|
+
Otherwise, returns a deepcopy of the default value from the BrickMeta.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
p (str): The name of the property to get.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
object: The value of the property.
|
|
41
|
+
"""
|
|
42
|
+
# If the property key exists in the patch, return its stored value
|
|
43
|
+
# (including explicit None). Otherwise return a deepcopy of the
|
|
44
|
+
# default value from the BrickMeta.
|
|
45
|
+
if p in self.ppatch:
|
|
46
|
+
return self.ppatch[p]
|
|
47
|
+
if p not in self._meta.p:
|
|
48
|
+
raise BrickError(f"Property '{p}' does not exist on brick type '{self._meta.name()}'")
|
|
49
|
+
pobj = self._meta.p.get(p)
|
|
50
|
+
return deepcopy(pobj)
|
|
51
|
+
|
|
52
|
+
def set_property(self, p: str, v: Hashable) -> Self:
|
|
53
|
+
"""Sets a property of the brick.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
p (str): The name of the property to set.
|
|
57
|
+
v (object): The value to set the property to.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Self: The Brick instance.
|
|
61
|
+
"""
|
|
62
|
+
self.ppatch[p] = v
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def edit_property(self, p: str, lf: Callable[[Hashable], Hashable]) -> Self:
|
|
66
|
+
"""
|
|
67
|
+
Edits a property of a brick using a lambda function.
|
|
68
|
+
BrickEdit counts None properties as not set -> ignored, goes to default.
|
|
69
|
+
You may use this to reset a property, however Brick.reset_property is usually preferred.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
p (str): The name of the property to edit.
|
|
73
|
+
lf (Callable[[Hashable], Hashable]): A lambda function that takes the current property
|
|
74
|
+
value and returns the new property value.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Self: The Brick instance.
|
|
78
|
+
"""
|
|
79
|
+
self.ppatch[p] = lf(self.get_property(p))
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def reset_property(self, p: str) -> Self:
|
|
83
|
+
"""Resets a property of the brick to its default value.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
p (str): The name of the property to reset.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Self: The Brick instance.
|
|
90
|
+
"""
|
|
91
|
+
if p in self.ppatch:
|
|
92
|
+
del self.ppatch[p]
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
def get_all_properties(self) -> dict[str, Hashable]:
|
|
96
|
+
"""Returns a dictionary of all properties of the brick, including modified and default values.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
dict[str, Hashable]: A dictionary of all properties of the brick.
|
|
100
|
+
"""
|
|
101
|
+
props = {}
|
|
102
|
+
for p in self._meta.p.keys():
|
|
103
|
+
props[p] = self.get_property(p)
|
|
104
|
+
return props
|
|
105
|
+
|
|
106
|
+
def __repr__(self) -> str:
|
|
107
|
+
return f'Brick({self.ref}, {self._meta.name()}, {self.pos!r}, {self.rot!r}, {self.ppatch})'
|
|
108
|
+
|
|
109
|
+
def __format__(self, spec) -> str:
|
|
110
|
+
"""Format the Brick instance. Each character adds a "flag" that affects the output. Order does not matter.
|
|
111
|
+
|
|
112
|
+
'f' for full: also display properties set to default values.
|
|
113
|
+
'h' for human: represents as a human-readable list instead of the programmer-readable dict.
|
|
114
|
+
'r' for repr: represent values using repr() instead of using str().
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
spec (str): The format specifier.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
f'{b:fh}' → Complete human readable format that could be displayed to the user
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
str: The formatted Brick instance.
|
|
124
|
+
"""
|
|
125
|
+
# ppatch formatting
|
|
126
|
+
displayed_properties = self.get_all_properties() if 'f' in spec else self.ppatch
|
|
127
|
+
display = repr if 'r' in spec else str
|
|
128
|
+
|
|
129
|
+
valid = {'f', 'h', 'r'}
|
|
130
|
+
invalid = set(spec) - valid
|
|
131
|
+
if invalid:
|
|
132
|
+
raise ValueError(f"Unknown format specifier(s): {''.join(sorted(invalid))}. Valid flags: {''.join(sorted(valid))}")
|
|
133
|
+
|
|
134
|
+
if 'h' in spec:
|
|
135
|
+
return (f"Identifier:"
|
|
136
|
+
f"\n Ref → {display(self.ref.id)}"
|
|
137
|
+
f"\n Weld group → {display(self.ref.weld)}"
|
|
138
|
+
f"\n Editor group → {display(self.ref.editor)}"
|
|
139
|
+
f"\nInternal name → {display(self.meta().name())}"
|
|
140
|
+
f"\nPosition → {display(self.pos)}"
|
|
141
|
+
f"\nRotation → {display(self.rot)}"
|
|
142
|
+
f"\nProperties:" +
|
|
143
|
+
''.join(f"\n {i+1:02d}. {display(k)} → {display(v)}" for i, (k, v) in enumerate(displayed_properties.items()))
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
return f"Brick({display(self.ref)}, {display(self.meta().name())}, {display(self.pos)}, {display(self.rot)}, {display(displayed_properties)})"
|
brickedit/brm.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import struct
|
|
3
|
+
from typing import Optional, Any
|
|
4
|
+
|
|
5
|
+
from .vec import Vec3 as _Vec3
|
|
6
|
+
from .brv import BRVFile
|
|
7
|
+
from .p import TextMeta as _UserTextSerialization
|
|
8
|
+
from .vhelper.time import net_ticks_now as _net_ticks_now
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_ENCODE_2DIGITS = tuple((i % 10) | ((i // 10) << 4) for i in range(100))
|
|
13
|
+
|
|
14
|
+
def encode_author(author: int) -> int:
|
|
15
|
+
result = 0
|
|
16
|
+
shift = 0
|
|
17
|
+
while author:
|
|
18
|
+
author, byte_value = divmod(author, 100)
|
|
19
|
+
result |= _ENCODE_2DIGITS[byte_value] << shift
|
|
20
|
+
shift += 8
|
|
21
|
+
return result
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def decode_author(buf: bytes | bytearray | memoryview) -> int:
|
|
25
|
+
result = 0
|
|
26
|
+
mul = 1
|
|
27
|
+
for b in buf:
|
|
28
|
+
lo = b & 0x0F
|
|
29
|
+
hi = b >> 4
|
|
30
|
+
|
|
31
|
+
result += lo * mul
|
|
32
|
+
mul *= 10
|
|
33
|
+
|
|
34
|
+
# Avoid branch misprediction penalty
|
|
35
|
+
result += hi * mul
|
|
36
|
+
mul *= 10
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, slots=True)
|
|
41
|
+
class BRMDeserializationConfig:
|
|
42
|
+
version: bool = False
|
|
43
|
+
name: bool = False
|
|
44
|
+
description: bool = False
|
|
45
|
+
brick_count: bool = False
|
|
46
|
+
size: bool = False
|
|
47
|
+
weight: bool = False
|
|
48
|
+
price: bool = False
|
|
49
|
+
author: bool = False
|
|
50
|
+
creation_time: bool = False
|
|
51
|
+
last_update_time: bool = False
|
|
52
|
+
visibility: bool = False
|
|
53
|
+
tags: bool = False
|
|
54
|
+
|
|
55
|
+
_length: int = 0
|
|
56
|
+
|
|
57
|
+
def __post_init__(self):
|
|
58
|
+
object.__setattr__(self, "_length", (
|
|
59
|
+
self.version << 0 |
|
|
60
|
+
self.name << 1 |
|
|
61
|
+
self.description << 2 |
|
|
62
|
+
self.brick_count << 3 |
|
|
63
|
+
self.size << 4 |
|
|
64
|
+
self.weight << 5 |
|
|
65
|
+
self.price << 6 |
|
|
66
|
+
self.author << 7 |
|
|
67
|
+
self.creation_time << 8 |
|
|
68
|
+
self.last_update_time << 9 |
|
|
69
|
+
self.visibility << 10 |
|
|
70
|
+
self.tags << 11
|
|
71
|
+
).bit_length())
|
|
72
|
+
|
|
73
|
+
def length(self) -> int:
|
|
74
|
+
return self._length
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class BRMFile:
|
|
79
|
+
|
|
80
|
+
def __init__(self, version: int, brv: Optional[BRVFile] = None):
|
|
81
|
+
self.version = version
|
|
82
|
+
self.brv = brv
|
|
83
|
+
|
|
84
|
+
def serialize(
|
|
85
|
+
self,
|
|
86
|
+
file_name: Optional[str] = None,
|
|
87
|
+
description: str = '',
|
|
88
|
+
brick_count: Optional[int] = None,
|
|
89
|
+
size: _Vec3 = _Vec3(0, 0, 0),
|
|
90
|
+
weight: float = 0.0,
|
|
91
|
+
price: float = 0.0,
|
|
92
|
+
author: int = 0,
|
|
93
|
+
visibility: int = 0,
|
|
94
|
+
tags: Optional[list[str]] = None,
|
|
95
|
+
creation_time: int | None = None,
|
|
96
|
+
last_update_time: int | None = None,
|
|
97
|
+
):
|
|
98
|
+
"""Serializes a BRMFile
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
file_name (Optional[str], optional): Auto-generated if it is an empty string. Can be an
|
|
102
|
+
empty string. Defaults to None.
|
|
103
|
+
description (str, optional): Description. Defaults to ''.
|
|
104
|
+
brick_count (Optional[int], optional): Auto-generated if None and a brv is provided.
|
|
105
|
+
Defaults to None.
|
|
106
|
+
size (_Vec3, optional): Size. Defaults to _Vec3(0, 0, 0).
|
|
107
|
+
weight (float, optional): Weight. Defaults to 0.0.
|
|
108
|
+
price (float, optional): Price. Defaults to 0.0.
|
|
109
|
+
creation_time (int | None, optional): Creation time in BR's format. Defaults to None.
|
|
110
|
+
last_update_time (int | None, optional): Creation time in BR's format. Defaults to None.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
creation_time = _net_ticks_now() if creation_time is None else creation_time
|
|
114
|
+
last_update_time = _net_ticks_now() if last_update_time is None else last_update_time
|
|
115
|
+
|
|
116
|
+
if self.brv is not None:
|
|
117
|
+
if brick_count is None:
|
|
118
|
+
brick_count = len(self.brv.bricks)
|
|
119
|
+
|
|
120
|
+
if file_name is None:
|
|
121
|
+
file_name = f'BrickEdit-{last_update_time}'
|
|
122
|
+
|
|
123
|
+
assert brick_count <= 65_534, "Too many bricks! Max: 65,534"
|
|
124
|
+
|
|
125
|
+
# Init buffer
|
|
126
|
+
buffer = bytearray()
|
|
127
|
+
|
|
128
|
+
# No repeated global lookups
|
|
129
|
+
write = buffer.extend
|
|
130
|
+
|
|
131
|
+
# Precompile struct
|
|
132
|
+
pack_B = struct.Struct('B').pack # 'B' → uint8
|
|
133
|
+
pack_H = struct.Struct('<H').pack # '<H' → uint16 LE
|
|
134
|
+
# pack_I = struct.Struct('<I').pack # '<I' → uint32 LE
|
|
135
|
+
pack_Q = struct.Struct('<Q').pack # '<Q' → uint64 LE
|
|
136
|
+
pack_f = struct.Struct('<f').pack # '<f' → sp float LE
|
|
137
|
+
pack_vec3 = struct.Struct('<3f').pack
|
|
138
|
+
|
|
139
|
+
# Write version
|
|
140
|
+
write(pack_B(self.version))
|
|
141
|
+
|
|
142
|
+
# Write name
|
|
143
|
+
write(_UserTextSerialization.serialize(file_name, self.version, {}))
|
|
144
|
+
# Write description
|
|
145
|
+
write(_UserTextSerialization.serialize(description, self.version, {}))
|
|
146
|
+
|
|
147
|
+
# Write brick count
|
|
148
|
+
write(pack_H(brick_count))
|
|
149
|
+
|
|
150
|
+
# Write size
|
|
151
|
+
write(pack_vec3(*size.as_tuple()))
|
|
152
|
+
|
|
153
|
+
# Write weight and price
|
|
154
|
+
write(pack_f(weight))
|
|
155
|
+
write(pack_f(price))
|
|
156
|
+
|
|
157
|
+
# Write author
|
|
158
|
+
# Convert author to string.
|
|
159
|
+
write(b'\x1D') # Steam id stuff
|
|
160
|
+
packed_author = encode_author(author)
|
|
161
|
+
# (... + 7) // 8 is like ceil() for bytes.
|
|
162
|
+
bin_author = packed_author.to_bytes((packed_author.bit_length() + 7)//8, 'little')
|
|
163
|
+
write(bin_author)
|
|
164
|
+
|
|
165
|
+
write(b'\x00\x00\x00\x00') # The 4 forbidden bytes that breaks brms if you edit them
|
|
166
|
+
|
|
167
|
+
# Creation and update time
|
|
168
|
+
write(pack_Q(creation_time))
|
|
169
|
+
write(pack_Q(last_update_time))
|
|
170
|
+
|
|
171
|
+
write(pack_B(visibility))
|
|
172
|
+
|
|
173
|
+
write(pack_H(len(tags)))
|
|
174
|
+
for t in tags:
|
|
175
|
+
write(pack_B(len(t)))
|
|
176
|
+
write(t.encode('ascii'))
|
|
177
|
+
|
|
178
|
+
return buffer.getvalue()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
_UNPACK_FROM_B = struct.Struct('B').unpack_from
|
|
183
|
+
_UNPACK_FROM_h = struct.Struct('<h').unpack_from
|
|
184
|
+
_UNPACK_FROM_H = struct.Struct('<H').unpack_from
|
|
185
|
+
_UNPACK_FROM_5f = struct.Struct('<5f').unpack_from
|
|
186
|
+
_UNPACK_FROM_2Q = struct.Struct('<2Q').unpack_from
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def deserialize(self, buffer: bytes | bytearray, config: BRMDeserializationConfig, auto_version: bool = False) -> list[Any]:
|
|
190
|
+
"""Deserializes the BRMFile according to the config.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
buffer (bytes | bytearray): The buffer to deserialize.
|
|
194
|
+
config (BRMDeserializationConfig): Configuration for deserialization.
|
|
195
|
+
auto_version (bool, optional): If true, will automatically set self.version
|
|
196
|
+
to the version found in the buffer. Defaults to False.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
dict: A dictionary with the deserialized data.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
result = []
|
|
203
|
+
|
|
204
|
+
# Get memory view
|
|
205
|
+
mv = memoryview(buffer)
|
|
206
|
+
|
|
207
|
+
# Get version before running other stuff
|
|
208
|
+
brm_version: int = mv[0]
|
|
209
|
+
if auto_version:
|
|
210
|
+
self.version = brm_version
|
|
211
|
+
if config.version:
|
|
212
|
+
result.append(brm_version)
|
|
213
|
+
|
|
214
|
+
# Precompute, local cache and other variables
|
|
215
|
+
last_step = config.length()
|
|
216
|
+
version = self.version
|
|
217
|
+
offset = 3 # 1 because we already loaded version + 2 because the next value also has a fixed size
|
|
218
|
+
|
|
219
|
+
unpack_from_B = self._UNPACK_FROM_B
|
|
220
|
+
unpack_from_h = self._UNPACK_FROM_h
|
|
221
|
+
unpack_from_H = self._UNPACK_FROM_H
|
|
222
|
+
unpack_from_5f = self._UNPACK_FROM_5f
|
|
223
|
+
unpack_from_2Q = self._UNPACK_FROM_2Q
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# -- Name and description
|
|
227
|
+
# for UTF-16, we use -2*name_len because each element in a UTF-16 string is 2 bytes
|
|
228
|
+
# we use - because utf-16/ascii is indicated by whether the length is negative or not
|
|
229
|
+
|
|
230
|
+
if last_step <= 1:
|
|
231
|
+
return result
|
|
232
|
+
|
|
233
|
+
name_len, = unpack_from_h(mv, 1) # Compute before because we use it eitherway
|
|
234
|
+
name_byte_len = name_len if name_len >= 0 else -2*name_len
|
|
235
|
+
if config.name:
|
|
236
|
+
if name_len >= 0: # ASCII
|
|
237
|
+
name = bytes(mv[offset : offset+name_byte_len]).decode('ascii')
|
|
238
|
+
else: # UTF-16
|
|
239
|
+
name = bytes(mv[offset : offset+name_byte_len]).decode('utf-16-le')
|
|
240
|
+
result.append(name)
|
|
241
|
+
offset += name_byte_len
|
|
242
|
+
|
|
243
|
+
if last_step <= 2:
|
|
244
|
+
return result
|
|
245
|
+
|
|
246
|
+
desc_len, = unpack_from_h(mv, offset)
|
|
247
|
+
desc_byte_len = desc_len if desc_len >= 0 else -2*desc_len
|
|
248
|
+
offset += 2
|
|
249
|
+
if config.description:
|
|
250
|
+
if desc_len >= 0: # ASCII
|
|
251
|
+
desc = bytes(mv[offset : offset+desc_byte_len]).decode('ascii')
|
|
252
|
+
else: # UTF-16
|
|
253
|
+
desc = bytes(mv[offset : offset+desc_byte_len]).decode('utf-16-le')
|
|
254
|
+
result.append(desc)
|
|
255
|
+
offset += desc_byte_len
|
|
256
|
+
|
|
257
|
+
if last_step <= 3:
|
|
258
|
+
return result
|
|
259
|
+
|
|
260
|
+
# -- Brick count
|
|
261
|
+
|
|
262
|
+
brick_count, = unpack_from_H(mv, offset)
|
|
263
|
+
if config.brick_count:
|
|
264
|
+
result.append(brick_count)
|
|
265
|
+
offset += 2
|
|
266
|
+
|
|
267
|
+
if last_step <= 4:
|
|
268
|
+
return result
|
|
269
|
+
|
|
270
|
+
# -- Size, weight, price
|
|
271
|
+
sx, sy, sz, weight, price = unpack_from_5f(mv, offset)
|
|
272
|
+
offset += 20
|
|
273
|
+
|
|
274
|
+
if config.size:
|
|
275
|
+
result.append(_Vec3(sx, sy, sz))
|
|
276
|
+
if config.weight:
|
|
277
|
+
result.append(weight)
|
|
278
|
+
if config.price:
|
|
279
|
+
result.append(price)
|
|
280
|
+
|
|
281
|
+
if last_step <= 6:
|
|
282
|
+
return result
|
|
283
|
+
|
|
284
|
+
# -- Author (insert thousand miles stare)
|
|
285
|
+
# AUTHOR_MARKER = 0x1D
|
|
286
|
+
# assert mv[offset] == AUTHOR_MARKER
|
|
287
|
+
offset += 1
|
|
288
|
+
|
|
289
|
+
author_len, = unpack_from_B(mv, offset)
|
|
290
|
+
offset += 1
|
|
291
|
+
if config.author:
|
|
292
|
+
author = decode_author(mv[offset : offset+author_len])
|
|
293
|
+
result.append(author)
|
|
294
|
+
offset += author_len
|
|
295
|
+
|
|
296
|
+
if last_step <= 7:
|
|
297
|
+
return result
|
|
298
|
+
|
|
299
|
+
# -- 4 bytes of dread
|
|
300
|
+
offset += 4
|
|
301
|
+
|
|
302
|
+
# Create and update time (.NET)
|
|
303
|
+
creation_time, last_update_time = unpack_from_2Q(mv, offset)
|
|
304
|
+
if config.creation_time:
|
|
305
|
+
result.append(creation_time)
|
|
306
|
+
if config.last_update_time:
|
|
307
|
+
result.append(last_update_time)
|
|
308
|
+
offset += 8
|
|
309
|
+
|
|
310
|
+
if last_step <= 9:
|
|
311
|
+
return result
|
|
312
|
+
|
|
313
|
+
# -- Visibility
|
|
314
|
+
if config.visibility:
|
|
315
|
+
result.append(mv[offset])
|
|
316
|
+
offset += 1
|
|
317
|
+
|
|
318
|
+
if last_step <= 10:
|
|
319
|
+
return result
|
|
320
|
+
|
|
321
|
+
# -- Tags
|
|
322
|
+
# Do not care about propertly updating offset if we don't load because this is EOF
|
|
323
|
+
if config.tags:
|
|
324
|
+
num_tags, = unpack_from_h(mv, offset)
|
|
325
|
+
offset += 2
|
|
326
|
+
tags = [None] * num_tags
|
|
327
|
+
for i in range(num_tags):
|
|
328
|
+
tag_len = mv[offset]
|
|
329
|
+
offset += 1
|
|
330
|
+
tag = mv[offset : offset+tag_len].decode('ascii')
|
|
331
|
+
offset += tag_len
|
|
332
|
+
tags[i] = tag
|
|
333
|
+
result.append(tags)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
return result
|