0
# Binary Data Handling
1
2
Low-level binary data reading and writing with endianness support, enabling direct manipulation of Unity's binary asset formats and custom data structures.
3
4
## Capabilities
5
6
### Binary Reader
7
8
Endian-aware binary data reader for parsing Unity's binary formats.
9
10
```python { .api }
11
class EndianBinaryReader:
12
"""
13
Endian-aware binary data reader for Unity asset files.
14
"""
15
16
def __init__(self, data, endian="<"):
17
"""
18
Initialize binary reader.
19
20
Parameters:
21
- data: bytes or file-like object to read from
22
- endian: Byte order ("<" for little-endian, ">" for big-endian)
23
"""
24
25
@property
26
def endian(self):
27
"""
28
Get current endianness.
29
30
Returns:
31
str: Endian string ("<" or ">")
32
"""
33
34
@endian.setter
35
def endian(self, value):
36
"""
37
Set endianness.
38
39
Parameters:
40
- value: Endian string ("<" or ">")
41
"""
42
43
def read_byte(self):
44
"""
45
Read a single byte.
46
47
Returns:
48
int: Byte value (0-255)
49
"""
50
51
def read_sbyte(self):
52
"""
53
Read a signed byte.
54
55
Returns:
56
int: Signed byte value (-128 to 127)
57
"""
58
59
def read_boolean(self):
60
"""
61
Read a boolean value.
62
63
Returns:
64
bool: Boolean value (True if non-zero)
65
"""
66
67
def read_int16(self):
68
"""
69
Read a 16-bit signed integer.
70
71
Returns:
72
int: 16-bit signed integer
73
"""
74
75
def read_uint16(self):
76
"""
77
Read a 16-bit unsigned integer.
78
79
Returns:
80
int: 16-bit unsigned integer
81
"""
82
83
def read_int32(self):
84
"""
85
Read a 32-bit signed integer.
86
87
Returns:
88
int: 32-bit signed integer
89
"""
90
91
def read_uint32(self):
92
"""
93
Read a 32-bit unsigned integer.
94
95
Returns:
96
int: 32-bit unsigned integer
97
"""
98
99
def read_int64(self):
100
"""
101
Read a 64-bit signed integer.
102
103
Returns:
104
int: 64-bit signed integer
105
"""
106
107
def read_uint64(self):
108
"""
109
Read a 64-bit unsigned integer.
110
111
Returns:
112
int: 64-bit unsigned integer
113
"""
114
115
def read_float(self):
116
"""
117
Read a 32-bit float.
118
119
Returns:
120
float: 32-bit floating point value
121
"""
122
123
def read_double(self):
124
"""
125
Read a 64-bit double.
126
127
Returns:
128
float: 64-bit floating point value
129
"""
130
131
def read_string(self, length=None, encoding="utf-8"):
132
"""
133
Read a string with length prefix or fixed length.
134
135
Parameters:
136
- length: Optional fixed length, otherwise reads length prefix
137
- encoding: Text encoding (default "utf-8")
138
139
Returns:
140
str: Decoded string
141
"""
142
143
def read_cstring(self, encoding="utf-8"):
144
"""
145
Read a null-terminated C-style string.
146
147
Parameters:
148
- encoding: Text encoding (default "utf-8")
149
150
Returns:
151
str: Decoded string
152
"""
153
154
def read_bytes(self, count):
155
"""
156
Read a specific number of bytes.
157
158
Parameters:
159
- count: Number of bytes to read
160
161
Returns:
162
bytes: Raw byte data
163
"""
164
165
def align_stream(self, alignment=4):
166
"""
167
Align stream position to boundary.
168
169
Parameters:
170
- alignment: Alignment boundary in bytes
171
"""
172
173
@property
174
def position(self):
175
"""
176
Get current stream position.
177
178
Returns:
179
int: Current position in bytes
180
"""
181
182
@position.setter
183
def position(self, value):
184
"""
185
Set stream position.
186
187
Parameters:
188
- value: New position in bytes
189
"""
190
191
def seek(self, position):
192
"""
193
Seek to absolute position.
194
195
Parameters:
196
- position: Absolute position in bytes
197
"""
198
199
def tell(self):
200
"""
201
Get current position.
202
203
Returns:
204
int: Current position in bytes
205
"""
206
```
207
208
### Binary Writer
209
210
Endian-aware binary data writer for creating Unity-compatible binary formats.
211
212
```python { .api }
213
class EndianBinaryWriter:
214
"""
215
Endian-aware binary data writer for Unity asset files.
216
"""
217
218
def __init__(self, endian="<"):
219
"""
220
Initialize binary writer.
221
222
Parameters:
223
- endian: Byte order ("<" for little-endian, ">" for big-endian)
224
"""
225
226
@property
227
def endian(self):
228
"""
229
Get current endianness.
230
231
Returns:
232
str: Endian string ("<" or ">")
233
"""
234
235
@endian.setter
236
def endian(self, value):
237
"""
238
Set endianness.
239
240
Parameters:
241
- value: Endian string ("<" or ">")
242
"""
243
244
def write_byte(self, value):
245
"""
246
Write a single byte.
247
248
Parameters:
249
- value: Byte value (0-255)
250
"""
251
252
def write_sbyte(self, value):
253
"""
254
Write a signed byte.
255
256
Parameters:
257
- value: Signed byte value (-128 to 127)
258
"""
259
260
def write_boolean(self, value):
261
"""
262
Write a boolean value.
263
264
Parameters:
265
- value: Boolean value
266
"""
267
268
def write_int16(self, value):
269
"""
270
Write a 16-bit signed integer.
271
272
Parameters:
273
- value: 16-bit signed integer
274
"""
275
276
def write_uint16(self, value):
277
"""
278
Write a 16-bit unsigned integer.
279
280
Parameters:
281
- value: 16-bit unsigned integer
282
"""
283
284
def write_int32(self, value):
285
"""
286
Write a 32-bit signed integer.
287
288
Parameters:
289
- value: 32-bit signed integer
290
"""
291
292
def write_uint32(self, value):
293
"""
294
Write a 32-bit unsigned integer.
295
296
Parameters:
297
- value: 32-bit unsigned integer
298
"""
299
300
def write_int64(self, value):
301
"""
302
Write a 64-bit signed integer.
303
304
Parameters:
305
- value: 64-bit signed integer
306
"""
307
308
def write_uint64(self, value):
309
"""
310
Write a 64-bit unsigned integer.
311
312
Parameters:
313
- value: 64-bit unsigned integer
314
"""
315
316
def write_float(self, value):
317
"""
318
Write a 32-bit float.
319
320
Parameters:
321
- value: 32-bit floating point value
322
"""
323
324
def write_double(self, value):
325
"""
326
Write a 64-bit double.
327
328
Parameters:
329
- value: 64-bit floating point value
330
"""
331
332
def write_string(self, value, encoding="utf-8"):
333
"""
334
Write a string with length prefix.
335
336
Parameters:
337
- value: String to write
338
- encoding: Text encoding (default "utf-8")
339
"""
340
341
def write_cstring(self, value, encoding="utf-8"):
342
"""
343
Write a null-terminated C-style string.
344
345
Parameters:
346
- value: String to write
347
- encoding: Text encoding (default "utf-8")
348
"""
349
350
def write_bytes(self, data):
351
"""
352
Write raw byte data.
353
354
Parameters:
355
- data: bytes or bytearray to write
356
"""
357
358
def align_stream(self, alignment=4):
359
"""
360
Pad stream to alignment boundary.
361
362
Parameters:
363
- alignment: Alignment boundary in bytes
364
"""
365
366
@property
367
def position(self):
368
"""
369
Get current stream position.
370
371
Returns:
372
int: Current position in bytes
373
"""
374
375
def to_bytes(self):
376
"""
377
Get written data as bytes.
378
379
Returns:
380
bytes: All written data
381
"""
382
```
383
384
## Usage Examples
385
386
### Reading Binary Asset Data
387
388
```python
389
from UnityPy.streams import EndianBinaryReader
390
import UnityPy
391
392
# Read binary data from a Unity asset file
393
env = UnityPy.load("binary_asset.dat")
394
395
for obj in env.objects:
396
if obj.type.name == "TextAsset":
397
text_asset = obj.read()
398
399
# If the text asset contains binary data
400
if hasattr(text_asset, 'm_Script') and text_asset.m_Script:
401
# Create reader from binary data
402
reader = EndianBinaryReader(text_asset.m_Script, endian="<")
403
404
# Read structured data
405
magic = reader.read_uint32()
406
version = reader.read_uint16()
407
count = reader.read_uint32()
408
409
print(f"Magic: 0x{magic:08X}")
410
print(f"Version: {version}")
411
print(f"Count: {count}")
412
413
# Read array of structures
414
entries = []
415
for i in range(count):
416
entry = {
417
'id': reader.read_uint32(),
418
'name': reader.read_string(),
419
'value': reader.read_float(),
420
'active': reader.read_boolean()
421
}
422
entries.append(entry)
423
424
# Align to 4-byte boundary
425
reader.align_stream(4)
426
427
print(f"Read {len(entries)} entries")
428
for entry in entries[:5]: # Show first 5
429
print(f" ID: {entry['id']}, Name: {entry['name']}, Value: {entry['value']}")
430
```
431
432
### Creating Binary Data
433
434
```python
435
from UnityPy.streams import EndianBinaryWriter
436
437
# Create binary data structure
438
writer = EndianBinaryWriter(endian="<")
439
440
# Write header
441
writer.write_uint32(0x12345678) # Magic number
442
writer.write_uint16(1) # Version
443
writer.write_uint32(3) # Entry count
444
445
# Write entries
446
entries = [
447
{"id": 1, "name": "First", "value": 1.5, "active": True},
448
{"id": 2, "name": "Second", "value": 2.5, "active": False},
449
{"id": 3, "name": "Third", "value": 3.5, "active": True}
450
]
451
452
for entry in entries:
453
writer.write_uint32(entry["id"])
454
writer.write_string(entry["name"])
455
writer.write_float(entry["value"])
456
writer.write_boolean(entry["active"])
457
writer.align_stream(4)
458
459
# Get the binary data
460
binary_data = writer.to_bytes()
461
print(f"Created {len(binary_data)} bytes of binary data")
462
463
# Write to file
464
with open("custom_data.bin", "wb") as f:
465
f.write(binary_data)
466
```
467
468
### Parsing Custom Unity Data Structures
469
470
```python
471
from UnityPy.streams import EndianBinaryReader
472
import UnityPy
473
474
def parse_custom_mesh_data(data):
475
"""Parse a custom mesh format."""
476
reader = EndianBinaryReader(data, endian="<")
477
478
# Read header
479
signature = reader.read_bytes(4)
480
if signature != b"MESH":
481
raise ValueError("Invalid mesh signature")
482
483
version = reader.read_uint32()
484
vertex_count = reader.read_uint32()
485
triangle_count = reader.read_uint32()
486
487
print(f"Mesh version: {version}")
488
print(f"Vertices: {vertex_count}")
489
print(f"Triangles: {triangle_count}")
490
491
# Read vertices
492
vertices = []
493
for i in range(vertex_count):
494
x = reader.read_float()
495
y = reader.read_float()
496
z = reader.read_float()
497
vertices.append((x, y, z))
498
499
# Read triangles
500
triangles = []
501
for i in range(triangle_count):
502
a = reader.read_uint16()
503
b = reader.read_uint16()
504
c = reader.read_uint16()
505
triangles.append((a, b, c))
506
reader.align_stream(4) # Align to 4 bytes
507
508
return {
509
'version': version,
510
'vertices': vertices,
511
'triangles': triangles
512
}
513
514
# Usage with Unity asset
515
env = UnityPy.load("custom_mesh_assets/")
516
517
for obj in env.objects:
518
if obj.type.name == "TextAsset":
519
text_asset = obj.read()
520
if text_asset.name.endswith("_mesh"):
521
try:
522
mesh_data = parse_custom_mesh_data(text_asset.m_Script)
523
print(f"Parsed mesh: {text_asset.name}")
524
print(f" Vertices: {len(mesh_data['vertices'])}")
525
print(f" Triangles: {len(mesh_data['triangles'])}")
526
except Exception as e:
527
print(f"Failed to parse {text_asset.name}: {e}")
528
```
529
530
### Endianness Handling
531
532
```python
533
from UnityPy.streams import EndianBinaryReader, EndianBinaryWriter
534
535
# Create data with different endianness
536
def create_test_data(endian):
537
writer = EndianBinaryWriter(endian=endian)
538
writer.write_uint32(0x12345678)
539
writer.write_float(3.14159)
540
writer.write_string("Test String")
541
return writer.to_bytes()
542
543
# Create little-endian and big-endian data
544
little_endian_data = create_test_data("<")
545
big_endian_data = create_test_data(">")
546
547
print(f"Little-endian data: {little_endian_data.hex()}")
548
print(f"Big-endian data: {big_endian_data.hex()}")
549
550
# Read with appropriate endianness
551
def read_test_data(data, endian):
552
reader = EndianBinaryReader(data, endian=endian)
553
magic = reader.read_uint32()
554
pi = reader.read_float()
555
text = reader.read_string()
556
return magic, pi, text
557
558
# Read little-endian data
559
magic, pi, text = read_test_data(little_endian_data, "<")
560
print(f"Little-endian: Magic=0x{magic:08X}, Pi={pi:.5f}, Text='{text}'")
561
562
# Read big-endian data
563
magic, pi, text = read_test_data(big_endian_data, ">")
564
print(f"Big-endian: Magic=0x{magic:08X}, Pi={pi:.5f}, Text='{text}'")
565
566
# Demonstrate endian switching
567
reader = EndianBinaryReader(little_endian_data, endian="<")
568
magic1 = reader.read_uint32()
569
reader.endian = ">" # Switch to big-endian
570
magic2 = reader.read_uint32() # This will read incorrectly
571
print(f"Same data, different endian: 0x{magic1:08X} vs 0x{magic2:08X}")
572
```
573
574
### Working with Alignment
575
576
```python
577
from UnityPy.streams import EndianBinaryReader, EndianBinaryWriter
578
579
# Create data with alignment requirements
580
writer = EndianBinaryWriter()
581
582
# Write header (8 bytes)
583
writer.write_uint32(0xDEADBEEF)
584
writer.write_uint32(42)
585
586
# Write variable-length string
587
writer.write_string("Variable length string")
588
589
# Align to 16-byte boundary before next structure
590
writer.align_stream(16)
591
start_pos = writer.position
592
593
# Write aligned structure
594
writer.write_float(1.0)
595
writer.write_float(2.0)
596
writer.write_float(3.0)
597
writer.write_float(4.0) # 16 bytes total
598
599
data = writer.to_bytes()
600
print(f"Total size: {len(data)} bytes")
601
print(f"Aligned structure starts at: {start_pos}")
602
603
# Read back with alignment
604
reader = EndianBinaryReader(data)
605
606
# Read header
607
magic = reader.read_uint32()
608
value = reader.read_uint32()
609
text = reader.read_string()
610
611
print(f"Magic: 0x{magic:08X}")
612
print(f"Value: {value}")
613
print(f"Text: '{text}'")
614
print(f"Position after string: {reader.position}")
615
616
# Align to same boundary
617
reader.align_stream(16)
618
print(f"Position after alignment: {reader.position}")
619
620
# Read aligned structure
621
floats = [reader.read_float() for _ in range(4)]
622
print(f"Float values: {floats}")
623
```
624
625
### Custom Data Format Implementation
626
627
```python
628
from UnityPy.streams import EndianBinaryReader, EndianBinaryWriter
629
import struct
630
631
class CustomDataFormat:
632
"""Example custom binary data format handler."""
633
634
def __init__(self):
635
self.magic = 0x43555354 # "CUST"
636
self.version = 1
637
self.entries = []
638
639
def add_entry(self, name, data_type, value):
640
"""Add a data entry."""
641
self.entries.append({
642
'name': name,
643
'type': data_type,
644
'value': value
645
})
646
647
def serialize(self):
648
"""Serialize to binary format."""
649
writer = EndianBinaryWriter(endian="<")
650
651
# Write header
652
writer.write_uint32(self.magic)
653
writer.write_uint16(self.version)
654
writer.write_uint16(len(self.entries))
655
656
# Write entries
657
for entry in self.entries:
658
writer.write_string(entry['name'])
659
writer.write_byte(entry['type'])
660
661
if entry['type'] == 0: # Integer
662
writer.write_int32(entry['value'])
663
elif entry['type'] == 1: # Float
664
writer.write_float(entry['value'])
665
elif entry['type'] == 2: # String
666
writer.write_string(entry['value'])
667
elif entry['type'] == 3: # Boolean
668
writer.write_boolean(entry['value'])
669
670
writer.align_stream(4)
671
672
return writer.to_bytes()
673
674
@classmethod
675
def deserialize(cls, data):
676
"""Deserialize from binary format."""
677
reader = EndianBinaryReader(data, endian="<")
678
679
# Read header
680
magic = reader.read_uint32()
681
if magic != 0x43555354:
682
raise ValueError(f"Invalid magic: 0x{magic:08X}")
683
684
version = reader.read_uint16()
685
count = reader.read_uint16()
686
687
# Create instance
688
instance = cls()
689
instance.version = version
690
691
# Read entries
692
for _ in range(count):
693
name = reader.read_string()
694
data_type = reader.read_byte()
695
696
if data_type == 0: # Integer
697
value = reader.read_int32()
698
elif data_type == 1: # Float
699
value = reader.read_float()
700
elif data_type == 2: # String
701
value = reader.read_string()
702
elif data_type == 3: # Boolean
703
value = reader.read_boolean()
704
else:
705
raise ValueError(f"Unknown data type: {data_type}")
706
707
instance.add_entry(name, data_type, value)
708
reader.align_stream(4)
709
710
return instance
711
712
# Usage example
713
custom_data = CustomDataFormat()
714
custom_data.add_entry("player_level", 0, 42)
715
custom_data.add_entry("player_health", 1, 75.5)
716
custom_data.add_entry("player_name", 2, "Hero")
717
custom_data.add_entry("is_alive", 3, True)
718
719
# Serialize
720
binary_data = custom_data.serialize()
721
print(f"Serialized {len(binary_data)} bytes")
722
723
# Deserialize
724
loaded_data = CustomDataFormat.deserialize(binary_data)
725
print(f"Loaded {len(loaded_data.entries)} entries")
726
for entry in loaded_data.entries:
727
print(f" {entry['name']}: {entry['value']} (type {entry['type']})")
728
```