Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import difflib
- import json
- import lzma
- import re
- import struct
- import sys
- from savegame import TTDSavegameDecoder
- TTD_TYPES = {
- 'SLE_BOOL': '?',
- 'SLE_UINT8': 'b',
- 'SLE_INT8': 'B',
- 'SLE_FILE_U8': 'b',
- 'SLE_UINT16': 'h',
- 'SLE_INT16': 'H',
- 'SLE_FILE_U16': 'h',
- 'SLE_UINT32': 'i',
- 'SLE_INT32': 'I',
- 'SLE_FILE_U32': 'i',
- 'SLE_UINT64': 'q',
- 'SLE_INT64': 'Q',
- 'REF_ORDER': 'i',
- 'REF_TOWN': 'i',
- 'REF_STATION': 'i',
- 'REF_STORAGE': 'i',
- }
- CONSTANTS = {
- 'NUM_TE': 6,
- }
- RX_SLE_VAR = re.compile(r'SLE_(?:VAR|REF)\([^,]*,\s+([^,]*),\s+([\w\s|]+)\)')
- RX_SLE_CONDVAR = re.compile(r'SLE_COND(?:VAR|REF)\([^,]*,\s+([^,]*),\s+([\w\s|]+),\s+(\w+),\s+(\w+)\)')
- RX_SLE_CONDARR = re.compile(r'SLE_CONDARR\([^,]*,\s+([^,]*),\s+([\w\s|]+),\s+(\d+),\s+(\w+),\s+(\w+)\)')
- RX_SLE_CONDNULL = re.compile(r'SLE_CONDNULL\((\d+),\s+(\w+),\s+(\w+)\)')
- def hex_str(s):
- if isinstance(s, (bytes, memoryview)):
- return ':'.join('{:02x}'.format(b) for b in s)
- return ':'.join('{:02x}'.format(ord(c)) for c in s)
- def parse_saveload_desc(str):
- fields = []
- for l in str.split('\n'):
- m = RX_SLE_VAR.search(l)
- if m:
- name, vtype = m.groups()
- fields.append((name, vtype))
- continue
- m = RX_SLE_CONDVAR.search(l)
- if m:
- name, vtype, _min_ver, max_ver = m.groups()
- if max_ver != 'SL_MAX_VERSION':
- continue
- fields.append((name, vtype))
- continue
- m = RX_SLE_CONDARR.search(l)
- if m:
- name, vtype, vlen, _min_ver, max_ver = m.groups()
- if max_ver != 'SL_MAX_VERSION':
- continue
- fields.append((name, (vtype, int(vlen))))
- continue
- m = RX_SLE_CONDNULL.search(l)
- if m:
- length, _min_ver, max_ver = m.groups()
- if max_ver != 'SL_MAX_VERSION':
- continue
- fields.append((None, ('SLE_UINT8', int(length))))
- continue
- # print (fields)
- # return {
- # 'fiel'
- # }
- fmt = '>'
- fn = []
- for name, p in fields:
- if isinstance(p, tuple):
- t, n = p
- else:
- t, n = p, 1
- if '|' in t: t = t.split('|')[0].strip()
- f = TTD_TYPES[t]
- fmt += f * n
- fn += [name] * n
- return (fmt, fn)
- def load_desc(desc, data):
- fmt, names = desc
- res = {}
- for n, v in zip(names, struct.unpack(fmt, data)):
- if n in res:
- if isinstance(res[n], list):
- res[n].append(v)
- else:
- res[n] = [res[n], v]
- else:
- res[n] = v
- return json.dumps(res, sort_keys=True)
- parse_saveload_desc("""
- SLE_CONDVAR(Town, xy, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_6),
- SLE_CONDVAR(Town, xy, SLE_UINT32, SLV_6, SL_MAX_VERSION),
- SLE_CONDNULL(2, SL_MIN_VERSION, SLV_3), ///< population, no longer in use
- SLE_CONDNULL(4, SLV_3, SLV_85), ///< population, no longer in use
- SLE_CONDNULL(2, SL_MIN_VERSION, SLV_92), ///< num_houses, no longer in use
- SLE_CONDVAR(Town, townnamegrfid, SLE_UINT32, SLV_66, SL_MAX_VERSION),
- SLE_VAR(Town, townnametype, SLE_UINT16),
- SLE_VAR(Town, townnameparts, SLE_UINT32),
- SLE_CONDSTR(Town, name, SLE_STR | SLF_ALLOW_CONTROL, 0, SLV_84, SL_MAX_VERSION),
- SLE_VAR(Town, flags, SLE_UINT8),
- SLE_CONDVAR(Town, statues, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_104),
- SLE_CONDVAR(Town, statues, SLE_UINT16, SLV_104, SL_MAX_VERSION),
- SLE_CONDNULL(1, SL_MIN_VERSION, SLV_2), ///< sort_index, no longer in use
- SLE_CONDVAR(Town, have_ratings, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_104),
- SLE_CONDVAR(Town, have_ratings, SLE_UINT16, SLV_104, SL_MAX_VERSION),
- SLE_CONDARR(Town, ratings, SLE_INT16, 8, SL_MIN_VERSION, SLV_104),
- SLE_CONDARR(Town, ratings, SLE_INT16, MAX_COMPANIES, SLV_104, SL_MAX_VERSION),
- /* failed bribe attempts are stored since savegame format 4 */
- SLE_CONDARR(Town, unwanted, SLE_INT8, 8, SLV_4, SLV_104),
- SLE_CONDARR(Town, unwanted, SLE_INT8, MAX_COMPANIES, SLV_104, SL_MAX_VERSION),
- SLE_CONDVAR(Town, supplied[CT_PASSENGERS].old_max, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- SLE_CONDVAR(Town, supplied[CT_MAIL].old_max, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- SLE_CONDVAR(Town, supplied[CT_PASSENGERS].new_max, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- SLE_CONDVAR(Town, supplied[CT_MAIL].new_max, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- SLE_CONDVAR(Town, supplied[CT_PASSENGERS].old_act, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- SLE_CONDVAR(Town, supplied[CT_MAIL].old_act, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- SLE_CONDVAR(Town, supplied[CT_PASSENGERS].new_act, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- SLE_CONDVAR(Town, supplied[CT_MAIL].new_act, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- SLE_CONDVAR(Town, supplied[CT_PASSENGERS].old_max, SLE_UINT32, SLV_9, SLV_165),
- SLE_CONDVAR(Town, supplied[CT_MAIL].old_max, SLE_UINT32, SLV_9, SLV_165),
- SLE_CONDVAR(Town, supplied[CT_PASSENGERS].new_max, SLE_UINT32, SLV_9, SLV_165),
- SLE_CONDVAR(Town, supplied[CT_MAIL].new_max, SLE_UINT32, SLV_9, SLV_165),
- SLE_CONDVAR(Town, supplied[CT_PASSENGERS].old_act, SLE_UINT32, SLV_9, SLV_165),
- SLE_CONDVAR(Town, supplied[CT_MAIL].old_act, SLE_UINT32, SLV_9, SLV_165),
- SLE_CONDVAR(Town, supplied[CT_PASSENGERS].new_act, SLE_UINT32, SLV_9, SLV_165),
- SLE_CONDVAR(Town, supplied[CT_MAIL].new_act, SLE_UINT32, SLV_9, SLV_165),
- SLE_CONDNULL(2, SL_MIN_VERSION, SLV_164), ///< pct_pass_transported / pct_mail_transported, now computed on the fly
- SLE_CONDVAR(Town, received[TE_FOOD].old_act, SLE_UINT16, SL_MIN_VERSION, SLV_165),
- SLE_CONDVAR(Town, received[TE_WATER].old_act, SLE_UINT16, SL_MIN_VERSION, SLV_165),
- SLE_CONDVAR(Town, received[TE_FOOD].new_act, SLE_UINT16, SL_MIN_VERSION, SLV_165),
- SLE_CONDVAR(Town, received[TE_WATER].new_act, SLE_UINT16, SL_MIN_VERSION, SLV_165),
- SLE_CONDARR(Town, goal, SLE_UINT32, NUM_TE, SLV_165, SL_MAX_VERSION),
- SLE_CONDSTR(Town, text, SLE_STR | SLF_ALLOW_CONTROL, 0, SLV_168, SL_MAX_VERSION),
- SLE_CONDVAR(Town, time_until_rebuild, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_54),
- SLE_CONDVAR(Town, grow_counter, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_54),
- SLE_CONDVAR(Town, growth_rate, SLE_FILE_U8 | SLE_VAR_I16, SL_MIN_VERSION, SLV_54),
- SLE_CONDVAR(Town, time_until_rebuild, SLE_UINT16, SLV_54, SL_MAX_VERSION),
- SLE_CONDVAR(Town, grow_counter, SLE_UINT16, SLV_54, SL_MAX_VERSION),
- SLE_CONDVAR(Town, growth_rate, SLE_FILE_I16 | SLE_VAR_U16, SLV_54, SLV_165),
- SLE_CONDVAR(Town, growth_rate, SLE_UINT16, SLV_165, SL_MAX_VERSION),
- SLE_VAR(Town, fund_buildings_months, SLE_UINT8),
- SLE_VAR(Town, road_build_months, SLE_UINT8),
- SLE_CONDVAR(Town, exclusivity, SLE_UINT8, SLV_2, SL_MAX_VERSION),
- SLE_CONDVAR(Town, exclusive_counter, SLE_UINT8, SLV_2, SL_MAX_VERSION),
- SLE_CONDVAR(Town, larger_town, SLE_BOOL, SLV_56, SL_MAX_VERSION),
- SLE_CONDVAR(Town, layout, SLE_UINT8, SLV_113, SL_MAX_VERSION),
- SLE_CONDLST(Town, psa_list, REF_STORAGE, SLV_161, SL_MAX_VERSION),
- SLE_CONDVAR(Town, cargo_produced, SLE_FILE_U32 | SLE_VAR_U64, SLV_166, SLV_EXTEND_CARGOTYPES),
- SLE_CONDVAR(Town, cargo_produced, SLE_UINT64, SLV_EXTEND_CARGOTYPES, SL_MAX_VERSION),
- /* reserve extra space in savegame here. (currently 30 bytes) */
- SLE_CONDNULL(30, SLV_2, SL_MAX_VERSION),
- """)
- indy_desc = parse_saveload_desc("""
- SLE_CONDVAR(Industry, location.tile, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_6),
- SLE_CONDVAR(Industry, location.tile, SLE_UINT32, SLV_6, SL_MAX_VERSION),
- SLE_VAR(Industry, location.w, SLE_FILE_U8 | SLE_VAR_U16),
- SLE_VAR(Industry, location.h, SLE_FILE_U8 | SLE_VAR_U16),
- SLE_REF(Industry, town, REF_TOWN),
- SLE_CONDREF(Industry, neutral_station, REF_STATION, SLV_SERVE_NEUTRAL_INDUSTRIES, SL_MAX_VERSION),
- SLE_CONDNULL( 2, SL_MIN_VERSION, SLV_61), ///< used to be industry's produced_cargo
- SLE_CONDARR(Industry, produced_cargo, SLE_UINT8, 2, SLV_78, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
- SLE_CONDARR(Industry, produced_cargo, SLE_UINT8, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
- SLE_CONDARR(Industry, incoming_cargo_waiting, SLE_UINT16, 3, SLV_70, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
- SLE_CONDARR(Industry, incoming_cargo_waiting, SLE_UINT16, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
- SLE_CONDARR(Industry, produced_cargo_waiting, SLE_UINT16, 2, SL_MIN_VERSION, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
- SLE_CONDARR(Industry, produced_cargo_waiting, SLE_UINT16, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
- SLE_CONDARR(Industry, production_rate, SLE_UINT8, 2, SL_MIN_VERSION, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
- SLE_CONDARR(Industry, production_rate, SLE_UINT8, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
- SLE_CONDNULL( 3, SL_MIN_VERSION, SLV_61), ///< used to be industry's accepts_cargo
- SLE_CONDARR(Industry, accepts_cargo, SLE_UINT8, 3, SLV_78, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
- SLE_CONDARR(Industry, accepts_cargo, SLE_UINT8, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
- SLE_VAR(Industry, prod_level, SLE_UINT8),
- SLE_CONDARR(Industry, this_month_production, SLE_UINT16, 2, SL_MIN_VERSION, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
- SLE_CONDARR(Industry, this_month_production, SLE_UINT16, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
- SLE_CONDARR(Industry, this_month_transported, SLE_UINT16, 2, SL_MIN_VERSION, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
- SLE_CONDARR(Industry, this_month_transported, SLE_UINT16, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
- SLE_CONDARR(Industry, last_month_pct_transported, SLE_UINT8, 2, SL_MIN_VERSION, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
- SLE_CONDARR(Industry, last_month_pct_transported, SLE_UINT8, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
- SLE_CONDARR(Industry, last_month_production, SLE_UINT16, 2, SL_MIN_VERSION, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
- SLE_CONDARR(Industry, last_month_production, SLE_UINT16, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
- SLE_CONDARR(Industry, last_month_transported, SLE_UINT16, 2, SL_MIN_VERSION, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
- SLE_CONDARR(Industry, last_month_transported, SLE_UINT16, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
- SLE_VAR(Industry, counter, SLE_UINT16),
- SLE_VAR(Industry, type, SLE_UINT8),
- SLE_VAR(Industry, owner, SLE_UINT8),
- SLE_VAR(Industry, random_colour, SLE_UINT8),
- SLE_CONDVAR(Industry, last_prod_year, SLE_FILE_U8 | SLE_VAR_I32, SL_MIN_VERSION, SLV_31),
- SLE_CONDVAR(Industry, last_prod_year, SLE_INT32, SLV_31, SL_MAX_VERSION),
- SLE_VAR(Industry, was_cargo_delivered, SLE_UINT8),
- SLE_CONDVAR(Industry, founder, SLE_UINT8, SLV_70, SL_MAX_VERSION),
- SLE_CONDVAR(Industry, construction_date, SLE_INT32, SLV_70, SL_MAX_VERSION),
- SLE_CONDVAR(Industry, construction_type, SLE_UINT8, SLV_70, SL_MAX_VERSION),
- SLE_CONDVAR(Industry, last_cargo_accepted_at[0], SLE_INT32, SLV_70, SLV_EXTEND_INDUSTRY_CARGO_SLOTS),
- SLE_CONDARR(Industry, last_cargo_accepted_at, SLE_INT32, 16, SLV_EXTEND_INDUSTRY_CARGO_SLOTS, SL_MAX_VERSION),
- SLE_CONDVAR(Industry, selected_layout, SLE_UINT8, SLV_73, SL_MAX_VERSION),
- SLEG_CONDARR(_old_ind_persistent_storage.storage, SLE_UINT32, 16, SLV_76, SLV_161),
- SLE_CONDREF(Industry, psa, REF_STORAGE, SLV_161, SL_MAX_VERSION),
- SLE_CONDNULL(1, SLV_82, SLV_197), // random_triggers
- SLE_CONDVAR(Industry, random, SLE_UINT16, SLV_82, SL_MAX_VERSION),
- SLE_CONDNULL(32, SLV_2, SLV_144), // old reserved space""")
- class TTDSavegameDecoder:
- def __init__(self):
- pass
- @staticmethod
- def _parse_label(data):
- return ''.join(map(chr, data[:4]))
- @staticmethod
- def _read_gamma(data):
- if (data[0] & 0xF0) == 0xE0:
- return (data[0] & 0x0F) << 24 | data[1] << 16 | data[2] << 8 | data[3], 4
- elif (data[0] & 0xE0) == 0xC0:
- return (data[0] & 0x1F) << 16 | data[1] << 8 | data[2], 3
- elif (data[0] & 0xC0) == 0x80:
- return (data[0] & 0x3F) << 8 | data[1], 2
- elif (data[0] & 0x80) == 0:
- return data[0] & 0x7F, 1
- raise ValueError('Invalid gamma')
- def _read_int(self, data, size, is_signed):
- fmt = {8: '>b', 16: '>h', 32: '>i'}[size]
- if not is_signed:
- fmt = fmt.upper()
- return struct.unpack_from(fmt, data)[0], size // 8
- def _load_pats(self, data, version):
- res = {}
- for k, v in settings_info.data.items():
- if 'SLF_NOT_IN_SAVE' in v['flags']:
- continue
- if not (v['from'] <= version <= v['to']):
- continue
- ctype = v['ctype']
- size = 0
- if ctype in ('SLE_UINT8', 'SLE_UINT16', 'SLE_UINT32'):
- value, size = self._read_int(data, int(ctype[8:]), False)
- elif ctype in ('SLE_INT8', 'SLE_INT16', 'SLE_INT32'):
- value, size = self._read_int(data, int(ctype[7:]), True)
- elif ctype == 'BOOL':
- value, size = bool(data[0]), 1
- elif ctype == 'SLE_STRQ':
- l, size = self._read_gamma(data)
- value = struct.unpack_from(f'{l}s', data, offset=size)[0].decode()
- size += l
- else:
- raise ValueError(f'Unsupported setting type {ctype}')
- data = data[size:]
- res[k] = value
- # print (f'{k} {value} ({ctype}: {size}) {len(data)}')
- return res
- def _load_order(self, data):
- return struct.unpack('>bbhibhhh', data)
- # (otype, flags, dest, onext, refit_cargo, wait_time, travel_time, max_speed)
- # SLE_VAR(Order, type, SLE_UINT8),
- # SLE_VAR(Order, flags, SLE_UINT8),
- # SLE_VAR(Order, dest, SLE_UINT16),
- # SLE_REF(Order, next, REF_ORDER),
- # SLE_CONDVAR(Order, refit_cargo, SLE_UINT8, SLV_36, SL_MAX_VERSION),
- # SLE_CONDVAR(Order, wait_time, SLE_UINT16, SLV_67, SL_MAX_VERSION),
- # SLE_CONDVAR(Order, travel_time, SLE_UINT16, SLV_67, SL_MAX_VERSION),
- # SLE_CONDVAR(Order, max_speed, SLE_UINT16, SLV_172, SL_MAX_VERSION),
- def _load_cargo_packet(self, data):
- # print(len(data))
- return struct.unpack('>hiihbqbh', data)
- # SLE_VAR(CargoPacket, source, SLE_UINT16),
- # SLE_VAR(CargoPacket, source_xy, SLE_UINT32),
- # SLE_VAR(CargoPacket, loaded_at_xy, SLE_UINT32),
- # SLE_VAR(CargoPacket, count, SLE_UINT16),
- # SLE_VAR(CargoPacket, days_in_transit, SLE_UINT8),
- # SLE_VAR(CargoPacket, feeder_share, SLE_INT64),
- # SLE_CONDVAR(CargoPacket, source_type, SLE_UINT8, SLV_125, SL_MAX_VERSION),
- # SLE_CONDVAR(CargoPacket, source_id, SLE_UINT16, SLV_125, SL_MAX_VERSION),
- # SLE_CONDNULL(1, SL_MIN_VERSION, SLV_121),
- def _load_date(self, data):
- return struct.unpack('>Ihhihiibibb', data)
- # SLEG_CONDVAR(_date, SLE_INT32, SLV_31, SL_MAX_VERSION),
- # SLEG_VAR(_date_fract, SLE_UINT16),
- # SLEG_VAR(_tick_counter, SLE_UINT16),
- # SLEG_CONDVAR(_cur_tileloop_tile, SLE_UINT32, SLV_6, SL_MAX_VERSION),
- # SLEG_VAR(_disaster_delay, SLE_UINT16),
- # SLEG_VAR(_random.state[0], SLE_UINT32),
- # SLEG_VAR(_random.state[1], SLE_UINT32),
- # SLEG_VAR(_cur_company_tick_index, SLE_FILE_U8 | SLE_VAR_U32),
- # SLEG_CONDVAR(_next_competitor_start, SLE_UINT32, SLV_109, SL_MAX_VERSION),
- # SLEG_VAR(_trees_tick_ctr, SLE_UINT8),
- # SLEG_CONDVAR(_pause_mode, SLE_UINT8, SLV_4, SL_MAX_VERSION),
- def _load_town_desc(self, data):
- pass
- # SLE_CONDVAR(Town, xy, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_6),
- # SLE_CONDVAR(Town, xy, SLE_UINT32, SLV_6, SL_MAX_VERSION),
- # SLE_CONDNULL(2, SL_MIN_VERSION, SLV_3), ///< population, no longer in use
- # SLE_CONDNULL(4, SLV_3, SLV_85), ///< population, no longer in use
- # SLE_CONDNULL(2, SL_MIN_VERSION, SLV_92), ///< num_houses, no longer in use
- # SLE_CONDVAR(Town, townnamegrfid, SLE_UINT32, SLV_66, SL_MAX_VERSION),
- # SLE_VAR(Town, townnametype, SLE_UINT16),
- # SLE_VAR(Town, townnameparts, SLE_UINT32),
- # SLE_CONDSTR(Town, name, SLE_STR | SLF_ALLOW_CONTROL, 0, SLV_84, SL_MAX_VERSION),
- # SLE_VAR(Town, flags, SLE_UINT8),
- # SLE_CONDVAR(Town, statues, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_104),
- # SLE_CONDVAR(Town, statues, SLE_UINT16, SLV_104, SL_MAX_VERSION),
- # SLE_CONDNULL(1, SL_MIN_VERSION, SLV_2), ///< sort_index, no longer in use
- # SLE_CONDVAR(Town, have_ratings, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_104),
- # SLE_CONDVAR(Town, have_ratings, SLE_UINT16, SLV_104, SL_MAX_VERSION),
- # SLE_CONDARR(Town, ratings, SLE_INT16, 8, SL_MIN_VERSION, SLV_104),
- # SLE_CONDARR(Town, ratings, SLE_INT16, MAX_COMPANIES, SLV_104, SL_MAX_VERSION),
- # /* failed bribe attempts are stored since savegame format 4 */
- # SLE_CONDARR(Town, unwanted, SLE_INT8, 8, SLV_4, SLV_104),
- # SLE_CONDARR(Town, unwanted, SLE_INT8, MAX_COMPANIES, SLV_104, SL_MAX_VERSION),
- # SLE_CONDVAR(Town, supplied[CT_PASSENGERS].old_max, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- # SLE_CONDVAR(Town, supplied[CT_MAIL].old_max, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- # SLE_CONDVAR(Town, supplied[CT_PASSENGERS].new_max, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- # SLE_CONDVAR(Town, supplied[CT_MAIL].new_max, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- # SLE_CONDVAR(Town, supplied[CT_PASSENGERS].old_act, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- # SLE_CONDVAR(Town, supplied[CT_MAIL].old_act, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- # SLE_CONDVAR(Town, supplied[CT_PASSENGERS].new_act, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- # SLE_CONDVAR(Town, supplied[CT_MAIL].new_act, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_9),
- # SLE_CONDVAR(Town, supplied[CT_PASSENGERS].old_max, SLE_UINT32, SLV_9, SLV_165),
- # SLE_CONDVAR(Town, supplied[CT_MAIL].old_max, SLE_UINT32, SLV_9, SLV_165),
- # SLE_CONDVAR(Town, supplied[CT_PASSENGERS].new_max, SLE_UINT32, SLV_9, SLV_165),
- # SLE_CONDVAR(Town, supplied[CT_MAIL].new_max, SLE_UINT32, SLV_9, SLV_165),
- # SLE_CONDVAR(Town, supplied[CT_PASSENGERS].old_act, SLE_UINT32, SLV_9, SLV_165),
- # SLE_CONDVAR(Town, supplied[CT_MAIL].old_act, SLE_UINT32, SLV_9, SLV_165),
- # SLE_CONDVAR(Town, supplied[CT_PASSENGERS].new_act, SLE_UINT32, SLV_9, SLV_165),
- # SLE_CONDVAR(Town, supplied[CT_MAIL].new_act, SLE_UINT32, SLV_9, SLV_165),
- # SLE_CONDNULL(2, SL_MIN_VERSION, SLV_164), ///< pct_pass_transported / pct_mail_transported, now computed on the fly
- # SLE_CONDVAR(Town, received[TE_FOOD].old_act, SLE_UINT16, SL_MIN_VERSION, SLV_165),
- # SLE_CONDVAR(Town, received[TE_WATER].old_act, SLE_UINT16, SL_MIN_VERSION, SLV_165),
- # SLE_CONDVAR(Town, received[TE_FOOD].new_act, SLE_UINT16, SL_MIN_VERSION, SLV_165),
- # SLE_CONDVAR(Town, received[TE_WATER].new_act, SLE_UINT16, SL_MIN_VERSION, SLV_165),
- # SLE_CONDARR(Town, goal, SLE_UINT32, NUM_TE, SLV_165, SL_MAX_VERSION),
- # SLE_CONDSTR(Town, text, SLE_STR | SLF_ALLOW_CONTROL, 0, SLV_168, SL_MAX_VERSION),
- # SLE_CONDVAR(Town, time_until_rebuild, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_54),
- # SLE_CONDVAR(Town, grow_counter, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_54),
- # SLE_CONDVAR(Town, growth_rate, SLE_FILE_U8 | SLE_VAR_I16, SL_MIN_VERSION, SLV_54),
- # SLE_CONDVAR(Town, time_until_rebuild, SLE_UINT16, SLV_54, SL_MAX_VERSION),
- # SLE_CONDVAR(Town, grow_counter, SLE_UINT16, SLV_54, SL_MAX_VERSION),
- # SLE_CONDVAR(Town, growth_rate, SLE_FILE_I16 | SLE_VAR_U16, SLV_54, SLV_165),
- # SLE_CONDVAR(Town, growth_rate, SLE_UINT16, SLV_165, SL_MAX_VERSION),
- # SLE_VAR(Town, fund_buildings_months, SLE_UINT8),
- # SLE_VAR(Town, road_build_months, SLE_UINT8),
- # SLE_CONDVAR(Town, exclusivity, SLE_UINT8, SLV_2, SL_MAX_VERSION),
- # SLE_CONDVAR(Town, exclusive_counter, SLE_UINT8, SLV_2, SL_MAX_VERSION),
- # SLE_CONDVAR(Town, larger_town, SLE_BOOL, SLV_56, SL_MAX_VERSION),
- # SLE_CONDVAR(Town, layout, SLE_UINT8, SLV_113, SL_MAX_VERSION),
- # SLE_CONDLST(Town, psa_list, REF_STORAGE, SLV_161, SL_MAX_VERSION),
- # SLE_CONDVAR(Town, cargo_produced, SLE_FILE_U32 | SLE_VAR_U64, SLV_166, SLV_EXTEND_CARGOTYPES),
- # SLE_CONDVAR(Town, cargo_produced, SLE_UINT64, SLV_EXTEND_CARGOTYPES, SL_MAX_VERSION),
- # /* reserve extra space in savegame here. (currently 30 bytes) */
- # SLE_CONDNULL(30, SLV_2, SL_MAX_VERSION),
- def _decode_ottx(self, data, version):
- decompressor = lzma.LZMADecompressor()
- d = memoryview(decompressor.decompress(data))
- # print('Savegame version', version, 'uncompressed size', len(d))
- res = {}
- while d:
- chunk_id = self._parse_label(d[:4])
- if chunk_id == '\0\0\0\0':
- break
- assert chunk_id not in res, f"Duplicate chunk {chunk_id}"
- # print('Chunk', chunk_id, end=' ')
- block_mode = d[4]
- d = d[5:]
- # CH_RIFF = 0,
- # CH_ARRAY = 1,
- # CH_SPARSE_ARRAY = 2,
- # CH_TYPE_MASK = 3,
- # CH_LAST = 8, ///< Last chunk in this array.
- # CH_AUTO_LENGTH = 16,
- if block_mode & 0xF == 0: # RIFF
- size = d[0] << 16 | d[1] << 8 | d[2] | (block_mode >> 4) << 24;
- # print('RIFF', size)
- # if chunk_id == 'PATS':
- # res['settings'] = self._load_pats(d[3:3 + size], version)
- parser = {
- 'DATE': self._load_date,
- }.get(chunk_id)
- if parser:
- res[chunk_id] = parser(d[3:3 + size])
- else:
- res[chunk_id] = d[3:3 + size]
- d = d[3 + size:]
- elif block_mode == 1 or block_mode == 2:
- items = []
- while True:
- item_size, l = self._read_gamma(d)
- # print(' ARRAY', block_mode, l, item_size)
- d = d[l:]
- if not item_size:
- break
- # if block_mode == 2: index = ReadGamma(stdin);
- if item_size > 1:
- items.append(d[:item_size - 1])
- d = d[item_size - 1:]
- parser = {
- 'ORDR': self._load_order,
- 'CAPA': self._load_cargo_packet,
- 'INDY': lambda d: load_desc(indy_desc, d),
- }.get(chunk_id)
- if parser:
- items = list(map(parser, items))
- res[chunk_id] = items
- else:
- raise ValueError(f'Unknown chunk mode {block_mode}')
- return res
- def decode(self, data):
- tag = self._parse_label(data)
- if tag != 'OTTX':
- raise ValueError(f'Unknown savegame format {tag}')
- major_version = data[4] * 8 + data[5]
- minor_version = data[6] * 8 + data[7]
- res = {}
- res['chunks'] = self._decode_ottx(data[8:], major_version)
- res['version'] = major_version
- return res
- d = TTDSavegameDecoder()
- s1 = d.decode(open(sys.argv[1], 'rb').read())
- s2 = d.decode(open(sys.argv[2], 'rb').read())
- c1, c2 = s1['chunks'], s2['chunks']
- chunks = set(c1.keys()) | set(c2.keys())
- print(f'Versions: {s1["version"]} {s2["version"]}')
- for c in chunks:
- print(f'Chunk {c}:', end='')
- if c not in c1:
- print('Missing in save 1')
- continue
- if c not in c2:
- print('Missing in save 2')
- continue
- d1 = c1[c]
- d2 = c2[c]
- if d1 == d2:
- print('OK')
- continue
- def fmt(data):
- if isinstance(data, memoryview):
- return hex_str(data)
- return data
- if isinstance(d1, list) and isinstance(d2, list):
- l1 = len(d1)
- l2 = len(d2)
- m = difflib.SequenceMatcher(a=d1, b=d2)
- print(f'LIST l1={l1} l2={l2} ratio={m.ratio()}')
- # print(next(m.get_grouped_opcodes()))
- for tag, i1, i2, j1, j2 in m.get_opcodes():
- if tag == 'equal':
- continue
- if tag == 'replace':
- for i, j in zip(range(i1, i2), range(j1, j2)):
- print(f' {i}: - {fmt(d1[i])}')
- print(f' {j}: + {fmt(d2[j])}')
- if tag == 'delete':
- for i in range(i1, i2):
- print(f' {i}: - {fmt(d1[i])}')
- if tag == 'insert':
- for j in range(j1, j2):
- print(f' {j}: - {fmt(d2[j])}')
- elif c.startswith('MAP') or c in ('M3LO', 'M3HI'):
- print()
- if c in ('MAP2', 'MAP8'):
- dd1, dd2 = d1.cast('h'), d2.cast('h')
- else:
- dd1, dd2 = d1, d2
- for t in range(len(dd1)):
- if (dd1[t] != dd2[t]):
- x, y = t & 2047, t >> 11
- print(f' Tile {t} ({x}x{y}): -{dd1[t]} +{dd2[t]}')
- else:
- print()
- print(f' - {fmt(d1)}')
- print(f' + {fmt(d2)}')
- print()
Add Comment
Please, Sign In to add comment