SimTower saved game

From Just Solve the File Format Problem
(Difference between revisions)
Jump to: navigation, search
(link to OpenSkyscraper document)
 
(add some code to parse the format (TODO: add usage notes))
Line 9: Line 9:
 
* [https://raw.githubusercontent.com/fabianschuiki/OpenSkyscraper/90e6e92a2d46869a4052332e48633dd1545256e4/doc/simtower/TDT_format.txt Format description from the OpenSkyscraper project]
 
* [https://raw.githubusercontent.com/fabianschuiki/OpenSkyscraper/90e6e92a2d46869a4052332e48633dd1545256e4/doc/simtower/TDT_format.txt Format description from the OpenSkyscraper project]
 
* [[Wikipedia:SimTower|Wikipedia article on SimTower]]
 
* [[Wikipedia:SimTower|Wikipedia article on SimTower]]
 +
 +
== Code ==
 +
<br clear="all">
 +
 +
=== tdt_cons.py ===
 +
 +
<nowiki># To the extent possible under law, the author(s) have dedicated all copyright
 +
# and related and neighboring rights to this software to the public domain
 +
# worldwide. This software is distributed without any warranty.
 +
#
 +
# See <http://creativecommons.org/publicdomain/zero/1.0/>.
 +
 +
import construct as _cns
 +
import util as _util
 +
 +
 +
#### File header
 +
 +
File_Header_Struct = _cns.Struct('file_header',
 +
_cns.Anchor('hdr_start_'),  # to debug, change Anchor to Probe
 +
 +
# SimTower doesn't seem to care about the first byte, but the second
 +
# must be 0x24. Larger values produce a "wrong version" warning.
 +
_cns.Padding(1),
 +
_cns.Const(_cns.ULInt8('version'), 0x24),
 +
 +
_cns.ULInt8('stars'),          # 1-5 stars, or 6 for "tower"
 +
_util.Unknown('_hdr3', 1),
 +
_cns.ULInt32('cash_balance'),  # units of $100
 +
_cns.ULInt32('other_income'),
 +
_cns.ULInt32('construction_costs'),
 +
_cns.ULInt32('last_quarter_cash'),
 +
_cns.ULInt16('frame_time'),
 +
_cns.ULInt32('day'),
 +
_util.Unknown('_hdr26', 2),
 +
_cns.ULInt8('lobby_height'),
 +
_util.Unknown('_hdr29', 9),
 +
_cns.ULInt16('viewport_x'),
 +
_cns.ULInt16('viewport_y'),
 +
_util.Unknown('_hdr42', 14),
 +
_cns.ULInt8('named_people_count'),
 +
 +
_util.Unknown('_hdr57', 503),
 +
_cns.Anchor('hdr_end_'),
 +
_util.Assert(lambda ctx: ctx.hdr_end_ == ctx.hdr_start_ + 560),
 +
)
 +
 +
 +
#### Tenants
 +
 +
Tenant_Type = _util.NumToSym(_cns.ULInt8('type'), {
 +
0:'floor',
 +
3:'hotel_single',
 +
4:'hotel_double',
 +
5:'hotal_suite',
 +
6:'restaurant',
 +
7:'office',
 +
9:'condo',
 +
10:'shop',
 +
11:'parking_space',
 +
12:'fast_food',
 +
13:'medical',
 +
14:'security',
 +
15:'housekeeping',
 +
18:'movie_theater_floor2', 19:'movie_theater',
 +
20:'recycling_floor2', 21:'recycling',
 +
24:'lobby',
 +
29:'hall_floor2', 30:'hall',
 +
33:'metro',
 +
34:'movie_screen_floor2', 35:'movie_screen',
 +
36:'cathedral_floor5', 37:'cathedral_floor4',
 +
38:'cathedral_floor3', 39:'cathedral_floor2',
 +
40:'cathedral',
 +
44:'parking_ramp',
 +
45:'metro_tunnel',
 +
48:'burned',
 +
})
 +
 +
Tenant_Struct = _cns.Struct('tenant',
 +
# Horizontal positions are given with half-open ranges [left,right)
 +
# in units of 8 pixels.
 +
_cns.ULInt16('left_edge'),
 +
_cns.ULInt16('right_edge'),
 +
_cns.Rename('type', Tenant_Type),
 +
_cns.ULInt8('status'),
 +
_cns.ULInt8('pertype_idx'),
 +
_util.Unknown('_unk7', 1),
 +
_cns.ULInt32('people_offset'),
 +
_cns.ULInt8('id'),
 +
_util.Unknown('_unk13', 3),
 +
_cns.ULInt8('rent_class'),
 +
_util.Unknown('_unk17', 1),
 +
)
 +
assert(Tenant_Struct.sizeof() == 18)
 +
 +
 +
#### Floors
 +
 +
def _floor_below_grade(raw_floor):
 +
return raw_floor < 10
 +
 +
def floor_id_from_raw(raw_floor):
 +
# Raw floor values {0..9} are basements: {-10..-1} or {B10..B1}.
 +
# Values {10..119} are floors {1..110}.
 +
if _floor_below_grade(raw_floor):
 +
return raw_floor-10
 +
else:
 +
return raw_floor-9
 +
 +
def _floor_could_be_lobby(raw_floor):
 +
return floor_id_from_raw(raw_floor) in (1, 15, 30, 45, 60, 75, 90)
 +
 +
Floor_Struct = _cns.Struct('floor',
 +
_cns.ULInt16('tenant_count'),
 +
_cns.ULInt16('left_edge'),
 +
_cns.ULInt16('right_edge'),
 +
_cns.Rename('tenants',
 +
_cns.Array(lambda ctx: ctx.tenant_count, Tenant_Struct)),
 +
_cns.Array(94, _cns.ULInt16('tenant_id_to_index')),
 +
)
 +
 +
 +
#### People
 +
 +
Person_Struct = _cns.Struct('person',
 +
_cns.ULInt8('tenant_floor'),
 +
_cns.ULInt8('tenant_index'),
 +
_cns.ULInt16('number_in_tenant'),
 +
_cns.Rename('tenant_type', Tenant_Type),
 +
_cns.ULInt8('status'),
 +
_cns.SLInt8('current_floor'),
 +
_util.Unknown('_unk6', 5),
 +
_cns.ULInt16('stress'),
 +
_cns.ULInt16('eval'),
 +
)
 +
assert(Person_Struct.sizeof() == 16)
 +
 +
 +
#### Retail
 +
 +
Retail_Struct = _cns.Struct('retail',
 +
_cns.ULInt8('floor'),
 +
_util.Unknown('_unk1', 10),
 +
_cns.ULInt8('type'),
 +
_util.Unknown('_unk12', 6),
 +
)
 +
assert(Retail_Struct.sizeof() == 18)
 +
 +
 +
#### Stairs
 +
 +
Stairs_Type = _util.NumToSym(_cns.ULInt8('type'),
 +
{0:'escalator', 1:'standard'})
 +
 +
Stairs_Struct = _cns.Struct('stairs',
 +
_util.Bool(_cns.ULInt8('present')),
 +
_cns.Rename('type', Stairs_Type),
 +
_cns.ULInt16('xpos'),
 +
_cns.ULInt8('bottom_floor'),
 +
_util.Unknown('_unk5', 5),
 +
)
 +
assert(Stairs_Struct.sizeof() == 10)
 +
 +
 +
#### Elevators
 +
 +
Elevator_Type = _util.NumToSym(_cns.ULInt8('type'),
 +
{0:'express', 1:'standard', 2:'service'})
 +
 +
def _potential_floors_served(ctx):
 +
if ctx.type == 'express':
 +
count = 0
 +
for i in range(ctx.bottom_floor, ctx.top_floor + 1):
 +
if _floor_below_grade(i) or _floor_could_be_lobby(i):
 +
count += 1
 +
else:
 +
count = 1 + (ctx.top_floor - ctx.bottom_floor)
 +
return count
 +
 +
Elevator_Struct = _cns.Struct('elevator',
 +
_cns.Anchor('start_'),
 +
 +
_util.Bool(_cns.ULInt8('present')),
 +
_cns.Rename('type', Elevator_Type),
 +
 +
# Capacity > 42 may crash SimTower.
 +
# 42 seems to work for non-express elevators.
 +
_cns.ULInt8('capacity'),
 +
_cns.ULInt8('car_count'),
 +
 +
# There are 4 "schedule" arrays of size 14: 7 weekday periods, followed
 +
# by 7 weekend periods.  The GUI shows 6 periods per day; the 7th is
 +
# not used.  The entire first array looks unused too.  The regex
 +
# '\x01{14}......\x05......\x05' can reliably find elevator data.
 +
_cns.Array(14, _cns.ULInt8('#unknown_sched')),
 +
_cns.If(lambda ctx: ctx.present,
 +
_util.Assert(lambda ctx: ctx['#unknown_sched'] == [1]*14)),
 +
_cns.Array(14, _cns.ULInt8('response_distances')),
 +
_cns.Array(14, _cns.ULInt8('express_modes')),
 +
_cns.Array(14, _cns.ULInt8('departure_delays')),
 +
 +
_util.Bool(_cns.ULInt8('shaft_visible')),
 +
_util.Unknown('_unk60', 1),
 +
_cns.ULInt16('left_edge'),
 +
_cns.ULInt8('top_floor'),
 +
_cns.ULInt8('bottom_floor'),
 +
_cns.Value('potential_floors_served_', _potential_floors_served),
 +
 +
# For each of 120 floors, value is 1 if served, otherwise 0.
 +
# Adding express elevator stops on improper floors (above ground and
 +
# not a multiple of 15) may crash SimTower, probably because there's
 +
# an array of size potential_floors_served.
 +
_cns.Array(120, _cns.ULInt8('floor_stops')),
 +
 +
# Resting floor for each car.
 +
_cns.Array(8, _cns.ULInt8('resting_floors')),
 +
 +
_cns.Anchor('fixed_end_'),
 +
_util.Assert(lambda ctx: ctx.fixed_end_ == ctx.start_ + 194),
 +
 +
_cns.If(lambda ctx: ctx.present, _util.AnonEmbed(
 +
_util.Unknown('_unk194', 3488),
 +
 +
# Information about people waiting for elevators
 +
# is likely to be in the following array.
 +
_cns.Array(lambda ctx: ctx.potential_floors_served_,
 +
_util.Unknown('_unk3682', 324)),
 +
)),
 +
_cns.Anchor('end_'),
 +
)
 +
 +
 +
#### Finances/population block
 +
 +
Finances_Struct = _cns.Struct('finances',
 +
_cns.Array(10, _cns.ULInt32('tenant_populations')),
 +
_cns.ULInt32('tower_population'),
 +
_cns.Array(10, _cns.ULInt32('tenant_incomes')),
 +
_cns.ULInt32('tower_income'),
 +
_cns.Array(10, _cns.ULInt32('tenant_maintenance')),
 +
_cns.ULInt32('tower_maintenance'),
 +
)
 +
 +
 +
#### Overall file structure
 +
 +
Tower_Struct = _cns.Struct('tower',
 +
_cns.Embedded(File_Header_Struct),
 +
 +
_cns.Rename('floors', _cns.Array(120, Floor_Struct)),
 +
 +
_cns.ULInt32('people_count'),
 +
_cns.Rename('people',
 +
_cns.Array(lambda ctx: ctx.people_count, Person_Struct)),
 +
 +
_cns.Rename('retail', _cns.Array(512, Retail_Struct)),
 +
 +
_cns.Anchor('elevators_start_'),
 +
_cns.Rename('elevators', _cns.Array(24, Elevator_Struct)),
 +
_cns.Anchor('elevators_end_'),
 +
 +
_util.Unknown('_unk_after_elev', 88),
 +
_cns.Anchor('finances_start_'),
 +
_cns.Embedded(Finances_Struct),
 +
_cns.Anchor('finances_end_'),
 +
 +
_util.Unknown('_unk_after_finpop', 1102),
 +
_cns.Anchor('stairs_start_'),
 +
_cns.Rename('stairs', _cns.Array(64, Stairs_Struct)),
 +
_cns.Anchor('stairs_end_'),
 +
 +
_util.Unknown('_unk_after_stairs', 17242),
 +
_cns.Rename('named_people',
 +
_cns.Array(lambda ctx: ctx.named_people_count,
 +
_util.ZStr(None, 16))),
 +
 +
_cns.Terminator  # must reach end of file
 +
)</nowiki>
 +
 +
=== util.py ===
 +
 +
<nowiki># To the extent possible under law, the author(s) have dedicated all copyright
 +
# and related and neighboring rights to this software to the public domain
 +
# worldwide. This software is distributed without any warranty.
 +
#
 +
# See <http://creativecommons.org/publicdomain/zero/1.0/>.
 +
 +
import construct as _cns
 +
 +
 +
#### Utility functions
 +
 +
Unknown = _cns.Field
 +
 +
class Assert(_cns.Construct):
 +
def __init__(self, fn):
 +
_cns.Construct.__init__(self, None)
 +
self.fn = fn
 +
def _sizeof(self, context): return 0
 +
def _assert(self, stream, context): assert self.fn(context)
 +
def _parse(self, stream, context): self._assert(stream, context)
 +
def _build(self, obj, stream, context): self._assert(stream, context)
 +
 +
def AnonEmbed(*subcons):
 +
return _cns.Embedded(_cns.Struct(None, *subcons))
 +
 +
def NumToSym(subcon, decoding):
 +
return _cns.MappingAdapter(subcon, decoding = decoding,
 +
encoding = dict((v,k) for (k,v) in decoding.items()),
 +
decdefault = _cns.Pass, encdefault = _cns.Pass)
 +
 +
def Bool(subcon):
 +
return NumToSym(subcon, {0:False, 1:True})
 +
 +
class _PaddedBytes(bytes):
 +
def __new__(cls, raw=b'', padding=None):
 +
b = super(_PaddedBytes, cls).__new__(cls, raw)
 +
b.padding = padding
 +
return b
 +
 +
class _ZStrAdapter(_cns.StringAdapter):
 +
def __init__(self, subcon):
 +
_cns.StringAdapter.__init__(self, subcon)
 +
def _decode(self, obj, context):
 +
obj = _PaddedBytes(*obj.split(b'\0', 1))
 +
return _cns.StringAdapter._decode(self, obj, context)
 +
def _encode(self, obj, context):
 +
out = _cns.StringAdapter._encode(self, obj, context)
 +
out += b'\0'
 +
try:
 +
if obj.padding is not None:
 +
out += obj.padding
 +
except AttributeError:
 +
pass
 +
 +
pad = self._sizeof(context) - len(out)
 +
if pad < 0:
 +
raise ValueError("string too long")
 +
out += b'\0' * pad
 +
 +
return out
 +
 +
def ZStr(name, length, encoding=None):
 +
fld = _cns.Field(name, length)
 +
return _ZStrAdapter(_cns.StringAdapter(fld, encoding=encoding))
 +
</nowiki>

Revision as of 01:14, 27 September 2016

File Format
Name SimTower saved game
Ontology
Extension(s) .tdt

Contents

Links

Code


tdt_cons.py

# To the extent possible under law, the author(s) have dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.
#
# See <http://creativecommons.org/publicdomain/zero/1.0/>.

import construct as _cns
import util as _util


#### File header

File_Header_Struct = _cns.Struct('file_header',
	_cns.Anchor('hdr_start_'),  # to debug, change Anchor to Probe

	# SimTower doesn't seem to care about the first byte, but the second
	# must be 0x24. Larger values produce a "wrong version" warning.
	_cns.Padding(1),
	_cns.Const(_cns.ULInt8('version'), 0x24),

	_cns.ULInt8('stars'),          # 1-5 stars, or 6 for "tower"
	_util.Unknown('_hdr3', 1),
	_cns.ULInt32('cash_balance'),  # units of $100
	_cns.ULInt32('other_income'),
	_cns.ULInt32('construction_costs'),
	_cns.ULInt32('last_quarter_cash'),
	_cns.ULInt16('frame_time'),
	_cns.ULInt32('day'),
	_util.Unknown('_hdr26', 2),
	_cns.ULInt8('lobby_height'),
	_util.Unknown('_hdr29', 9),
	_cns.ULInt16('viewport_x'),
	_cns.ULInt16('viewport_y'),
	_util.Unknown('_hdr42', 14),
	_cns.ULInt8('named_people_count'),

	_util.Unknown('_hdr57', 503),
	_cns.Anchor('hdr_end_'),
	_util.Assert(lambda ctx: ctx.hdr_end_ == ctx.hdr_start_ + 560),
)


#### Tenants

Tenant_Type = _util.NumToSym(_cns.ULInt8('type'), {
	 0:'floor',
	 3:'hotel_single',
	 4:'hotel_double',
	 5:'hotal_suite',
	 6:'restaurant',
	 7:'office',
	 9:'condo',
	10:'shop',
	11:'parking_space',
	12:'fast_food',
	13:'medical',
	14:'security',
	15:'housekeeping',
	18:'movie_theater_floor2', 19:'movie_theater',
	20:'recycling_floor2', 21:'recycling',
	24:'lobby',
	29:'hall_floor2', 30:'hall',
	33:'metro',
	34:'movie_screen_floor2', 35:'movie_screen',
	36:'cathedral_floor5', 37:'cathedral_floor4',
		38:'cathedral_floor3', 39:'cathedral_floor2',
		40:'cathedral',
	44:'parking_ramp',
	45:'metro_tunnel',
	48:'burned',
})

Tenant_Struct = _cns.Struct('tenant',
	# Horizontal positions are given with half-open ranges [left,right)
	# in units of 8 pixels.
	_cns.ULInt16('left_edge'),
	_cns.ULInt16('right_edge'),
	_cns.Rename('type', Tenant_Type),
	_cns.ULInt8('status'),
	_cns.ULInt8('pertype_idx'),
	_util.Unknown('_unk7', 1),
	_cns.ULInt32('people_offset'),
	_cns.ULInt8('id'),
	_util.Unknown('_unk13', 3),
	_cns.ULInt8('rent_class'),
	_util.Unknown('_unk17', 1),
)
assert(Tenant_Struct.sizeof() == 18)


#### Floors

def _floor_below_grade(raw_floor):
	return raw_floor < 10

def floor_id_from_raw(raw_floor):
	# Raw floor values {0..9} are basements: {-10..-1} or {B10..B1}.
	# Values {10..119} are floors {1..110}.
	if _floor_below_grade(raw_floor):
		return raw_floor-10
	else:
		return raw_floor-9

def _floor_could_be_lobby(raw_floor):
	return floor_id_from_raw(raw_floor) in (1, 15, 30, 45, 60, 75, 90)

Floor_Struct = _cns.Struct('floor',
	_cns.ULInt16('tenant_count'),
	_cns.ULInt16('left_edge'),
	_cns.ULInt16('right_edge'),
	_cns.Rename('tenants',
		_cns.Array(lambda ctx: ctx.tenant_count, Tenant_Struct)),
	_cns.Array(94, _cns.ULInt16('tenant_id_to_index')),
)


#### People

Person_Struct = _cns.Struct('person',
	_cns.ULInt8('tenant_floor'),
	_cns.ULInt8('tenant_index'),
	_cns.ULInt16('number_in_tenant'),
	_cns.Rename('tenant_type', Tenant_Type),
	_cns.ULInt8('status'),
	_cns.SLInt8('current_floor'),
	_util.Unknown('_unk6', 5),
	_cns.ULInt16('stress'),
	_cns.ULInt16('eval'),
)
assert(Person_Struct.sizeof() == 16)


#### Retail

Retail_Struct = _cns.Struct('retail',
	_cns.ULInt8('floor'),
	_util.Unknown('_unk1', 10),
	_cns.ULInt8('type'),
	_util.Unknown('_unk12', 6),
)
assert(Retail_Struct.sizeof() == 18)


#### Stairs

Stairs_Type = _util.NumToSym(_cns.ULInt8('type'),
	{0:'escalator', 1:'standard'})

Stairs_Struct = _cns.Struct('stairs',
	_util.Bool(_cns.ULInt8('present')),
	_cns.Rename('type', Stairs_Type),
	_cns.ULInt16('xpos'),
	_cns.ULInt8('bottom_floor'),
	_util.Unknown('_unk5', 5),
)
assert(Stairs_Struct.sizeof() == 10)


#### Elevators

Elevator_Type = _util.NumToSym(_cns.ULInt8('type'),
		{0:'express', 1:'standard', 2:'service'})

def _potential_floors_served(ctx):
	if ctx.type == 'express':
		count = 0
		for i in range(ctx.bottom_floor, ctx.top_floor + 1):
			if _floor_below_grade(i) or _floor_could_be_lobby(i):
				count += 1
	else:
		count = 1 + (ctx.top_floor - ctx.bottom_floor)
	return count

Elevator_Struct = _cns.Struct('elevator',
	_cns.Anchor('start_'),

	_util.Bool(_cns.ULInt8('present')),
	_cns.Rename('type', Elevator_Type),

	# Capacity > 42 may crash SimTower.
	# 42 seems to work for non-express elevators.
	_cns.ULInt8('capacity'),
	_cns.ULInt8('car_count'),

	# There are 4 "schedule" arrays of size 14: 7 weekday periods, followed
	# by 7 weekend periods.  The GUI shows 6 periods per day; the 7th is
	# not used.  The entire first array looks unused too.  The regex
	# '\x01{14}......\x05......\x05' can reliably find elevator data.
	_cns.Array(14, _cns.ULInt8('#unknown_sched')),
	_cns.If(lambda ctx: ctx.present,
		_util.Assert(lambda ctx: ctx['#unknown_sched'] == [1]*14)),
	_cns.Array(14, _cns.ULInt8('response_distances')),
	_cns.Array(14, _cns.ULInt8('express_modes')),
	_cns.Array(14, _cns.ULInt8('departure_delays')),

	_util.Bool(_cns.ULInt8('shaft_visible')),
	_util.Unknown('_unk60', 1),
	_cns.ULInt16('left_edge'),
	_cns.ULInt8('top_floor'),
	_cns.ULInt8('bottom_floor'),
	_cns.Value('potential_floors_served_', _potential_floors_served),

	# For each of 120 floors, value is 1 if served, otherwise 0.
	# Adding express elevator stops on improper floors (above ground and
	# not a multiple of 15) may crash SimTower, probably because there's
	# an array of size potential_floors_served.
	_cns.Array(120, _cns.ULInt8('floor_stops')),

	# Resting floor for each car.
	_cns.Array(8, _cns.ULInt8('resting_floors')),

	_cns.Anchor('fixed_end_'),
	_util.Assert(lambda ctx: ctx.fixed_end_ == ctx.start_ + 194),

	_cns.If(lambda ctx: ctx.present, _util.AnonEmbed(
		_util.Unknown('_unk194', 3488),

		# Information about people waiting for elevators
		# is likely to be in the following array.
		_cns.Array(lambda ctx: ctx.potential_floors_served_,
			_util.Unknown('_unk3682', 324)),
	)),
	_cns.Anchor('end_'),
)


#### Finances/population block

Finances_Struct = _cns.Struct('finances',
	_cns.Array(10, _cns.ULInt32('tenant_populations')),
	_cns.ULInt32('tower_population'),
	_cns.Array(10, _cns.ULInt32('tenant_incomes')),
	_cns.ULInt32('tower_income'),
	_cns.Array(10, _cns.ULInt32('tenant_maintenance')),
	_cns.ULInt32('tower_maintenance'),
)


#### Overall file structure

Tower_Struct = _cns.Struct('tower',
	_cns.Embedded(File_Header_Struct),

	_cns.Rename('floors', _cns.Array(120, Floor_Struct)),

	_cns.ULInt32('people_count'),
	_cns.Rename('people',
		_cns.Array(lambda ctx: ctx.people_count, Person_Struct)),

	_cns.Rename('retail', _cns.Array(512, Retail_Struct)),

	_cns.Anchor('elevators_start_'),
	_cns.Rename('elevators', _cns.Array(24, Elevator_Struct)),
	_cns.Anchor('elevators_end_'),

	_util.Unknown('_unk_after_elev', 88),
	_cns.Anchor('finances_start_'),
	_cns.Embedded(Finances_Struct),
	_cns.Anchor('finances_end_'),

	_util.Unknown('_unk_after_finpop', 1102),
	_cns.Anchor('stairs_start_'),
	_cns.Rename('stairs', _cns.Array(64, Stairs_Struct)),
	_cns.Anchor('stairs_end_'),

	_util.Unknown('_unk_after_stairs', 17242),
	_cns.Rename('named_people',
		_cns.Array(lambda ctx: ctx.named_people_count,
			_util.ZStr(None, 16))),

	_cns.Terminator  # must reach end of file
)

util.py

# To the extent possible under law, the author(s) have dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.
#
# See <http://creativecommons.org/publicdomain/zero/1.0/>.

import construct as _cns


#### Utility functions

Unknown = _cns.Field

class Assert(_cns.Construct):
	def __init__(self, fn):
		_cns.Construct.__init__(self, None)
		self.fn = fn
	def _sizeof(self, context): return 0
	def _assert(self, stream, context): assert self.fn(context)
	def _parse(self, stream, context): self._assert(stream, context)
	def _build(self, obj, stream, context): self._assert(stream, context)

def AnonEmbed(*subcons):
	return _cns.Embedded(_cns.Struct(None, *subcons))

def NumToSym(subcon, decoding):
	return _cns.MappingAdapter(subcon, decoding = decoding,
		encoding = dict((v,k) for (k,v) in decoding.items()),
		decdefault = _cns.Pass, encdefault = _cns.Pass)

def Bool(subcon):
	return NumToSym(subcon, {0:False, 1:True})

class _PaddedBytes(bytes):
	def __new__(cls, raw=b'', padding=None):
		b = super(_PaddedBytes, cls).__new__(cls, raw)
		b.padding = padding
		return b

class _ZStrAdapter(_cns.StringAdapter):
	def __init__(self, subcon):
		_cns.StringAdapter.__init__(self, subcon)
	def _decode(self, obj, context):
		obj = _PaddedBytes(*obj.split(b'\0', 1))
		return _cns.StringAdapter._decode(self, obj, context)
	def _encode(self, obj, context):
		out = _cns.StringAdapter._encode(self, obj, context)
		out += b'\0'
		try:
			if obj.padding is not None:
				out += obj.padding
		except AttributeError:
			pass

		pad = self._sizeof(context) - len(out)
		if pad < 0:
			raise ValueError("string too long")
		out += b'\0' * pad

		return out

def ZStr(name, length, encoding=None):
	fld = _cns.Field(name, length)
	return _ZStrAdapter(_cns.StringAdapter(fld, encoding=encoding))

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox