Example #1
0
    def read_tex1(self):
        self.textures = []
        self.num_textures = read_u16(self.data, 8)
        self.texture_header_list_offset = read_u32(self.data, 0x0C)
        for texture_index in range(self.num_textures):
            bti_header_offset = self.texture_header_list_offset + texture_index * 0x20
            texture = BTI(self.data, bti_header_offset)
            self.textures.append(texture)

        self.string_section_offset = read_u32(self.data, 0x10)
        self.num_strings = read_u16(self.data, self.string_section_offset)
        self.string_unknown_1 = read_u16(self.data,
                                         self.string_section_offset + 2)
        self.string_unknown_2 = read_u16(self.data,
                                         self.string_section_offset + 4)
        self.string_data_offset = read_u16(self.data,
                                           self.string_section_offset + 6)
        self.string_unknown_3 = read_bytes(self.data,
                                           self.string_section_offset + 8,
                                           self.string_data_offset - 8)

        self.texture_names = []
        self.textures_by_name = OrderedDict()
        offset_in_string_list = self.string_data_offset
        for texture in self.textures:
            filename = read_str_until_null_character(
                self.data, self.string_section_offset + offset_in_string_list)
            self.texture_names.append(filename)
            if filename not in self.textures_by_name:
                self.textures_by_name[filename] = []
            self.textures_by_name[filename].append(texture)

            offset_in_string_list += len(filename) + 1
Example #2
0
class TEX1(J3DChunk):
    def read_chunk_specific_data(self):
        # This string is 0x14 bytes long, but sometimes there are random garbage bytes after the null byte.
        self.filename = read_str_until_null_character(self.data, 0xC)

        bti_data = BytesIO(read_bytes(self.data, 0x20, self.size - 0x20))
        self.bti = BTI(bti_data)

    def save_chunk_specific_data(self):
        self.data.seek(0x20)
        self.bti.save_header_changes()
        header_bytes = read_bytes(self.bti.data, self.bti.header_offset, 0x20)
        self.data.write(header_bytes)

        self.bti.image_data.seek(0)
        self.data.write(self.bti.image_data.read())

        if self.bti.needs_palettes():
            self.bti.palette_data.seek(0)
            self.data.write(self.bti.palette_data.read())
Example #3
0
    def read_chunk_specific_data(self):
        self.textures = []
        self.num_textures = read_u16(self.data, 8)
        self.texture_header_list_offset = read_u32(self.data, 0x0C)
        for texture_index in range(self.num_textures):
            bti_header_offset = self.texture_header_list_offset + texture_index * 0x20
            texture = BTI(self.data, bti_header_offset)
            self.textures.append(texture)

        self.string_table_offset = read_u32(self.data, 0x10)
        self.texture_names = self.read_string_table(self.string_table_offset)
        self.textures_by_name = OrderedDict()
        for i, texture in enumerate(self.textures):
            texture_name = self.texture_names[i]
            if texture_name not in self.textures_by_name:
                self.textures_by_name[texture_name] = []
            self.textures_by_name[texture_name].append(texture)
Example #4
0
    def __init__(self, jpc_data, section_offset):
        self.magic = read_str(jpc_data, section_offset, 4)
        self.size = read_u32(jpc_data, section_offset + 4)

        jpc_data.seek(section_offset)
        self.data = BytesIO(jpc_data.read(self.size))

        if self.magic == "TEX1":
            # This string is 0x14 bytes long, but sometimes there are random garbage bytes after the null byte.
            self.filename = read_str_until_null_character(self.data, 0xC)

            bti_data = BytesIO(read_bytes(self.data, 0x20, self.size - 0x20))
            self.bti = BTI(bti_data)
        elif self.magic == "TDB1":
            # Texture ID database (list of texture IDs in this JPC file used by this particle)

            num_texture_ids = ((self.size - 0xC) // 2)
            self.texture_ids = []
            for texture_id_index in range(0, num_texture_ids):
                texture_id = read_u16(self.data, 0xC + texture_id_index * 2)
                self.texture_ids.append(texture_id)

            self.texture_filenames = [
            ]  # Leave this list empty for now, it will be populated after the texture list is read.
Example #5
0
    def read_chunk_specific_data(self):
        # This string is 0x14 bytes long, but sometimes there are random garbage bytes after the null byte.
        self.filename = read_str_until_null_character(self.data, 0xC)

        bti_data = BytesIO(read_bytes(self.data, 0x20, self.size - 0x20))
        self.bti = BTI(bti_data)
Example #6
0
    def read(self):
        if self.magic == "TEX1":
            # This string is 0x14 bytes long, but sometimes there are random garbage bytes after the null byte.
            self.filename = read_str_until_null_character(self.data, 0xC)

            bti_data = BytesIO(read_bytes(self.data, 0x20, self.size - 0x20))
            self.bti = BTI(bti_data)
        elif self.magic == "TDB1":
            # Texture ID database (list of texture IDs in this JPC file used by this particle)

            num_texture_ids = ((self.size - 0xC) // 2)
            self.texture_ids = []
            for texture_id_index in range(num_texture_ids):
                texture_id = read_u16(self.data, 0xC + texture_id_index * 2)
                self.texture_ids.append(texture_id)

            # There's an issue with reading texture IDs where it can include false positives because the texture ID list pads the end with null bytes, which can be interpreted as the texture with ID 0.
            # So we use a heuristic to guess when the list really ends and the padding starts.
            # Simply, we count all texture IDs up until the last nonzero ID, then stop counting zero IDs after that.
            # However, we always include the texture ID at index 0, even if it's zero.
            # TODO: This is a bit hacky. A proper way would involve completely implementing all JPC sections and reading all the texture ID indexes from them, and then reading only the texture IDs at those indexes. But that would be much more work, and this appears to work fine.
            last_nonzero_texture_id_index = None
            for texture_id_index in reversed(range(num_texture_ids)):
                if self.texture_ids[texture_id_index] != 0:
                    last_nonzero_texture_id_index = texture_id_index
                    break
            if last_nonzero_texture_id_index is None:
                last_nonzero_texture_id_index = 0
            self.texture_ids = self.texture_ids[:
                                                last_nonzero_texture_id_index +
                                                1]

            self.texture_filenames = [
            ]  # Leave this list empty for now, it will be populated after the texture list is read.
        elif self.magic == "BSP1":
            self.color_flags = read_u8(self.data, 0xC + 0x1B)

            r = read_u8(self.data, 0xC + 0x20)
            g = read_u8(self.data, 0xC + 0x21)
            b = read_u8(self.data, 0xC + 0x22)
            a = read_u8(self.data, 0xC + 0x23)
            self.color_prm = (r, g, b, a)
            r = read_u8(self.data, 0xC + 0x24)
            g = read_u8(self.data, 0xC + 0x25)
            b = read_u8(self.data, 0xC + 0x26)
            a = read_u8(self.data, 0xC + 0x27)
            self.color_env = (r, g, b, a)

            self.color_prm_anm_data_count = 0
            self.color_prm_anm_table = []
            if self.color_flags & 0x02 != 0:
                self.color_prm_anm_data_offset = read_u16(self.data, 0xC + 0x4)
                self.color_prm_anm_data_count = read_u8(self.data, 0xC + 0x1C)
                self.color_prm_anm_table = self.read_color_table(
                    self.color_prm_anm_data_offset,
                    self.color_prm_anm_data_count)

            self.color_env_anm_data_count = 0
            self.color_env_anm_table = []
            if self.color_flags & 0x08 != 0:
                self.color_env_anm_data_offset = read_u16(self.data, 0xC + 0x6)
                self.color_env_anm_data_count = read_u8(self.data, 0xC + 0x1D)
                self.color_env_anm_table = self.read_color_table(
                    self.color_env_anm_data_offset,
                    self.color_env_anm_data_count)
        elif self.magic == "SSP1":
            r = read_u8(self.data, 0xC + 0x3C)
            g = read_u8(self.data, 0xC + 0x3D)
            b = read_u8(self.data, 0xC + 0x3E)
            a = read_u8(self.data, 0xC + 0x3F)
            self.color_prm = (r, g, b, a)
            r = read_u8(self.data, 0xC + 0x40)
            g = read_u8(self.data, 0xC + 0x41)
            b = read_u8(self.data, 0xC + 0x42)
            a = read_u8(self.data, 0xC + 0x43)
            self.color_env = (r, g, b, a)
Example #7
0
class ParticleSection:
    def __init__(self, jpc_data, section_offset):
        self.magic = read_str(jpc_data, section_offset, 4)
        self.size = read_u32(jpc_data, section_offset + 4)

        jpc_data.seek(section_offset)
        self.data = BytesIO(jpc_data.read(self.size))

        self.read()

    def read(self):
        if self.magic == "TEX1":
            # This string is 0x14 bytes long, but sometimes there are random garbage bytes after the null byte.
            self.filename = read_str_until_null_character(self.data, 0xC)

            bti_data = BytesIO(read_bytes(self.data, 0x20, self.size - 0x20))
            self.bti = BTI(bti_data)
        elif self.magic == "TDB1":
            # Texture ID database (list of texture IDs in this JPC file used by this particle)

            num_texture_ids = ((self.size - 0xC) // 2)
            self.texture_ids = []
            for texture_id_index in range(num_texture_ids):
                texture_id = read_u16(self.data, 0xC + texture_id_index * 2)
                self.texture_ids.append(texture_id)

            # There's an issue with reading texture IDs where it can include false positives because the texture ID list pads the end with null bytes, which can be interpreted as the texture with ID 0.
            # So we use a heuristic to guess when the list really ends and the padding starts.
            # Simply, we count all texture IDs up until the last nonzero ID, then stop counting zero IDs after that.
            # However, we always include the texture ID at index 0, even if it's zero.
            # TODO: This is a bit hacky. A proper way would involve completely implementing all JPC sections and reading all the texture ID indexes from them, and then reading only the texture IDs at those indexes. But that would be much more work, and this appears to work fine.
            last_nonzero_texture_id_index = None
            for texture_id_index in reversed(range(num_texture_ids)):
                if self.texture_ids[texture_id_index] != 0:
                    last_nonzero_texture_id_index = texture_id_index
                    break
            if last_nonzero_texture_id_index is None:
                last_nonzero_texture_id_index = 0
            self.texture_ids = self.texture_ids[:
                                                last_nonzero_texture_id_index +
                                                1]

            self.texture_filenames = [
            ]  # Leave this list empty for now, it will be populated after the texture list is read.
        elif self.magic == "BSP1":
            self.color_flags = read_u8(self.data, 0xC + 0x1B)

            r = read_u8(self.data, 0xC + 0x20)
            g = read_u8(self.data, 0xC + 0x21)
            b = read_u8(self.data, 0xC + 0x22)
            a = read_u8(self.data, 0xC + 0x23)
            self.color_prm = (r, g, b, a)
            r = read_u8(self.data, 0xC + 0x24)
            g = read_u8(self.data, 0xC + 0x25)
            b = read_u8(self.data, 0xC + 0x26)
            a = read_u8(self.data, 0xC + 0x27)
            self.color_env = (r, g, b, a)

            self.color_prm_anm_data_count = 0
            self.color_prm_anm_table = []
            if self.color_flags & 0x02 != 0:
                self.color_prm_anm_data_offset = read_u16(self.data, 0xC + 0x4)
                self.color_prm_anm_data_count = read_u8(self.data, 0xC + 0x1C)
                self.color_prm_anm_table = self.read_color_table(
                    self.color_prm_anm_data_offset,
                    self.color_prm_anm_data_count)

            self.color_env_anm_data_count = 0
            self.color_env_anm_table = []
            if self.color_flags & 0x08 != 0:
                self.color_env_anm_data_offset = read_u16(self.data, 0xC + 0x6)
                self.color_env_anm_data_count = read_u8(self.data, 0xC + 0x1D)
                self.color_env_anm_table = self.read_color_table(
                    self.color_env_anm_data_offset,
                    self.color_env_anm_data_count)
        elif self.magic == "SSP1":
            r = read_u8(self.data, 0xC + 0x3C)
            g = read_u8(self.data, 0xC + 0x3D)
            b = read_u8(self.data, 0xC + 0x3E)
            a = read_u8(self.data, 0xC + 0x3F)
            self.color_prm = (r, g, b, a)
            r = read_u8(self.data, 0xC + 0x40)
            g = read_u8(self.data, 0xC + 0x41)
            b = read_u8(self.data, 0xC + 0x42)
            a = read_u8(self.data, 0xC + 0x43)
            self.color_env = (r, g, b, a)

    def save_changes(self):
        if self.magic == "TEX1":
            self.bti.save_header_changes()

            self.data.seek(0x40)

            self.bti.image_data.seek(0)
            self.data.write(self.bti.image_data.read())

            if self.bti.needs_palettes():
                self.bti.palette_data.seek(0)
                self.data.write(self.bti.palette_data.read())
        elif self.magic == "TDB1":
            # Save the texture IDs (which were updated by the JPC's save_changes function).
            for texture_id_index, texture_id in enumerate(self.texture_ids):
                write_u16(self.data, 0xC + texture_id_index * 2, texture_id)
        elif self.magic == "BSP1":
            write_u8(self.data, 0xC + 0x1B, self.color_flags)

            r, g, b, a = self.color_prm
            write_u8(self.data, 0xC + 0x20, r)
            write_u8(self.data, 0xC + 0x21, g)
            write_u8(self.data, 0xC + 0x22, b)
            write_u8(self.data, 0xC + 0x23, a)
            r, g, b, a = self.color_env
            write_u8(self.data, 0xC + 0x24, r)
            write_u8(self.data, 0xC + 0x25, g)
            write_u8(self.data, 0xC + 0x26, b)
            write_u8(self.data, 0xC + 0x27, a)

            if self.color_flags & 0x02 != 0:
                # Changing size not implemented.
                assert len(
                    self.color_prm_anm_table) == self.color_prm_anm_data_count
                self.save_color_table(self.color_prm_anm_table,
                                      self.color_prm_anm_data_offset)

            if self.color_flags & 0x08 != 0:
                # Changing size not implemented.
                assert len(
                    self.color_env_anm_table) == self.color_env_anm_data_count
                self.save_color_table(self.color_env_anm_table,
                                      self.color_env_anm_data_offset)
        elif self.magic == "SSP1":
            r, g, b, a = self.color_prm
            write_u8(self.data, 0xC + 0x3C, r)
            write_u8(self.data, 0xC + 0x3D, g)
            write_u8(self.data, 0xC + 0x3E, b)
            write_u8(self.data, 0xC + 0x3F, a)
            r, g, b, a = self.color_env
            write_u8(self.data, 0xC + 0x40, r)
            write_u8(self.data, 0xC + 0x41, g)
            write_u8(self.data, 0xC + 0x42, b)
            write_u8(self.data, 0xC + 0x43, a)

        align_data_to_nearest(self.data, 0x20)

        self.size = data_len(self.data)
        write_str(self.data, 0, self.magic, 4)
        write_u32(self.data, 4, self.size)

    def read_color_table(self, color_data_offset, color_data_count):
        color_table = []
        for i in range(color_data_count):
            keyframe_time = read_u16(self.data, color_data_offset + i * 6 + 0)
            r = read_u8(self.data, color_data_offset + i * 6 + 2)
            g = read_u8(self.data, color_data_offset + i * 6 + 3)
            b = read_u8(self.data, color_data_offset + i * 6 + 4)
            a = read_u8(self.data, color_data_offset + i * 6 + 5)
            color_table.append((keyframe_time, (r, g, b, a)))

        return color_table

    def save_color_table(self, color_table, color_data_offset):
        for i, (keyframe_time, (r, g, b, a)) in enumerate(color_table):
            write_u16(self.data, color_data_offset + i * 6 + 0, keyframe_time)
            write_u8(self.data, color_data_offset + i * 6 + 2, r)
            write_u8(self.data, color_data_offset + i * 6 + 3, g)
            write_u8(self.data, color_data_offset + i * 6 + 4, b)
            write_u8(self.data, color_data_offset + i * 6 + 5, a)