Beispiel #1
0
def get_tags(coll_path, model_in_path):
    mod2_path = str(Path(model_in_path).with_suffix('')) + "_COLL.gbxmodel"

    # get whether or not the collision tag is stubbs
    stubbs = tag_header_def.build(filepath=coll_path).version == 11

    if stubbs:
        coll_tag = stubbs_coll_def.build(filepath=coll_path)
    else:
        coll_tag = coll_def.build(filepath=coll_path)

    mod2_tag = mod2_def.build()
    mod2_tag.filepath = mod2_path
    model_in_rawdata = None

    guessed_mode = False
    while model_in_rawdata is None and model_in_path:
        try:
            model_in_rawdata = get_rawdata(filepath=model_in_path)
        except Exception:
            if guessed_mode:
                model_in_rawdata = None
                model_in_path = Path(askopenfilename(
                    initialdir=model_in_path.parent, filetypes=(
                        ('All', '*'), ('Gbxmodel', '*.gbxmodel')),
                    title="Select the gbxmodel to extract nodes from"))
            else:
                model_in_path = model_in_path.with_suffix(".model")
                guessed_mode = True

    if model_in_rawdata is not None:
        # we dont actually care about the geometries or shaders of the gbxmodel
        # tag we're loading, so null them out to speed up the loading process.
        geom_off = 64 + 4*9 + 2*5 + 126 + 12*3

        # make a copy so we dont edit the file
        model_in_rawdata = bytearray(model_in_rawdata)
        model_in_rawdata[geom_off:64 + 232] = b'\x00'*(64 + 232 - geom_off)

        if model_in_rawdata[36:40] == b"mod2":
            model_in_tag = mod2_def.build(rawdata=model_in_rawdata)
        elif stubbs:
            model_in_tag = stubbs_mode_def.build(rawdata=model_in_rawdata)
        else:
            model_in_tag = mode_def.build(rawdata=model_in_rawdata)

        mod2_tag.data.tagdata.nodes = model_in_tag.data.tagdata.nodes
    else:
        model_in_tag = None
        mod2_tag.data.tagdata.nodes.STEPTREE.append()
        node = mod2_tag.data.tagdata.nodes.STEPTREE[-1]
        node.name = "COLLISION ROOT"
        print("    %s" % model_in_path)
        print("    Could not load gbxmodel. Gbxmodel wont have nodes and "
              "the geometry will not be positioned or rotated properly.")

    return coll_tag, model_in_tag, mod2_tag
Beispiel #2
0
    def load_map(self, map_path, **kwargs):
        decompress_overwrite = kwargs.get("decompress_overwrite")
        comp_data = get_rawdata(filepath=map_path, writable=False)

        map_header = get_map_header(comp_data, True)
        if map_header is None:
            print("    Could not read map header.")
            comp_data.close()
            return

        # set self.engine early so self.decomp_file_ext will be accurate
        self.engine = get_map_version(map_header)
        self.map_name = map_header.map_name

        self.is_compressed = get_is_compressed_map(comp_data, map_header)
        if self.is_compressed:
            decomp_path = os.path.splitext(map_path)
            while decomp_path[1]:
                decomp_path = os.path.splitext(decomp_path[0])

            decomp_path = decomp_path[0] + "_DECOMP" + self.decomp_file_ext
            if not decompress_overwrite and os.path.isfile(decomp_path):
                decomp_path = os.path.join(
                    tempfile.gettempdir(), os.path.basename(decomp_path))

            print("    Decompressing to: %s" % decomp_path)
            self.map_data = decompress_map(comp_data, map_header, decomp_path)
            if comp_data is not self.map_data:
                comp_data.close()
        else:
            self.map_data = comp_data
            decomp_path = map_path

        map_header = get_map_header(self.map_data)
        tag_index  = self.orig_tag_index = get_tag_index(
            self.map_data, map_header)

        if tag_index is None:
            print("    Could not read tag index.")
            return

        self.maps[self.map_name] = self
        self.filepath        = sanitize_path(map_path)
        self.decomp_filepath = sanitize_path(decomp_path)
        self.map_header  = map_header
        self.index_magic = get_index_magic(map_header)
        self.map_magic   = get_map_magic(map_header)
        self.tag_index   = tag_index
        self.map_pointer_converter = MapPointerConverter()
