def save(self, filename=None, compress=False): if self.file_platform not in XNB_PLATFORMS: raise ReaderError("bad platform: '{!r}'".format(self.file_platform)) if self.file_version not in XNB_VERSIONS: raise ReaderError("bad version: {}".format(self.file_version)) attribs = 0 if self.file_version >= VERSION_40: if self.graphics_profile not in XNB_PROFILES: raise ReaderError("bad profile: {}".format(self.graphics_profile)) attribs |= self.graphics_profile & _PROFILE_MASK do_compress = False if self.file_version >= VERSION_30: if compress: do_compress = True attribs |= _COMPRESS_MASK stream = BinaryStream() if do_compress: raise ReaderError("Recompression not supported") else: data = self.getvalue() size = len(data) + stream.calc_size(_XNB_HEADER) stream.pack(_XNB_HEADER, XNB_SIGNATURE, self.file_platform, self.file_version, attribs, size) stream.write(data) if filename is not None: filename = os.path.normpath(filename) dirname = os.path.dirname(filename) if not os.path.isdir(dirname): os.makedirs(dirname) if not filename.endswith(XNB_EXTENSION): filename += XNB_EXTENSION stream.write_file(filename) else: return stream.getvalue()
def find_assets(self): for pak_file in self.content_pak_files: filename = os.path.join(self.root_dir, pak_file) if not os.path.isfile(filename): raise ReaderError("Content pak not found in content root: '{}'".format(filename)) stream = BinaryStream(filename=filename) capacity = stream.read_int32() for _ in range(capacity): asset_name = stream.read_string() asset_size = stream.read_int32() asset_data = stream.read(asset_size) asset_name = asset_name.replace('\\', '/') asset_name = asset_name.lower() yield asset_name, asset_data
def __init__(self, data=None, filename=None): # open in little endian initially stream = BinaryStream(data=data, filename=filename) del data # check sig to find actual endianess h_sig = stream.peek(len(XGS_L_SIGNATURE)) if h_sig == XGS_L_SIGNATURE: big_endian = False elif h_sig == XGS_B_SIGNATURE: big_endian = True else: raise ValueError("bad sig: {!r}".format(h_sig)) # switch stream to correct endianess stream.set_endian(big_endian)
def __init__(self, data, file_platform=PLATFORM_WINDOWS, file_version=VERSION_40, graphics_profile=PROFILE_REACH, compressed=False, parse=True, expected_type=None): BinaryStream.__init__(self, data=data) del data if XNBReader._type_reader_manager is None: XNBReader._type_reader_manager = TypeReaderManager() self.type_reader_manager = XNBReader._type_reader_manager self.file_platform = file_platform self.file_version = file_version self.graphics_profile = graphics_profile self.compressed = compressed self.needs_swap = self.file_platform == PLATFORM_XBOX self.type_readers = [] self.shared_objects = [] self.content = None if parse: self.parse(expected_type=expected_type)
def __init__(self, header, data, dpds=None, seek=None, needs_swap=False): self.header_raw = header self.data_raw = data self.dpds_raw = dpds self.seek_raw = seek self.needs_swap = needs_swap h_s = BinaryStream(data=self.header_raw, big_endian=needs_swap) waveformatex_size = h_s.calc_size(self._waveformatex) (self.h_format_tag, self.h_channels, self.h_samples_per_sec, self.h_avg_bytes_per_sec, self.h_block_align, self.h_bits_per_sample) = h_s.unpack(self._waveformatex) header_size = waveformatex_size # do we have a WAVEFORMATEX self.h_size = None if len(self.header_raw) >= waveformatex_size + 2: self.h_size = h_s.read_uint16() header_size += 2 if self.h_format_tag == WAVE_FORMAT_XMA2: waveformat_xma2_size = h_s.calc_size(self._waveformat_xma2) if self.h_size != waveformat_xma2_size: raise ReaderError("Unknown cbSize for XMA2WAVEFORMATEX: {}".format(self.h_size)) (self.hx_num_streams, self.hx_channel_mask, self.hx_samples_encoded, self.hx_bytes_per_block, self.hx_play_begin, self.hx_play_length, self.hx_loop_begin, self.hx_loop_length, self.hx_loop_count, self.hx_encoder_version, self.hx_block_count) = h_s.unpack(self._waveformat_xma2) header_size += waveformat_xma2_size elif self.h_format_tag == WAVE_FORMAT_EXTENSIBLE: waveformat_extensible_size = h_s.calc_size(self._waveformat_extensible) if self.h_size < waveformat_extensible_size: raise ReaderError("Invalid cbSize for WAVEFORMATEXTENSIBLE: {}".format(self.h_size)) (self.he_valid_bits_per_sample, self.he_channel_mask, he_subformat_bytes) = h_s.unpack(self._waveformat_extensible) self.he_subformat = UUID(bytes_le=he_subformat_bytes) header_size += waveformat_extensible_size self.he_remainder = None if self.h_size > waveformat_extensible_size: self.he_remainder = h_s.read(self.h_size - waveformat_extensible_size) header_size += self.h_size - waveformat_extensible_size raise ReaderError("Extra bytes in WAVEFORMATEXTENSIBLE: {}".format(len(self.he_remainder))) self.h_remainder = h_s.read() if len(self.h_remainder): header_size += len(self.h_remainder) if header_size != len(self.header_raw): raise ReaderError("Header size mismatch: {} != {}".format(header_size, len(self.header_raw)))
def load(cls, data=None, filename=None, parse=True, expected_type=None): if filename is not None: filename = os.path.normpath(filename) stream = BinaryStream(data=data, filename=filename) del data (sig, platform, version, attribs, size) = stream.unpack(_XNB_HEADER) if sig != XNB_SIGNATURE: raise ReaderError("bad sig: '{!r}'".format(sig)) if platform not in XNB_PLATFORMS: raise ReaderError("bad platform: '{!r}'".format(platform)) if version not in XNB_VERSIONS: raise ReaderError("bad version: {}".format(version)) stream_length = stream.length() if stream_length != size: raise ReaderError("bad size: {} != {}".format(stream_length, size)) compressed = False profile = 0 if version >= VERSION_40: profile = attribs & _PROFILE_MASK if profile not in XNB_PROFILES: raise ReaderError("bad profile: {}".format(profile)) if version >= VERSION_30: compressed = bool(attribs & _COMPRESS_MASK) size -= stream.calc_size(_XNB_HEADER) if compressed: uncomp = stream.read_int32() size -= 4 content_comp = stream.read(size) content = decompress(content_comp, uncomp) else: content = stream.read(size) return cls(content, platform, version, profile, compressed, parse=parse, expected_type=expected_type)
def write_bytearray(self, filename, rows): stream = BinaryStream(big_endian=True) # http://www.w3.org/TR/PNG/#5PNG-file-signature stream.write(PyPngWriter._SIGNATURE) # http://www.w3.org/TR/PNG/#11IHDR PyPngWriter._write_chunk(stream, b'IHDR', struct.pack('!II B B BBB', self.width, self.height, 8, 6, 0, 0, 0)) # http://www.w3.org/TR/PNG/#11IDAT compressor = zlib.compressobj() data = bytearray() for row in rows: data.append(0) data.extend(row) if len(data) > self.chunk_limit: compressed = compressor.compress(bytes(data)) if len(compressed): PyPngWriter._write_chunk(stream, b'IDAT', compressed) data = bytearray() if len(data): compressed = compressor.compress(bytes(data)) else: compressed = bytes() flushed = compressor.flush() if len(compressed) or len(flushed): PyPngWriter._write_chunk(stream, b'IDAT', compressed + flushed) # http://www.w3.org/TR/PNG/#11IEND PyPngWriter._write_chunk(stream, b'IEND') stream.write_file(filename)
def save(self, filename=None, compress=False): if self.file_platform not in XNB_PLATFORMS: raise ReaderError("bad platform: '{!r}'".format( self.file_platform)) if self.file_version not in XNB_VERSIONS: raise ReaderError("bad version: {}".format(self.file_version)) attribs = 0 if self.file_version >= VERSION_40: if self.graphics_profile not in XNB_PROFILES: raise ReaderError("bad profile: {}".format( self.graphics_profile)) attribs |= self.graphics_profile & _PROFILE_MASK do_compress = False if self.file_version >= VERSION_30: if compress: do_compress = True attribs |= _COMPRESS_MASK stream = BinaryStream() if do_compress: raise ReaderError("Recompression not supported") else: data = self.getvalue() size = len(data) + stream.calc_size(_XNB_HEADER) stream.pack(_XNB_HEADER, XNB_SIGNATURE, self.file_platform, self.file_version, attribs, size) stream.write(data) if filename is not None: filename = os.path.normpath(filename) dirname = os.path.dirname(filename) if not os.path.isdir(dirname): os.makedirs(dirname) if not filename.endswith(XNB_EXTENSION): filename += XNB_EXTENSION stream.write_file(filename) else: return stream.getvalue()
def find_assets(self): for pak_file in self.content_pak_files: filename = os.path.join(self.root_dir, pak_file) if os.path.isfile(filename): stream = BinaryStream(filename=filename) capacity = stream.read_int32() for _ in range(capacity): asset_name = stream.read_string() asset_size = stream.read_int32() asset_data = stream.read(asset_size) asset_name = asset_name.replace('\\', '/') asset_name = asset_name.lower() yield asset_name, asset_data
def find_assets(self): for pak_file in self.content_pak_files: filename = os.path.join(self.root_dir, pak_file) if not os.path.isfile(filename): raise ReaderError( "Content pak not found in content root: '{}'".format( filename)) stream = BinaryStream(filename=filename) capacity = stream.read_int32() for _ in range(capacity): asset_name = stream.read_string() asset_size = stream.read_int32() asset_data = stream.read(asset_size) asset_name = asset_name.replace('\\', '/') asset_name = asset_name.lower() yield asset_name, asset_data
def __init__(self, data=None, filename=None, audio_engine=None): self.audio_engine = audio_engine # open in little endian initially stream = BinaryStream(data=data, filename=filename) del data # check sig to find actual endianess h_sig = stream.peek(len(SB_L_SIGNATURE)) if h_sig == SB_L_SIGNATURE: big_endian = False elif h_sig == SB_B_SIGNATURE: big_endian = True else: raise ValueError("bad sig: {!r}".format(h_sig)) # switch stream to correct endianess stream.set_endian(big_endian) (h_sig, self.version, self.header_version, self.crc, buildtime_raw_low, buildtime_raw_high, self.platform, h_simple_cue_count, h_complex_cue_count, h_unknown_count, h_cue_name_hash_count, h_wave_bank_count, h_sound_count, h_cue_names_length, simple_cue_offset_raw, complex_cue_offset_raw, cue_name_offset_raw, unknown_offset_raw, variation_offset_raw, transition_offset_raw, wave_bank_offset_raw, cue_name_hash_offset_raw, cue_name_table_offset_raw, sound_offset_raw, h_name_raw) = stream.unpack(_SB_HEADER) h_simple_cue_offset = fix_offset(simple_cue_offset_raw) h_complex_cue_offset = fix_offset(complex_cue_offset_raw) h_cue_name_offset = fix_offset(cue_name_offset_raw) h_unknown_offset = fix_offset(unknown_offset_raw) h_variation_offset = fix_offset(variation_offset_raw) h_transition_offset = fix_offset(transition_offset_raw) h_wave_bank_offset = fix_offset(wave_bank_offset_raw) h_cue_name_hash_offset = fix_offset(cue_name_hash_offset_raw) h_cue_name_table_offset = fix_offset(cue_name_table_offset_raw) h_sound_offset = fix_offset(sound_offset_raw) self.name = h_name_raw.rstrip(b'\x00').decode('iso8859-1') del h_name_raw self.buildtime = filetime_to_datetime(buildtime_raw_low, buildtime_raw_high) self.wave_banks = [] if h_wave_bank_count and h_wave_bank_offset: stream.seek(h_wave_bank_offset) self.wave_banks = [stream.read(64).rstrip(b'\x00').decode('iso8859-1') for _ in range(h_wave_bank_count)] else: raise ReaderError("No wave banks found in sound bank") cue_name_hash = [] if h_cue_name_hash_count and h_cue_name_hash_offset: stream.seek(h_cue_name_hash_offset) cue_name_hash = [stream.read_int16() for _ in range(h_cue_name_hash_count)] cue_name_hash_entry = [] if h_cue_names_length and h_cue_name_table_offset: stream.seek(h_cue_name_table_offset) cue_name_hash_entry = [(stream.read_int32(), stream.read_int16()) for _ in range(h_simple_cue_count + h_complex_cue_count)] cue_names = [] for (name_offset, _) in cue_name_hash_entry: stream.seek(name_offset) cue_names.append(stream.read_cstring()) self.cues = [] self.cues_name = OrderedDict() if h_simple_cue_count and h_simple_cue_offset: stream.seek(h_simple_cue_offset) for i in range(h_simple_cue_count): cue_name = None if cue_names: cue_name = cue_names[i] cue = Cue(cue_name, stream) self.cues.append(cue) if cue_name: self.cues_name[cue_name] = cue if h_complex_cue_count and h_complex_cue_offset: stream.seek(h_complex_cue_offset) for i in range(h_complex_cue_count): cue_name = None if cue_names: cue_name = cue_names[h_simple_cue_count + i] cue = Cue(cue_name, stream, is_complex=True) self.cues.append(cue) if cue_name: self.cues_name[cue_name] = cue
def __init__(self, data=None, filename=None, audio_engine=None): self.audio_engine = audio_engine # open in little endian initially stream = BinaryStream(data=data, filename=filename) del data # check sig to find actual endianess h_sig = stream.peek(len(SB_L_SIGNATURE)) if h_sig == SB_L_SIGNATURE: big_endian = False elif h_sig == SB_B_SIGNATURE: big_endian = True else: raise ValueError("bad sig: {!r}".format(h_sig)) # switch stream to correct endianess stream.set_endian(big_endian) (h_sig, self.version, self.header_version, self.crc, buildtime_raw_low, buildtime_raw_high, self.platform, h_simple_cue_count, h_complex_cue_count, h_unknown_count, h_cue_name_hash_count, h_wave_bank_count, h_sound_count, h_cue_names_length, simple_cue_offset_raw, complex_cue_offset_raw, cue_name_offset_raw, unknown_offset_raw, variation_offset_raw, transition_offset_raw, wave_bank_offset_raw, cue_name_hash_offset_raw, cue_name_table_offset_raw, sound_offset_raw, h_name_raw) = stream.unpack(_SB_HEADER) h_simple_cue_offset = fix_offset(simple_cue_offset_raw) h_complex_cue_offset = fix_offset(complex_cue_offset_raw) h_cue_name_offset = fix_offset(cue_name_offset_raw) h_unknown_offset = fix_offset(unknown_offset_raw) h_variation_offset = fix_offset(variation_offset_raw) h_transition_offset = fix_offset(transition_offset_raw) h_wave_bank_offset = fix_offset(wave_bank_offset_raw) h_cue_name_hash_offset = fix_offset(cue_name_hash_offset_raw) h_cue_name_table_offset = fix_offset(cue_name_table_offset_raw) h_sound_offset = fix_offset(sound_offset_raw) self.name = h_name_raw.rstrip(b'\x00').decode('iso8859-1') del h_name_raw self.buildtime = filetime_to_datetime(buildtime_raw_low, buildtime_raw_high) self.wave_banks = [] if h_wave_bank_count and h_wave_bank_offset: stream.seek(h_wave_bank_offset) self.wave_banks = [ stream.read(64).rstrip(b'\x00').decode('iso8859-1') for _ in range(h_wave_bank_count) ] else: raise ReaderError("No wave banks found in sound bank") cue_name_hash = [] if h_cue_name_hash_count and h_cue_name_hash_offset: stream.seek(h_cue_name_hash_offset) cue_name_hash = [ stream.read_int16() for _ in range(h_cue_name_hash_count) ] cue_name_hash_entry = [] if h_cue_names_length and h_cue_name_table_offset: stream.seek(h_cue_name_table_offset) cue_name_hash_entry = [ (stream.read_int32(), stream.read_int16()) for _ in range(h_simple_cue_count + h_complex_cue_count) ] cue_names = [] for (name_offset, _) in cue_name_hash_entry: stream.seek(name_offset) cue_names.append(stream.read_cstring()) self.cues = [] self.cues_name = OrderedDict() if h_simple_cue_count and h_simple_cue_offset: stream.seek(h_simple_cue_offset) for i in range(h_simple_cue_count): cue_name = None if cue_names: cue_name = cue_names[i] cue = Cue(cue_name, stream) self.cues.append(cue) if cue_name: self.cues_name[cue_name] = cue if h_complex_cue_count and h_complex_cue_offset: stream.seek(h_complex_cue_offset) for i in range(h_complex_cue_count): cue_name = None if cue_names: cue_name = cue_names[h_simple_cue_count + i] cue = Cue(cue_name, stream, is_complex=True) self.cues.append(cue) if cue_name: self.cues_name[cue_name] = cue
def write(self, filename): h_s = BinaryStream() h_s.pack(self._waveformatex, self.h_format_tag, self.h_channels, self.h_samples_per_sec, self.h_avg_bytes_per_sec, self.h_block_align, self.h_bits_per_sample) if self.h_size is not None: h_s.write_uint16(self.h_size) if self.h_format_tag == WAVE_FORMAT_XMA2: # hack so mono sounds end up center rather than left if self.h_channels == 1 and self.hx_channel_mask == 1: hx_channel_mask = 0 else: hx_channel_mask = self.hx_channel_mask h_s.pack(self._waveformat_xma2, self.hx_num_streams, hx_channel_mask, self.hx_samples_encoded, self.hx_bytes_per_block, self.hx_play_begin, self.hx_play_length, self.hx_loop_begin, self.hx_loop_length, self.hx_loop_count, self.hx_encoder_version, self.hx_block_count) elif self.h_format_tag == WAVE_FORMAT_EXTENSIBLE: h_s.pack(self._waveformat_extensible, self.he_valid_bits_per_sample, self.he_channel_mask, self.he_subformat.bytes_le) if self.he_remainder: h_s.write(self.he_remainder) if self.h_remainder: h_s.write(self.h_remainder) header_raw = h_s.getvalue() if self.dpds_raw: dpds_size = len(self.dpds_raw) else: dpds_size = None if self.seek_raw: seek_size = len(self.seek_raw) else: seek_size = None o_s = BinaryStream() if self.h_format_tag == WAVE_FORMAT_WMAUDIO2 or self.h_format_tag == WAVE_FORMAT_WMAUDIO3: riff_type = b'XWMA' else: riff_type = b'WAVE' self.write_header(o_s, riff_type, len(header_raw), len(self.data_raw), dpds_size, seek_size) self.write_chunk(o_s, b'fmt ', header_raw) if self.dpds_raw: self.write_chunk(o_s, b'dpds', self.dpds_raw) if self.seek_raw: self.write_chunk(o_s, b'seek', self.seek_raw) self.write_chunk(o_s, b'data', self.data_raw) if self.h_format_tag == WAVE_FORMAT_XMA2: full_filename = filename + '.xma' elif self.h_format_tag == WAVE_FORMAT_WMAUDIO2 or self.h_format_tag == WAVE_FORMAT_WMAUDIO2: full_filename = filename + '.xwma' else: full_filename = filename + '.wav' o_s.write_file(full_filename)
def __init__(self, header, data, dpds=None, seek=None, needs_swap=False): self.header_raw = header self.data_raw = data self.dpds_raw = dpds self.seek_raw = seek self.needs_swap = needs_swap h_s = BinaryStream(data=self.header_raw, big_endian=needs_swap) waveformatex_size = h_s.calc_size(self._waveformatex) (self.h_format_tag, self.h_channels, self.h_samples_per_sec, self.h_avg_bytes_per_sec, self.h_block_align, self.h_bits_per_sample) = h_s.unpack(self._waveformatex) header_size = waveformatex_size # do we have a WAVEFORMATEX self.h_size = None if len(self.header_raw) >= waveformatex_size + 2: self.h_size = h_s.read_uint16() header_size += 2 if self.h_format_tag == WAVE_FORMAT_XMA2: waveformat_xma2_size = h_s.calc_size(self._waveformat_xma2) if self.h_size != waveformat_xma2_size: raise ReaderError( "Unknown cbSize for XMA2WAVEFORMATEX: {}".format( self.h_size)) (self.hx_num_streams, self.hx_channel_mask, self.hx_samples_encoded, self.hx_bytes_per_block, self.hx_play_begin, self.hx_play_length, self.hx_loop_begin, self.hx_loop_length, self.hx_loop_count, self.hx_encoder_version, self.hx_block_count) = h_s.unpack(self._waveformat_xma2) header_size += waveformat_xma2_size elif self.h_format_tag == WAVE_FORMAT_EXTENSIBLE: waveformat_extensible_size = h_s.calc_size( self._waveformat_extensible) if self.h_size < waveformat_extensible_size: raise ReaderError( "Invalid cbSize for WAVEFORMATEXTENSIBLE: {}".format( self.h_size)) (self.he_valid_bits_per_sample, self.he_channel_mask, he_subformat_bytes) = h_s.unpack(self._waveformat_extensible) self.he_subformat = UUID(bytes_le=he_subformat_bytes) header_size += waveformat_extensible_size self.he_remainder = None if self.h_size > waveformat_extensible_size: self.he_remainder = h_s.read(self.h_size - waveformat_extensible_size) header_size += self.h_size - waveformat_extensible_size raise ReaderError( "Extra bytes in WAVEFORMATEXTENSIBLE: {}".format( len(self.he_remainder))) self.h_remainder = h_s.read() if len(self.h_remainder): header_size += len(self.h_remainder) if header_size != len(self.header_raw): raise ReaderError("Header size mismatch: {} != {}".format( header_size, len(self.header_raw)))
def __init__(self, data=None, filename=None, audio_engine=None): self.audio_engine = audio_engine # open in little endian initially stream = BinaryStream(data=data, filename=filename) del data # check sig to find actual endianess h_sig = stream.peek(len(WB_L_SIGNATURE)) if h_sig == WB_L_SIGNATURE: big_endian = False elif h_sig == WB_B_SIGNATURE: big_endian = True else: raise ValueError("bad sig: {!r}".format(h_sig)) # switch stream to correct endianess stream.set_endian(big_endian) (h_sig, self.h_version, self.h_header_version) = stream.unpack(_WB_HEADER) regions = { k: XWBRegion._make(stream.unpack(_WB_REGION)) for k in _REGIONS } # pylint: disable-msg=W0212 # check if we have a valid BANKDATA region and parse it bankdata_size = stream.calc_size(_WB_DATA) if regions['BANKDATA'].length != bankdata_size: raise ReaderError("Invalid BANKDATA size: {} != {}".format( regions['BANKDATA'].length, bankdata_size)) stream.seek(regions['BANKDATA'].offset) (self.flags, h_entry_count, h_bank_name_raw, h_entry_metadata_element_size, h_entry_name_element_size, self.alignment, h_compact_format, buildtime_raw_low, buildtime_raw_high) = stream.unpack(_WB_DATA) self.bank_name = h_bank_name_raw.rstrip(b'\x00').decode('iso8859-1') del h_bank_name_raw self.buildtime = filetime_to_datetime(buildtime_raw_low, buildtime_raw_high) if self.flags & ~(WB_TYPE_MASK | WB_FLAGS_MASK): raise ReaderError("Unknown flags in WAVEBANK") # check what type of ENTRYMETADATA we have and parse it if self.has_compact: raise ReaderError("Compact format not supported") bankentry_size = stream.calc_size(_WB_ENTRY) if bankentry_size != h_entry_metadata_element_size: raise ReaderError( "Unknown EntryMetaDataElementSize: {} != {}".format( bankentry_size, h_entry_metadata_element_size)) if regions['ENTRYMETADATA'].length != bankentry_size * h_entry_count: raise ReaderError("Invalid ENTRYMETADATA size: {} != {}".format( regions['ENTRYMETADATA'].length, bankentry_size * h_entry_count)) stream.seek(regions['ENTRYMETADATA'].offset) entry_metadata = [ XWBEntry._make( stream.unpack(_WB_ENTRY)) # pylint: disable-msg=W0212,E1101 for _ in range(h_entry_count) ] # read ENTRYNAMES if present entry_names = [] if self.has_entry_names and regions['ENTRYNAMES'].offset and regions[ 'ENTRYNAMES'].length: if regions[ 'ENTRYNAMES'].length != h_entry_name_element_size * h_entry_count: raise ReaderError( "Invalid ENTRYNAMES region size: {} != {}".format( regions['ENTRYNAMES'].length, h_entry_name_element_size * h_entry_count)) stream.seek(regions['ENTRYNAMES'].offset) entry_names = [ stream.read(h_entry_name_element_size).rstrip(b'\x00').decode( 'iso8859-1') for _ in range(h_entry_count) ] # read SEEKTABLES if present entry_seektables = [] if self.has_seek_tables and regions['SEEKTABLES'].offset and regions[ 'SEEKTABLES'].length: stream.seek(regions['SEEKTABLES'].offset) seek_offsets = [] for _ in range(h_entry_count): seek_offsets.append(stream.read_int32()) seek_data_offset = stream.tell() for cur_offset in seek_offsets: if cur_offset >= 0: stream.seek(seek_data_offset + cur_offset) packet_count = stream.read_uint32() cur_seek_data = BinaryStream() for _ in range(packet_count): cur_seek_data.write_uint32(stream.read_uint32()) entry_seektables.append(cur_seek_data.getvalue()) else: entry_seektables.append(None) self.entries = [] for i, cur_meta in enumerate(entry_metadata): c_entry_flags = cur_meta.flags_duration & WB_ENTRY_FLAGS_MASK c_duration = (cur_meta.flags_duration & WB_ENTRY_DURATION_MASK) >> 4 c_format_tag = cur_meta.format & WB_FORMAT_TAG_MASK c_channels = (cur_meta.format & WB_FORMAT_CHANNELS) >> 2 c_samples_per_sec = (cur_meta.format & WB_FORMAT_SAMPLES_PER_SEC) >> 5 c_block_align = (cur_meta.format & WB_FORMAT_BLOCK_ALIGN) >> 23 c_bits_per_sample = (cur_meta.format & WB_FORMAT_BITS_PER_SAMPLE) >> 31 entry_name = None if entry_names: entry_name = entry_names[i] entry_dpds = None entry_seek = None extra_header = bytes() # build format specific header and seek data if c_format_tag == WB_FORMAT_TAG_PCM: c_format_tag = WAVE_FORMAT_PCM if c_bits_per_sample == 1: c_bits_per_sample = 16 else: c_bits_per_sample = 8 c_avg_bytes_per_sec = c_samples_per_sec * c_block_align elif c_format_tag == WB_FORMAT_TAG_ADPCM: c_format_tag = WAVE_FORMAT_ADPCM c_bits_per_sample = 4 c_block_align = (c_block_align + ADPCM_BLOCK_ALIGN_OFFSET) * c_channels cx_samples_per_block = ( (c_block_align - (7 * c_channels)) * 8) // (c_bits_per_sample * c_channels) + 2 c_avg_bytes_per_sec = (c_samples_per_sec // cx_samples_per_block) * c_block_align cx_num_coef = len(ADPCM_COEF) extra_header = _ADPCM_WAVEFORMAT.pack(cx_samples_per_block, cx_num_coef) for coef in ADPCM_COEF: extra_header += _ADPCM_WAVEFORMAT_COEF.pack( coef[0], coef[1]) elif c_format_tag == WB_FORMAT_TAG_WMA: if c_bits_per_sample == 1: c_format_tag = WAVE_FORMAT_WMAUDIO3 else: c_format_tag = WAVE_FORMAT_WMAUDIO2 c_bits_per_sample = 16 c_avg_bytes_per_sec = WMA_AVG_BYTES_PER_SEC[c_block_align >> 5] c_block_align = WMA_BLOCK_ALIGN[c_block_align & 0x1f] if entry_seektables: entry_dpds = entry_seektables[i] else: raise ReaderError("No SEEKTABLES found for xWMA format") elif c_format_tag == WB_FORMAT_TAG_XMA: # lots of placeholders in here but seems to decode ok c_format_tag = WAVE_FORMAT_XMA2 c_bits_per_sample = 16 c_avg_bytes_per_sec = 0 cx_num_streams = 1 if c_channels == 2: cx_channel_mask = 3 else: cx_channel_mask = 0 cx_samples_encoded = 0 cx_bytes_per_block = 0 cx_play_begin = 0 cx_play_length = 0 cx_loop_begin = 0 cx_loop_length = 0 cx_loop_count = 0 cx_encoder_version = 4 cx_block_count = 1 extra_header = _XMA_WAVEFORMAT.pack( cx_num_streams, cx_channel_mask, cx_samples_encoded, cx_bytes_per_block, cx_play_begin, cx_play_length, cx_loop_begin, cx_loop_length, cx_loop_count, cx_encoder_version, cx_block_count) if entry_seektables: entry_seek = entry_seektables[i] else: raise ReaderError("No SEEKTABLES found for XMA2 format") else: raise ReaderError( "Unhandled entry format: {}".format(c_format_tag)) cx_size = len(extra_header) entry_header = _WAVEFORMATEX.pack(c_format_tag, c_channels, c_samples_per_sec, c_avg_bytes_per_sec, c_block_align, c_bits_per_sample, cx_size) entry_header += extra_header # read entry wave data stream.seek(regions['ENTRYWAVEDATA'].offset + cur_meta.play_offset) # manually swap PCM data if needed entry_data = stream.read(cur_meta.play_length) if big_endian and c_format_tag == WAVE_FORMAT_PCM and c_bits_per_sample == 16: entry_data = bytearray(entry_data) entry_data[1::2], entry_data[0::2] = entry_data[ 0::2], entry_data[1::2] self.entries.append( Entry(entry_name, entry_header, entry_data, entry_dpds, entry_seek))
def __init__(self, data=None, filename=None, audio_engine=None): self.audio_engine = audio_engine # open in little endian initially stream = BinaryStream(data=data, filename=filename) del data # check sig to find actual endianess h_sig = stream.peek(len(WB_L_SIGNATURE)) if h_sig == WB_L_SIGNATURE: big_endian = False elif h_sig == WB_B_SIGNATURE: big_endian = True else: raise ValueError("bad sig: {!r}".format(h_sig)) # switch stream to correct endianess stream.set_endian(big_endian) (h_sig, self.h_version, self.h_header_version) = stream.unpack(_WB_HEADER) regions = {k: XWBRegion._make(stream.unpack(_WB_REGION)) for k in _REGIONS} # pylint: disable-msg=W0212 # check if we have a valid BANKDATA region and parse it bankdata_size = stream.calc_size(_WB_DATA) if regions['BANKDATA'].length != bankdata_size: raise ReaderError("Invalid BANKDATA size: {} != {}".format(regions['BANKDATA'].length, bankdata_size)) stream.seek(regions['BANKDATA'].offset) (self.flags, h_entry_count, h_bank_name_raw, h_entry_metadata_element_size, h_entry_name_element_size, self.alignment, h_compact_format, buildtime_raw_low, buildtime_raw_high) = stream.unpack(_WB_DATA) self.bank_name = h_bank_name_raw.rstrip(b'\x00').decode('iso8859-1') del h_bank_name_raw self.buildtime = filetime_to_datetime(buildtime_raw_low, buildtime_raw_high) if self.flags & ~(WB_TYPE_MASK | WB_FLAGS_MASK): raise ReaderError("Unknown flags in WAVEBANK") # check what type of ENTRYMETADATA we have and parse it if self.has_compact: raise ReaderError("Compact format not supported") bankentry_size = stream.calc_size(_WB_ENTRY) if bankentry_size != h_entry_metadata_element_size: raise ReaderError("Unknown EntryMetaDataElementSize: {} != {}".format(bankentry_size, h_entry_metadata_element_size)) if regions['ENTRYMETADATA'].length != bankentry_size * h_entry_count: raise ReaderError("Invalid ENTRYMETADATA size: {} != {}".format(regions['ENTRYMETADATA'].length, bankentry_size * h_entry_count)) stream.seek(regions['ENTRYMETADATA'].offset) entry_metadata = [XWBEntry._make(stream.unpack(_WB_ENTRY)) # pylint: disable-msg=W0212,E1101 for _ in range(h_entry_count)] # read ENTRYNAMES if present entry_names = [] if self.has_entry_names and regions['ENTRYNAMES'].offset and regions['ENTRYNAMES'].length: if regions['ENTRYNAMES'].length != h_entry_name_element_size * h_entry_count: raise ReaderError("Invalid ENTRYNAMES region size: {} != {}".format( regions['ENTRYNAMES'].length, h_entry_name_element_size * h_entry_count)) stream.seek(regions['ENTRYNAMES'].offset) entry_names = [stream.read(h_entry_name_element_size).rstrip(b'\x00').decode('iso8859-1') for _ in range(h_entry_count)] # read SEEKTABLES if present entry_seektables = [] if self.has_seek_tables and regions['SEEKTABLES'].offset and regions['SEEKTABLES'].length: stream.seek(regions['SEEKTABLES'].offset) seek_offsets = [] for _ in range(h_entry_count): seek_offsets.append(stream.read_int32()) seek_data_offset = stream.tell() for cur_offset in seek_offsets: if cur_offset >= 0: stream.seek(seek_data_offset + cur_offset) packet_count = stream.read_uint32() cur_seek_data = BinaryStream() for _ in range(packet_count): cur_seek_data.write_uint32(stream.read_uint32()) entry_seektables.append(cur_seek_data.getvalue()) else: entry_seektables.append(None) self.entries = [] for i, cur_meta in enumerate(entry_metadata): c_entry_flags = cur_meta.flags_duration & WB_ENTRY_FLAGS_MASK c_duration = (cur_meta.flags_duration & WB_ENTRY_DURATION_MASK) >> 4 c_format_tag = cur_meta.format & WB_FORMAT_TAG_MASK c_channels = (cur_meta.format & WB_FORMAT_CHANNELS) >> 2 c_samples_per_sec = (cur_meta.format & WB_FORMAT_SAMPLES_PER_SEC) >> 5 c_block_align = (cur_meta.format & WB_FORMAT_BLOCK_ALIGN) >> 23 c_bits_per_sample = (cur_meta.format & WB_FORMAT_BITS_PER_SAMPLE) >> 31 entry_name = None if entry_names: entry_name = entry_names[i] entry_dpds = None entry_seek = None extra_header = bytes() # build format specific header and seek data if c_format_tag == WB_FORMAT_TAG_PCM: c_format_tag = WAVE_FORMAT_PCM if c_bits_per_sample == 1: c_bits_per_sample = 16 else: c_bits_per_sample = 8 c_avg_bytes_per_sec = c_samples_per_sec * c_block_align elif c_format_tag == WB_FORMAT_TAG_ADPCM: c_format_tag = WAVE_FORMAT_ADPCM c_bits_per_sample = 4 c_block_align = (c_block_align + ADPCM_BLOCK_ALIGN_OFFSET) * c_channels cx_samples_per_block = ((c_block_align - (7 * c_channels)) * 8) // (c_bits_per_sample * c_channels) + 2 c_avg_bytes_per_sec = (c_samples_per_sec // cx_samples_per_block) * c_block_align cx_num_coef = len(ADPCM_COEF) extra_header = _ADPCM_WAVEFORMAT.pack(cx_samples_per_block, cx_num_coef) for coef in ADPCM_COEF: extra_header += _ADPCM_WAVEFORMAT_COEF.pack(coef[0], coef[1]) elif c_format_tag == WB_FORMAT_TAG_WMA: if c_bits_per_sample == 1: c_format_tag = WAVE_FORMAT_WMAUDIO3 else: c_format_tag = WAVE_FORMAT_WMAUDIO2 c_bits_per_sample = 16 c_avg_bytes_per_sec = WMA_AVG_BYTES_PER_SEC[c_block_align >> 5] c_block_align = WMA_BLOCK_ALIGN[c_block_align & 0x1f] if entry_seektables: entry_dpds = entry_seektables[i] else: raise ReaderError("No SEEKTABLES found for xWMA format") elif c_format_tag == WB_FORMAT_TAG_XMA: # lots of placeholders in here but seems to decode ok c_format_tag = WAVE_FORMAT_XMA2 c_bits_per_sample = 16 c_avg_bytes_per_sec = 0 cx_num_streams = 1 if c_channels == 2: cx_channel_mask = 3 else: cx_channel_mask = 0 cx_samples_encoded = 0 cx_bytes_per_block = 0 cx_play_begin = 0 cx_play_length = 0 cx_loop_begin = 0 cx_loop_length = 0 cx_loop_count = 0 cx_encoder_version = 4 cx_block_count = 1 extra_header = _XMA_WAVEFORMAT.pack(cx_num_streams, cx_channel_mask, cx_samples_encoded, cx_bytes_per_block, cx_play_begin, cx_play_length, cx_loop_begin, cx_loop_length, cx_loop_count, cx_encoder_version, cx_block_count) if entry_seektables: entry_seek = entry_seektables[i] else: raise ReaderError("No SEEKTABLES found for XMA2 format") else: raise ReaderError("Unhandled entry format: {}".format(c_format_tag)) cx_size = len(extra_header) entry_header = _WAVEFORMATEX.pack(c_format_tag, c_channels, c_samples_per_sec, c_avg_bytes_per_sec, c_block_align, c_bits_per_sample, cx_size) entry_header += extra_header # read entry wave data stream.seek(regions['ENTRYWAVEDATA'].offset + cur_meta.play_offset) # manually swap PCM data if needed entry_data = stream.read(cur_meta.play_length) if big_endian and c_format_tag == WAVE_FORMAT_PCM and c_bits_per_sample == 16: entry_data = bytearray(entry_data) entry_data[1::2], entry_data[0::2] = entry_data[0::2], entry_data[1::2] self.entries.append(Entry(entry_name, entry_header, entry_data, entry_dpds, entry_seek))