Example #1
0
 def parse_graph(self, raw_graphs):
     start = 0
     self.one_year = [parse_int32(raw_graphs[x : x + 4]) for x in range(start, start + 12 * 4, 4)]
     start += 12 * 4
     self.ten_years = [parse_int32(raw_graphs[x : x + 4]) for x in range(start, start + 20 * 4, 4)]
     start += 20 * 4
     self.hundred_years = [parse_int32(raw_graphs[x : x + 4]) for x in range(start, start + 20 * 4, 4)]
Example #2
0
def mac_fix(input_data):
    """
    Makes a Mac city file compatible with the Win95 version of the game.
    Basically, we don't need the first 0x80 bytes from the Mac file, something about a resource fork. Also, some of the files have garbage data at the end, which is also trimmed.
    Args:
        input_data (bytes): raw city information.
    Returns:
        Bytes comprising a compatible SC2k Win95 city file from the Mac file.
    """
    reported_size = parse_int32(input_data[0x84 : 0x88]) + 8
    return input[0x80 : 0x80 + reported_size]
Example #3
0
def check_file(input_data, input_type):
    """
    Does some basic checks of the file to make sure it's valid and includes special handling for the Mac version of the game. The IFF standard is from 1985, so it's not super robust...
    Untested with some of the weirder versions of SC2k, such as Amiga, PocketPC/Windows Mobile, etc.
    Currently only supports parsing for FORM and MIFF files.
    Args:
        input_data (bytes): bytes containing the entirety of the city.
        input_type (str): type of input file, supported are 'mif' for .mif tileset/MIFF file and 'sc2' for .sc2 city file.
    Returns:
        A tuple containing a dictionary and the input.
        The dictionary looks like {'type_id': header, 'data_size': reported_size, 'file_type': file_type} where the header is the opening 4 bytes of input as a bytestring, reported_size is an int of the size the file claims to be and file_type is one of b"SC2K" (tileset) of b"SCDH" (city).
    Raises:
        SC2Parse: an error relating to parsing .sc2 files. Could be caused by the file being a SimCity classic city (currently an unsupported format), not a city file at all, or being corrupted.
        MIFFParse: an error relating to parsing .mif files. Could be caused by file corruption of not actually being a tileset file.
    """
    # Check and convert if this is a Mac city file.
    city_name = None
    if mac_check(input_data):
        input_data, city_name = mac_fix(input_data)
    # This should be "FORM" for .sc2
    header = input_data[0 : 4]
    # The reported size saved in the .sc2, we don't count the first 8 bytes though, so we need to add them back.
    reported_size = parse_int32(input_data[4 : 8]) + 8
    # This should be "SCDH"
    file_type = input_data[8 : 12]
    # Actual size of our input file
    actual_size = len(input_data)
    # Check and see if this is a Simcity Classic city.
    if input_type == 'sc2':
        if header != b"FORM":
            # Check and see if this is a Simcity Classic city.
            if input_data[0x41 : 0x49] == b'\x43\x49\x54\x59\x4D\x43\x52\x50' and header[0 : 2] == b'\x00\x0d':
                error_message = "Simcity Classic city files are not supported."
            else:
                error_message = f"Not a FORM type IFF file, claiming: {header}"
            raise SC2Parse(error_message)
        if reported_size != actual_size:
            error_message = f"File reports being: {reported_size}B, but is actually {actual_size}B long."
            raise SC2Parse(error_message)
        if file_type != b"SCDH":
            error_message = f"File type is not SCDH, claiming: {file_type}"
            raise SC2Parse(error_message)
    elif input_type == 'mif':
        if header != b"MIFF":
            error_message = f"Not a MIFF type IFF file, claiming: {header}"
            raise MIFFParse(error_message)
        if reported_size != actual_size:
            error_message = f"File reports being: {reported_size}B, but is actually {actual_size}B long."
            raise MIFFParse(error_message)
        if file_type != b"SC2K":
            error_message = f"File type is not SC2K, claiming: {file_type}"
            raise MIFFParse(error_message)
    return {'type_id': header, 'data_size': reported_size, 'file_type': file_type, "city_name": city_name}, input_data
Example #4
0
 def misc_uninterleave_data(self, keys, offset, length, misc_data):
     """
     Args:
         keys (): list of keys? representing the data we want to parse.
         offset (int): Offset into MISC where the segment we want to uninterleave starts.
         length (int): Total length of the section.
         misc_data: Data from the MISC section that needs to be uninterlaved
     Returns:
         A dictionary with the key being .
     """
     num_keys = len(keys)
     values = [[] for _ in range(num_keys)]
     for idx, val in enumerate(range(offset, offset + length, 4)):
         data = parse_int32(misc_data[offset : offset + 4])
         values[idx % num_keys].extend([data])
         offset += 4
     output = {}
     for idx, key_name in enumerate(keys):
         output[key_name] = values[idx]
     return output
Example #5
0
    def parse_misc(self, misc_data):
        """
        Parses the MISC section of the .sc2 file and populates the City object with its values.
        See .sc2 file spec docs for more, at:
        Args:
            misc_data (bytes): MISC segment of the raw data from the .sc2 file.
        """
        # This is the offset of the section that's being parsed from MISC.
        parse_order = {
            '0x0000': 'FirstEntry',  # nominally the same in every city.
            '0x0004': 'GameMode',
            '0x0008': 'Compass',  # rotation
            '0x000c': 'baseYear',
            '0x0010': 'simCycle',
            '0x0014': 'TotalFunds',
            '0x0018': 'TotalBonds',
            '0x001c': 'GameLevel',
            '0x0020': 'CityStatus',
            '0x0024': 'CityValue',
            '0x0028': 'LandValue',
            '0x002c': 'CrimeCount',
            '0x0030': 'TrafficCount',
            '0x0034': 'Pollution',
            '0x0038': 'CityFame',
            '0x003c': 'Advertising',
            '0x0040': 'Garbage',
            '0x0044': 'WorkerPercent',
            '0x0048': 'WorkerHealth',
            '0x004c': 'WorkerEducate',
            '0x0050': 'NationalPop',
            '0x0054': 'NationalValue',
            '0x0058': 'NationalTax',
            '0x005c': 'NationalTrend',
            '0x0060': 'heat',
            '0x0064': 'wind',
            '0x0068': 'humid',
            '0x006c': 'weatherTrend',
            '0x0070': 'NewDisaster',
            '0x0074': 'oldResPop',
            '0x0078': 'Rewards',
            '0x007c': 'Population Graphs',
            '0x016c': 'Industry Graphs',
            '0x01f0': 'Tile Counts',
            '0x05f0': 'ZonePop|0',
            '0x05f4': 'ZonePop|1',
            '0x05f8': 'ZonePop|2',
            '0x05fc': 'ZonePop|3',
            '0x0600': 'ZonePop|4',
            '0x0604': 'ZonePop|5',
            '0x0608': 'ZonePop|6',
            '0x060c': 'ZonePop|7',
            '0x0610': 'Bonds',
            '0x06d8': 'Neighbours',
            '0x0718': 'Valve?|0',  # reverse engineered from the game, may be a typo in original.
            '0x071c': 'Valve?|1',
            '0x0720': 'Valve?|2',
            '0x0724': 'Valve?|3',
            '0x0728': 'Valve?|4',
            '0x072c': 'Valve?|5',
            '0x0730': 'Valve?|6',
            '0x0734': 'Valve?|7',
            '0x0738': 'gas_power',
            '0x073c': 'nuclear_power',
            '0x0740': 'solar_power',
            '0x0744': 'wind_power',
            '0x0748': 'microwave_power',
            '0x074c': 'fusion_power',
            '0x0750': 'airport',
            '0x0754': 'highways',
            '0x0758': 'buses',
            '0x075c': 'subways',
            '0x0760': 'water_treatment',
            '0x0764': 'desalinisation',
            '0x0768': 'plymouth',
            '0x076c': 'forest',
            '0x0770': 'darco',
            '0x0774': 'launch',
            '0x0778': 'highway_2',
            '0x077c': 'Budget',
            '0x0e3c': 'YearEnd',
            '0x0e40': 'GlobalSeaLevel',
            '0x0e44': 'terCoast',
            '0x0e48': 'terRiver',
            '0x0e4c': 'Military',
            '0x0e50': 'Paper List',
            '0x0ec8': 'News List',
            '0x0fa0': 'Ordinances',
            '0x0fa4': 'unemployed',
            '0x0fa8': 'Military Count',
            '0x0fe8': 'SubwayCnt',
            '0x0fec': 'GameSpeed',
            '0x0ff0': 'AutoBudget',
            '0x0ff4': 'AutoGo',
            '0x0ff8': 'UserSoundOn',
            '0x0ffc': 'UserMusicOn',
            '0x1000': 'NoDisasters',
            '0x1004': 'PaperDeliver',
            '0x1008': 'PaperExtra',
            '0x100c': 'PaperChoice',
            '0x1010': 'unknown128',
            '0x1014': 'Zoom',
            '0x1018': 'CityCentX',
            '0x101c': 'CityCentY',
            '0x1020': 'GlobalArcoPop',
            '0x1024': 'ConnectTiles',
            '0x1028': 'TeamsActive',
            '0x102c': 'TotalPop',
            '0x1030': 'IndustryBonus',
            '0x1034': 'PolluteBonus',
            '0x1038': 'oldArrest',
            '0x103c': 'PoliceBonus',
            '0x1040': 'DisasterObject',
            '0x1044': 'CurrentDisaster',
            '0x1048': 'GoDisaster',
            '0x104c': 'SewerBonus',
            '0x1050': 'Extra', }
        handle_special = ['Population Graphs', 'Industry Graphs', 'Tile Counts', 'Bonds', 'Neighbours', 'Budget',
                          'Military Count', 'Paper List', 'News List', 'Extra', 'Ordinances'] + list(
            self.simulator_settings.keys()) + list(self.game_settings.keys()) + list(self.inventions.keys())

        # Make sure the dict is sorted because following code requires the sorting.
        #sorted(parse_order.keys())

        # Parse misc and generate city attributes.
        for k, v in parse_order.items():
            offset = int(k, 16)
            if v not in handle_special:
                self.city_attributes[v] = parse_uint32(misc_data[offset : offset + 4])
            elif v == 'Population Graphs':
                length = 240
                self.population_graphs = self.misc_uninterleave_data(self._population_graph_names, offset, length, misc_data)
            elif v == 'Industry Graphs':
                length = 132
                self.industry_graphs = self.misc_uninterleave_data(self._industry_graph_names, offset, length, misc_data)
            elif v == 'Tile Counts':
                for x in range(0, 256):
                    self.building_count[x] = parse_int32(misc_data[offset: offset + 4])
                    offset += 4
            elif v in ('Bonds', 'Ordinances'):
                # Handled along with the budget.
                continue
            elif v == 'Neighbours':
                neighbour_types = ['Name', 'Population', 'Value', 'Fame']
                # Calculate their offsets. 64 = 4 neighbours at 4 x 4B entries each
                for idx, start_offset in enumerate(range(offset, offset + 64, 16)):
                    # 16 = 4 entries x 4B per entry.
                    neighbour = collections.OrderedDict()
                    for x in range(start_offset, start_offset + 16, 4):
                        type_key = neighbour_types[((x + 8) % 16) // 4]
                        neighbour[type_key] = parse_int32(misc_data[x : x + 4])
                    self.neighbor_info[idx] = neighbour
            elif v == 'Budget':
                self.budget = Budget()
                self.budget.parse_budget(misc_data)
            elif v == 'Military Count':
                num_items = 16
                for idx, x in enumerate(range(offset, offset + num_items * 4, 4)):
                    key = "{}|{}".format(v, idx)
                    self.city_attributes[key] = parse_int32(misc_data[x : x + 4])
            elif v == 'Paper List':
                num_items = 6 * 5
                for idx, x in enumerate(range(offset, offset + num_items * 4, 4)):
                    key = "{}|{}".format(v, idx)
                    self.city_attributes[key] = parse_int32(misc_data[x : x + 4])
            elif v == 'News List':
                num_items = 9 * 6
                for idx, x in enumerate(range(offset, offset + num_items * 4, 4)):
                    key = "{}|{}".format(v, idx)
                    self.city_attributes[key] = parse_int32(misc_data[x : x + 4])
            elif v == 'Extra':
                for idx, x in enumerate(range(offset, 4800, 4)):
                    key = "{}|{}".format(v, idx)
                    self.city_attributes[key] = parse_int32(misc_data[x : x + 4])
            elif v in list(self.simulator_settings.keys()):
                self.simulator_settings[v] = parse_int32(misc_data[offset : offset + 4])
            elif v in list(self.game_settings.keys()):
                self.game_settings[v] = parse_int32(misc_data[offset : offset + 4])
            elif v in list(self.inventions.keys()):
                self.inventions[v] = parse_int32(misc_data[offset : offset + 4])
            else:
                # Fallthrough, this should never, ever, be hit.
                print("MISC is missing something!", k, v)