Beispiel #3
0
    def parse(self, **kwargs):
        ''''''
        if kwargs.get('filepath') is None and kwargs.get('rawdata') is None:
            kwargs['filepath'] = self.filepath

        rawdata = get_rawdata(**kwargs)
        if rawdata:
            rawdata = kwargs['rawdata'] = BytearrayBuffer(rawdata)
        if 'filepath' in kwargs:
            del kwargs['filepath']

        if rawdata is not None:
            is_ce = self.validate_checksum(rawdata, CE_CRC32_OFF)
            is_pc = self.validate_checksum(rawdata, PC_CRC32_OFF)
            #if the checksum doesnt check out for either PC or CE,
            #see if the big endian version of the checksum checks out
            if not (is_ce or is_pc):
                if self.validate_checksum(rawdata, PC_CRC32_OFF, '>'):
                    #turns out the gametype is big endian, who woulda thought?
                    self.is_powerpc = is_pc = True

            self.is_xbox = not (is_ce or is_pc)

            #if the gametype isnt a valid PC gametype, make it a hybrid of both
            if is_ce and not is_pc:
                #copy the checksum to the PC Halo specific location
                rawdata[0x94:0x9C] = rawdata[0xD4:0xDC]
                #copy the gametype settings to the PC Halo specific location
                rawdata[0x7C:0x94] = rawdata[0x9C:0xB4]
        else:
            self.is_xbox = False
            self.is_powerpc = False

        #make sure to force all the fields to read normal endianness
        #after trying to read the tag, even if there is an exception
        try:
            if self.is_powerpc:
                FieldType.force_big()
            return XboxSaveTag.parse(self, **kwargs)
        finally:
            if self.is_powerpc:
                FieldType.force_normal()
Beispiel #4
0
    def get_writable_map_data(self):
        if not self.map_data:
            return None
        elif getattr(self.map_data, "writable", False):
            return self.map_data

        try:
            writable_map_data = get_rawdata(
                filepath=self.decomp_filepath, writable=True)
        except Exception:
            writable_map_data = None

        if not getattr(writable_map_data, "writable"):
            raise OSError("Cannot open map in write mode: %s" %
                          self.decomp_filepath)

        self.map_data.close()
        self.map_data = writable_map_data

        # need to reopen the map as a writable stream
        # and replace self.map_data with it
        return self.map_data
Beispiel #5
0
def decompress_map_deflate(comp_data, header, decomp_path="", writable=False):
    comp_data.seek(0)
    decomp_start = 2048
    decomp_len = header.decomp_len
    version = get_map_version(header)
    if version == "pcstubbs":
        decomp_len -= 2048
    elif version == "halo2vista":
        decomp_start = 0

    os.makedirs(os.path.dirname(decomp_path), exist_ok=True)
    if not decomp_path:
        decomp_path = "decomp.map"

    with open(decomp_path, "wb+") as f:
        if decomp_start:
            comp_data.seek(0)
            f.write(comp_data.read(decomp_start))
            comp_data.seek(decomp_start)
            comp_data = comp_data.read()

        decomp_obj = zlib.decompressobj()
        last_comp_len = len(comp_data)
        while comp_data:
            # decompress map 64Mb at a time
            f.write(decomp_obj.decompress(comp_data, 64 * 1024 * 1024))
            comp_data = decomp_obj.unconsumed_tail
            if len(comp_data) == last_comp_len:
                # need to do this to not get stuck in an infinite
                # loop where the compressed data doesnt actually
                # decode to anything because it is all zeros.
                break
            last_comp_len = len(comp_data)

        # pad the file to its decompressed length
        f.write(b'\xca' * (decomp_len - f.tell()))

    # have to do this separate or seeking will be f****d
    return get_rawdata(filepath=decomp_path, writable=writable)
Beispiel #6
0
    def build(self, **kwargs):
        '''Builds and returns a block'''
        desc = self.descriptor
        f_type = desc[TYPE]

        kwargs.setdefault("offset", 0)
        kwargs.setdefault("root_offset", 0)
        kwargs.setdefault("int_test", False)
        kwargs.setdefault("rawdata", get_rawdata(**kwargs))
        kwargs.pop("filepath", None)  # rawdata and filepath cant both exist

        # create the Block instance to parse the rawdata into
        new_block = desc.get(NODE_CLS, f_type.node_cls)(desc, init_attrs=False)

        if kwargs.pop("allow_corrupt", False):
            try:
                new_block.parse(**kwargs)
            except Exception:
                print(format_exc())
        else:
            new_block.parse(**kwargs)
        return new_block
Beispiel #7
0
def decompress_map_lzma(comp_data, header, decomp_path="", writable=False):
    vap_header = header.vap_header

    os.makedirs(os.path.dirname(decomp_path), exist_ok=True)
    if not decomp_path:
        decomp_path = "decomp.map"

    # get the info needed to decompress each block
    comp_offs = list(b.file_offset for b in vap_header.blocks)
    comp_sizes = list(b.file_size for b in vap_header.blocks)
    if not comp_offs:
        # if blocks is 0, entire compressed size is assumed to be one stream
        comp_offs.append(header.SIZE)
        comp_sizes.append(vap_header.compressed_size - header.SIZE)

    # copy the header and change the compression fields
    header_copy = deepcopy(header)
    header_copy.vap_header.compression_type.set_to("uncompressed")
    header_copy.vap_header.block_count = 0
    header_copy.vap_header.compressed_size = 0
    del header_copy.vap_header.blocks[:]

    with open(decomp_path, "wb+") as f:
        # size the uncompressed file to the right size
        f.seek(vap_header.decompressed_size - 1)
        f.write(b"\x00")

        # write the copied header to the map
        f.seek(0)
        f.write(header_copy.serialize())

        for in_off, in_size in zip(comp_offs, comp_sizes):
            comp_data.seek(in_off)
            f.write(lzma.decompress(comp_data.read(in_size)))

    # have to do this separate or seeking will be f****d
    return get_rawdata(filepath=decomp_path, writable=writable)
Beispiel #8
0
    def import_node(self):
        '''Prompts the user for an exported node file.
        Imports data into the node from the file.'''
        try:
            initialdir = self.tag_window.app_root.last_load_dir
        except AttributeError:
            initialdir = None

        ext = self.field_ext

        filepath = askopenfilename(
            initialdir=initialdir, defaultextension=ext,
            filetypes=[(self.name, "*" + ext), ('All', '*')],
            title="Import sound data from...", parent=self)

        if not filepath:
            return

        filepath = Path(filepath)
        ext = filepath.suffix.lower()

        curr_size = None
        index = self.attr_index

        try:
            curr_size = self.parent.get_size(attr_index=index)
            if ext == '.wav':
                # if the file is wav, we need to give it a header
                wav_file = wav_def.build(filepath=filepath)

                sound_data = self.parent.get_root().data.tagdata
                channel_count = sound_data.encoding.data + 1
                sample_rate = 22050 * (sound_data.sample_rate.data + 1)

                wav_header = wav_file.data.wav_header
                wav_format = wav_file.data.wav_format
                wav_chunks = wav_file.data.wav_chunks
                typ = wav_format.fmt.enum_name
                block_align = (2 if typ == "pcm" else 36) * wav_format.channels

                data_chunk = None
                for chunk in wav_chunks:
                    if chunk.sig.enum_name == "data":
                        data_chunk = chunk
                        break

                if wav_header.riff_sig != wav_header.get_desc("DEFAULT", "riff_sig"):
                    raise ValueError(
                        "RIFF signature is invalid. Not a valid wav file.")
                elif wav_header.wave_sig != wav_header.get_desc("DEFAULT", "wave_sig"):
                    raise ValueError(
                        "WAVE signature is invalid. Not a valid wav file.")
                elif wav_format.sig != wav_format.get_desc("DEFAULT", "sig"):
                    raise ValueError(
                        "Format signature is invalid. Not a valid wav file.")
                elif data_chunk is None:
                    raise ValueError(
                        "Data chunk not present. Not a valid wav file.")
                elif typ not in ('ima_adpcm', 'xbox_adpcm', 'pcm'):
                    raise TypeError(
                        "Wav file audio format must be either IMA ADPCM " +
                        "Xbox ADPCM, or PCM, not %s" % wav_format.fmt.enum_name)
                elif sound_data.encoding.data + 1 != wav_format.channels:
                    raise TypeError(
                        "Wav file channel count does not match this sound " +
                        "tags channel count. Expected %s, not %s" %
                        (channel_count, wav_format.channels))
                elif sample_rate != wav_format.sample_rate:
                    raise TypeError(
                        "Wav file sample rate does not match this sound " +
                        "tags sample rate. Expected %skHz, not %skHz" %
                        (sample_rate, wav_format.sample_rate))
                elif block_align != wav_format.block_align:
                    raise TypeError(
                        "Wav file block size does not match this sound " +
                        "tags block size. Expected %sbytes, not %sbytes" %
                        (block_align, wav_format.block_align))

                rawdata = data_chunk.data
                do_pcm_byteswap = (typ == 'pcm')
            else:
                rawdata = get_rawdata(filepath=filepath, writable=False)
                do_pcm_byteswap = False

            undo_node = self.node
            self.parent.set_size(len(rawdata), attr_index=index)
            self.parent.parse(rawdata=rawdata, attr_index=index)

            if do_pcm_byteswap:
                byteswap_pcm16_samples(self.parent)

            self.node = self.parent[index]

            self.set_edited()
            self.edit_create(undo_node=undo_node, redo_node=self.node)

            # reload the parent field widget so sizes will be updated
            try:
                self.f_widget_parent.reload()
            except Exception:
                print(format_exc())
                print("Could not reload after importing sound data.")
        except Exception:
            print(format_exc())
            print("Could not import sound data.")
            try: self.parent.set_size(curr_size, attr_index=index)
            except Exception: pass
Beispiel #9
0
    def load_map(self, map_path, **kwargs):
        map_data = get_rawdata(filepath=map_path, writable=False)

        resource_type = unpack("<I", map_data.read(4))[0]
        map_data.seek(0)
        rsrc_map = self.rsrc_map = halo1_rsrc_map_def.build(rawdata=map_data)

        self.orig_tag_index = rsrc_map.data.tags

        # check if this is a pc or ce cache. cant rip pc ones
        pth = self.orig_tag_index[0].tag.path if self.orig_tag_index else ""
        self.filepath = map_path

        ce_engine = ""
        for halo_map in self.maps.values():
            ce_engine = getattr(halo_map, "engine")
            if ce_engine:
                break

        rsrc_tag_count = len(rsrc_map.data.tags)
        if resource_type == 3 or (pth.endswith('__pixels')
                                  or pth.endswith('__permutations')):
            if ce_engine:
                self.engine = ce_engine
            else:
                self.engine = "halo1ce"
        elif ((resource_type == 1 and rsrc_tag_count == 1107)
              or (resource_type == 2 and rsrc_tag_count == 7192)):
            self.engine = "halo1pcdemo"
        else:
            self.engine = "halo1pc"

        # so we don't have to redo a lot of code, we'll make a
        # fake tag_index and map_header and just fill in info
        self.map_header = head = map_header_def.build()
        self.tag_index = tags = tag_index_pc_def.build()
        self.map_magic = 0
        self.map_data = map_data
        self.is_resource = True

        self.index_magic = 0

        index_mul = 2
        if self.engine == "halo1pc" or resource_type == 3:
            index_mul = 1

        rsrc_tag_count = rsrc_tag_count // index_mul
        if resource_type == 1:
            head.map_name = "bitmaps"
            tag_classes, def_cls = bitmap_exts, 'bitmap'
        elif resource_type == 2:
            head.map_name = "sounds"
            tag_classes, def_cls = sound_exts, 'sound'
        elif resource_type == 3:
            head.map_name = "loc"
            tag_classes, def_cls = loc_exts, 'NONE'
            if self.engine == "halo1ce" and rsrc_tag_count != 176:
                # this is a custom edition loc.map, but we can't trust it to be accurate
                # if it contains a different number than 176 tags.
                tag_classes = ()
        else:
            raise ValueError("Unknown resource map type.")

        # allow an override to be specified before the map is loaded
        if self.tag_classes is None:
            self.tag_classes = tag_classes

        self.maps[head.map_name] = self
        self.map_name = head.map_name
        self.tag_classes += (def_cls, ) * (rsrc_tag_count -
                                           len(self.tag_classes))
        tags.tag_index.extend(rsrc_tag_count)

        tags.scenario_tag_id = 0
        tags.tag_count = rsrc_tag_count
        # fill in the fake tag_index
        for i in range(rsrc_tag_count):
            j = i * index_mul
            if index_mul != 1:
                j += 1

            tag_ref = tags.tag_index[i]
            tag_ref.class_1.set_to(self.tag_classes[i])
            tag_ref.id = i

            tag_ref.meta_offset = rsrc_map.data.tags[j].offset
            tag_ref.path = rsrc_map.data.tags[j].tag.path
            # not necessary to set it as indexed, as it isn't used
            # when extracting the tags, and leaving it as False
            # allows efinery to display pointer information, and
            # change the classes of tags in custom loc maps.
            #tag_ref.indexed      = 1

        # apparently this is needed(thank spv3/open sauce v4/neil
        #                           for f*****g up resource maps)
        self.basic_deprotection()

        self.map_pointer_converter = MapPointerConverter((0, 0, 0xFFffFFff))
        self.tag_index_manager = TagIndexManager(tags.tag_index)
        self.snd_rsrc_tag_index_manager = TagIndexManager(
            tags.tag_index, sound_rsrc_id_map)
        self.clear_map_cache()