KillzXGaming / Switch-Toolbox

A tool to edit many video game file formats
GNU General Public License v3.0
968 stars 154 forks source link

Info on the txtg file structure. #596

Closed zoalasaurusrawr closed 1 year ago

zoalasaurusrawr commented 1 year ago

I hope this is okay @KillzXGaming, I’ve spent quite a bit of time the last several days reverse engineering the txtg file format and I wrote up a gist in hopes that it will help others. The long and ushort of it is that it appears to be a modified version of the 6pack archive format, but with heavy modifications to use zstd instead of fastlz for single file entry compression.

https://gist.github.com/zoeysaurusrex/0c8a1ae2e218d1c76959765bf9e9c408

CosmicDreamsOfCode commented 1 year ago

Hi the data on these is just raw texture data You can use a tool like raw texture cooker to get it One of the unknowns is probably the indicator for the format of the texture (ie. BC1, BC7 etc etc)

zoalasaurusrawr commented 1 year ago

Thank you for the tip, let me try that!

EDIT: THAT DID THE TRICK EEEEEEEEEEEEEEE. I will update the format docs and try to upload the tool I made to decode them today!

CosmicDreamsOfCode commented 1 year ago

Thank you for the tip, let me try that!

EDIT: THAT DID THE TRICK EEEEEEEEEEEEEEE. I will update the format docs and try to upload the tool I made to decode them today!

Btw i also suspect the value at 0E is mipcount, if the files get progressively smaller, it would probably indicate mipmaps

CosmicDreamsOfCode commented 1 year ago

Thank you for the tip, let me try that! EDIT: THAT DID THE TRICK EEEEEEEEEEEEEEE. I will update the format docs and try to upload the tool I made to decode them today!

Btw i also suspect the value at 0E is mipcount, if the files get progressively smaller, it would probably indicate mipmaps

struct mipfileinfo {
    u32 size;
    u32 unk1; //always 6...
};

struct mipinfo {
    u16 textureindex;
    u8 mipindex;
    u8 unk1; //slice index?
};

struct txtgheader {
    char magic[8];
    u16 width;
    u16 height;
    u16 texturecount;
    u8 mipcount;
    char unkblock[65];
    mipinfo mipinfos[mipcount*texturecount];
    mipfileinfo mipfileinfos[mipcount*texturecount];
};

txtgheader header @0x00;
//zstd data

heres what ive figured out for the format. the unknown block i have no clue on the details of

KillzXGaming commented 1 year ago

Some info on the unknown area.

image

image

Channels use the swap list at 0x18 to determine RGBA. 0x14 looks like another channel list, but it may be another value depending on 0x13 value and I don't know when that one is used.

The formats seem to be at 0x3C, with format and type as bytes. I assume the second one is type as it stays the same as format, but changes when srgb is used.

KillzXGaming commented 1 year ago

Also format 1 = ASTC_4x4_SRGB, 0 = ASTC_4x4_UNORM. Used by link.

Edit: The format might actually be a ushort, since there is also ASTC_8x8 0x105

zoalasaurusrawr commented 1 year ago

Fantastic stuff @KillzXGaming! I have a utility to split these and I’d love to share any code if it would be helpful. I discovered for certain that each file contains multiple sizes of the same texture today. I was able to use texture cooker to convert the raw data to DDS, using Switch Swizzle and DXT1 for txtg files ending _Alb in the Totk rom.

Torphedo commented 1 year ago

Some info on the unknown area.

image

image

Channels use the swap list at 0x18 to determine RGBA. 0x14 looks like another channel list, but it may be another value depending on 0x13 value and I don't know when that one is used.

The formats seem to be at 0x3C, with format and type as bytes. I assume the second one is type as it stays the same as format, but changes when srgb is used.

Could you elaborate on this? I'm not really sure what the channels variable is for or where the format or channel type enums should be used. Your message talks about for format information being at 0x3C, but even when I copy down your struct exactly, the offset of the format field is 0x40 (via offsetof(textogo_header, format);).

typedef struct {
    uint16_t header_size;
    uint16_t version;
    uint32_t magic;
    uint16_t width;
    uint16_t height;
    uint16_t texture_count;
    uint8_t mip_count;

    uint8_t unk1;
    uint8_t unk2;
    uint16_t padding;
    uint8_t unk3;
    uint32_t unk4;

    uint8_t channels[4];
    uint8_t checksum[32];
    uint8_t format;
    uint8_t type;
}textogo_header;
link444112 commented 1 year ago

Fantastic stuff @KillzXGaming! I have a utility to split these and I’d love to share any code if it would be helpful. I discovered for certain that each file contains multiple sizes of the same texture today. I was able to use texture cooker to convert the raw data to DDS, using Switch Swizzle and DXT1 for txtg files ending _Alb in the Totk rom.

Do you think you could upload the tool you made? Thanks in advance.

Torphedo commented 1 year ago

I think this hex pattern sums up all the information we know so far. GitHub didn't like the .hexpat extension so I made it .txt. Is there anything I'm missing? textogo.hexpat.txt

Torphedo commented 1 year ago

Some info on the unknown area. image image Channels use the swap list at 0x18 to determine RGBA. 0x14 looks like another channel list, but it may be another value depending on 0x13 value and I don't know when that one is used. The formats seem to be at 0x3C, with format and type as bytes. I assume the second one is type as it stays the same as format, but changes when srgb is used.

Could you elaborate on this? I'm not really sure what the channels variable is for or where the format or channel type enums should be used. Your message talks about for format information being at 0x3C, but even when I copy down your struct exactly, the offset of the format field is 0x40 (via offsetof(textogo_header, format);).

typedef struct {
    uint16_t header_size;
    uint16_t version;
    uint32_t magic;
    uint16_t width;
    uint16_t height;
    uint16_t texture_count;
    uint8_t mip_count;

    uint8_t unk1;
    uint8_t unk2;
    uint16_t padding;
    uint8_t unk3;
    uint32_t unk4;

    uint8_t channels[4];
    uint8_t checksum[32];
    uint8_t format;
    uint8_t type;
}textogo_header;

This seems to be some struct padding problem. I changed my struct a little and it seems to be working now.

typedef struct {
    uint16_t header_size;
    uint16_t version;
    uint32_t magic;
    uint16_t width;
    uint16_t height;
    uint16_t texture_count;
    uint8_t mip_count;

    uint64_t unknown;

    uint8_t channels[4];
    uint8_t checksum[32];
    uint8_t format;
    uint8_t type;
}textogo_header;
Torphedo commented 1 year ago

Also format 1 = ASTC_4x4_SRGB, 0 = ASTC_4x4_UNORM. Used by link.

Edit: The format might actually be a ushort, since there is also ASTC_8x8 0x105

I ran my code on every textogo file in the game, and there are 4 other format values that are used but unaccounted for: 0x9, 0xA, 0xB, and 0xC. Interestingly, it seems like 0x8 isn't used at all. I figure the 4 unknown values are for BC6 and BC7 compression.

Also, how are you figuring out which value is which format?

KillzXGaming commented 1 year ago

I am implimented support for the format so I can tell what format is used.

Also I can confirm the checksum value is not quite a checksum, I assume it is just a unique hash used for every texture. Replacing the image works fine as is and the value always changes, even if the data is identical with another texture.

KillzXGaming commented 1 year ago

image

image

Currently known so far. Some small changes with the format, since I think it does use both values or something

zoalasaurusrawr commented 1 year ago

I am implimented support for the format so I can tell what format is used.

Also I can confirm the checksum value is not quite a checksum, I assume it is just a unique hash used for every texture. Replacing the image works fine as is and the value always changes, even if the data is identical with another texture.

Awesome! I'm glad that you and others are more familiar with this territory. I feel like I have a long way to go towards really being decent at this type of work. I was able to work out the swizzle for DXT1 from the raw data, last night in code, but at this point I'm not familiar not familiar enough with the file formats to know if it's even useful to tackle the swizzle that way, or if it's going to be a thing where it's slightly different for each format.

In case it's useful, it looks like the Swizzle is just BGRA, at least in cases where I was able to use texture cooker to decode a DXT1 file.

public static byte[] ReadColor(this BinaryReader reader)
{
    if (reader == null)
        throw new ArgumentNullException(nameof(reader));

    var color = reader.ReadInt32();

    var originalColor = Color.FromArgb(color);
    return new byte[] { originalColor.B, originalColor.G, originalColor.R, originalColor.A };
}

When I get home a bit, I'll upload the code I'm using to split the individual files out from the original stream + zlib decompression. It feels like the total efforts in the thread are eclipsing anything I might be able to add. I'll make sure to update the gist as well, so that I don't mislead others on the file format as well!

ArchLeaders commented 1 year ago

image

image

Currently known so far. Some small changes with the format, since I think it does use both values or something

The area around the MipMap count seems weird to me. Mainly the positioning of the u16 padding after the two unknown byte values. I feel like they would put the FormatFlag directly after Unk2 and then two bytes padding afterwards. But even that seems weird to me, cause the header isn't padded to 4-byte regions, making the padding there somewhat redundant (if I understand how padding works correctly)

KillzXGaming commented 1 year ago

It might not be, though I think it's always 0. I'll need to make a dump of all the values used to understand some things more clearly.

ArchLeaders commented 1 year ago

Also something else I noticed, TextureSetting1 is a float value which is typically !f32 70, but it's !f32 50 in MaterialsAlb. Changing it works (the game loads), but I can't see any visible changes. (Changed it to 10k, 100k, and -10)

KillzXGaming commented 1 year ago

It has been added.