Users browsing this thread: 3 Guest(s)
[TUTORIAL] Autopsy of Unity data files
#1
I haven't found any good description, so I thought I write one. This is what I've learned implementing GAME RIPPER.

First, if the file is an ".apk", rename it to ".zip" and extract with WinZip.

The Unity games folder have "something.exe", which is the game executable and a "something_Data" folder which stores the game data. That latter directory might have four kinds of files: bundles, assets, fsbs and resources. We'll take a deep dive into these to figure out how images are stored. I'd like to point out that the exact file names and extensions are irrelevant and might change from game to game, but even then there's only these four types, no more.

Glossary

Before we start, we must clearify some definitions, which are very important for Unity files.

- byte: smallest memory unit, its value goes from 0 to 255 (0x00 to 0xFF).
- integer: a bigger number, usually stored on 4 or 8 bytes.
- little-endian: when the first byte of integer stores the smallest digit (0x01020304 is stored as 0x04, 0x03, 0x02, 0x01).
- big-endian: when the first byte of the integer stores the largest digit (0x01020304 is stored as 0x01, 0x02, 0x03, 0x04).
- ASCIIZ: a chain of non-zero bytes, terminated by a zero byte.
- string: an array of bytes, stored as length on 4 bytes, then the characters, then no or some zero bytes for 4 byte padding.

Unity FSB

These files start with the magic "FSB5". They store one or more encoded audio streams. As we are focusing on how images stored, this is for a future post.

Code:
00000000  46 53 42 35 01 00 00 00  01 00 00 00 a4 02 00 00  |FSB5............|
00000010  00 00 00 00 c0 d8 17 00  0f 00 00 00 01 00 00 00  |................|
00000020  00 00 00 00 40 40 fa 43  34 88 e9 31 3a 9a b8 46  |....@@.C4..1:..F|
00000030  5b 5b 12 90 36 7c 62 bd  59 c8 65 f0 33 00 00 00  |[[..6|b.Y.e.3...|
00000040  00 20 eb 00 10 05 00 16  7a 1e 59 20 80 02 00 00  |. ......z.Y ....|
00000050  c0 ba 00 00 ff 43 00 00  40 76 01 00 d6 8d 00 00  |.....C..@v......|
00000060  40 32 02 00 45 d5 00 00  c0 ea 02 00 09 1d 01 00  |@2..E...........|
00000070  c0 a8 03 00 df 68 01 00  c0 62 04 00 1c b0 01 00  |.....h...b......|
...

Unity Resource

There's no structure whatsoever, it is just a bit-chunk. We'll have to interpret objects to make heads and tails of it.

Unity Bundle

These files usually have the extension ".bundle" or often called "data.unity3d". If we take a look, we can see that they start with a magic byte and header:

Code:
00000000  55 6e 69 74 79 46 53 00  00 00 00 08 35 2e 78 2e  |UnityFS.....5.x.|
00000010  78 00 32 30 32 31 2e 33  2e 32 33 66 31 00 00 00  |x.2021.3.23f1...|
00000020  00 00 00 00 47 f1 00 00  00 41 00 00 00 5b 00 00  |....G....A...[..|
00000030  02 43 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |.C..............|
00000040  1e 00 01 00 b1 01 00 01  48 78 00 00 47 61 00 03  |........Hx..Ga..|
00000050  0e 00 09 1c 00 00 1a 00  f0 18 00 04 43 41 42 2d  |............CAB-|
00000060  66 37 39 62 30 39 37 63  36 62 31 30 65 61 39 65  |f79b097c6b10ea9e|
00000070  62 62 34 30 36 30 62 63  33 30 34 34 66 34 66 34  |bb4060bc3044f4f4|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000090  16 00 01 00 15 16 0a 00  32 01 32 81 08 00 23 48  |........2.2...#H|
000000a0  78 10 00 14 c0 1b 00 d0  32 30 32 31 2e 33 2e 32  |x.......2021.3.2|
000000b0  33 66 31 00 13 1b 00 50  02 00 00 00 8e 1a 00 f3  |3f1....P........|
000000c0  0c ff ff 97 da 5f 46 88  e4 5a 57 c8 b4 2d 4f 42  |....._F..ZW..-OB|
000000d0  49 72 97 3a 00 00 00 f2  00 00 00 03 3c 00 81 37  |Ir.:........<..7|
000000e0  00 00 80 ff ff ff ff 0d  00 26 80 00 6f 00 92 00  |.........&..o...|
000000f0  01 00 48 03 00 80 ab 01  20 00 10 01 14 00 27 80  |..H..... .....'.|
...

The fields here are:

- The file starts with the "UnityFS" ASCIIZ (some older versions used "UnityRaw").
- UnityFS format's version big-endian integer (which is 8 in our example).
- Unity engine's version ASCIIZ.
- Unity engine's revision ASCIIZ.
- Total file size on 8 bytes big-endian (0x47f1).
- Compressed directory size on 4 bytes big-endian (0x41).
- Uncompressed directory size on 4 bytes big-endian (0x5b).
- Directory compression flags on 4 bytes big-endian (0x243).
- Padding to make the header multiple of 16 bytes.

Now from here there are two variations, either it's header+directory+contents or header+contents+directory, depending on the compression flags bit 7 (0x80). Since this bit is not set in our example (we have 0x243 and not 0x2C3), therefore we're dealing with header+directory+contents layout.

The lower 6 bits of the compression flags tell what kind of compression is used: 0 = none, 1 = lzma, 2/3 = LZ4. The lower 6 bits of 0x243 is 0x03, so we have LZ4 compressed directory of 0x41 bytes which will be 0x5b bytes long after uncompression.

Code:
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 01 00 01 48 78  00 00 47 61 00 03 00 00  |......Hx..Ga....|
00000020  00 01 00 00 00 00 00 00  00 00 00 00 00 00 00 01  |................|
00000030  48 78 00 00 00 04 43 41  42 2d 66 37 39 62 30 39  |Hx....CAB-f79b09|
00000040  37 63 36 62 31 30 65 61  39 65 62 62 34 30 36 30  |7c6b10ea9ebb4060|
00000050  62 63 33 30 34 34 66 34  66 34 00                 |bc3044f4f4.|
0000005b

Here we have:

- 16 bytes unknown, it is said to be some kind of hash, but I have always seen this to be zeros.
- number of streaming blocks on 4 bytes big-endian (1 in our example).
- streaming blocks, each 10 bytes (4 bytes compressed size, 4 bytes uncompressed size, 2 bytes flags). We simply skip these.
- number of directory records on 4 bytes big-endian (1 in our example).
- directory records: 8 bytes big-endian offset, 8 bytes big-endian size, 4 bytes flags, file name ASCIIZ.

Now the offset is relative to the contents block in the archive, and we have a header+directory+contents layout, so we have header size of 0x40 bytes and compressed directory of 0x41 bytes, therefore starts at 0x81 in our example. We need to add 0x81 to every offset in the directory, this giving us the first (and only) file in the archive is starting at offset 0x81, it is 0x14878 bytes in size, and its name is "CAB-f79b097c6b10ea9ebb4060bc3044f4f4".

Files in the UnityFS bundle can be resources or assets.

Unity Assets

Let's take a look at an asset file. These have no standard extension (sometimes ".assets", but for example "level0" is stored in this format as well). This has a header+types+objects+other stuff layout.

Code:
00000000  00 00 00 00 00 00 00 00  00 00 00 16 00 00 00 00  |................|
00000010  00 00 00 00 00 00 21 87  00 00 00 00 00 4f 53 70  |......!......OSp|
00000020  00 00 00 00 00 00 21 c0  00 00 00 00 00 00 00 00  |......!.........|
00000030  32 30 32 31 2e 33 2e 32  33 66 31 00 13 00 00 00  |2021.3.23f1.....|
00000040  00 10 00 00 00 15 00 00  00 00 ff ff c6 00 98 ac  |................|
00000050  66 a2 8b 50 aa 05 80 db  11 bf 01 8c 1c 00 00 00  |f..P............|
00000060  00 ff ff 0d 08 41 4c fd  5b db 0d 22 79 20 11 bd  |.....AL.[.."y ..|
00000070  a9 ab 26 30 00 00 00 00  ff ff f0 c1 84 27 2a 05  |..&0.........'*.|
00000080  e5 44 47 e3 63 66 ec 48  76 a6 31 00 00 00 00 ff  |.DG.cf.Hv.1.....|
00000090  ff 48 6b a4 e1 5d bd 6a  ea 8a c1 a0 64 30 58 89  |.Hk..].j....d0X.|
000000a0  c8 53 00 00 00 00 ff ff  57 a6 37 0a 00 1d 54 49  |.S......W.7...TI|
000000b0  ab d3 7c 21 84 ec 51 38  d5 00 00 00 00 ff ff 70  |..|!..Q8.......p|
000000c0  12 a9 da 77 6e d5 37 74  e2 20 13 56 5d bb d2 72  |...wn.7t. .V]..r|
000000d0  00 00 00 00 04 00 73 6b  9f 99 8f 72 00 bc b4 70  |......sk...r...p|
000000e0  44 51 92 55 28 e1 1f 7f  1a 53 63 4a 52 1a 7e e2  |DQ.U(....ScJR.~.|
000000f0  d5 63 44 33 e6 2c 72 00  00 00 00 07 00 ea 29 4e  |.cD3.,r.......)N|
...

There's no magic, we can only guess that this is an assets file. Here the fields are:

- meta size on 4 bytes big-endian (0 in our example).
- file size on 4 bytes big-endian (0 in our example).
- version on 4 bytes big-endian (0x16, so version 22).
- data offset on 4 bytes big-endian (0 in our example).
- big-endian flag on 4 bytes LITTLE-ENDIAN (0 in our example).
- meta size on 4 bytes big-endian (again, but this time it has 0x2187).
- file size on 8 bytes big-endian (again, but this is 64 bit, we have 0x4f5370).
- data offset on 8 bytes big-endian (again, but this is 64 bit, we have 0x21C0).
- 8 bytes unknown (always seems to be zeros).
- Unity revision ASCIIZ.
- Unity target platform (we have 0x13).
- for version 13 and up, node tree flag (we have 0, so no node tree).

The second meta size and the 64 bit fields only exists if the asset format version is 22 or higher. Also note that ALL integers after the revision is either little-endian or big-endian, depending on the big-endian flag field. We have 0 here, so all the further integers (including the target) we'll be little-endian.

After the header, comes the types block:

- the number of types on 4 bytes (we have 0x10 here, and it is only little-endian because big-endian flag is not set).
- type records.

For each type record, we have:

- 4 bytes class ID (for the first record, type index 0, we have 0x15, so 21).
- stripped type 1 byte (we have 0, meaning not stripped, but don't be fooled, it is stripped).
- script type index 2 bytes (we have 0xFFFF here).
- if class ID is 114 (MonoBehaviour), then this is followed by a 16 bytes hash value. We don't have this field here (21 != 114).
- another 16 bytes hash value. No clue what this might do.
- should node tree flag be set, then here would be a node blob, but it is cleared in our example, so this is all, next record follows.

For completeness, if it would have a node tree, then:

- number of nodes on 4 bytes.
- length of string table on 4 bytes.
- node records, each 24 or 32 bytes (latter when asset version is 22 or up).
- string table.
- number of parent classes on 4 bytes.
- inheritance, each 4 bytes type index.

One node record goes like:

- 2 bytes Version
- 1 byte level
- 1 byte IsArray
- 4 bytes type string (ASCIIZ, offset into the string table that follows)
- 4 bytes name string (ASCIIZ, offset into the string table that follows)
- 4 bytes ByteSize
- 4 bytes Index
- 4 bytes MetaFlags
- 8 bytes references (only if asset version is 22 or up)

But we don't have an embedded node tree, so we must look up the same information in this revision's dump file. We search for "classID{21}", and we get:
Code:
// classID{21}: Material <- NamedObject <- EditorExtension <- Object
Material Base // ByteSize{ffffffff}, Index{0}, Version{8}, IsArray{0}, MetaFlag{8000}
    string m_Name // ByteSize{ffffffff}, Index{1}, Version{1}, IsArray{0}, MetaFlag{88001}
        Array Array // ByteSize{ffffffff}, Index{2}, Version{1}, IsArray{1}, MetaFlag{84001}
            int size // ByteSize{4}, Index{3}, Version{1}, IsArray{0}, MetaFlag{80001}
            char data // ByteSize{1}, Index{4}, Version{1}, IsArray{0}, MetaFlag{80001}
    PPtr<Shader> m_Shader // ByteSize{c}, Index{5}, Version{1}, IsArray{0}, MetaFlag{0}
...

The second type block starts at offset 0x5C, and we have 0x1C (28, Texture2D) here, so we'll have to look for objects with type index of 1.

After the types, we have the object list at 0x255.

Code:
00000250  8d 4a 6e 9c 47 43 01 00  00 00 00 00 01 00 00 00  |.Jn.GC..........|
00000260  00 00 00 00 00 00 00 00  00 00 00 00 90 02 00 00  |................|
00000270  00 00 00 00 02 00 00 00  00 00 00 00 90 02 00 00  |................|
00000280  00 00 00 00 94 02 00 00  00 00 00 00 03 00 00 00  |................|
00000290  00 00 00 00 28 05 00 00  00 00 00 00 14 03 00 00  |....(...........|
000002a0  00 00 00 00 04 00 00 00  00 00 00 00 40 08 00 00  |............@...|
000002b0  00 00 00 00 bc 02 00 00  00 00 00 00 05 00 00 00  |................|
000002c0  00 00 00 00 00 0b 00 00  00 00 00 00 cc 02 00 00  |................|
000002d0  00 00 00 00 06 00 00 00  00 00 00 00 d0 0d 00 00  |................|
000002e0  00 00 00 00 c4 02 00 00  00 00 00 00 07 00 00 00  |................|
000002f0  00 00 00 00 98 10 00 00  00 00 00 00 e4 02 00 00  |................|
00000300  00 00 00 00 08 00 00 00  00 00 00 00 80 13 00 00  |................|
00000310  00 00 00 00 20 02 00 00  00 00 00 00 09 00 00 00  |.... ...........|
00000320  00 00 00 00 a0 15 00 00  00 00 00 00 c8 01 00 00  |................|
00000330  00 00 00 00 0a 00 00 00  00 00 00 00 68 17 00 00  |............h...|

Here we have:

- number of objects on 4 bytes (we have 0x143 here).
- object records, each aligned on 4 bytes.

One object record goes like this:

- path id on 8 bytes.
- data offset on 4 bytes (older versions) or 8 bytes (asset version 22 and up).
- data size on 4 bytes.
- type index on 4 bytes.

The data offset is relative to what's given in the data offset in the header (which was 0x21C0 in our example).

Let's find an object with Texture2D class. It takes a while, but finally we'll have one at 0xBBC:

Code:
...
00000bb0  00 00 00 00 7c 01 00 00  00 00 00 00 65 00 00 00  |....|.......e...|
00000bc0  00 00 00 00 20 d6 00 00  00 00 00 00 90 00 00 00  |.... ...........|
00000bd0  01 00 00 00 66 00 00 00  00 00 00 00 b0 d6 00 00  |....f...........|
00000be0  00 00 00 00 90 00 00 00  01 00 00 00 67 00 00 00  |............g...|
00000bf0  00 00 00 00 40 d7 00 00  00 00 00 00 8c 00 00 00  |....@...........|
00000c00  01 00 00 00 68 00 00 00  00 00 00 00 d0 d7 00 00  |....h...........|
00000c10  00 00 00 00 90 00 00 00  01 00 00 00 69 00 00 00  |............i...|
...

Here we have path id 0x65, data offset 0xD620, size 0x90 and type index 1. Adding data offset in header gets us 0xD620 + 0x21C0 = 0xF7E0:

Code:
...
0000F7E0 0B 00 00 00 │ 42 30 32 5F │ 41 6C 6C 65 │ 79 5F 34 00  ....B02_Alley_4.
0000F7F0 04 00 00 00 │ 00 00 00 00 │ 00 08 00 00 │ 00 04 00 00  ................
0000F800 AC AA AA 00 │ 00 00 00 00 │ 04 00 00 00 │ 0C 00 00 00  ................
0000F810 00 00 00 00 │ 00 00 00 00 │ 01 00 00 00 │ 02 00 00 00  ................
0000F820 01 00 00 00 │ 01 00 00 00 │ 00 00 00 00 │ 00 00 00 00  ................
0000F830 00 00 00 00 │ 00 00 00 00 │ 00 00 00 00 │ 00 00 00 00  ................
0000F840 00 00 00 00 │ 00 00 00 00 │ 00 00 00 00 │ 00 00 00 00  ................
0000F850 AC AA AA 00 │ 15 00 00 00 │ 72 65 73 6F │ 75 72 63 65  ........resource
0000F860 73 2E 61 73 │ 73 65 74 73 │ 2E 72 65 73 │ 53 00 00 00  s.assets.resS...
0000F870 09 00 00 00 │ 4D 65 65 74 │ 69 6E 67 30 │ 31 00 00 00  ....Meeting01...
0000F880 04 00 00 00 │ 00 00 00 00 │ 00 08 00 00 │ 00 08 00 00  ................
0000F890 54 55 55 01 │ 00 00 00 00 │ 04 00 00 00 │ 0C 00 00 00  TUU.............
0000F8A0 00 00 00 00 │ 00 00 00 00 │ 01 00 00 00 │ 02 00 00 00  ................
...

Now to interpret this data, we'll need the node dump that I've linked before. This time search for "classID{28}" in it, which yields:

Code:
// classID{28}: Texture2D <- Texture <- NamedObject <- EditorExtension <- Object
Texture2D Base // ByteSize{ffffffff}, Index{0}, Version{2}, IsArray{0}, MetaFlag{8000}
    string m_Name // ByteSize{ffffffff}, Index{1}, Version{1}, IsArray{0}, MetaFlag{88001}
        Array Array // ByteSize{ffffffff}, Index{2}, Version{1}, IsArray{1}, MetaFlag{84001}
            int size // ByteSize{4}, Index{3}, Version{1}, IsArray{0}, MetaFlag{80001}
            char data // ByteSize{1}, Index{4}, Version{1}, IsArray{0}, MetaFlag{80001}
    int m_ForcedFallbackFormat // ByteSize{4}, Index{5}, Version{1}, IsArray{0}, MetaFlag{0}
    bool m_DownscaleFallback // ByteSize{1}, Index{6}, Version{1}, IsArray{0}, MetaFlag{0}
    bool m_IsAlphaChannelOptional // ByteSize{1}, Index{7}, Version{1}, IsArray{0}, MetaFlag{4000}
    int m_Width // ByteSize{4}, Index{8}, Version{1}, IsArray{0}, MetaFlag{10}
    int m_Height // ByteSize{4}, Index{9}, Version{1}, IsArray{0}, MetaFlag{10}
    unsigned int m_CompleteImageSize // ByteSize{4}, Index{a}, Version{1}, IsArray{0}, MetaFlag{10}
    int m_MipsStripped // ByteSize{4}, Index{b}, Version{1}, IsArray{0}, MetaFlag{10}
    int m_TextureFormat // ByteSize{4}, Index{c}, Version{1}, IsArray{0}, MetaFlag{1}
    int m_MipCount // ByteSize{4}, Index{d}, Version{1}, IsArray{0}, MetaFlag{10}
    bool m_IsReadable // ByteSize{1}, Index{e}, Version{1}, IsArray{0}, MetaFlag{0}
    bool m_IsPreProcessed // ByteSize{1}, Index{f}, Version{1}, IsArray{0}, MetaFlag{1}
    bool m_IgnoreMasterTextureLimit // ByteSize{1}, Index{10}, Version{1}, IsArray{0}, MetaFlag{0}
    bool m_StreamingMipmaps // ByteSize{1}, Index{11}, Version{1}, IsArray{0}, MetaFlag{4000}
    int m_StreamingMipmapsPriority // ByteSize{4}, Index{12}, Version{1}, IsArray{0}, MetaFlag{4000}
    int m_ImageCount // ByteSize{4}, Index{13}, Version{1}, IsArray{0}, MetaFlag{10}
    int m_TextureDimension // ByteSize{4}, Index{14}, Version{1}, IsArray{0}, MetaFlag{1}
    GLTextureSettings m_TextureSettings // ByteSize{18}, Index{15}, Version{2}, IsArray{0}, MetaFlag{0}
        int m_FilterMode // ByteSize{4}, Index{16}, Version{1}, IsArray{0}, MetaFlag{0}
        int m_Aniso // ByteSize{4}, Index{17}, Version{1}, IsArray{0}, MetaFlag{0}
        float m_MipBias // ByteSize{4}, Index{18}, Version{1}, IsArray{0}, MetaFlag{0}
        int m_WrapU // ByteSize{4}, Index{19}, Version{1}, IsArray{0}, MetaFlag{0}
        int m_WrapV // ByteSize{4}, Index{1a}, Version{1}, IsArray{0}, MetaFlag{0}
        int m_WrapW // ByteSize{4}, Index{1b}, Version{1}, IsArray{0}, MetaFlag{0}
    int m_LightmapFormat // ByteSize{4}, Index{1c}, Version{1}, IsArray{0}, MetaFlag{0}
    int m_ColorSpace // ByteSize{4}, Index{1d}, Version{1}, IsArray{0}, MetaFlag{0}
    vector m_PlatformBlob // ByteSize{ffffffff}, Index{1e}, Version{1}, IsArray{0}, MetaFlag{8000}
        Array Array // ByteSize{ffffffff}, Index{1f}, Version{1}, IsArray{1}, MetaFlag{4000}
            int size // ByteSize{4}, Index{20}, Version{1}, IsArray{0}, MetaFlag{0}
            UInt8 data // ByteSize{1}, Index{21}, Version{1}, IsArray{0}, MetaFlag{0}
    TypelessData image data // ByteSize{ffffffff}, Index{22}, Version{1}, IsArray{1}, MetaFlag{4001}
        int size // ByteSize{4}, Index{23}, Version{1}, IsArray{0}, MetaFlag{1}
        UInt8 data // ByteSize{1}, Index{24}, Version{1}, IsArray{0}, MetaFlag{1}
    StreamingInfo m_StreamData // ByteSize{ffffffff}, Index{25}, Version{2}, IsArray{0}, MetaFlag{8000}
        UInt64 offset // ByteSize{8}, Index{26}, Version{1}, IsArray{0}, MetaFlag{0}
        unsigned int size // ByteSize{4}, Index{27}, Version{1}, IsArray{0}, MetaFlag{0}
        string path // ByteSize{ffffffff}, Index{28}, Version{1}, IsArray{0}, MetaFlag{8000}
            Array Array // ByteSize{ffffffff}, Index{29}, Version{1}, IsArray{1}, MetaFlag{4001}
                int size // ByteSize{4}, Index{2a}, Version{1}, IsArray{0}, MetaFlag{1}
                char data // ByteSize{1}, Index{2b}, Version{1}, IsArray{0}, MetaFlag{1}

So the first field is called "m_Name", which is an Array of "char" (bytes). This is prefixed by the length on 4 bytes. Let's see if this aligns with our data:

Code:
0000F7E0 0B 00 00 00 │ 42 30 32 5F │ 41 6C 6C 65 │ 79 5F 34 00  ....B02_Alley_4.

Yes, nice! Also note that the Array type has a meta with flag 0x4000. This means that there's a 4 bytes padding at the end of the string (that's NOT a terminating zero, that's padding).

Next, we have "m_ForcedFallbackFormat" on 4 bytes, "m_DownscaleFallback" on 1 byte, and "m_IsAlphaChannelOptional" on 1 byte again. NOTE: this latter has meta flag of 0x4000 so it has a 4 bytes long padding, making the next field start on a 4 bytes boundary. That next field is "m_Width" on 4 bytes, followed by "m_Height" on 4 bytes. Let's see if these match up with what we have:

Code:
0000F7F0 04 00 00 00 │ 00 00 00 00 │ 00 08 00 00 │ 00 04 00 00  ................

So "m_ForcedFallbackFormat" is 4, "m_DownscaleFallback" is not set, "m_IsAlphaChannelOptional" is not set either, 2 zero bytes padding, then "m_Width" is 0x800 and "m_Height" is 0x400. This latter two gives the size of the image in pixels, 2048 x 1024.

I'll spare you from the rest, you get the drill by now. At the end of the day, we'll either have "TypelessData.size" non-zero, in which case the image data is stored in the "TypelessData.data" array; or if that size field is zero, then we'll have "StreamingInfo.offset", "StreamingInfo.size" and "StreamingInfo.path" set.

For the latter, path gives a path to a resource file (in our example "resources.assets.resS"), offset gives a file offset into that file (0x0000000000000000 in our example), and size gives the size in bytes (0xAAAAAC bytes).

Unity Textures

Alright, we finally have the image data (either from "TypelessData", or from the resource pointed by "StreamingInfo") and we also know the dimensions in pixels from "m_Width" and "m_Height". But how to interpret the pixel data? The objects of Texture2D class also have a "m_TextureFormat" field on 4 bytes, this is what we need.

Code:
1 - each pixel is one byte, an alpha value (mask or grayscale image)
2 - each pixel is 2 bytes, 4 bits alpha channel, 4 bits red channel, 4 bits green channel, 4 bits blue channel
3 - each pixel is 3 bytes, 1 byte red channel, 1 byte green channel, 1 byte blue channel
4 - each pixel is 4 bytes, 1 byte red channel, 1 byte green channel, 1 byte blue channel, 1 byte alpha channel
5 - each pixel is 4 bytes, 1 byte alpha channel, 1 byte red channel, 1 byte green channel, 1 byte blue channel
6 - each pixel is 32 bytes, 4 bytes alpha float, 4 bytes red float, 4 bytes green float, 4 bytes blue float
7 - each pixel is 2 bytes, 5 bits red channel, 6 bits green channel, 5 bits blue channel
8 - each pixel is 3 bytes, 1 byte blue channel, 1 byte green channel, 1 byte red channel
9 - each pixel is 2 bytes, 2 bytes red channel
10 - data is DXT1 encoded (like in a DDS, BC1)
11 - data is DXT3 encoded (BC3)
12 - data is DXT5 encoded (variant of BC3)
13 - each pixel is 2 bytes, 4 bits red channel, 4 bits green channel, 4 bits blue channel, 4 bits alpha channel
14 - each pixel is 4 bytes, 1 byte blue channel, 1 byte green channel, 1 byte red channel, 1 byte alpha channel
15 - each pixel is 2 bytes, 2 bytes red half-float
16 - each pixel is 4 bytes, 2 bytes red half-float, 2 bytes green half-float
18 - each pixel is 8 bytes, 2 bytes red half-float, 2 bytes green half-float, 2 bytes blue half-float, 2 bytes alpha half-float
19 - each pixel is 4 bytes, 4 bytes red float
20 - each pixel is 8 bytes, 4 bytes red float, 4 bytes green float
21 - each pixel is 16 bytes, 4 bytes red float, 4 bytes green float, 4 bytes blue float, 4 bytes alpha float
22 - data is YUY2 encoded
23 - each pixel is 4 bytes, 9e5float encoded, each channel is 9 bits
24 - each pixel is 12 bytes, 4 bytes red float, 4 bytes green float, 4 bytes blue float
25 - data is BC6 encoded
26 - data is BC7 encoded
27 - data is BC4 encoded
28 - data is BC5 encoded
29 - data is DXT1 encoded and crunched
30 - data is DXT5 encoded and crunched
31 - data is PVRTC_RGB2 encoded
32 - data is PVRTC_RGBA2 encoded
33 - data is PVRTC_RGB4 encoded
34 - data is PVRTC_RGBA4 encoded
35 - data is ETC_RGB4 encoded
36 - data is ATC_RGB4 encoded
37 - data is ATC_RGBA8 encoded
...etc.

You get the idea. Some are just pixelformats that you can decode easily, others are complex compressions requiring some DDS library to decode.
Reply
Thanked by:


Messages In This Thread
[TUTORIAL] Autopsy of Unity data files - by gameripper - 12-18-2024, 10:16 AM

Forum Jump: