or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

asset-classes.mdasset-export.mdasset-loading.mdbinary-data.mdenumerations.mdfile-formats.mdindex.md

binary-data.mddocs/

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

```