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
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()
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()
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
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)
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
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)
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
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()