From 263047e81d5fbd52aabc85517bcd9f57c6c02871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Bie=C5=84kowski?= Date: Thu, 14 Jan 2021 11:07:24 +0100 Subject: [PATCH] Initial commit --- pygbx/__init__.py | 5 + pygbx/bytereader.py | 236 ++++++++++ pygbx/gbx.py | 771 +++++++++++++++++++++++++++++++++ pygbx/headers.py | 216 +++++++++ pygbx/stadium_block_offsets.py | 439 +++++++++++++++++++ pygbx/stadium_blocks.py | 369 ++++++++++++++++ setup.cfg | 2 + setup.py | 26 ++ 8 files changed, 2064 insertions(+) create mode 100644 pygbx/__init__.py create mode 100644 pygbx/bytereader.py create mode 100644 pygbx/gbx.py create mode 100644 pygbx/headers.py create mode 100644 pygbx/stadium_block_offsets.py create mode 100644 pygbx/stadium_blocks.py create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/pygbx/__init__.py b/pygbx/__init__.py new file mode 100644 index 0000000..2a68c1b --- /dev/null +++ b/pygbx/__init__.py @@ -0,0 +1,5 @@ +from pygbx.headers import CGameHeader, CGameCtnCollectorList, CollectorStock, MapBlock, Vector3, CGameChallenge, CGameBlockItem, CGameWaypointSpecialProperty, CGameCommon, CGameReplayRecord, CGameGhost, CGameCtnGhost, ControlEntry, GhostSampleRecord +from pygbx.bytereader import ByteReader +from pygbx.stadium_blocks import STADIUM_BLOCKS +from pygbx.stadium_block_offsets import STADIUM_BLOCK_OFFSETS +from pygbx.gbx import Gbx, GbxType, GbxLoadError \ No newline at end of file diff --git a/pygbx/bytereader.py b/pygbx/bytereader.py new file mode 100644 index 0000000..c04761a --- /dev/null +++ b/pygbx/bytereader.py @@ -0,0 +1,236 @@ +import logging +import struct +from io import IOBase +from pygbx.headers import Vector3 + +class PositionInfo(object): + """ + This classes holds information that is mainly private to + the Gbx class but can still be retrieved through the positions member. + + The PositionInfo marks a specific section in the file through it's position and size. + """ + + def __init__(self, pos, size): + """Constructs a new PositionInfo""" + + self.pos = pos + self.size = size + + @property + def valid(self): + """Checks if the instance of the section is valid + + Returns: + True if the instance points to a valid section in the file, False otherwise + """ + return self.pos > -1 and self.size > 0 + + +class ByteReader(object): + """The ByteReader class is used by the Gbx class to read specific data types supported by the GBX file format. + + The class provides convinience methods for reading raw types such as integers, strings and vectors, which + are the main data types of the GBX file format. While reading the file, the Gbx class may instantiate multiple + instances of ByteReader to read different parts of the file. This is because some chunks depend on the state + of the reader, this state can be e.g: lookback strings. + + ByteReader accepts reading from raw bytes as well as from a file handle. + """ + def __init__(self, obj): + """Constructs a new ByteReader with the provided object. + + Args: + obj (file/bytes): a file handle opened through open() or a bytes object + """ + self.data = obj + if isinstance(obj, IOBase): + self.get_bytes = self.__get_bytes_file + else: + self.get_bytes = self.__get_bytes_generic + + self.pos = 0 + self.seen_loopback = False + self.stored_strings = [] + self.current_info = PositionInfo(-1, 0) + + def push_info(self): + """Begins a section that can be then retrieved with pop_info.""" + self.current_info = PositionInfo(self.pos, 0) + + + def pop_info(self): + """Ends the section began with push_info. + + Returns: + a PositionInfo marking the section + """ + self.current_info.size = self.pos - self.current_info.pos + info = self.current_info + self.current_info = PositionInfo(-1, 0) + return info + + def read(self, num_bytes, typestr=None): + """Reads an arbitrary amount of bytes from the buffer. + + Reads the buffer of length num_bytes and optionally + takes a type string that is passed to struct.unpack if not None. + + Args: + num_bytes (int): the number of bytes to read from the buffer + typestr (str): the format character used by the struct module, passing None does not unpack the bytes + + Returns: + the bytes object, if no type string was provided, type returned by struct.unpack otherwise + """ + val = self.get_bytes(num_bytes) + self.pos += num_bytes + if typestr == None: + return val + try: + return struct.unpack(typestr, val)[0] + except Exception as e: + logging.error(e) + return 0 + + def __get_bytes_file(self, num_bytes): + self.data.seek(self.pos) + return self.data.read(num_bytes) + + def __get_bytes_generic(self, num_bytes): + return self.data[self.pos:self.pos + num_bytes] + + def read_int32(self): + """Reads a signed int32. + + Returns: + the integer read from the buffer + """ + return self.read(4, 'i') + + def read_uint32(self): + """Reads an unsigned int32. + + Returns: + the integer read from the buffer + """ + return self.read(4, 'I') + + def read_int16(self): + """Reads a signed int16. + + Returns: + the integer read from the buffer + """ + return self.read(2, 'h') + + def read_uint16(self): + """Reads an unsigned int16. + + Returns: + the integer read from the buffer + """ + return self.read(2, 'H') + + def read_int8(self): + """Reads a signed int8. + + Returns: + the integer read from the buffer + """ + return self.read(1, 'b') + + def read_float(self): + """Reads a 32 bit float. + + Returns: + the float read from the buffer + """ + return self.read(4, 'f') + + def read_vec3(self): + """Reads 12 bytes as 3 floats from the buffer and packs them into a Vector3. + + Returns: + the vector read from the buffer + """ + return Vector3(self.read_float(), self.read_float(), self.read_float()) + + def read_string(self): + """Reads a string from the buffer, first reading the length, then it's data. + + Returns: + the string read from the buffer, None if there was an error + """ + strlen = self.read_uint32() + try: + return self.read(strlen, str(strlen) + 's').decode('utf-8') + except UnicodeDecodeError as e: + logging.error(f'Failed to read string: {e}') + return None + + def read_byte(self): + """Reads a single byte from the buffer. + + Returns: + the single byte read from the buffer + """ + val = self.get_bytes(1)[0] + self.pos += 1 + return val + + def skip(self, num_bytes): + """Skips provided amount of bytes in the buffer + + Args: + num_bytes (int): the number of bytes to skip + """ + self.pos += num_bytes + + def read_string_lookback(self): + """Reads a special string type in the GBX file format called the lookbackstring. + + Such type is used to reference already read strings, or introduce them if they were not + read yet. A ByteReader instance keeps track of lookback strings previously read and + returns an already existing string, if the data references it. For more information, + see the lookbackstring type in the GBX file format: https://wiki.xaseco.org/wiki/GBX. + + Returns: + the lookback string read from the buffer + """ + if not self.seen_loopback: + self.read_uint32() + + self.seen_loopback = True + inp = self.read_uint32() + if (inp & 0xc0000000) != 0 and (inp & 0x3fffffff) == 0: + s = self.read_string() + self.stored_strings.append(s) + return s + + if inp == 0: + s = self.read_string() + self.stored_strings.append(s) + return s + + if inp == -1: + return '' + + if (inp & 0x3fffffff) == inp: + if inp == 11: + return 'Valley' + elif inp == 12: + return 'Canyon' + elif inp == 17: + return 'TMCommon' + elif inp == 202: + return 'Storm' + elif inp == 299: + return 'SMCommon' + elif inp == 10003: + return 'Common' + + inp &= 0x3fffffff + if inp - 1 >= len(self.stored_strings): + return '' + return self.stored_strings[inp - 1] diff --git a/pygbx/gbx.py b/pygbx/gbx.py new file mode 100644 index 0000000..f2d4d20 --- /dev/null +++ b/pygbx/gbx.py @@ -0,0 +1,771 @@ +import logging +from enum import IntEnum + +import lzo +import zlib + +import pygbx.headers as headers +from pygbx.bytereader import ByteReader + +class GbxType(IntEnum): + """Represents the type of the main or auxiliary class contained within the GBX file. + + Some classes may have a different or multiple ID's, depending on what version of the GBX file is being parsed. + """ + CHALLENGE = 0x03043000 + CHALLENGE_OLD = 0x24003000 + COLLECTOR_LIST = 0x0301B000 + CHALLENGE_PARAMS = 0x0305B000 + BLOCK_SKIN = 0x03059000 + WAYPOINT_SPECIAL_PROP = 0x0313B000 + ITEM_MODEL = 0x2E002000 + REPLAY_RECORD = 0x03093000 + REPLAY_RECORD_OLD = 0x02407E000 + GAME_GHOST = 0x0303F005 + CTN_GHOST = 0x03092000 + CTN_GHOST_OLD = 0x2401B000 + CTN_COLLECTOR = 0x0301A000 + CTN_OBJECT_INFO = 0x0301C000 + CTN_DECORATION = 0x03038000 + CTN_COLLECTION = 0x03033000 + GAME_SKIN = 0x03031000 + GAME_PLAYER_PROFILE = 0x0308C000 + MW_NOD = 0x01001000 + UNKNOWN = 0x0 + +class GbxLoadError(Exception): + """Thrown when the Gbx class fails to parse the provided Gbx object""" + def __init__(self, message): + """Initializes the Exception instance with a message. + + Args: + message (str): the message + """ + self.message = message + + +class Gbx(object): + """The Gbx class provides the main interface for parsing GBX files and retrieving data + that is contained within these files. The class provides support primarily for parsing Challenges and Replays. + The class should properly support most TMNF/TMUF files, with support for some TM2 files. + + It is not guaranteed that the class will parse all of the data in a GBX file. If an error is encountered, + an error will be logged with the logging module, but the class will attempt to read until the end of the file, unless + the file is not a GBX file, then a GbxLoadError is thrown. + + The class uses the ByteReader class which provides support for reading data types exposed by the GBX file format, + found on https://wiki.xaseco.org/wiki/GBX. + + If the class does not provide support for reading a chunk you want to specifically read, use find_raw_chunk_id + to obtain access to a ByteReader with the cursor positioned at the beggining of provided chunk ID. + """ + + def __init__(self, obj): + """Creates the main Gbx instance from a file path or from bytes object. + + Parses the Gbx file sequentially, reading all supported chunks until no more chunks have + been found. Parsing can fail depending on the what classes or chunks it contains and what + version of the GBX file is being parsed. + + Args: + obj (str/bytes): a file path to the Gbx file or bytes object containing the Gbx data + + Raises: + GbxLoadError: raised when the supplied object is not a GBX file or data + """ + if isinstance(obj, str): + self.f = open(obj, 'rb') + self.root_parser = ByteReader(self.f) + else: + self.root_parser = ByteReader(obj) + + self.magic = self.root_parser.read(3, '3s') + if self.magic.decode('utf-8') != 'GBX': + raise GbxLoadError(f'obj is not a valid Gbx data: magic string is incorrect') + self.version = self.root_parser.read(2, 'H') + self.classes = {} + self.root_classes = {} + self.positions = {} + self.__current_class = None + self.__current_waypoint = None + self.__replay_header_info = {} + + self.root_parser.skip(3) + if self.version >= 4: + self.root_parser.skip(1) + + if self.version >= 3: + self.class_id = self.root_parser.read_uint32() + try: + self.type = GbxType(self.class_id) + except ValueError: + self.type = GbxType.UNKNOWN + + if self.version >= 6: + self._read_user_data() + + self.num_nodes = self.root_parser.read_uint32() + + self.num_external_nodes = self.root_parser.read_uint32() + if self.num_external_nodes > 0: + self.root_parser.read_uint32() + self.__read_sub_folder() + for node in range(self.num_external_nodes): + flags = self.root_parser.read_uint32() + if (flags & 4) == 0: + self.root_parser.read_string() + else: + self.root_parser.read_uint32() + + self.root_parser.skip(4) + if self.version >= 5: + self.root_parser.skip(4) + + if (flags & 4) == 0: + self.root_parser.skip(4) + + self.root_parser.push_info() + self.positions['data_size'] = self.root_parser.pop_info() + + data_size = self.root_parser.read_uint32() + compressed_data_size = self.root_parser.read_uint32() + cdata = self.root_parser.read(compressed_data_size) + self.data = bytearray(lzo.decompress(cdata, False, data_size)) + + bp = ByteReader(self.data[:]) + self._read_node(self.class_id, -1, bp) + + def __read_sub_folder(self): + num_sub_folders = self.root_parser.read_uint32() + for folder in range(num_sub_folders): + self.root_parser.read_string() + self.__read_sub_folder() + + """Finds a raw chunk ID in the file, skipping through any data that does not match the chunk ID provided. + + It is not guaranteed that the chunk found is indeed the desired data, as it could be other unrelated + chunk that bytes happened to form the chunk ID provided. + + Args: + chunk_id (int): the chunk ID to search for + + Returns: + ByteParser with the current position set right after the chunk ID, or None + if no specified chunk ID was found + """ + def find_raw_chunk_id(self, chunk_id): + bp = ByteReader(self.data[:]) + for i in range(len(self.data) - 4): + bp.pos = i + if bp.read_uint32() == chunk_id: + return bp + + return None + + def get_class_by_id(self, class_id): + """Finds the header that corresponds to the provided class ID. + + This is a wrapper around get_classes_by_ids that returns the first + element if any headers have been found for a single ID. See get_classes_by_ids for more details. + + Args: + class_id (int): the class ID to be retrieved + + Returns: + a single instance of the header corresponding to the provided class ID + """ + classes = self.get_classes_by_ids([class_id]) + if len(classes) == 0: + return None + + return classes[0] + + def get_classes_by_ids(self, class_ids): + """Retrieves all headers that match any of the class ID's in the provided array. + + Searches through the list of parsed headers and returns an array of headers that match + any of the provided ID's in the array. See GbxType for predefined class ID's that are supported. + + Args: + class_ids (list): the class ID's that have to be matched + + Returns: + a list of matched headers + """ + classes = [] + for cl in list(self.classes.values()) + list(self.root_classes.values()): + if cl.id in class_ids: + classes.append(cl) + + return classes + + def _read_user_data(self): + entries = {} + + self.root_parser.push_info() + self.user_data_size = self.root_parser.read_uint32() + self.positions['user_data_size'] = self.root_parser.pop_info() + + user_data_pos = self.root_parser.pos + num_chunks = self.root_parser.read_uint32() + for _ in range(num_chunks): + cid = self.root_parser.read_uint32() + self.root_parser.push_info() + size = self.root_parser.read_uint32() + self.positions[str(cid)] = self.root_parser.pop_info() + entries[cid] = size + + for cid, size in entries.items(): + self._read_header_entry(cid, size) + + self.root_parser.pos = user_data_pos + self.user_data_size + + def _read_header_entry(self, cid, size): + if cid == 0x03043002 or cid == 0x24003002: + version = self.root_parser.read_byte() + if version < 3: + for _ in range(3): + self.root_parser.read_string_lookback() + self.root_parser.read_string() + + self.root_parser.skip(4) + if version >= 1: + self.root_parser.skip(16) + if version == 2: + self.root_parser.skip(4) + + if version >= 4: + self.root_parser.skip(4) + if version >= 5: + self.root_parser.skip(4) + + if version == 6: + self.root_parser.skip(4) + + if version >= 7: + self.root_parser.read_uint32() + + if version >= 9: + self.root_parser.skip(4) + + if version >= 10: + self.root_parser.skip(4) + + if version >= 11: + self.root_parser.skip(4) + + if version >= 12: + self.root_parser.skip(4) + + if version >= 13: + self.root_parser.skip(8) + + elif cid == 0x03043003 or cid == 0x24003003: + p = self.root_parser.pos + self.root_parser.read_byte() + for _ in range(3): + self.root_parser.read_string_lookback() + + game_class = headers.CGameCommon(cid) + + self.root_parser.push_info() + game_class.track_name = self.root_parser.read_string() + self.positions['track_name'] = self.root_parser.pop_info() + + self.root_parser.read_byte() + + self.root_classes[cid] = game_class + + self.root_parser.pos = p + size + elif cid == 0x03043005 or cid == 0x24003005: + self.__community = self.root_parser.read_string() + elif cid == 0x03093000 or cid == 0x2403F000: + version = self.root_parser.read_uint32() + self.__replay_header_info['version'] = version + if version >= 2: + for _ in range(3): + self.root_parser.read_string_lookback() + self.root_parser.skip(4) + self.__replay_header_info['nickname'] = self.root_parser.read_string() + if version >= 6: + self.__replay_header_info['driver_login'] = self.root_parser.read_string() + self.root_parser.skip(1) + self.root_parser.read_string_lookback() + elif cid == 0x03093002 or cid == 0x2403F002: + self.root_parser.skip(8) + for _ in range(4): + self.root_parser.read_string() + else: + self.root_parser.skip(size) + + def _read_node(self, class_id, depth, bp, add=True): + oldcid = 0 + cid = 0 + + if class_id == GbxType.CHALLENGE or class_id == GbxType.CHALLENGE_OLD: + game_class = headers.CGameChallenge(class_id) + if hasattr(self, '__community'): + game_class.community = self.__community + elif class_id == GbxType.REPLAY_RECORD or class_id == GbxType.REPLAY_RECORD_OLD: + game_class = headers.CGameReplayRecord(class_id) + if 'nickname' in self.__replay_header_info: + game_class.nickname = self.__replay_header_info['nickname'] + if 'driver_login' in self.__replay_header_info: + game_class.driver_login = self.__replay_header_info['driver_login'] + + elif class_id == GbxType.WAYPOINT_SPECIAL_PROP or class_id == 0x2E009000: + game_class = headers.CGameWaypointSpecialProperty(class_id) + self.__current_waypoint = game_class + add = False + elif class_id == GbxType.CTN_GHOST or class_id == GbxType.CTN_GHOST_OLD: + game_class = headers.CGameCtnGhost(class_id) + elif class_id == GbxType.GAME_GHOST: + game_class = headers.CGameGhost(class_id) + elif class_id == GbxType.COLLECTOR_LIST: + game_class = headers.CGameCtnCollectorList(class_id) + else: + game_class = headers.CGameHeader(class_id) + + self.__current_class = game_class + + if add: + self.classes[depth] = game_class + + while True: + oldcid = cid + cid = bp.read_uint32() + logging.debug(f'Reading chunk {hex(cid)}') + + if cid == 0xFACADE01: + logging.debug('Encountered chunk 0xFACADE01, stopping') + break + + skipsize = -1 + skip = bp.read_int32() + if skip == 0x534B4950: + skipsize = bp.read_uint32() + else: + bp.pos -= 4 + + if cid == 0x0304300D or cid == 0x2400300D: + bp.read_string_lookback() + bp.read_string_lookback() + bp.read_string_lookback() + elif cid == 0x03043011 or cid == 0x24003011: + for _ in range(2): + idx = bp.read_int32() + if idx >= 0 and idx not in self.classes: + _class_id = bp.read_int32() + self._read_node(_class_id, idx, bp) + bp.read_uint32() + elif cid == 0x301B000 or cid == 0x2403C000: + itemsct = bp.read_uint32() + for _ in range(itemsct): + bp.read_string_lookback() + bp.read_string_lookback() + bp.read_string_lookback() + bp.read_uint32() + elif cid == 0x0305B000 or cid == 0x2400C000: + bp.skip(8 * 4) + elif cid == 0x0305B001 or cid == 0x2400C001: + bp.read_string() + bp.read_string() + bp.read_string() + bp.read_string() + elif cid == 0x0305B004 or cid == 0x2400C004: + game_class.times = { + 'bronze': bp.read_int32(), + 'silver': bp.read_int32(), + 'gold': bp.read_int32(), + 'author': bp.read_int32() + } + + bp.read_uint32() + elif cid == 0x0305B005 or cid == 0x2400C005: + bp.skip(4 * 3) + elif cid == 0x0305B006 or cid == 0x2400C006: + count = bp.read_uint32() + bp.skip(count * 4) + elif cid == 0x0305B008 or cid == 0x2400C008: + bp.skip(2 * 4) + elif cid == 0x0305B00A: + bp.skip(9 * 4) + elif cid == 0x0305B00D: + bp.skip(1 * 4) + elif cid == 0x03043014 or cid == 0x03043029: + bp.read(16 + 4) + # m = hashlib.md5() + # m.update(bp.read(16)) + # if isinstance(self.__current_class, headers.CGameChallenge): + # self.__current_class.password_hash = m.hexdigest() + # self.__current_class.password_crc = bp.read_uint32() + elif cid == 0x0304301F or cid == 0x2400301F: + game_class.map_uid = bp.read_string_lookback() + game_class.environment = bp.read_string_lookback() + game_class.map_author = bp.read_string_lookback() + + bp.push_info() + game_class.map_name = bp.read_string() + self.positions['map_name'] = bp.pop_info() + + bp.push_info() + game_class.mood = bp.read_string_lookback() + self.positions['mood'] = bp.pop_info() + game_class.env_bg = bp.read_string_lookback() + game_class.env_author = bp.read_string_lookback() + + game_class.map_size = ( + bp.read_int32(), + bp.read_int32(), + bp.read_int32() + ) + + game_class.req_unlock = bp.read_int32() + game_class.flags = bp.read_int32() + + bp.push_info() + num_blocks = bp.read_uint32() + i = 0 + while i < num_blocks: + block = headers.MapBlock() + block.name = bp.read_string_lookback() + if block.name != 'Unassigned1': + game_class.blocks.append(block) + block.rotation = bp.read_byte() + block.position = headers.Vector3( + bp.read_byte(), + bp.read_byte(), + bp.read_byte() + ) + + if game_class.flags > 0: + block.flags = bp.read_uint32() + else: + block.flags = bp.read_uint16() + + if block.flags == 0xFFFFFFFF: + continue + + if (block.flags & 0x8000) != 0: + block.skin_author = bp.read_string_lookback() + if game_class.flags >= 6: + # TM2 flags + bp.read_string() # Block waypoint type {Spawn, Goal} + bp.read_int32() + self._read_node(0x2E009000, 0, bp, False) + else: + block.skin = bp.read_int32() + if block.skin >= 0 and block.skin not in self.classes: + _class_id = bp.read_int32() + self._read_node(_class_id, block.skin, bp) + + if (block.flags & 0x100000) != 0: + block.params = bp.read_int32() + if block.params >= 0 and block.params not in self.classes: + _class_id = bp.read_int32() + self._read_node(_class_id, block.params, bp) + + i += 1 + + self.positions['block_data'] = bp.pop_info() + elif cid == 0x03043022: + bp.skip(4) + elif cid == 0x03043024: + version = bp.read_byte() + if version >= 3: + bp.skip(32) + + file_path = bp.read_string() + if len(file_path) > 0 or version >= 3: + bp.read_string() + elif cid == 0x03043025: + bp.skip(4 * 4) + elif cid == 0x03043026: + idx = bp.read_int32() + if idx >= 0 and idx not in self.classes: + _class_id = bp.read_int32() + self._read_node(_class_id, idx, bp) + # elif cid == 0x03043028: + # archive_gm_cam_val = bp.read_int32() + # if archive_gm_cam_val == 1: + # bp.skip(1 + 4 * 7) + + # bp.read_string() + elif cid == 0x0304302A: + bp.read_int32() + elif cid == 0x3043040: + bp.pos -= 4 + bp.push_info() + bp.pos += 4 + + item_bp = ByteReader(bp.data) + item_bp.pos = bp.pos + + item_bp.skip(2 * 4) + + item_bp.push_info() + item_bp.skip(2 * 4) + + num_items = item_bp.read_uint32() + for i in range(num_items): + item_bp.skip(4 * 3) + item = headers.CGameBlockItem() + item.path = item_bp.read_string_lookback() + item.collection = item_bp.read_string_lookback() + item.author = item_bp.read_string_lookback() + item.rotation = item_bp.read_float() + item_bp.skip(15) + + item.position = item_bp.read_vec3() + + idx = item_bp.read_int32() + if idx >= 0: + self._read_node(0x2E009000, idx, item_bp) + + item.waypoint = self.__current_waypoint + self.__current_waypoint = None + + item_bp.skip(4 * 4 + 2) + + self._read_node(0x3101004, 0, item_bp, add=False) + + game_class.items.append(item) + + item_bp.skip(4) + bp.pos = item_bp.pos + elif cid == 0x03059002 or cid == 0x2403A002: + bp.read_string() + for i in range(2): + version = bp.read_byte() + if version >= 3: + bp.skip(32) + + file_path = bp.read_string() + # (?) according to https://wiki.xaseco.org/wiki/GBX + # we need to check if the file path is not empty + # here but it crashes reading the file for TM2 challenges + if len(file_path) > 0 and version >= 1: + bp.read_string() + elif cid == 0x03043022: + bp.read_int32() + elif cid == 0x03043024: + bp.read_byte() + bp.skip(32) + bp.read_string() + bp.read_string() + elif cid == 0x03043025: + bp.skip(16) + elif cid == 0x03043026: + idx = bp.read_int32() + if idx >= 0 and idx not in self.classes: + _class_id = bp.read_int32() + self._read_node(_class_id, idx, bp) + elif cid == 0x03043028: + p = bp.read_int32() + if p != 0: + bp.skip(1 + 4 * 3 * 3 + 4 * 3 + 4 + 4 + 4) + + bp.read_string() + elif cid == 0x0304302A: + bp.read_int32() + elif cid == GbxType.WAYPOINT_SPECIAL_PROP or cid == 0x2E009000: + version = bp.read_uint32() + if version == 1: + game_class.spawn = bp.read_uint32() + game_class.order = bp.read_uint32() + elif version == 2: + game_class.tag = bp.read_string() + game_class.order = bp.read_uint32() + elif cid == 0x03059000: + bp.read_string() + bp.read_string() + elif cid == 0x0303F005: + Gbx.read_ghost(game_class, bp) + elif cid == 0x0303F006: + bp.skip(4) + Gbx.read_ghost(game_class, bp) + elif cid == 0x03093002 or cid == 0x2403F002: + map_gbx_size = bp.read_uint32() + data = bytes(bp.read(map_gbx_size)) + game_class.track = Gbx(data) + elif cid == 0x03093007: + bp.skip(4) + elif cid == 0x03093014 or cid == 0x2403F014: + bp.skip(4) + num_ghosts = bp.read_uint32() + for _ in range(num_ghosts): + idx = bp.read_int32() + if idx >= 0 and idx not in self.classes: + _class_id = bp.read_uint32() + self._read_node(_class_id, idx, bp) + + bp.skip(4) + # num_extras = bp.read_uint32() + # bp.skip(num_extras * 8) + elif cid == 0x3093015: + idx = bp.read_int32() + if idx >= 0 and idx not in self.classes: + _class_id = bp.read_uint32() + self._read_node(_class_id, idx, bp) + + elif cid == 0x03043021 or cid == 0x24003021: + for _ in range(3): + idx = bp.read_int32() + if idx >= 0 and idx not in self.classes: + _class_id = bp.read_int32() + self._read_node(_class_id, idx, bp) + elif cid == 0x03043022 or cid == 0x24003022: + bp.skip(4) + elif cid == 0x03043024 or cid == 0x24003024: + version = bp.read_byte() + if version >= 3: + bp.skip(32) + + path = bp.read_string() + if len(path) > 0 and version >= 1: + bp.read_string() + elif cid == 0x03043025 or cid == 0x24003025: + bp.skip(4 * 4) + elif cid == 0x03043026 or cid == 0x24003026: + idx = bp.read_int32() + if idx >= 0 and idx not in self.classes: + _class_id = bp.read_int32() + self._read_node(_class_id, idx, bp) + elif cid == 0x03092005 or cid == 0x2401B005: + game_class.race_time = bp.read_uint32() + elif cid == 0x03092008 or cid == 0x2401B008: + game_class.num_respawns = bp.read_uint32() + elif cid == 0x03092009 or cid == 0x2401B009: + game_class.light_trail_color = bp.read_vec3() + elif cid == 0x0309200A or cid == 0x2401B00A: + game_class.stunts_score = bp.read_uint32() + # The GBX spec is wrong here. + # 0x0309200B contains how many CP times there are + # after that, there is a list of (uint32, uint32) + # tuples, the first element is the time, the second + # is unknown + elif cid == 0x0309200B or cid == 0x2401B00B: + num = bp.read_uint32() + cp_times = [] + for i in range(num): + cp_times.append(bp.read_uint32()) + bp.skip(4) + + game_class.cp_times = cp_times + elif cid == 0x309200C or cid == 0x2401B00C: + bp.skip(4) + elif cid == 0x309200E or cid == 0x2401B00E: + game_class.uid = bp.read_string_lookback() + + # For TM2 + if ('version' in self.__replay_header_info + and self.__replay_header_info['version'] >= 8): + game_class.login = bp.read_string() + elif cid == 0x309200F or cid == 0x2401B00F: + game_class.login = bp.read_string() + elif cid == 0x3092010 or cid == 0x2401B010: + bp.read_string_lookback() + elif cid == 0x3092012 or cid == 0x2401B012: + bp.skip(4 + 16) + # import binascii + # bp.read(4) + # print(f'{binascii.hexlify(bp.read(16))}') + # print() + elif cid == 0x3092013 or cid == 0x2401B013: + bp.skip(4 + 4) + elif cid == 0x3092014 or cid == 0x2401B014: + bp.skip(4) + elif cid == 0x3092015 or cid == 0x2401B015: + bp.read_string_lookback() + elif cid == 0x3092018 or cid == 0x2401B018: + bp.read_string_lookback() + bp.read_string_lookback() + bp.read_string_lookback() + elif cid == 0x3092019 or cid == 0x03092025 or cid == 0x2401B019: + if cid == 0x03092025: + bp.skip(4) + + game_class.events_duration = bp.read_uint32() + bp.skip(4) + + num_control_names = bp.read_uint32() + game_class.control_names = [] + for _ in range(num_control_names): + name = bp.read_string_lookback() + if name != '': + game_class.control_names.append(name) + + if len(game_class.control_names) == 0: + continue + + num_control_entries = bp.read_uint32() + bp.skip(4) + for _ in range(num_control_entries): + time = bp.read_uint32() - 100000 + name = game_class.control_names[bp.read_byte()] + entry = headers.ControlEntry(time, name, bp.read_uint16(), bp.read_uint16()) + game_class.control_entries.append(entry) + + game_class.game_version = bp.read_string() + bp.skip(3 * 4) + bp.read_string() + bp.skip(4) + elif cid == 0x309201c: + bp.skip(32) + elif cid == 0x03093004 or cid == 0x2403f004: + bp.skip(4 * 4) + elif skipsize != -1: + bp.skip(skipsize) + cid = oldcid + else: + return + + @staticmethod + def read_ghost(game_class, bp): + uncomp_sz = bp.read_uint32() + comp_sz = bp.read_uint32() + comp_data = bp.read(comp_sz) + data = zlib.decompress(comp_data, 0, uncomp_sz) + + gr = ByteReader(data) + gr.skip(3 * 4) + game_class.sample_period = gr.read_uint32() + gr.skip(1 * 4) + + sample_data_sz = gr.read_uint32() + sample_data_pos = gr.pos + gr.skip(sample_data_sz) + + sample_sizes = [] + num_samples = gr.read_uint32() + fso = 0 + if num_samples > 0: + fso = gr.read_uint32() + if num_samples > 1: + sps = gr.read_int32() + if sps == -1: + sample_sizes = [] + for _ in range(num_samples - 1): + sample_sizes.append(gr.read_uint32()) + else: + sample_sizes.append(sps) + + gr.pos = sample_data_pos + gr.skip(fso) + for i in range(num_samples): + sample_pos = gr.pos + + record = headers.GhostSampleRecord( + gr.read_vec3(), gr.read_uint16(), gr.read_int16(), + gr.read_int16(), gr.read_int16(), + gr.read_int8(), gr.read_int8()) + + if len(sample_sizes) == 1: + sample_sz = sample_sizes[0] + else: + sample_sz = sample_sizes[i] + + record.raw_data = gr.read(sample_sz - (gr.pos - sample_pos)) + # import binascii + # print(f'{i} {binascii.hexlify(record.raw_data)}') + game_class.records.append(record) diff --git a/pygbx/headers.py b/pygbx/headers.py new file mode 100644 index 0000000..235c291 --- /dev/null +++ b/pygbx/headers.py @@ -0,0 +1,216 @@ +from pygbx.stadium_blocks import STADIUM_BLOCKS +import math + +class CGameHeader(object): + """A generic header class that contains it's class ID.""" + def __init__(self, id): + self.id = id + + +class CGameCtnCollectorList(object): + """A header that holds a list of CollectorStock's.""" + def __init__(self, id): + self.id = id + self.stocks = [] + + +class CollectorStock(object): + """A header that holds a stock.""" + def __init__(self, block_name, collection, author): + self.block_name = block_name + self.collection = collection + self.author = author + + +class MapBlock(object): + """A header that holds information about a specific block contained within the Challenge data.""" + def __init__(self): + self.name = None + self.rotation = 0 + self.position = Vector3() + self.speed = 0 + self.flags = 0 + self.params = 0 + self.skin_author = None + self.skin = 0 + + # 0b1000000000000 + # (flags & 0x1000) != 0 is on terrain + # (flags & 0x1) != 0 connected once with another RoadMain + # (flags & 0x2) != 0 connected twice with blocks (curve line) + # (flags & 0x3) != 0 connected twice with blocks (straight line) + # (flags & 0x4) != 0 connected three times with blocks + # (flags & 0x5) != 0 connected four times with blocks + # 6? + # (flags & 0x7) == 0 not connected to any blocks + def __str__(self): + return ( + 'Name: {}\n' + 'Rotation: {}\n' + 'Position: {}\n' + 'Flags: {}\n' + ).format(self.name, self.rotation, self.position.as_array(), bin(self.flags)) + +class Vector3(object): + """The Vector3 class represents a 3D vector, usually read directly from the GBX file.""" + def __init__(self, x=0, y=0, z=0): + self.x = x + self.y = y + self.z = z + + def __add__(self, other): + return Vector3(self.x + other.x, self.y + other.y, self.z + other.z) + + def __sub__(self, other): + return Vector3(self.x - other.x, self.y - other.y, self.z - other.z) + + def __getitem__(self, key): + if key == 0: + return self.x + elif key == 1: + return self.y + elif key == 2: + return self.z + return None + + def __eq__(self, other): + if isinstance(other, list): + return self.x == other[0] and self.y == other[1] and self.z == other[2] + + return self.x == other.x and self.y == other.y and self.z == other.z + + def as_array(self): + """Returns the vector as a list. + + Returns: + the vector as a list made of 3 elements + """ + return [self.x, self.y, self.z] + + +class CGameChallenge(CGameHeader): + """A header that contains data related to the CGameChallenge class.""" + def __init__(self, id): + self.id = id + self.times = {} + self.map_uid = None + self.environment = None + self.map_author = None + self.map_name = None + self.mood = None + self.env_bg = None + self.env_author = None + self.map_size = () + self.flags = 0 + self.req_unlock = 0 + self.blocks = [] + self.items = [] + self.password_hash = None + self.password_crc = None + + +class CGameBlockItem(CGameHeader): + """A header that contains data related to the CGameBlockItem class.""" + def __init__(self): + self.id = id + self.path = None + self.collection = None + self.author = None + self.waypoint = None + self.position = Vector3() + self.rotation = 0.0 + + +class CGameWaypointSpecialProperty(CGameHeader): + """A header that contains data related to the CGameWaypointSpecialProperty class.""" + def __init__(self, id): + self.id = id + self.tag = None + self.spawn = 0 + self.order = 0 + + +class CGameCommon(CGameHeader): + """A header that contains data related to the CGameCommon class.""" + def __init__(self, id): + self.id = id + self.track_name = None + + +class CGameReplayRecord(CGameCommon): + """A header that contains data related to the CGameReplayRecord class.""" + def __init__(self, id): + self.id = id + self.track = None + self.nickname = None + self.driver_login = None + + +class CGameGhost(CGameHeader): + """A header that contains data related to the CGameGhost class.""" + def __init__(self, id): + self.id = id + self.records = [] + self.sample_period = None + + +class CGameCtnGhost(CGameGhost): + """A header that contains data related to the CGameCtnGhost class.""" + def __init__(self, id): + self.id = id + self.race_time = 0 + self.num_respawns = 0 + self.light_trail_color = Vector3() + self.stunts_score = 0 + self.uid = None + self.login = None + self.cp_times = [] + self.control_entries = [] + self.game_version = '' + self.control_names = [] + self.events_duration = 0 + super(CGameCtnGhost, self).__init__(id) + +class ControlEntry(object): + """A header that contains data related to the control entries contained within the CGameCtnGhost class.""" + def __init__(self, time, event_name, enabled, flags): + self.time = time + self.event_name = event_name + self.enabled = enabled + self.flags = flags + +class GhostSampleRecord(object): + """A header that contains a single sample out of the ghost data such as position, rotation of the car and more.""" + BLOCK_SIZE_XZ = 32 + BLOCK_SIZE_Y = 8 + + def __init__(self, position, angle, axis_heading, axis_pitch, speed, vel_heading, vel_pitch): + self.position = position + self.angle = angle + self.axis_heading = axis_heading + self.axis_pitch = axis_pitch + self.speed = speed + self.vel_heading = vel_heading + self.vel_pitch = vel_pitch + + @property + def display_speed(self): + """Returns the display speed, which would be shown in the race. + + Returns: + an integer from 0 to 1000 that represents the display speed""" + if self.speed == 0x8000: + return 0 + + return int(abs(math.exp(self.speed / 1000.0) * 3.6)) + + def get_block_position(self, xoff=0, yoff=0, zoff=0): + """Calculates the block coordinates that the car is currently passing through in this sample record. + + Returns: + a Vector3 containing the block coordinates + """ + x = int((self.position.x + xoff) / self.BLOCK_SIZE_XZ) + y = int((self.position.y + yoff) / self.BLOCK_SIZE_Y) + z = int((self.position.z + zoff) / self.BLOCK_SIZE_XZ) + return Vector3(x, y, z) diff --git a/pygbx/stadium_block_offsets.py b/pygbx/stadium_block_offsets.py new file mode 100644 index 0000000..eba73d2 --- /dev/null +++ b/pygbx/stadium_block_offsets.py @@ -0,0 +1,439 @@ +# Generated via MainaScript: +# #RequireContext CEditorPlugin +# #Include "TextLib" as TL + +# main() { +# log("------------- PrintBlocks started -----------------"); +# foreach(Block in Blocks) { +# declare Text bInfo; +# bInfo = bInfo ^ "'"; +# if (!Block.BlockModel.IsTerrain +# && !TL::Find("Clip", Block.BlockModel.Name, False, False)) { +# bInfo = bInfo ^ Block.BlockModel.Name; +# } else { +# continue; +# } + +# bInfo = bInfo ^ "': ["; + +# declare Integer i = 0; +# foreach (BlockUnit in Block.BlockUnits) { +# declare CBlockUnitModel model <=> BlockUnit.BlockUnitModel; +# bInfo = bInfo ^ "["; +# bInfo = bInfo ^ model.RelativeOffset.X ^ ", "; +# bInfo = bInfo ^ model.RelativeOffset.Y ^ ", "; +# bInfo = bInfo ^ model.RelativeOffset.Z ^ "]"; +# if (i != Block.BlockUnits.count - 1) { +# bInfo = bInfo ^ ", "; +# } +# i = i + 1; +# } + +# bInfo = bInfo ^ "],"; +# log(bInfo); +# } + +# while(True) +# { +# yield; +# } +# } + +STADIUM_BLOCK_OFFSETS = { + 'StadiumGrass': [[0, 0, 0]], + 'StadiumDirt': [[0, 0, 0]], + 'StadiumDirtHill': [[0, 0, 0], [0, 1, 0]], + 'StadiumDirtBorder': [[0, 0, 0]], + 'StadiumRoadMain': [[0, 0, 0]], + 'StadiumRoadMainGTCurve2': [[0, 0, 0], [1, 0, 0], [0, 0, 1], [1, 0, 1]], + 'StadiumRoadMainGTCurve3': [[0, 0, 0], [0, 0, 1], [1, 0, 0], [1, 0, 1], [1, 0, 2], [2, 0, 0], [2, 0, 1], [2, 0, 2]], + 'StadiumRoadMainGTCurve4': [[0, 0, 0], [0, 0, 1], [1, 0, 0], [1, 0, 1], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 0, 3], [3, 0, 1], [3, 0, 2], [3, 0, 3]], + 'StadiumRoadMainGTCurve5': [[0, 0, 0], [0, 0, 1], [1, 0, 0], [1, 0, 1], [2, 0, 0], [2, 0, 1], [2, 0, 2], [3, 0, 1], [3, 0, 2], [3, 0, 3], [3, 0, 4], [4, 0, 2], [4, 0, 3], [4, 0, 4]], + 'StadiumLoopLeft': [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1], [0, 3, 1], [0, 4, 1], [0, 5, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 1], [1, 4, 1], [1, 5, 1], [1, 6, 1], [1, 7, 1], [1, 8, 0], [1, 8, 1], [1, 9, 0], [1, 9, 1], [1, 10, 0], [2, 5, 1], [2, 6, 1], [2, 7, 1], [2, 8, 0], [2, 8, 1], [2, 9, 0], [2, 9, 1], [2, 10, 0]], + 'StadiumLoopRight': [[2, 0, 0], [2, 1, 0], [2, 1, 1], [2, 2, 0], [2, 2, 1], [2, 3, 1], [2, 4, 1], [2, 5, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 1], [1, 4, 1], [1, 5, 1], [1, 6, 1], [1, 7, 1], [1, 8, 0], [1, 8, 1], [1, 9, 0], [1, 9, 1], [1, 10, 0], [0, 5, 1], [0, 6, 1], [0, 7, 1], [0, 8, 0], [0, 8, 1], [0, 9, 0], [0, 9, 1], [0, 10, 0]], + 'StadiumRoadMainStartLine': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainCheckpoint': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainBirds': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainFinishLine': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainStartFinishLine': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainTurbo': [[0, 0, 0]], + 'StadiumRoadMainTurboRoulette': [[0, 0, 0]], + 'StadiumRoadMainFW': [[0, 0, 0]], + 'StadiumGrassCheckpoint': [[0, 0, 0], [0, 1, 0]], + 'StadiumGrassBirds': [[0, 0, 0]], + 'StadiumHolePillar': [[0, 0, 0]], + 'StadiumHolePillar2Front': [[0, 0, 0]], + 'StadiumHolePillar2Line': [[0, 0, 0]], + 'StadiumHolePillar3': [[0, 0, 0]], + 'StadiumHole': [[0, 0, 0]], + 'StadiumRoadStretch': [[0, 0, 0]], + 'StadiumBump1': [[0, 0, 0], [0, 1, 0]], + 'StadiumRamp': [[0, 0, 0]], + 'StadiumRampLow': [[0, 0, 0]], + 'StadiumRoadMainInBeam': [[0, 0, 0]], + 'StadiumRoadMainXBeam': [[0, 0, 0]], + 'StadiumRoadMainGTDiag2x2': [[0, 0, 0], [0, 0, 1], [1, 0, 0], [1, 0, 1]], + 'StadiumRoadMainGTDiag2x2Mirror': [[0, 0, 0], [0, 0, 1], [1, 0, 0], [1, 0, 1]], + 'StadiumRoadMainGTDiag3x2': [[0, 0, 0], [0, 0, 1], [0, 0, 2], [1, 0, 0], [1, 0, 1], [1, 0, 2]], + 'StadiumRoadMainGTDiag3x2Mirror': [[0, 0, 0], [0, 0, 1], [0, 0, 2], [1, 0, 0], [1, 0, 1], [1, 0, 2]], + 'StadiumRoadMainGTDiag4x3': [[0, 0, 0], [0, 0, 1], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 0, 3], [2, 0, 1], [2, 0, 2], [2, 0, 3]], + 'StadiumRoadMainGTDiag4x3Mirror': [[0, 0, 1], [0, 0, 2], [0, 0, 3], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 0, 3], [2, 0, 0], [2, 0, 1]], + 'StadiumRoadMainYShapedDiag2': [[0, 0, 0], [0, 0, 1], [1, 0, 0], [1, 0, 1], [2, 0, 0], [2, 0, 1]], + 'StadiumRoadMainYShapedCurve2': [[0, 0, 0], [0, 0, 1], [1, 0, 0], [1, 0, 1], [2, 0, 0], [2, 0, 1]], + 'StadiumRoadMainSlopeBase2': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumRoadMainSlopeBase': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainSlopeBase1x2': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1]], + 'StadiumRoadMainSlopeStraight': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainBiSlopeStart': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1]], + 'StadiumRoadMainBiSlopeEnd': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1]], + 'StadiumRoadMainSlopeUTop': [[0, 0, 0]], + 'StadiumRoadMainSlopeUBottom': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainCheckpointUp': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainCheckpointDown': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainTurboUp': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainTurboDown': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainTurboRouletteDown': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainTurboRouletteUp': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainFWSlope': [[0, 0, 0], [0, 1, 0]], + 'StadiumHolePillarSlope': [[0, 0, 0], [0, 1, 0]], + 'StadiumHoleSlope': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadStretchSlope': [[0, 0, 0], [0, 1, 0]], + 'StadiumBump1Slope': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainInBeamSlope': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainXBeamSlope': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainGTDiag2x2Slope': [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 2, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 2, 1]], + 'StadiumRoadMainGTDiag2x2SlopeMirror': [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 2, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 2, 1]], + 'StadiumRoadMainGTDiag3x2Slope': [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 2, 1], [0, 2, 2], [0, 3, 2], [1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 2, 1], [1, 2, 2], [1, 3, 2]], + 'StadiumRoadMainGTDiag3x2SlopeMirror': [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 2, 1], [0, 2, 2], [0, 3, 2], [1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 2, 1], [1, 2, 2], [1, 3, 2]], + 'StadiumRoadMainGTDiag4x3Slope': [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 2, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 2, 1], [1, 2, 2], [1, 3, 2], [1, 3, 3], [2, 2, 1], [2, 2, 2], [2, 3, 2], [2, 3, 3], [2, 4, 3]], + 'StadiumRoadMainGTDiag4x3SlopeMirror': [[0, 1, 1], [0, 2, 1], [0, 2, 2], [0, 3, 2], [0, 3, 3], [0, 4, 3], [1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 2, 1], [1, 2, 2], [1, 3, 2], [1, 3, 3], [2, 0, 0], [2, 1, 0], [2, 1, 1], [2, 2, 1]], + 'StadiumRoadMainYShapedDiag2SlopeUp': [[0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [2, 0, 1], [2, 1, 0], [2, 1, 1], [2, 2, 0]], + 'StadiumRoadMainYShapedDiag2SlopeDown': [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 2, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 2, 1], [2, 0, 0], [2, 1, 0], [2, 1, 1], [2, 2, 1]], + 'StadiumRoadTiltStraight': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadTiltTransition1Left': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadTiltTransition1Right': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadTiltTransition2Left': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1]], + 'StadiumRoadTiltTransition2Right': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1]], + 'StadiumRoadTiltTransition2CurveLeft': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], + 'StadiumRoadTiltTransition2CurveRight': [[1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1]], + 'StadiumRoadTiltTransition2DiagLeft': [[0, 0, 0], [0, 0, 1], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 1]], + 'StadiumRoadTiltTransition2DiagRight': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 1], [1, 0, 0], [1, 0, 1], [1, 1, 1]], + 'StadiumRoadTiltStraightBirds': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainCheckpointLeft': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumRoadMainCheckpointRight': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumRoadMainTurboLeft': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainTurboRight': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainTurboRouletteLeft': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainTurboRouletteRight': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainFWTilt': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadTiltCorner': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadTiltGTCurve2': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], + 'StadiumRoadTiltGTCurve3': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 0], [2, 1, 1], [2, 1, 2]], + 'StadiumRoadTiltGTCurve4': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 0, 3], [2, 1, 0], [2, 1, 1], [3, 0, 1], [3, 0, 2], [3, 0, 3], [3, 1, 1], [3, 1, 2], [3, 1, 3]], + 'StadiumRoadMainGTDiag2x2Tilt': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 1, 0], [1, 1, 1], [1, 2, 1]], + 'StadiumRoadMainGTDiag3x2Tilt': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 1, 2], [1, 1, 0], [1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 2, 2]], + 'StadiumRoadMainGTDiag2x2TiltMirror': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0]], + 'StadiumRoadMainGTDiag3x2TiltMirror': [[0, 1, 0], [0, 1, 1], [0, 1, 2], [0, 2, 1], [0, 2, 2], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 1, 2]], + 'StadiumRoadTiltCornerDownLeft': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadTiltCornerDownRight': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadTiltCornerUpLeft': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadTiltCornerUpRight': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadTiltGTCurve2DownLeft': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 1, 0], [1, 1, 1], [1, 2, 1]], + 'StadiumRoadTiltGTCurve2DownRight': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0]], + 'StadiumRoadTiltGTCurve2UpLeft': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0]], + 'StadiumRoadTiltGTCurve2UpRight': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 1, 0], [1, 1, 1], [1, 2, 1]], + 'StadiumHolePillarTilt': [[0, 0, 0], [0, 1, 0]], + 'StadiumHoleTilt': [[0, 0, 0], [0, 1, 0]], + 'StadiumStretchTilt': [[0, 0, 0], [0, 1, 0]], + 'StadiumBump1Tilt': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainInBeamTilt': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadMainXBeamTilt': [[0, 0, 0], [0, 1, 0]], + 'StadiumPlatformLoopStart': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumPlatformLoopEnd': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumPlatformWallBorder': [[0, 0, 0]], + 'StadiumPlatformWall1': [[0, 0, 0]], + 'StadiumPlatformWall1Screen': [[0, 0, 0]], + 'StadiumPlatformWallCheckpointH': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumPlatformWall2': [[0, 0, 0], [0, 1, 0]], + 'StadiumPlatformWall4': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumPlatformWallCheckpointV': [[0, 0, 0]], + 'StadiumPlatformWallPub2': [[0, 0, 0], [0, 1, 0]], + 'StadiumPlatformWall1Birds': [[0, 0, 0]], + 'StadiumPlatformRoad': [[0, 0, 0]], + 'StadiumPlatformSlope2Straight': [[0, 0, 0], [0, 1, 0]], + 'StadiumPlatformBiSlope2StartSmall': [[0, 0, 0]], + 'StadiumPlatformBiSlope2Start': [[0, 0, 0], [0, 1, 0]], + 'StadiumPlatformBiSlope2End': [[0, 0, 0]], + 'StadiumPlatformTurbo': [[0, 0, 0]], + 'StadiumPlatformTurboDown': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumPlatformTurboLeft': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumPlatformTurboRight': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumPlatformTurboUp': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumPlatformFW': [[0, 0, 0]], + 'StadiumPlatformFWSlope2': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCheckpointRingHRoad': [[0, 0, 0]], + 'StadiumCheckpointRingV': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumCheckpointRing2x1V': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 3, 0]], + 'StadiumCheckpointRing2x1H': [[0, 0, 0], [1, 0, 0]], + 'StadiumPlatformMultilap': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumPlatformCheckpoint': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumPlatformCheckpointDown': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumPlatformCheckpointLeft': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumPlatformCheckpointRight': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumPlatformCheckpointUp': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumPlatformGTCurve2Wall1': [[0, 0, 0], [1, 0, 0], [1, 0, 1]], + 'StadiumPlatformGTCurve2Wall2': [[0, 0, 0], [0, 1, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], + 'StadiumPlatformGTCurve2Wall4': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 0], [1, 3, 1]], + 'StadiumPlatformGTCurve3Wall1': [[0, 0, 0], [1, 0, 0], [2, 0, 0], [2, 0, 1], [2, 0, 2]], + 'StadiumPlatformGTCurve3Wall2': [[0, 0, 0], [0, 1, 0], [1, 0, 0], [1, 1, 0], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 0], [2, 1, 1], [2, 1, 2]], + 'StadiumPlatformGTCurve3Wall4': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 3, 0], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 0], [2, 1, 1], [2, 1, 2], [2, 2, 0], [2, 2, 1], [2, 2, 2], [2, 3, 0], [2, 3, 1], [2, 3, 2]], + 'StadiumPlatformToRoad': [[0, 0, 0]], + 'StadiumPlatformToRoad2': [[0, 0, 0], [0, 0, 1]], + 'StadiumPlatformToRoad2Mirror': [[0, 0, 0], [0, 0, 1]], + 'StadiumPlatformToRoadMain': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitToRoadMain': [[0, 0, 0], [0, 1, 0]], + 'StadiumPlatformGridCheckpoint': [[0, 0, 0], [0, 1, 0]], + 'StadiumPlatformGridFW': [[0, 0, 0]], + 'StadiumPlatformGridStraight': [[0, 0, 0]], + 'StadiumPlatformGridTurbo': [[0, 0, 0]], + 'StadiumPlatformGridStraightBirds': [[0, 0, 0]], + 'StadiumCircuitBase': [[0, 0, 0]], + 'StadiumCircuitSlopeStart': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitSlopeStraight': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitSlopeEnd': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitTurbo': [[0, 0, 0]], + 'StadiumCircuitTurboLeft': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitTurboUp': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitTurboRight': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitTurboDown': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitFreeWheeling': [[0, 0, 0]], + 'StadiumCircuitFreeWheelingSlope2Air': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderStraight': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitBorderSlopeStraightLeft': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumCircuitBorderSlopeStraightTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderSlopeStraightRight': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumCircuitBorderSlopeStraightBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderSlopeStartLeft': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderSlopeStartRight': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderSlopeEndRight': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderDiagIn': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitBorderSlopeDiagInLeftBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumCircuitBorderSlopeDiagInLeftTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumCircuitBorderSlopeDiagInRightTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumCircuitBorderSlopeDiagInRightBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumCircuitBorderDiagOut': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitBorderSlopeDiagOutLeftBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderSlopeDiagOutLeftTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderSlopeDiagOutRightTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderSlopeDiagOutRightBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderCornerIn': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitBorderSlopeCornerInLeftBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumCircuitBorderSlopeCornerInLeftTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumCircuitBorderSlopeCornerInRightTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumCircuitBorderSlopeCornerInRightBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumCircuitBorderCornerOut': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitSlopeCornerInLeftBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderSlopeCornerOutRightTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderSlopeCornerOutRightBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumCircuitBorderGTCurve2In': [[0, 0, 1], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], + 'StadiumCircuitBorderSlopeGTCurve2InLeftBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 4, 1], [1, 3, 1], [1, 4, 1], [1, 5, 1]], + 'StadiumCircuitBorderSlopeGTCurve2InLeftTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 4, 1], [0, 5, 1], [1, 0, 0], [1, 1, 0], [1, 2, 0]], + 'StadiumCircuitBorderSlopeGTCurve2InRightTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 2, 1], [1, 3, 0], [1, 3, 1], [1, 4, 1], [1, 5, 1]], + 'StadiumCircuitBorderSlopeGTCurve2InRightBottom': [[0, 3, 1], [0, 4, 1], [0, 5, 1], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 2, 1], [1, 3, 0], [1, 3, 1], [1, 4, 1]], + 'StadiumCircuitBorderGTCurve2Out': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 1], [1, 1, 1]], + 'StadiumCircuitBorderSlopeGTCurve2OutLeftBottom': [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1], [0, 3, 1], [1, 1, 1], [1, 2, 1], [1, 3, 1], [1, 4, 1]], + 'StadiumCircuitBorderSlopeGTCurve2OutLeftTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 4, 1], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 3, 0]], + 'StadiumCircuitBorderSlopeGTCurve2OutRightTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 2, 1], [1, 3, 0], [1, 3, 1], [1, 4, 1]], + 'StadiumCircuitBorderSlopeGTCurve2OutRightBottom': [[0, 1, 1], [0, 2, 1], [0, 3, 1], [0, 4, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 1]], + 'StadiumCircuitBorderGTCurve3In': [[0, 0, 2], [0, 1, 2], [1, 0, 1], [1, 0, 2], [1, 1, 1], [1, 1, 2], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 0], [2, 1, 1]], + 'StadiumCircuitBorderSlopeGTCurve3InLeftBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 4, 1], [0, 5, 1], [0, 5, 2], [0, 6, 2], [1, 6, 2], [1, 5, 2], [2, 6, 2], [2, 7, 2], [2, 5, 2], [0, 4, 2], [1, 4, 2], [1, 4, 1], [1, 3, 1], [1, 5, 1]], + 'StadiumCircuitBorderSlopeGTCurve3InLeftTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 4, 1], [0, 5, 1], [1, 0, 0], [1, 1, 0], [1, 2, 0], [2, 0, 0], [2, 1, 0], [2, 2, 0], [1, 2, 1], [1, 3, 1], [1, 3, 0], [0, 5, 2], [0, 6, 2], [0, 7, 2], [0, 4, 2]], + 'StadiumCircuitBorderSlopeGTCurve3InRightTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0], [2, 2, 0], [2, 2, 1], [2, 3, 1], [2, 4, 1], [2, 5, 1], [2, 5, 2], [2, 6, 2], [2, 7, 2], [1, 3, 0], [2, 1, 0], [2, 0, 0], [1, 2, 1], [1, 3, 1], [2, 4, 2], [2, 3, 0]], + 'StadiumCircuitBorderGTCurve3Out': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1], [1, 1, 2], [2, 0, 1], [2, 0, 2], [2, 1, 1], [2, 1, 2]], + 'StadiumCircuitBorderSlopeGTCurve3OutLeftBottom': [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1], [0, 3, 1], [1, 1, 1], [1, 2, 1], [1, 3, 1], [1, 4, 1], [1, 4, 2], [1, 5, 2], [2, 5, 2], [2, 6, 2], [2, 4, 2], [2, 3, 2], [2, 4, 1], [2, 3, 1], [1, 3, 2], [1, 1, 0], [1, 2, 0]], + 'StadiumCircuitBorderSlopeGTCurve3OutLeftTop': [[1, 0, 0], [1, 1, 0], [2, 0, 0], [2, 1, 0], [2, 2, 0], [2, 3, 0], [1, 2, 0], [1, 2, 1], [1, 3, 1], [1, 4, 1], [1, 5, 1], [0, 2, 1], [0, 3, 1], [0, 4, 1], [0, 5, 2], [0, 4, 2], [0, 6, 2], [0, 5, 1], [1, 3, 0], [2, 2, 1], [2, 3, 1], [1, 4, 2], [1, 5, 2]], + 'StadiumCircuitBorderSlopeGTCurve3OutRightTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 2, 1], [1, 3, 1], [1, 4, 1], [2, 2, 1], [2, 3, 1], [2, 4, 1], [2, 4, 2], [2, 5, 2], [1, 5, 2], [2, 6, 2], [1, 5, 1], [2, 5, 1], [1, 4, 2], [0, 2, 1], [0, 3, 1], [1, 3, 0]], + 'StadiumCircuitBorderSlopeGTCurve3OutRightBottom': [[0, 3, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 1], [2, 2, 1], [2, 1, 1], [2, 1, 0], [2, 2, 0], [2, 0, 0], [2, 3, 1], [1, 3, 2], [1, 4, 2], [1, 5, 2], [0, 5, 2], [0, 6, 2], [0, 4, 2], [0, 3, 2], [1, 4, 1], [0, 2, 1], [0, 4, 1]], + 'StadiumCircuitBorderGTCurve4In': [[0, 0, 3], [0, 1, 3], [1, 0, 3], [1, 1, 3], [2, 0, 2], [2, 0, 3], [2, 1, 2], [2, 1, 3], [3, 0, 0], [3, 0, 1], [3, 0, 2], [3, 1, 0], [3, 1, 1], [3, 1, 2]], + 'StadiumCircuitBorderSlopeGTCurve4InLeftBottom': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 4, 1], [0, 5, 2], [0, 6, 2], [1, 6, 2], [1, 6, 3], [1, 7, 3], [1, 8, 3], [2, 8, 3], [3, 8, 3], [3, 9, 3], [1, 7, 2], [0, 5, 1], [0, 4, 2], [2, 7, 3], [3, 7, 3], [1, 5, 2], [1, 4, 2], [2, 6, 3]], + 'StadiumCircuitBorderSlopeGTCurve4InLeftTop': [[3, 1, 0], [3, 0, 0], [3, 2, 0], [2, 2, 0], [1, 2, 0], [1, 1, 0], [2, 1, 0], [1, 2, 1], [0, 2, 1], [0, 3, 1], [0, 4, 1], [0, 4, 2], [0, 5, 2], [0, 6, 2], [0, 6, 3], [0, 7, 3], [0, 8, 3], [1, 3, 1], [1, 3, 0], [1, 4, 1], [0, 5, 1], [0, 7, 2], [2, 0, 0], [1, 0, 0], [0, 9, 3], [2, 3, 0], [1, 5, 1]], + 'StadiumCircuitBorderSlopeGTCurve4InRightTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 3, 0], [2, 0, 0], [2, 1, 0], [2, 2, 0], [2, 3, 0], [3, 3, 1], [3, 4, 1], [3, 5, 1], [2, 3, 1], [2, 4, 1], [2, 5, 1], [3, 5, 2], [3, 6, 2], [3, 7, 3], [3, 7, 2], [3, 8, 3], [3, 9, 3], [3, 4, 2], [3, 6, 3], [3, 2, 1], [2, 2, 1]], + 'StadiumCircuitBorderSlopeGTCurve4InRightBottom': [[3, 3, 1], [3, 2, 0], [3, 1, 0], [3, 0, 0], [3, 2, 1], [3, 4, 1], [3, 5, 1], [3, 5, 2], [3, 6, 2], [2, 6, 2], [2, 7, 2], [2, 7, 3], [2, 8, 3], [1, 8, 3], [0, 8, 3], [0, 9, 3], [3, 3, 0], [3, 4, 2], [0, 7, 3], [1, 7, 3], [2, 5, 2], [2, 6, 3], [1, 6, 3]], + 'StadiumCircuitBorderGTCurve4Out': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [2, 0, 1], [2, 0, 2], [2, 0, 3], [2, 1, 1], [2, 1, 2], [3, 0, 2], [3, 0, 3], [3, 1, 2], [3, 1, 3]], + 'StadiumCircuitBorderSlopeGTCurve4OutLeftBottom': [[0, 0, 0], [0, 1, 0], [0, 1, 1], [1, 1, 1], [0, 2, 0], [0, 2, 1], [0, 3, 1], [1, 3, 1], [2, 3, 1], [2, 3, 2], [2, 4, 2], [2, 5, 2], [2, 5, 3], [2, 6, 3], [2, 7, 3], [3, 7, 3], [3, 8, 3], [3, 6, 2], [3, 5, 2], [3, 6, 3], [3, 5, 3], [1, 2, 1], [1, 2, 0], [2, 4, 1], [3, 4, 2], [2, 2, 1], [1, 1, 0]], + 'StadiumCircuitBorderSlopeGTCurve4OutLeftTop': [[2, 3, 1], [2, 0, 0], [3, 0, 0], [3, 1, 0], [2, 1, 0], [3, 2, 0], [3, 3, 0], [2, 2, 0], [3, 2, 1], [3, 3, 1], [2, 2, 1], [2, 4, 1], [2, 5, 1], [2, 4, 2], [2, 5, 2], [2, 6, 2], [1, 4, 2], [0, 4, 2], [0, 5, 2], [0, 6, 2], [1, 5, 2], [1, 6, 2], [1, 7, 2], [0, 7, 3], [0, 8, 3], [1, 6, 3], [1, 7, 3], [0, 6, 3], [3, 4, 1]], + 'StadiumCircuitBorderSlopeGTCurve4OutRightTop': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 2, 1], [1, 3, 1], [1, 4, 1], [0, 2, 1], [0, 3, 1], [3, 4, 2], [2, 4, 2], [2, 5, 2], [3, 5, 2], [1, 5, 2], [1, 5, 1], [3, 6, 2], [2, 6, 2], [1, 6, 2], [3, 6, 3], [3, 7, 3], [3, 8, 3], [2, 6, 3], [2, 7, 3], [2, 7, 2], [1, 4, 2]], + 'StadiumCircuitBorderSlopeGTCurve4OutRightBottom': [[3, 0, 1], [3, 1, 1], [3, 2, 1], [3, 3, 1], [3, 0, 0], [3, 1, 0], [2, 1, 1], [2, 2, 1], [2, 3, 1], [2, 1, 0], [2, 0, 0], [3, 2, 0], [2, 2, 0], [1, 3, 1], [1, 4, 1], [1, 4, 2], [1, 5, 2], [1, 6, 2], [1, 6, 3], [1, 7, 3], [0, 7, 3], [0, 6, 3], [0, 8, 3], [0, 6, 2], [0, 5, 2], [1, 3, 2], [1, 2, 1], [1, 5, 3], [0, 5, 3]], + 'StadiumCircuitLoopStart': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0]], + 'StadiumCircuitRampBig': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitRampSmall1x05': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitRampSmall1x1': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitHole': [[0, 0, 0]], + 'StadiumCircuitBumpUp': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitBumpUp2': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1]], + 'StadiumCircuitBumpDown': [[0, 0, 0]], + 'StadiumCircuitPillar1': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitPillar2': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitPillar3': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitPillar5': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitPillar6': [[0, 0, 0], [0, 1, 0]], + 'StadiumCircuitPillar7': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadDirtFenceStraight': [[0, 0, 0]], + 'StadiumRoadDirtFenceCorner': [[0, 0, 0]], + 'StadiumRoadDirt': [[0, 0, 0]], + 'StadiumRoadDirtCheckpoint': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadDirtMultiLap': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadDirtTurbo': [[0, 0, 0]], + 'StadiumRoadDirtFreeWheeling': [[0, 0, 0]], + 'StadiumRoadDirtGTCurve2': [[0, 0, 0], [0, 0, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0]], + 'StadiumRoadDirtGTCurve3': [[0, 0, 0], [0, 0, 1], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 0], [2, 1, 1]], + 'StadiumRoadDirtGTCurve4': [[0, 0, 0], [0, 0, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 1], [2, 1, 2], [3, 0, 1], [3, 0, 2], [3, 0, 3], [3, 1, 1], [3, 1, 2], [2, 0, 3], [2, 1, 0]], + 'StadiumRoadDirtStraightBirds': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadDirtHighFenceStraight': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadDirtHighFenceCorner': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadDirtHigh': [[0, 0, 0]], + 'StadiumRoadDirtHighCheckpoint': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumRoadDirtHighMultiLap': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumRoadDirtHighTurbo': [[0, 0, 0]], + 'StadiumRoadDirtHighFreeWheeling': [[0, 0, 0]], + 'StadiumRoadDirtHighGTCurve2': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], + 'StadiumRoadDirtHighGTCurve3': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 0], [2, 1, 1], [2, 1, 2]], + 'StadiumRoadDirtHighFenceStraightBirds': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumRoadDirtToRoadDirtHigh': [[0, 0, 0]], + 'StadiumRoadDirtToRoadDirtHigh2': [[0, 0, 0], [0, 0, 1]], + 'StadiumRoadDirtToRoadDirtHighBridge': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadDirtToRoadDirtHighCross': [[0, 0, 0]], + 'StadiumRoadDirtHighToRoad': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadDirtToRoad': [[0, 0, 0]], + 'StadiumRoadDirtToRoadB': [[0, 0, 0]], + 'StadiumBump1InGround': [[0, 0, 0]], + 'StadiumRoadDirtHillWave': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1]], + 'StadiumRoadDirtHillSlope': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumRoadDirtHillSlope2': [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1]], + 'StadiumRoadMainSlopeUBottomInGround': [[0, 0, 0]], + 'StadiumRoadDirtHillSlopeGT2': [[0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 0]], + 'StadiumRoadDirtWave_x1': [[0, 0, 0]], + 'StadiumRoadDirtHillSlopeGT2Bis': [[0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1], [0, 3, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1]], + 'StadiumDirtDeadendDoor': [[0, 0, 0], [0, 1, 0]], + 'StadiumRoadDirtHillTiltStraight': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 0, 1]], + 'StadiumRoadDirtHillTiltCornerIn': [[0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1], [0, 3, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 2, 0], [1, 3, 0], [0, 0, 0]], + 'StadiumRoadDirtHillTiltGTCurve3In': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 2, 0], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [2, 0, 1], [2, 0, 2], [2, 1, 1], [2, 1, 2], [2, 2, 1], [2, 2, 2], [1, 1, 2], [0, 1, 1]], + 'StadiumRoadDirtHillTiltCornerOut': [[0, 0, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 2, 0]], + 'StadiumRoadDirtHillTiltCheckpointLeft': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0]], + 'StadiumRoadDirtHillTiltCheckpointRight': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0]], + 'StadiumRoadDirtHillTiltToRoadLeft': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 2, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 2, 0]], + 'StadiumRoadDirtHillTiltToRoadRight': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 2, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 2, 0]], + 'StadiumRoadDirtHillTiltCornerLeft': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0]], + 'StadiumRoadDirtHillTiltCornerRight': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0]], + 'StadiumRoadDirtDiagonaleLeft': [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 1], [0, 1, 2], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1]], + 'StadiumRoadDirtDiagonaleRight': [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 1], [1, 1, 2]], + 'StadiumRoadDirtHillTiltToRoadSlope': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1]], + 'StadiumRoadDirtHillTiltToRoadSlopeX2': [[0, 0, 0], [1, 0, 1], [1, 1, 1], [0, 0, 1], [0, 1, 0], [0, 2, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 2, 1]], + 'StadiumRoadDirtHighGTCurve4': [[0, 0, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 0], [2, 1, 1], [3, 0, 1], [3, 0, 2], [3, 0, 3], [3, 1, 2], [3, 1, 1], [0, 1, 0], [3, 1, 3], [1, 1, 1], [2, 1, 2], [0, 0, 1], [2, 0, 3]], + 'StadiumRoadDirtHillTiltToRoadSlopeMirror': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1]], + 'StadiumRoadDirtHillTiltToRoadSlopeX2Mirror': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 0, 1], [0, 1, 1], [1, 0, 1], [1, 0, 0], [1, 1, 0], [1, 2, 0], [0, 2, 1]], + 'StadiumRoadDirtHillTiltToRoadSlopeX4': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 2, 0], [2, 0, 0], [2, 0, 1], [2, 1, 0], [2, 1, 1], [2, 2, 0], [2, 2, 1], [3, 0, 1], [3, 1, 0], [3, 1, 1], [3, 2, 0], [3, 2, 1]], + 'StadiumRoadDirtHillTiltToRoadSlopeX4Mirror': [[0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [2, 0, 0], [2, 0, 1], [2, 1, 0], [2, 2, 0], [3, 0, 0], [3, 0, 1], [3, 1, 0], [3, 1, 1], [3, 2, 0]], + 'StadiumTrenchStraight': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchStraightTunnel': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchCorner': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchCross': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchCrossTunnel': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchGTCurve2': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]], + 'StadiumTrenchGTCurve3': [[0, 0, 0], [0, 1, 0], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 1], [2, 1, 2], [0, 0, 1]], + 'StadiumTrenchGTCurve3Tunnel': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 0, 1], [1, 0, 2], [2, 0, 1], [2, 0, 2], [2, 1, 2], [2, 0, 0]], + 'StadiumTrenchGTCurve2Tunnel': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 0, 1], [1, 1, 1]], + 'StadiumTrenchCheckpoint': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchMultiLap': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchFreeWheeling': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchTurbo': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchInPillar': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchToRoadMain': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchToRoadMainX2': [[0, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 1]], + 'StadiumTrenchToRoadMainBiSlopeStart': [[0, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 1]], + 'StadiumTrenchXRoadMain': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchToRoadDirt': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchToRoadDirtX2': [[0, 0, 0], [0, 1, 0], [0, 0, 1], [0, 1, 1]], + 'StadiumTrenchToPlatformBiSlopeStart': [[0, 0, 0], [0, 1, 0]], + 'StadiumTrenchToLoopStart': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumSculptBridgeSuspendSlope2': [[0, 0, 2], [0, 0, 3], [0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 2], [0, 2, 3], [0, 2, 4], [0, 3, 2], [0, 3, 3], [0, 3, 4], [0, 4, 2], [0, 4, 3], [0, 4, 4], [0, 5, 1], [0, 5, 2], [0, 5, 3], [0, 5, 4], [0, 6, 1], [0, 6, 2], [0, 6, 3], [0, 6, 4], [0, 7, 1], [0, 7, 2], [0, 7, 3], [0, 7, 4], [0, 8, 1], [0, 8, 2], [0, 8, 3], [0, 8, 4], [0, 9, 1], [0, 9, 2], [0, 9, 3], [0, 9, 4], [0, 10, 1], [0, 10, 2], [0, 10, 3], [0, 10, 4], [0, 11, 1], [0, 11, 2], [0, 11, 3], [0, 11, 4], [0, 12, 1], [0, 12, 2], [0, 12, 3], [0, 12, 4], [0, 13, 1], [0, 13, 3], [0, 13, 4], [0, 14, 1], [0, 14, 3], [0, 14, 4], [0, 14, 5], [0, 15, 1], [0, 15, 2], [0, 15, 3], [0, 15, 4], [0, 15, 5], [0, 16, 0], [0, 16, 1], [0, 16, 2], [0, 16, 3], [0, 16, 4], [0, 16, 5], [0, 17, 0], [0, 17, 1], [0, 17, 3], [0, 17, 4], [0, 17, 5], [0, 18, 0], [0, 18, 1], [0, 18, 4], [0, 18, 5], [0, 19, 0], [0, 19, 1], [0, 19, 2], [0, 19, 3], [0, 19, 4], [0, 19, 5], [0, 20, 0], [0, 20, 1], [0, 20, 5], [0, 21, 0], [0, 21, 1], [0, 22, 0], [0, 22, 1], [0, 23, 0], [0, 24, 0]], + 'StadiumSculptBridgeSuspendSlope2Mirror': [[0, 0, 2], [0, 0, 3], [0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 2], [0, 2, 3], [0, 2, 4], [0, 3, 2], [0, 3, 3], [0, 3, 4], [0, 4, 2], [0, 4, 3], [0, 4, 4], [0, 5, 1], [0, 5, 2], [0, 5, 3], [0, 5, 4], [0, 6, 1], [0, 6, 2], [0, 6, 3], [0, 6, 4], [0, 7, 1], [0, 7, 2], [0, 7, 3], [0, 7, 4], [0, 8, 1], [0, 8, 2], [0, 8, 3], [0, 8, 4], [0, 9, 1], [0, 9, 2], [0, 9, 3], [0, 9, 4], [0, 10, 1], [0, 10, 2], [0, 10, 3], [0, 10, 4], [0, 11, 1], [0, 11, 2], [0, 11, 3], [0, 11, 4], [0, 12, 1], [0, 12, 3], [0, 12, 4], [0, 13, 1], [0, 13, 3], [0, 13, 4], [0, 14, 1], [0, 14, 3], [0, 14, 4], [0, 15, 1], [0, 15, 2], [0, 15, 4], [0, 15, 5], [0, 16, 0], [0, 16, 1], [0, 16, 2], [0, 16, 3], [0, 16, 4], [0, 16, 5], [0, 17, 0], [0, 17, 1], [0, 17, 3], [0, 17, 4], [0, 17, 5], [0, 18, 0], [0, 18, 1], [0, 18, 4], [0, 18, 5], [0, 19, 0], [0, 19, 1], [0, 19, 2], [0, 19, 3], [0, 19, 4], [0, 19, 5], [0, 20, 0], [0, 20, 1], [0, 20, 5], [0, 21, 0], [0, 21, 1], [0, 22, 0], [0, 22, 1], [0, 23, 0], [0, 24, 0]], + 'StadiumSculptBridgeSuspendSlopeEnd': [[0, 0, 2], [0, 0, 3], [0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 2], [0, 2, 3], [0, 2, 4], [0, 3, 2], [0, 3, 3], [0, 3, 4], [0, 4, 2], [0, 4, 3], [0, 4, 4], [0, 5, 1], [0, 5, 2], [0, 5, 3], [0, 5, 4], [0, 6, 1], [0, 6, 2], [0, 6, 3], [0, 6, 4], [0, 7, 1], [0, 7, 2], [0, 7, 3], [0, 7, 4], [0, 8, 1], [0, 8, 2], [0, 8, 3], [0, 8, 4], [0, 9, 1], [0, 9, 2], [0, 9, 3], [0, 9, 4], [0, 10, 1], [0, 10, 2], [0, 10, 3], [0, 10, 4], [0, 11, 1], [0, 11, 2], [0, 11, 3], [0, 11, 4], [0, 12, 1], [0, 12, 3], [0, 12, 4], [0, 13, 1], [0, 13, 3], [0, 13, 4], [0, 14, 1], [0, 14, 3], [0, 14, 4], [0, 14, 5], [0, 15, 0], [0, 15, 1], [0, 15, 2], [0, 15, 4], [0, 15, 5], [0, 16, 0], [0, 16, 1], [0, 16, 2], [0, 16, 3], [0, 16, 4], [0, 16, 5], [0, 17, 0], [0, 17, 1], [0, 17, 3], [0, 17, 4], [0, 17, 5], [0, 18, 0], [0, 18, 4], [0, 18, 5], [0, 19, 5], [0, 20, 5]], + 'StadiumSculptBridgeSuspendSlopeEndMirror': [[0, 0, 2], [0, 0, 3], [0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 2], [0, 2, 3], [0, 2, 4], [0, 3, 2], [0, 3, 3], [0, 3, 4], [0, 4, 2], [0, 4, 3], [0, 4, 4], [0, 5, 1], [0, 5, 2], [0, 5, 3], [0, 5, 4], [0, 6, 1], [0, 6, 2], [0, 6, 3], [0, 6, 4], [0, 7, 1], [0, 7, 2], [0, 7, 3], [0, 7, 4], [0, 8, 1], [0, 8, 2], [0, 8, 3], [0, 8, 4], [0, 9, 1], [0, 9, 2], [0, 9, 3], [0, 9, 4], [0, 10, 1], [0, 10, 2], [0, 10, 3], [0, 10, 4], [0, 11, 1], [0, 11, 2], [0, 11, 3], [0, 11, 4], [0, 12, 1], [0, 12, 3], [0, 12, 4], [0, 13, 1], [0, 13, 3], [0, 13, 4], [0, 14, 1], [0, 14, 3], [0, 14, 4], [0, 14, 5], [0, 15, 0], [0, 15, 1], [0, 15, 2], [0, 15, 4], [0, 15, 5], [0, 16, 0], [0, 16, 1], [0, 16, 2], [0, 16, 3], [0, 16, 4], [0, 16, 5], [0, 17, 0], [0, 17, 1], [0, 17, 3], [0, 17, 4], [0, 17, 5], [0, 18, 0], [0, 18, 4], [0, 18, 5], [0, 19, 5], [0, 20, 5]], + 'StadiumSculptBridgePillar': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0]], + 'StadiumSculptBridgePillarMirror': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0]], + 'StadiumSculptBridgeSlopeStart': [[0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 4, 0]], + 'StadiumSculptBridgeSlopeStartMirror': [[0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 4, 0]], + 'StadiumSculptBridgeSlopeEnd': [[0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 4, 0]], + 'StadiumSculptBridgeSlopeEndMirror': [[0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 4, 0]], + 'StadiumSculptBridgeStraight': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1]], + 'StadiumSculptBridgeStraightSmall': [[0, 0, 0], [0, 1, 0]], + 'StadiumTubePillarCap': [[0, 0, 0]], + 'StadiumTubeV1': [[0, 0, 0]], + 'StadiumTubeV4': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0]], + 'StadiumTubeV8': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0]], + 'StadiumTube': [[0, 0, 0]], + 'StadiumTubePillar': [[0, 0, 0]], + 'StadiumTubePillarBranch': [[0, 0, 0]], + 'StadiumTubePillarBranch2': [[0, 0, 0]], + 'StadiumTubePillarBranch4': [[0, 0, 0]], + 'StadiumTubeRoad': [[0, 0, 0]], + 'StadiumTubeRoadDown': [[0, 0, 0]], + 'StadiumTubeRoadUp': [[0, 0, 0]], + 'StadiumTubeRoadCross': [[0, 0, 0]], + 'StadiumControlRoadGlass': [[0, 0, 0]], + 'StadiumControlRoadPub': [[0, 0, 0]], + 'StadiumControlRoadCamera': [[0, 0, 0]], + 'StadiumControlCameraPub': [[0, 0, 0]], + 'StadiumControlLight': [[0, 0, 0]], + 'StadiumControlLightBase': [[0, 0, 0]], + 'StadiumTubeRoadLightSystem': [[0, 0, 0]], + 'StadiumTubeRoadSoundSystem': [[0, 0, 0]], + 'StadiumFabricStraight1x1': [[0, 4, 0], [0, 5, 0], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 3, 0], [1, 4, 0]], + 'StadiumFabricPillarAir': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumFabricCornerIn': [[0, 0, 1], [0, 1, 1], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 4, 0], [0, 4, 1], [0, 5, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 0], [1, 4, 0]], + 'StadiumFabricPillarCornerInAir': [[0, 0, 1], [0, 1, 1], [0, 2, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1]], + 'StadiumFabricCornerOut': [[0, 5, 0], [0, 5, 1], [1, 0, 0], [1, 1, 0], [1, 2, 0], [1, 3, 0], [1, 4, 0], [1, 5, 0], [1, 5, 1]], + 'StadiumFabricPillarCornerOut': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumFabricCross1x1': [[0, 0, 0]], + 'StadiumFabricCross3x3': [[2, 0, 0], [2, 1, 0], [1, 1, 0], [1, 0, 0], [0, 0, 0], [0, 1, 0], [2, 1, 1], [2, 1, 2], [1, 1, 2], [0, 1, 2], [0, 1, 1], [1, 1, 1], [0, 0, 2], [0, 0, 1], [1, 0, 2], [2, 0, 2], [2, 0, 1]], + 'StadiumFabricCross3x3Screen': [[0, 0, 1], [0, 1, 1], [0, 2, 1], [0, 3, 0], [0, 3, 1], [0, 3, 2], [0, 4, 0], [0, 4, 1], [0, 4, 2], [0, 5, 0], [0, 5, 1], [0, 5, 2], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1], [1, 1, 2], [1, 2, 0], [1, 2, 1], [1, 2, 2], [1, 3, 0], [1, 3, 1], [1, 3, 2], [1, 4, 0], [1, 4, 1], [1, 4, 2], [1, 5, 0], [1, 5, 1], [1, 5, 2], [2, 0, 1], [2, 1, 1], [2, 2, 1], [2, 3, 0], [2, 3, 1], [2, 3, 2], [2, 4, 0], [2, 4, 1], [2, 4, 2], [2, 5, 0], [2, 5, 1], [2, 5, 2]], + 'StadiumFabricPillarAirScreen': [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [0, 2, 0], [0, 2, 1]], + 'StadiumFabricPillarScreenSmall': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumFabricRamp': [[0, 0, 0]], + 'StadiumFabricRampCornerOut': [[0, 0, 0]], + 'StadiumFabricRampCornerIn': [[0, 0, 1], [1, 0, 0], [1, 0, 1]], + 'StadiumInflatableSupport': [[0, 0, 0]], + 'StadiumInflatablePillar': [[0, 0, 0]], + 'StadiumInflatableTube': [[0, 0, 0]], + 'StadiumInflatableAdvert': [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 2, 0], [1, 2, 0], [0, 3, 0], [1, 3, 0]], + 'StadiumAirship': [[0, 0, 2], [0, 1, 1], [0, 1, 2], [0, 2, 1], [0, 2, 2], [0, 2, 3], [0, 2, 4], [0, 2, 5], [0, 3, 0], [0, 3, 1], [0, 3, 2], [0, 3, 3], [0, 3, 4], [0, 3, 5], [0, 4, 0], [0, 4, 1], [0, 4, 2], [0, 4, 3], [0, 4, 4], [0, 4, 5], [0, 5, 0], [0, 5, 1], [0, 5, 2], [0, 5, 3], [0, 5, 4], [0, 5, 5], [0, 6, 0], [0, 6, 1], [0, 6, 2], [0, 6, 3], [0, 6, 4], [0, 6, 5]], + 'StadiumAirshipDiag': [[0, 4, 0], [2, 0, 2], [2, 1, 2], [2, 2, 2], [2, 3, 2], [2, 4, 2], [2, 5, 2], [2, 6, 2], [2, 6, 1], [2, 5, 1], [2, 4, 1], [2, 3, 1], [2, 2, 1], [1, 2, 2], [1, 3, 2], [1, 4, 2], [1, 5, 2], [1, 6, 2], [1, 6, 1], [1, 5, 1], [1, 4, 1], [1, 3, 1], [0, 5, 1], [0, 4, 1], [0, 3, 1], [1, 5, 0], [1, 4, 0], [1, 3, 0], [2, 2, 3], [2, 3, 3], [2, 4, 3], [2, 5, 3], [2, 6, 3], [3, 6, 2], [3, 5, 2], [3, 4, 2], [3, 3, 2], [3, 2, 2], [3, 2, 3], [3, 3, 3], [3, 4, 3], [3, 5, 3], [3, 6, 3], [4, 6, 4], [4, 5, 4], [4, 4, 4], [4, 3, 4], [4, 2, 4], [4, 3, 3], [4, 4, 3], [4, 5, 3], [3, 5, 4], [3, 4, 4], [3, 3, 4], [1, 2, 1], [0, 3, 0], [0, 5, 0]], + 'StadiumInflatableCastle': [[0, 0, 0], [0, 1, 0], [0, 2, 0]], + 'StadiumInflatableCastleDoor': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [1, 1, 0], [2, 0, 0], [2, 1, 0], [2, 2, 0]], + 'StadiumInflatableCastleBig': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1]], + 'StadiumInflatableCactus': [[0, 0, 0], [0, 1, 0]], + 'StadiumInflatableSnowTree': [[0, 0, 0], [0, 1, 0]], + 'StadiumInflatablePalmTree': [[0, 0, 0], [0, 1, 0]], + 'StadiumSculptA': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 0]], + 'StadiumSculptB': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 0]], + 'StadiumSculptC': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 0]], + 'StadiumSculptArchRingEnd': [[0, 1, 1], [0, 2, 1], [0, 3, 1], [0, 4, 1], [0, 4, 2], [0, 5, 1], [0, 5, 2], [0, 6, 1], [0, 6, 2], [0, 7, 1], [0, 7, 2], [0, 7, 3], [0, 8, 2], [0, 8, 3], [0, 9, 2], [0, 9, 3], [0, 10, 2], [0, 10, 3], [0, 11, 3], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 0], [1, 3, 1], [1, 4, 1], [1, 4, 2], [1, 5, 1], [1, 5, 2], [1, 6, 1], [1, 6, 2], [1, 7, 1], [1, 7, 2], [1, 7, 3], [1, 8, 2], [1, 8, 3], [1, 9, 2], [1, 9, 3], [1, 9, 4], [1, 10, 2], [1, 10, 3], [1, 10, 4], [1, 10, 5], [1, 11, 3], [1, 11, 4], [1, 11, 5], [1, 12, 4], [1, 12, 5], [2, 0, 0], [2, 0, 1], [2, 1, 0], [2, 1, 1], [2, 2, 0], [2, 2, 1], [2, 9, 4], [2, 10, 4], [2, 10, 5], [2, 11, 4], [2, 11, 5], [2, 12, 4], [2, 12, 5], [2, 13, 5], [3, 10, 4], [3, 10, 5], [3, 11, 4], [3, 11, 5], [3, 12, 4], [3, 12, 5], [3, 13, 5]], + 'StadiumSculptArchRingEndMirror': [[0, 10, 4], [0, 10, 5], [0, 11, 4], [0, 11, 5], [0, 12, 4], [0, 12, 5], [0, 13, 5], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 9, 4], [1, 10, 4], [1, 10, 5], [1, 11, 4], [1, 11, 5], [1, 12, 4], [1, 12, 5], [1, 13, 5], [2, 0, 0], [2, 0, 1], [2, 1, 0], [2, 1, 1], [2, 2, 0], [2, 2, 1], [2, 3, 0], [2, 3, 1], [2, 4, 1], [2, 4, 2], [2, 5, 1], [2, 5, 2], [2, 6, 1], [2, 6, 2], [2, 7, 1], [2, 7, 2], [2, 7, 3], [2, 8, 2], [2, 8, 3], [2, 9, 2], [2, 9, 3], [2, 9, 4], [2, 10, 2], [2, 10, 3], [2, 10, 4], [2, 10, 5], [2, 11, 3], [2, 11, 4], [2, 11, 5], [2, 12, 4], [2, 12, 5], [3, 1, 1], [3, 2, 1], [3, 3, 1], [3, 4, 1], [3, 4, 2], [3, 5, 1], [3, 5, 2], [3, 6, 1], [3, 6, 2], [3, 7, 1], [3, 7, 2], [3, 7, 3], [3, 8, 2], [3, 8, 3], [3, 9, 2], [3, 9, 3], [3, 10, 2], [3, 10, 3], [3, 11, 3]], + 'StadiumSculptArchRingStart': [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 1], [0, 1, 2], [0, 2, 1], [0, 2, 2], [0, 2, 3], [0, 3, 2], [0, 3, 3], [0, 4, 2], [0, 4, 3], [0, 5, 2], [0, 5, 3], [0, 6, 3], [0, 7, 3], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 2, 2], [1, 2, 3], [1, 3, 2], [1, 3, 3], [1, 4, 2], [1, 4, 3], [1, 5, 2], [1, 5, 3], [1, 5, 4], [1, 6, 3], [1, 6, 4], [1, 7, 3], [1, 7, 4], [1, 8, 3], [1, 8, 4], [1, 9, 3], [1, 9, 4], [1, 10, 4], [2, 0, 0], [2, 6, 4], [2, 7, 3], [2, 7, 4], [2, 8, 3], [2, 8, 4], [2, 9, 3], [2, 9, 4], [2, 10, 4], [2, 10, 5], [2, 11, 4], [2, 11, 5], [2, 12, 4], [3, 8, 4], [3, 9, 4], [3, 10, 4], [3, 10, 5], [3, 11, 4], [3, 11, 5], [3, 12, 4], [3, 12, 5], [3, 13, 4]], + 'StadiumSculptArchRingStartMirror': [[0, 8, 4], [0, 9, 4], [0, 10, 4], [0, 10, 5], [0, 11, 4], [0, 11, 5], [0, 12, 4], [0, 12, 5], [0, 13, 4], [1, 0, 0], [1, 6, 3], [1, 6, 4], [1, 7, 3], [1, 7, 4], [1, 8, 3], [1, 8, 4], [1, 9, 3], [1, 9, 4], [1, 10, 4], [1, 10, 5], [1, 11, 4], [1, 11, 5], [1, 12, 4], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 1], [2, 1, 2], [2, 2, 1], [2, 2, 2], [2, 2, 3], [2, 3, 2], [2, 3, 3], [2, 4, 2], [2, 4, 3], [2, 5, 2], [2, 5, 3], [2, 5, 4], [2, 6, 3], [2, 6, 4], [2, 7, 3], [2, 7, 4], [2, 8, 3], [2, 8, 4], [2, 9, 3], [2, 9, 4], [2, 10, 4], [3, 0, 0], [3, 0, 1], [3, 0, 2], [3, 1, 1], [3, 1, 2], [3, 2, 1], [3, 2, 2], [3, 2, 3], [3, 3, 2], [3, 3, 3], [3, 4, 2], [3, 4, 3], [3, 5, 2], [3, 5, 3], [3, 6, 3], [3, 7, 3]], + 'StadiumPodium': [[0, 0, 0], [0, 1, 0]], + 'StadiumDecoTowerCore6': [[0, 0, 0], [0, 1, 0], [0, 5, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0]], + 'StadiumDecoTowerCore': [[0, 0, 0]], + 'StadiumDecoTowerBeam': [[0, 0, 0]], + 'StadiumDecoTowerA': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0]], + 'StadiumDecoTowerB': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0]], + 'StadiumDecoTowerD': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0]], + 'StadiumDecoTowerC': [[0, 0, 0], [0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0]] +} + +DYNAMIC_GROUND_OFFSETS = { + 'StadiumFabricPillarCornerOut': [[0, 0, 0]], + 'StadiumFabricPillarCornerInAir': [[0, 0, 1], [1, 0, 0], [1, 0, 1]], + 'StadiumFabricPillarAir': [[0, 0, 0]] +} \ No newline at end of file diff --git a/pygbx/stadium_blocks.py b/pygbx/stadium_blocks.py new file mode 100644 index 0000000..c955f28 --- /dev/null +++ b/pygbx/stadium_blocks.py @@ -0,0 +1,369 @@ +STADIUM_BLOCKS = { + 'StadiumGrassClip': 1, + 'StadiumDirt': 2, + 'StadiumDirtBorder': 3, + 'StadiumWater': 4, + 'StadiumPool': 5, + 'StadiumRoadMain': 6, + 'StadiumRoadMainGTCurve2': 7, + 'StadiumRoadMainGTCurve3': 8, + 'StadiumRoadMainGTCurve4': 9, + 'StadiumRoadMainGTCurve5': 10, + 'StadiumLoopLeft': 11, + 'StadiumLoopRight': 12, + 'StadiumRoadMainCheckpoint': 13, + 'StadiumRoadMainStartLine': 14, + 'StadiumRoadMainFinishLine': 15, + 'StadiumRoadMainStartFinishLine': 16, + 'StadiumRoadMainTurbo': 17, + 'StadiumRoadMainTurboRoulette': 18, + 'StadiumRoadMainFW': 19, + 'StadiumGrassCheckpoint': 20, + 'StadiumHolePillar': 21, + 'StadiumHolePillar2Front': 22, + 'StadiumHolePillar2Line': 23, + 'StadiumHolePillar3': 24, + 'StadiumHole': 25, + 'StadiumRoadStretch': 26, + 'StadiumBump1': 27, + 'StadiumRamp': 28, + 'StadiumRampLow': 29, + 'StadiumRoadMainGTDiag2x2': 30, + 'StadiumRoadMainGTDiag2x2Mirror': 31, + 'StadiumRoadMainGTDiag3x2': 32, + 'StadiumRoadMainGTDiag3x2Mirror': 33, + 'StadiumRoadMainGTDiag4x3': 34, + 'StadiumRoadMainGTDiag4x3Mirror': 35, + 'StadiumRoadMainYShapedDiag2': 36, + 'StadiumRoadMainYShapedCurve2': 37, + 'StadiumRoadMainSlopeBase2': 38, + 'StadiumRoadMainSlopeBase': 39, + 'StadiumRoadMainSlopeStraight': 40, + 'StadiumRoadMainBiSlopeStart': 41, + 'StadiumRoadMainBiSlopeEnd': 42, + 'StadiumRoadMainSlopeUTop': 43, + 'StadiumRoadMainSlopeUBottom': 44, + 'StadiumRoadMainCheckpointUp': 45, + 'StadiumRoadMainCheckpointDown': 46, + 'StadiumRoadMainTurboUp': 47, + 'StadiumRoadMainTurboDown': 48, + 'StadiumRoadMainTurboRouletteDown': 49, + 'StadiumRoadMainTurboRouletteUp': 50, + 'StadiumHolePillarSlope': 51, + 'StadiumHoleSlope': 52, + 'StadiumRoadStretchSlope': 53, + 'StadiumBump1Slope': 54, + 'StadiumRoadMainGTDiag2x2Slope': 55, + 'StadiumRoadMainGTDiag2x2SlopeMirror': 56, + 'StadiumRoadMainGTDiag3x2Slope': 57, + 'StadiumRoadMainGTDiag3x2SlopeMirror': 58, + 'StadiumRoadMainGTDiag4x3Slope': 59, + 'StadiumRoadMainYShapedDiag2SlopeUp': 60, + 'StadiumRoadMainYShapedDiag2SlopeDown': 61, + 'StadiumRoadMainGTDiag4x3SlopeMirror': 62, + 'StadiumRoadTiltStraight': 63, + 'StadiumRoadTiltTransition1Left': 64, + 'StadiumRoadTiltTransition1Right': 65, + 'StadiumRoadTiltTransition2Left': 66, + 'StadiumRoadTiltTransition2Right': 67, + 'StadiumRoadTiltTransition2CurveLeft': 68, + 'StadiumRoadTiltTransition2CurveRight': 69, + 'StadiumRoadTiltTransition2DiagLeft': 70, + 'StadiumRoadTiltTransition2DiagRight': 71, + 'StadiumRoadMainCheckpointLeft': 72, + 'StadiumRoadMainCheckpointRight': 73, + 'StadiumRoadMainTurboLeft': 74, + 'StadiumRoadMainTurboRight': 75, + 'StadiumRoadMainTurboRouletteLeft': 76, + 'StadiumRoadMainTurboRouletteRight': 77, + 'StadiumRoadTiltCorner': 78, + 'StadiumRoadTiltGTCurve2': 79, + 'StadiumRoadTiltGTCurve3': 80, + 'StadiumRoadTiltGTCurve4': 81, + 'StadiumRoadMainGTDiag2x2Tilt': 82, + 'StadiumRoadMainGTDiag2x2TiltMirror': 83, + 'StadiumRoadMainGTDiag3x2Tilt': 84, + 'StadiumRoadMainGTDiag3x2TiltMirror': 85, + 'StadiumRoadTiltCornerDownLeft': 86, + 'StadiumRoadTiltCornerDownRight': 87, + 'StadiumRoadTiltCornerUpLeft': 88, + 'StadiumRoadTiltCornerUpRight': 89, + 'StadiumRoadTiltGTCurve2DownLeft': 90, + 'StadiumRoadTiltGTCurve2DownRight': 91, + 'StadiumRoadTiltGTCurve2UpLeft': 92, + 'StadiumRoadTiltGTCurve2UpRight': 93, + 'StadiumHolePillarTilt': 94, + 'StadiumHoleTilt': 95, + 'StadiumStretchTilt': 96, + 'StadiumBump1Tilt': 97, + 'StadiumPlatformLoopStart': 98, + 'StadiumPlatformLoopEnd': 99, + 'StadiumPlatformWallBorder': 100, + 'StadiumPlatformWall1': 101, + 'StadiumPlatformWall2': 102, + 'StadiumPlatformWall4': 103, + 'StadiumPlatformWallPub2': 104, + 'StadiumPlatformRoad': 105, + 'StadiumPlatformSlope2Straight': 106, + 'StadiumPlatformBiSlope2Start': 107, + 'StadiumPlatformBiSlope2End': 108, + 'StadiumPlatformTurbo': 109, + 'StadiumPlatformTurboDown': 110, + 'StadiumPlatformTurboLeft': 111, + 'StadiumPlatformTurboRight': 112, + 'StadiumPlatformTurboUp': 113, + 'StadiumCheckpointRingV': 114, + 'StadiumCheckpointRingHRoad': 115, + 'StadiumPlatformCheckpoint': 116, + 'StadiumPlatformCheckpointDown': 117, + 'StadiumPlatformCheckpointLeft': 118, + 'StadiumPlatformCheckpointRight': 119, + 'StadiumPlatformCheckpointUp': 120, + 'StadiumPlatformGTCurve2Wall1': 121, + 'StadiumPlatformGTCurve2Wall2': 122, + 'StadiumPlatformGTCurve2Wall4': 123, + 'StadiumPlatformGTCurve3Wall1': 124, + 'StadiumPlatformGTCurve3Wall2': 125, + 'StadiumPlatformGTCurve3Wall4': 126, + 'StadiumPlatformToRoad': 127, + 'StadiumPlatformToRoad2': 128, + 'StadiumPlatformToRoad2Mirror': 129, + 'StadiumPlatformToRoadMain': 130, + 'StadiumCircuitBorderStraight': 131, + 'StadiumCircuitBorderDiagIn': 132, + 'StadiumCircuitBorderDiagOut': 133, + 'StadiumCircuitBorderCornerIn': 134, + 'StadiumCircuitBorderCornerOut': 135, + 'StadiumCircuitBase': 136, + 'StadiumCircuitSlopeStart': 137, + 'StadiumCircuitSlopeStraight': 138, + 'StadiumCircuitSlopeEnd': 139, + 'StadiumCircuitTurbo': 140, + 'StadiumCircuitTurboLeft': 141, + 'StadiumCircuitTurboUp': 142, + 'StadiumCircuitTurboRight': 143, + 'StadiumCircuitTurboDown': 144, + 'StadiumCircuitBorderGTCurve2In': 145, + 'StadiumCircuitBorderGTCurve3In': 146, + 'StadiumCircuitBorderGTCurve4In': 147, + 'StadiumCircuitBorderGTCurve2Out': 148, + 'StadiumCircuitBorderGTCurve3Out': 149, + 'StadiumCircuitBorderGTCurve4Out': 150, + 'StadiumCircuitBorderSlopeStartLeft': 151, + 'StadiumCircuitBorderSlopeStartRight': 152, + 'StadiumCircuitBorderSlopeStraightLeft': 153, + 'StadiumCircuitBorderSlopeStraightRight': 154, + 'StadiumCircuitBorderSlopeStraightBottom': 155, + 'StadiumCircuitBorderSlopeStraightTop': 156, + 'StadiumCircuitBorderSlopeEndLeft': 157, + 'StadiumCircuitBorderSlopeEndRight': 158, + 'StadiumCircuitBorderSlopeCornerInLeftBottom': 159, + 'StadiumCircuitBorderSlopeCornerInLeftTop': 160, + 'StadiumCircuitBorderSlopeCornerInRightTop': 161, + 'StadiumCircuitBorderSlopeCornerInRightBottom': 162, + 'StadiumCircuitSlopeCornerInLeftBottom': 163, + 'StadiumCircuitBorderSlopeCornerOutLeftTop': 164, + 'StadiumCircuitBorderSlopeCornerOutRightTop': 165, + 'StadiumCircuitBorderSlopeCornerOutRightBottom': 166, + 'StadiumCircuitBorderSlopeGTCurve2InLeftBottom': 167, + 'StadiumCircuitBorderSlopeGTCurve2InLeftTop': 168, + 'StadiumCircuitBorderSlopeGTCurve2InRightTop': 169, + 'StadiumCircuitBorderSlopeGTCurve2InRightBottom': 170, + 'StadiumCircuitBorderSlopeGTCurve2OutLeftBottom': 171, + 'StadiumCircuitBorderSlopeGTCurve2OutLeftTop': 172, + 'StadiumCircuitBorderSlopeGTCurve2OutRightTop': 173, + 'StadiumCircuitBorderSlopeGTCurve2OutRightBottom': 174, + 'StadiumCircuitBorderSlopeDiagInLeftBottom': 175, + 'StadiumCircuitBorderSlopeDiagInLeftTop': 176, + 'StadiumCircuitBorderSlopeDiagInRightTop': 177, + 'StadiumCircuitBorderSlopeDiagInRightBottom': 178, + 'StadiumCircuitBorderSlopeDiagOutLeftBottom': 179, + 'StadiumCircuitBorderSlopeDiagOutLeftTop': 180, + 'StadiumCircuitBorderSlopeDiagOutRightTop': 181, + 'StadiumCircuitBorderSlopeDiagOutRightBottom': 182, + 'StadiumCircuitLoopStart': 183, + 'StadiumCircuitRampBig': 184, + 'StadiumCircuitRampSmall1x05': 185, + 'StadiumCircuitRampSmall1x1': 186, + 'StadiumCircuitHole': 187, + 'StadiumCircuitBumpUp': 188, + 'StadiumCircuitBumpUp2': 189, + 'StadiumCircuitPillar1': 190, + 'StadiumCircuitPillar2': 191, + 'StadiumCircuitPillar3': 192, + 'StadiumCircuitPillar5': 193, + 'StadiumCircuitPillar6': 194, + 'StadiumCircuitPillar7': 195, + 'StadiumRoadDirtFenceStraight': 196, + 'StadiumRoadDirtFenceCorner': 197, + 'StadiumRoadDirt': 198, + 'StadiumRoadDirtTurbo': 199, + 'StadiumRoadDirtCheckpoint': 200, + 'StadiumRoadDirtGTCurve2': 201, + 'StadiumRoadDirtGTCurve3': 202, + 'StadiumRoadDirtDiagonaleRight': 203, + 'StadiumRoadDirtDiagonaleLeft': 204, + 'StadiumRoadDirtHighFenceStraight': 205, + 'StadiumRoadDirtHighFenceCorner': 206, + 'StadiumRoadDirtHigh': 207, + 'StadiumRoadDirtHighCheckpoint': 208, + 'StadiumRoadDirtHighGTCurve3': 209, + 'BStadiumRoadDirtToRoadDirtHigh': 210, + 'StadiumRoadDirtToRoadDirtHigh2': 211, + 'StadiumRoadDirtToRoadDirtHighBridge': 212, + 'StadiumRoadDirtToRoadDirtHighCross': 213, + 'StadiumRoadDirtToRoadDirtHigh': 214, + 'StadiumRoadDirtToRoad': 215, + 'StadiumRoadDirtHillSlope2': 216, + 'StadiumRoadDirtHillSlope': 217, + 'StadiumRoadDirtHillWave': 218, + 'StadiumRoadDirtHillSlopeGT2Bis': 219, + 'StadiumRoadDirtHillSlopeGT2': 220, + 'StadiumRoadDirtWave_x2': 221, + 'StadiumRoadDirtWave_x1': 222, + 'StadiumRoadDirtToRoadGrass': 223, + 'StadiumDirtDeadendDoor': 224, + 'StadiumDirtDeadendSpot': 225, + 'StadiumDirtDeadendTent1': 226, + 'StadiumRoadDirtHillTiltStraight': 227, + 'StadiumRoadDirtHillTiltCornerIn': 228, + 'StadiumRoadDirtHillTiltCornerOut': 229, + 'StadiumRoadDirtHillTiltCornerLeft': 230, + 'StadiumRoadDirtHillTiltCornerRight': 231, + 'StadiumRoadDirtHillTiltToRoadLeft': 232, + 'StadiumRoadDirtHillTiltToRoadRight': 233, + 'StadiumSculptBridgeSuspendSlope2': 234, + 'StadiumSculptBridgeSuspendSlope2Mirror': 235, + 'StadiumSculptBridgeSuspendSlopeEnd': 236, + 'StadiumSculptBridgeSuspendSlopeEndMirror': 237, + 'StadiumSculptA': 238, + 'StadiumSculptB': 239, + 'StadiumSculptC': 240, + 'StadiumSculptArchRingEnd': 241, + 'StadiumSculptArchRingEndMirror': 242, + 'StadiumSculptArchRingStart': 243, + 'StadiumSculptArchRingStartMirror': 244, + 'StadiumSculptBridgePillar': 245, + 'StadiumSculptBridgePillarMirror': 246, + 'StadiumSculptBridgeSlopeStart': 247, + 'StadiumSculptBridgeSlopeStartMirror': 248, + 'StadiumSculptBridgeSlopeEnd': 249, + 'StadiumSculptBridgeSlopeEndMirror': 250, + 'StadiumSculptBridgeStraight': 251, + 'StadiumSculptBridgeStraightSmall': 252, + 'StadiumTubePillarCap': 253, + 'StadiumTubeV1': 254, + 'StadiumTubeV4': 255, + 'StadiumTubeV8': 256, + 'StadiumTube': 257, + 'StadiumTubePillar': 258, + 'StadiumTubePillarBranch': 259, + 'StadiumTubeRoad': 260, + 'StadiumTubeRoadDown': 261, + 'StadiumTubeRoadUp': 262, + 'StadiumTubeRoadCross': 263, + 'StadiumControlRoadGlass': 264, + 'StadiumControlRoadPub': 265, + 'StadiumControlRoadCamera': 266, + 'StadiumControlCameraPub': 267, + 'StadiumControlLight': 268, + 'StadiumControlLightBase': 269, + 'StadiumTubeRoadLightSystem': 270, + 'StadiumTubeRoadSoundSystem': 271, + 'StadiumFabricStraight1x1': 272, + 'StadiumFabricPillarAir': 273, + 'StadiumFabricCornerIn': 274, + 'StadiumFabricPillarCornerInAir': 275, + 'StadiumFabricCornerOut': 276, + 'StadiumFabricPillarCornerOut': 277, + 'StadiumFabricCross1x1': 278, + 'StadiumFabricCross3x3': 279, + 'StadiumFabricCross3x3Screen': 280, + 'StadiumFabricPillarAirScreen': 281, + 'StadiumInflatableSupport': 282, + 'StadiumInflatablePillar': 283, + 'StadiumInflatableTube': 284, + 'StadiumInflatableAdvert': 285, + 'StadiumAirship': 286, + 'StadiumInflatableCastle': 287, + 'StadiumInflatableCastleDoor': 288, + 'StadiumInflatableCastleBig': 289, + 'StadiumInflatableCactus': 290, + 'StadiumInflatableSnowTree': 291, + 'StadiumInflatablePalmTree': 292, + + # TM2 Stadium specifc blocks + 'StadiumRoadMainBirds': 293, + 'StadiumGrassBirds': 294, + 'StadiumRoadMainInBeam': 295, + 'StadiumRoadMainXBeam': 296, + 'StadiumRoadMainFWSlope': 297, + 'StadiumRoadMainInBeamSlope': 298, + 'StadiumRoadMainXBeamSlope': 299, + 'StadiumRoadMainTiltStraightBirds': 300, + 'StadiumRoadMainFWTilt': 301, + 'StadiumRoadMainInBeamTilt': 302, + 'StadiumRoadMainXBeamTilt': 303, + 'StadiumRoadMainSlopeBase1x2': 304, + 'StadiumBump1InGround': 305, + 'StadiumPlatformWallCheckpointH': 306, + 'StadiumPlatformWallCheckpointV': 307, + 'StadiumPlatformWall1Birds': 308, + 'StadiumPlatformFW': 309, + 'StadiumPlatformFWSlope2': 310, + 'StadiumPlatformMultilap': 311, + 'StadiumPlatformGridCheckpoint': 312, + 'StadiumPlatformGridFW': 313, + 'StadiumPlatformGridStraight': 314, + 'StadiumPlatformGridTurbo': 315, + 'StadiumPlatformGridStraightBirds': 316, + 'StadiumCheckpointRing2x1V': 317, + 'StadiumCheckpointRing2x1H': 318, + 'StadiumCircuitToRoadMain': 319, + 'StadiumCircuitFreeWheeling': 320, + 'StadiumCircuitFreeWheelingSlope2Air': 321, + 'StadiumRoadDirtGTCurve4': 322, + 'StadiumRoadDirtStartFinishLine': 323, + 'StadiumRoadDirtMultiLap': 324, + 'StadiumRoadDirtStraightBirds': 325, + 'StadiumRoadDirtHighMultiLap': 326, + 'StadiumRoadDirtHighDTCurve4': 327, + 'StadiumRoadDirtHighFenceStraightBirds': 328, + 'StadiumTrenchStraight': 329, + 'StadiumTrenchStraightTunnel': 330, + 'StadiumTrenchCorner': 331, + 'StadiumTrenchCross': 332, + 'StadiumTrenchCrossTunnel': 333, + 'StadiumTrenchGTCurve2': 334, + 'StadiumTrenchGTCurve3': 335, + 'StadiumTrenchGTCurve3Tunnel': 336, + 'StadiumTrenchGTCurve2Tunnel': 337, + 'StadiumTrenchCheckpoint': 338, + 'StadiumTrenchMultiLap': 339, + 'StadiumTrenchFreeWheeling': 340, + 'StadiumTrenchStraightScreen': 341, + 'StadiumTrenchInPillar': 342, + 'StadiumTrenchToRoadMain': 343, + 'StadiumTrenchToRoadMainBiSlopeStart': 344, + 'StadiumTrenchXRoadMain': 345, + 'StadiumTrenchToRoadDirt': 346, + 'StadiumTrenchToRoadDirtX2': 347, + 'StadiumTrenchToPlatformBiSlopeStart': 348, + 'StadiumTrenchToLoopStart': 349, + 'StadiumPodium': 350, + 'StadiumDecoTowerCore6': 351, + 'StadiumDecoTowerCore': 352, + 'StadiumDecoTowerTowerBeam': 353, + 'StadiumDecoTowerTowerA': 354, + 'StadiumDecoTowerTowerB': 355, + 'StadiumDecoTowerTowerD': 356, + 'StadiumDecoTowerTowerC': 357, + + # TODO: sort this out + 'StadiumRoadDirtHighToRoad': 358, + 'StadiumDirtHill': 359, + 'StadiumPlatformBiSlope2StartSmall': 360, + 'StadiumWaterClip': 361, + 'StadiumDirtClip': 362, + 'StadiumGrass': 363, +} diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..224a779 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = README.md \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..bcc6e9e --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +from distutils.core import setup +setup( + name = 'pygbx', + packages = ['pygbx'], + version = '0.1', + license='GPL3', + description = 'A Python library to parse GBX files', + author = 'Adam Bieńkowski', + author_email = 'donadigos159@gmail.com', + url = 'https://github.com/donadigo/pygbx', + download_url = 'https://github.com/user/reponame/archive/v_01.tar.gz', + keywords = ['GBX', 'parser', 'TrackMania'], + install_requires=[ + 'python-lzo', + ], + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Build Tools', + 'License :: OSI Approved :: GPL3 License', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], +) \ No newline at end of file