Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make sure that a DirectDraw surface has a correct file format?

I need to make a simple check if a dds file is of valid format. I just need to do a general check of the dds file, so I do not need to check if it is a dxt1, dxt5, dx10 etc. For example if I have a png image and I rename the extensions to .dds the dds format will ofcourse be wrong and I would then need to tell the user that he is trying to use a dds file with a wrong format. However if I have a dds that indeed has a correct file format I will not need to do any further investigation since I do not care of what type of dds file it is (at this moment). So I do only have to read the parts of the dds file that will stay the same on ALL dds files. I'm thinking I could read the dds header and magic number in some way. I have to do the same validation for a png file and there I am reading the png header like this:

    var png = new byte[] { 0x89, 0x50, 0x4e, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }
    using (FileStream fs = new FileStream(fileToCheck, FileMode.Open, FileAccess.Read))
            {
               if (fs.Length > buffer.Length)
                 {
                   fs.Read(buffer, 0, buffer.Length);
                   fs.Position = (int)fs.Length - endBuffer.Length;
                   fs.Read(bufferEnd, 0, endBuffer.Length);
                 }

               fs.Close();
            } 
    for (int i = 0; i < png.Length; i++)
        {
            if (buffer[i] != png[i])
                return false;
        }

    return true;

And I was looking if there is a similiar way to check the format of a dds file. I am new to this and the problem I am having is to know what bytes in the dds file that will always be the same for all dds files (so that I can check these bytes to see if the dds format is valid) and how I can implement the code in a simple way to check the dds format. Any help will be appreciated.

Edit: So thanks to @NaCl I have got the first part of the question answered. I now know what parts in the dds file that is required but I do not know how to locate it in the dds file. I have opened a lot of dds files with a hex editor but I am not very good at reverse engineering so I can't understand where the bytes that I need to check is located which will furthermore make me not to know how I can implement code to search for bytes at the specified positions (which I can do with the png file since I found much more document about that file) since I do not know at what position to go to.

If anybody can Point me in the right direction or help me in some other way I would appreciate it very much . Thanks.

like image 856
Jacce Avatar asked Aug 22 '18 20:08

Jacce


1 Answers

The first thing to be done with a file that has a header is to check for its magic numbers (if any is given on documentation), in case of DDS file, the magic number is 0x44445220 which is equal to "DDS " in "plain text".

After that, just deal with the specs. Everything (excepting magic number) is stored in little endian format, so be careful when dealing with bytes, according to Microsoft, the definition of the DDS header is (C++):

typedef struct {
  DWORD           dwSize;                 /* Always equal to 124 */
  DWORD           dwFlags;
  DWORD           dwHeight;
  DWORD           dwWidth;
  DWORD           dwPitchOrLinearSize;
  DWORD           dwDepth;
  DWORD           dwMipMapCount;
  DWORD           dwReserved1[11];
  DDS_PIXELFORMAT ddspf;
  DWORD           dwCaps;
  DWORD           dwCaps2;
  DWORD           dwCaps3;
  DWORD           dwCaps4;
  DWORD           dwReserved2;
} DDS_HEADER;

Based on that, I just came with the following code for any "generic" DDS file:

public static bool IsValidDDS(string path)
{
  if (path == null)
    throw new ArgumentNullException(nameof(path));
  if (!File.Exists(path)) // Check for existence.
    return false;

  uint magicNumber = 0; byte[] headerLength = new byte[sizeof(byte)];
  using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
  {
    byte[] magic = new byte[sizeof(uint)];

    // I'm still pretty used to C and fread, so here you go :^)
    if (fs.Read(magic, 0, sizeof(uint)) != sizeof(uint))
      return false; // Not even a valid file.

    if (fs.Read(headerLength, 0, sizeof(byte)) != sizeof(byte))
      return false; // Not enough bytes, even if the first 4 bytes were checked.

    // Convert to a big endian integer.
    magicNumber = (uint)((magic[0] << 24) | (magic[1] << 16) | (magic[2] << 8) | magic[3]);
  }
  return (headerLength[0] == 0x7C) && (magicNumber == 0x44445320);
}

The code above just checks for the first 5 bytes of the input file (passed as argument path), those are always the same as I have seen and should be equal to:

0x44 0x44 0x53 0x20 0x7C 0x00 0x00 0x00 (8 bytes)

The code above was tested with the .dds files on this repository: https://github.com/toji/webgl-texture-utils/tree/master/sample/img, and both files had exactly the same header:

iamnacl:~/workspace $ cd dds-test/
iamnacl:~/workspace/dds-test $ dotnet run ../test-dxt1.dds 
The file is a valid DDS file? True
iamnacl:~/workspace/dds-test $ dotnet run ../test-dxt5.dds                                                                                               
The file is a valid DDS file? True
iamnacl:~/workspace/dds-test $ 

You can optimize the code above to perform a single call to fs.Read with a size of ulong and check the whole thing against this value: 0x444453207C000000.

Hope this helps :)

like image 89
NaCl Avatar answered Nov 01 '22 06:11

NaCl